Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"> | |
<title>Autotutorial.ai</title> | |
<link rel="manifest" href="manifest.json"> <!-- Create this file for PWA --> | |
<meta name="apple-mobile-web-app-capable" content="yes"> | |
<meta name="apple-mobile-web-app-status-bar-style" content="black"> | |
<!-- Ionic CSS --> | |
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ionic/core@7/css/ionic.bundle.css"> | |
<!-- Google Fonts (Inter) --> | |
<link rel="preconnect" href="https://fonts.googleapis.com"> | |
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> | |
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet"> | |
<style> | |
:root { | |
--ion-font-family: 'Inter', sans-serif; | |
/* Define app-specific colors (based on previous designs, refined)*/ | |
--app-primary: #4f46e5; /* Indigo-600 from Tailwind/Radix */ | |
--app-primary-rgb: 79,70,229; | |
--app-primary-contrast: #ffffff; | |
--app-primary-contrast-rgb: 255,255,255; | |
--app-primary-shade: #4640cc; /* Slightly darker shade */ | |
--app-primary-tint: #615ce6; /* Slightly lighter tint */ | |
--ion-color-primary: var(--app-primary); | |
--ion-color-primary-rgb: var(--app-primary-rgb); | |
--ion-color-primary-contrast: var(--app-primary-contrast); | |
--ion-color-primary-contrast-rgb: var(--app-primary-contrast-rgb); | |
--ion-color-primary-shade: var(--app-primary-shade); | |
--ion-color-primary-tint: var(--app-primary-tint); | |
} | |
/* Global Style Tweaks */ | |
body { | |
font-family: var(--ion-font-family); | |
-webkit-font-smoothing: antialiased; | |
-moz-osx-font-smoothing: grayscale; | |
} | |
.video-preview-container { | |
border-radius: var(--ion-border-radius); | |
overflow: hidden; | |
position: relative; /* For overlay */ | |
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); /* Subtle shadow */ | |
} | |
.video-title-overlay { | |
position: absolute; | |
bottom: 16px; | |
left: 16px; | |
color: white; | |
font-size: 1.2em; | |
font-weight: 600; | |
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.5); | |
} | |
.hidden { | |
display: none ; | |
} | |
</style> | |
</head> | |
<body> | |
<ion-app> | |
<ion-header> | |
<ion-toolbar color="primary"> | |
<ion-title>Autotutorial.ai</ion-title> | |
<ion-buttons slot="end"> | |
<ion-button id="helpButton"> | |
<ion-icon slot="icon-only" name="help-circle"></ion-icon> | |
</ion-button> | |
</ion-buttons> | |
</ion-toolbar> | |
</ion-header> | |
<ion-content class="ion-padding"> | |
<!-- Main Content: Generate Tutorial --> | |
<div id="generate-content"> | |
<ion-card> | |
<ion-card-header> | |
<ion-card-title>Generate Tutorial</ion-card-title> | |
<ion-card-subtitle>Create a new video tutorial</ion-card-subtitle> | |
</ion-card-header> | |
<ion-card-content> | |
<ion-list lines="full"> | |
<ion-item> | |
<ion-label position="floating">Tutorial Topic</ion-label> | |
<ion-input id="tutorialTopic" value="CSS Grid Layout"></ion-input> | |
</ion-item> | |
<ion-item> | |
<ion-label>Duration</ion-label> | |
<ion-select id="tutorialDuration" value="10"> | |
<ion-select-option value="5">5 Minutes</ion-select-option> | |
<ion-select-option value="10">10 Minutes</ion-select-option> | |
<ion-select-option value="15">15 Minutes</ion-select-option> | |
<ion-select-option value="20">20 Minutes</ion-select-option> | |
</ion-select> | |
</ion-item> | |
<ion-item> | |
<ion-label position="floating">OpenAI API Key (Optional)</ion-label> | |
<ion-input id="openaiApiKey" type="text" placeholder="sk-..." value="sk-DEMO_OPENAI_KEY"></ion-input> | |
</ion-item> | |
<ion-item> | |
<ion-label position="floating">Video Gen. API Key (Optional)</ion-label> | |
<ion-input id="videoGenApiKey" type="text" placeholder="vg-..." value="vg-DEMO_VIDEO_KEY"></ion-input> | |
</ion-item> | |
</ion-list> | |
<ion-button id="generateButton" expand="block" color="primary" style="margin-top: 20px;"> | |
<ion-icon slot="start" name="play-circle"></ion-icon> | |
Generate | |
</ion-button> | |
<ion-button id="resetButton" expand="block" color="medium" style="margin-top: 10px;" class="hidden"> | |
<ion-icon slot="start" name="refresh"></ion-icon> | |
Reset | |
</ion-button> | |
</ion-card-content> | |
</ion-card> | |
<ion-card id="statusCard" class="hidden"> | |
<ion-card-header> | |
<ion-card-title>Status</ion-card-title> | |
</ion-card-header> | |
<ion-card-content> | |
<ion-list id="statusList"></ion-list> | |
<ion-progress-bar id="progressBar" type="indeterminate"></ion-progress-bar> <!-- Changed to indeterminate --> | |
<ion-text color="danger" id="errorMessage" class="hidden"></ion-text> | |
</ion-card-content> | |
</ion-card> | |
<ion-card id="previewCard" class="hidden"> | |
<ion-card-header> | |
<ion-card-title>Tutorial Preview</ion-card-title> | |
</ion-card-header> | |
<ion-card-content> | |
<div class="video-preview-container"> | |
<video id="tutorialVideo" controls width="100%" preload="metadata"> | |
<source src="https://vjs.zencdn.net/v/oceans.mp4" type="video/mp4"> <!-- Better demo video --> | |
Your browser does not support the video tag. | |
</video> | |
<div id="videoTitleOverlay" class="video-title-overlay"></div> | |
</div> | |
<ion-button expand="block" id="downloadButton" style="margin-top: 16px;"> | |
<ion-icon slot="start" name="download"></ion-icon> | |
Download (Demo) | |
</ion-button> | |
</ion-card-content> | |
</ion-card> | |
</div> | |
<!-- Tutorial History (Initially Hidden) --> | |
<div id="history-content" class="hidden"> | |
<ion-card> | |
<ion-card-header> | |
<ion-card-title>Tutorial History</ion-card-title> | |
<ion-card-subtitle>View your past tutorials</ion-card-subtitle> | |
</ion-card-header> | |
<ion-card-content> | |
<ion-list id="historyList"> | |
<ion-item button class="history-item" data-title="React Hooks Guide"> | |
<ion-label>React Hooks Guide</ion-label> | |
<ion-note slot="end">Oct 28, 2023</ion-note> <!-- Use ion-note for date --> | |
</ion-item> | |
<ion-item button class="history-item" data-title="Vue.js Composition API"> | |
<ion-label>Vue.js Composition API</ion-label> | |
<ion-note slot="end">Oct 27, 2023</ion-note> | |
</ion-item> | |
<ion-item button class="history-item" data-title="Python for Data Science"> | |
<ion-label>Python for Data Science</ion-label> | |
<ion-note slot="end">Oct 26, 2023</ion-note> | |
</ion-item> | |
</ion-list> | |
<ion-text color="medium"> | |
<p>This is a simulated history for demo purposes.</p> | |
</ion-text> | |
</ion-card-content> | |
</ion-card> | |
</div> | |
<!-- Settings (Initially Hidden) --> | |
<div id="settings-content" class="hidden"> | |
<ion-card> | |
<ion-card-header> | |
<ion-card-title>Settings</ion-card-title> | |
<ion-card-subtitle>Configure app preferences</ion-card-subtitle> | |
</ion-card-header> | |
<ion-card-content> | |
<ion-list lines="full"> | |
<ion-item> | |
<ion-label>Default Duration</ion-label> | |
<ion-select id="defaultDuration" value="10"> | |
<ion-select-option value="5">5 Minutes</ion-select-option> | |
<ion-select-option value="10">10 Minutes</ion-select-option> | |
<ion-select-option value="15">15 Minutes</ion-select-option> | |
<ion-select-option value="20">20 Minutes</ion-select-option> | |
</ion-select> | |
</ion-item> | |
<ion-item> | |
<ion-label>Default Quality</ion-label> | |
<ion-select id="defaultQuality" value="720p"> | |
<ion-select-option value="720p">720p</ion-select-option> | |
<ion-select-option value="1080p">1080p</ion-select-option> | |
<ion-select-option value="4k">4K (Premium)</ion-select-option> | |
</ion-select> | |
</ion-item> | |
<ion-item> | |
<ion-label>Voiceover Language</ion-label> | |
<ion-select id="defaultVoiceover" value="en-US"> | |
<ion-select-option value="en-US">English (US)</ion-select-option> | |
<ion-select-option value="en-GB">English (UK)</ion-select-option> | |
<ion-select-option value="es-ES">Spanish (ES)</ion-select-option> | |
</ion-select> | |
</ion-item> | |
<ion-item> | |
<ion-label>Output Format</ion-label> | |
<ion-select id="defaultOutput" value="mp4"> | |
<ion-select-option value="mp4">MP4</ion-select-option> | |
<ion-select-option value="mov">MOV</ion-select-option> | |
<ion-select-option value="webm">WebM</ion-select-option> | |
</ion-select> | |
</ion-item> | |
</ion-list> | |
<ion-button id="applySettingsButton" expand="block" style="margin-top: 20px;"> | |
<ion-icon slot="start" name="save"></ion-icon> | |
Apply Settings | |
</ion-button> | |
</ion-card-content> | |
</ion-card> | |
</div> | |
<!-- Help Modal (Initially Hidden) --> | |
<ion-modal id="helpModal" trigger="helpButton"> | |
<ion-header> | |
<ion-toolbar> | |
<ion-title>Help & Support</ion-title> | |
<ion-buttons slot="end"> | |
<ion-button onclick="closeHelpModal()"> | |
<ion-icon slot="icon-only" name="close"></ion-icon> | |
</ion-button> | |
</ion-buttons> | |
</ion-toolbar> | |
</ion-header> | |
<ion-content class="ion-padding"> | |
<p>Welcome to Autotutorial.ai, the app that makes creating video tutorials a breeze!</p> | |
<p><strong>Getting Started:</strong></p> | |
<ol> | |
<li>Navigate to the <strong>Generate Tutorial</strong> section.</li> | |
<li>Enter the topic of your tutorial in the <strong>Tutorial Topic</strong> field.</li> | |
<li>Select your desired tutorial length using the <strong>Duration</strong> dropdown.</li> | |
<li><em>(Optional)</em> If you have API keys for OpenAI and a video generation service, you can enter them in the respective fields for enhanced tutorial creation. This is not required for basic functionality.</li> | |
<li>Click the <strong>Generate</strong> button to start the automated tutorial creation process.</li> | |
<li>Monitor the <strong>Status</strong> section to see the progress of your tutorial generation.</li> | |
<li>Once complete, preview your generated tutorial in the <strong>Tutorial Preview</strong> section.</li> | |
</ol> | |
<p><strong>Tutorial History</strong> allows you to view a list of your previously generated tutorials (simulated in this demo).</p> | |
<p><strong>Settings</strong> let you configure default preferences for new tutorials (also simulated in this demo).</p> | |
<p>For any questions or support, please contact us at: <a href="mailto:[email protected]">[email protected]</a></p> | |
</ion-content> | |
</ion-modal> | |
</ion-content> | |
<ion-footer> | |
<ion-toolbar> | |
<ion-tabs> | |
<ion-tab-bar slot="bottom"> | |
<ion-tab-button tab="generate" onclick="showSection('generate')"> | |
<ion-icon name="play-circle"></ion-icon> | |
<ion-label>Generate</ion-label> | |
</ion-tab-button> | |
<ion-tab-button tab="history" onclick="showSection('history')"> | |
<ion-icon name="time"></ion-icon> | |
<ion-label>History</ion-label> | |
</ion-tab-button> | |
<ion-tab-button tab="settings" onclick="showSection('settings')"> | |
<ion-icon name="settings"></ion-icon> | |
<ion-label>Settings</ion-label> | |
</ion-tab-button> | |
</ion-tab-bar> | |
</ion-tabs> | |
</ion-toolbar> | |
</ion-footer> | |
</ion-app> | |
<!-- Ionic JavaScript --> | |
<script type="module" src="https://cdn.jsdelivr.net/npm/@ionic/core@7/dist/ionic/ionic.esm.js"></script> | |
<script nomodule src="https://cdn.jsdelivr.net/npm/@ionic/core@7/dist/ionic/ionic.js"></script> | |
<script> | |
function showSection(sectionId) { | |
// Hide all content sections | |
document.getElementById('generate-content').classList.add('hidden'); | |
document.getElementById('history-content').classList.add('hidden'); | |
document.getElementById('settings-content').classList.add('hidden'); | |
// Remove active class from all tab buttons. Important for visual state. | |
document.querySelectorAll('ion-tab-button').forEach(btn => btn.classList.remove('tab-selected')); | |
// Show the selected section | |
document.getElementById(`${sectionId}-content`).classList.remove('hidden'); | |
// Highlight active tab button | |
document.querySelector(`ion-tab-button[tab="${sectionId}"]`).classList.add('tab-selected'); | |
// Hide preview and status cards when switching tabs *away* from generate | |
if (sectionId !== 'generate') { | |
document.getElementById('previewCard').classList.add('hidden'); | |
document.getElementById('statusCard').classList.add('hidden'); | |
} | |
} | |
function closeHelpModal() { | |
const modal = document.getElementById('helpModal'); | |
modal.dismiss(); | |
} | |
document.addEventListener('DOMContentLoaded', () => { | |
const generateButton = document.getElementById('generateButton'); | |
const resetButton = document.getElementById('resetButton'); | |
const statusCard = document.getElementById('statusCard'); | |
const statusList = document.getElementById('statusList'); | |
const progressBar = document.getElementById('progressBar'); | |
const previewCard = document.getElementById('previewCard'); | |
const videoTitleOverlay = document.getElementById('videoTitleOverlay'); | |
const tutorialVideo = document.getElementById('tutorialVideo'); | |
const errorMessage = document.getElementById('errorMessage'); | |
// History click handler (simulated "load") | |
const historyList = document.getElementById('historyList'); | |
historyList.addEventListener('click', (event) => { | |
const item = event.target.closest('.history-item'); // Find closest item | |
if (item) { | |
const title = item.dataset.title; | |
videoTitleOverlay.textContent = title; | |
previewCard.classList.remove('hidden'); // Show preview card | |
// You could simulate loading different videos here if you have multiple sources | |
// tutorialVideo.src = 'another_video.mp4'; | |
// tutorialVideo.load(); | |
statusCard.classList.remove('hidden'); // Show status | |
statusList.innerHTML = `<ion-item><ion-label color="primary">Loading ${title}...</ion-label></ion-item>`; // Show loading message | |
} | |
}); | |
// Apply Settings simulation | |
document.getElementById('applySettingsButton').addEventListener('click', () => { | |
// Simulate setting changes (could visually update UI elements if desired) | |
statusCard.classList.remove('hidden'); | |
statusList.innerHTML = `<ion-item><ion-label color="success">Settings applied!</ion-label></ion-item>`; | |
}); | |
// Generate Button Click Handler | |
generateButton.addEventListener('click', () => { | |
const topic = document.getElementById('tutorialTopic').value; | |
const duration = document.getElementById('tutorialDuration').value; | |
const openaiKey = document.getElementById('openaiApiKey').value; | |
const videoGenKey = document.getElementById('videoGenApiKey').value; | |
// Clear previous status and error messages | |
statusList.innerHTML = ''; | |
errorMessage.textContent = ''; | |
errorMessage.classList.add('hidden'); | |
// Show status and progress bar | |
statusCard.classList.remove('hidden'); | |
progressBar.type = 'indeterminate'; // Start indeterminate | |
// Hide preview card (until generation is complete) | |
previewCard.classList.add('hidden'); | |
// Hide Generate, show Reset | |
generateButton.classList.add('hidden'); | |
resetButton.classList.remove('hidden'); | |
const steps = [ | |
{ message: "Analyzing Topic...", delay: 800 }, | |
{ message: "Generating Script Outline (AI)...", delay: 1500 }, | |
{ message: "Writing Script Content (AI)...", delay: 2200 }, | |
{ message: "Synthesizing Voiceover (AI)...", delay: 1800 }, | |
{ message: "Selecting Relevant Visuals...", delay: 1600 }, | |
{ message: "Assembling Video Scenes...", delay: 2400 }, | |
{ message: "Adding Transitions and Effects...", delay: 1500 }, | |
{ message: "Rendering Final Video...", delay: 3000 } | |
]; | |
let currentStep = 0; | |
function simulateStep() { | |
if (currentStep < steps.length) { | |
const listItem = document.createElement('ion-item'); | |
listItem.innerHTML = `<ion-label>${steps[currentStep].message}</ion-label>`; | |
statusList.appendChild(listItem); | |
// Simulate varying delays | |
const delay = steps[currentStep].delay + (Math.random() * 500 - 250); // +/- 250ms variation | |
setTimeout(() => { | |
currentStep++; | |
simulateStep(); | |
}, delay); | |
} else { | |
// Generation "complete" | |
progressBar.type = 'determinate'; // Switch to determinate for final "fill" | |
progressBar.value = 1; // Set to 100% | |
statusList.innerHTML = `<ion-item><ion-label color="success">Tutorial generation complete!</ion-label></ion-item>`; | |
videoTitleOverlay.textContent = `${topic} (${duration} min)`; | |
previewCard.classList.remove('hidden'); | |
// Show generate, hide reset | |
generateButton.classList.remove('hidden'); | |
resetButton.classList.add('hidden'); | |
} | |
} | |
simulateStep(); | |
}); | |
// Reset Button Click Handler | |
resetButton.addEventListener('click', () => { | |
// Clear form fields | |
document.getElementById('tutorialTopic').value = ''; | |
document.getElementById('tutorialDuration').value = '10'; | |
document.getElementById('openaiApiKey').value = ''; | |
document.getElementById('videoGenApiKey').value = ''; | |
// Hide status, error, and preview cards | |
statusCard.classList.add('hidden'); | |
errorMessage.classList.add('hidden'); | |
previewCard.classList.add('hidden'); | |
// Clear status list | |
statusList.innerHTML = ''; | |
// Reset progress bar | |
progressBar.value = 0; | |
progressBar.type = 'indeterminate'; | |
// Show Generate, hide Reset | |
generateButton.classList.remove('hidden'); | |
resetButton.classList.add('hidden'); | |
}); | |
document.getElementById('downloadButton').addEventListener('click', () => { | |
// Simulate download | |
statusCard.classList.remove('hidden'); | |
statusList.innerHTML = `<ion-item><ion-label color="primary">Downloading (simulated)...</ion-label></ion-item>`; | |
}); | |
}); | |
</script> | |
</body> | |
</html> |