diff --git a/.dockerignore b/.dockerignore index 7425896..9547660 100644 --- a/.dockerignore +++ b/.dockerignore @@ -4,7 +4,6 @@ __pycache__ *.pyd *.env *venv/ -.env *.git .gitignore Dockerfile diff --git a/app/app.py b/app/app.py index 1d5882c..739ce92 100644 --- a/app/app.py +++ b/app/app.py @@ -1,63 +1,68 @@ -from flask import Flask, render_template, Response, request -from main import get_auto_transcript, get_video_id, create_and_stream, log, output_stream, awaiter -from asyncio import sleep -from datetime import datetime -import pytz -import threading +import logging +import os +from flask import Flask, render_template, Response, request, session +from main import yoink, process, user_streams, stream_lock +import uuid # Import UUID - - app = Flask(__name__, static_folder="website/static", template_folder="website") +app.secret_key = os.urandom(24) # Necessary for using sessions + + +# Configure logging +logging.basicConfig( + filename='./logs/app.log', + level=logging.DEBUG, + format='%(asctime)s %(levelname)s: %(message)s', + datefmt='%Y-%m-%d %H:%M:%S' +) + +def create_session(): + session_id = str(uuid.uuid4()) + # This should never happen but I'm putting the logic there anyways + try: + if user_streams[session_id]: + session_id = create_session() + except KeyError: + pass + return session_id + @app.route('/') def home(): - return render_template('index.html') + session_id = create_session() + session['id'] = session_id + logging.info(f"Home page accessed. Assigned initial session ID: {session_id}") + return render_template('index.html', session_id=session_id) @app.route('/process_url', methods=['POST']) def process_url(): - global thread - log(f"\n\n\n## New Entry at {datetime.now(pytz.timezone('America/New_York')).strftime('%Y-%m-%d %H:%M:%S')}\n\n") + session_id = session.get('id') + if not session_id: + session_id = create_session() + session['id'] = session_id + logging.info(f"No existing session. Created new session ID: {session_id}") + url = request.form['url'] - log(f"URL: {url}\n") - # Extract the video ID from the URL - video_id = get_video_id(url) # Modify this function to accept the URL - if not video_id: - log(f"Could not parse video id from URL: {url}") - return "Couldn't parse video ID from URL. (Are you sure you entered a valid YouTube.com or YouTu.be URL?)" - log(f"Video ID: {video_id}\n\n") - - # Get the transcript for that video ID - transcript = get_auto_transcript(video_id) - if (not transcript): - log("## Error: could not retrieve transcript, Assistant won't be called.") - return "Successfully parsed video ID from URL, however the ID was either invalid, the transcript was disabled by the video owner, or some other error was raised because of YouTube." - - thread = threading.Thread(name="create_stream", target=create_and_stream, args=(transcript,)) # The comma here is very intentional, it's so that it iterates it as a tuple rather than iterateing the string. - log("Stream preperation complete, sending reply...\n\n") - return Response("Processing started. Check /stream_output for updates.", content_type='text/plain', status=200) # Add more detailed output if needed + logging.info(f"Received URL for processing from session {session_id}: {url}") + success, msg, status_code, = process(url, session_id) + + if success: + logging.info(f"Processing started successfully for session {session_id}.") + return Response("Processing started. Check /stream_output for updates.", content_type='text/plain', status=200) + else: + logging.error(f"Processing failed for session {session_id}: {msg}") + return Response(msg, content_type='text/plain', status=status_code) @app.route('/stream_output') def stream_output(): - def yoink(): - log("
\nStarting stream thread...\n\n") - thread.start() - # Start streaming output from output_stream - log("Starting to stream output.") - while not output_stream.done: - if output_stream.buffer != []: - delta = output_stream.buffer.pop(0) - yield bytes(delta, encoding="utf-8") - else: - awaiter(sleep(0.05)) - log(f"\nStream successfully completely.\n\n
\n\n---\n\n### Completed Assistant Response:\n{output_stream.response}\n\n---\n\n") - output_stream.reset() - thread.join() - log("\n### Task completed sucessfully without errors!") - return - return Response(yoink(), content_type='text/plain', status=200) + session_id = session.get('id') + if not session_id or session_id not in user_streams: + logging.warning(f"Stream requested without a valid session ID: {session_id}") + return Response("No active stream for this session.", content_type='text/plain', status=400) + logging.info(f"Streaming output requested for session {session_id}.") + return Response(yoink(session_id), content_type='text/plain', status=200) - - - -if __name__ == '__main__': - app.run(debug=True) \ No newline at end of file +if __name__ == '__main__': + logging.info("Starting Flask application.") + app.run(debug=True, threaded=True) # Enable threaded to handle multiple requests + \ No newline at end of file diff --git a/app/website/index.html b/app/website/index.html index 4461d05..fd97afb 100644 --- a/app/website/index.html +++ b/app/website/index.html @@ -2,29 +2,27 @@ - - Screw You Bardo - - - + +
+
+
Response will appear here.
+
-
-
Response will appear here.
-
- - -
- - -
+
+
+ + +
+
+
\ No newline at end of file diff --git a/app/website/static/script.js b/app/website/static/script.js index d28aab1..891ad5e 100644 --- a/app/website/static/script.js +++ b/app/website/static/script.js @@ -1,18 +1,25 @@ -document.addEventListener("DOMContentLoaded", (event) => { - const response_area = document.getElementById('response-area'); - const submit_button = document.getElementById('submit') - submit_button.addEventListener('click', function() { - var url = document.getElementById('url_box').value; +document.addEventListener("DOMContentLoaded", () => { + const responseArea = document.getElementById('response-area'); + const responseSection = document.getElementById('response-section'); + const submitButton = document.getElementById('submit'); + const urlForm = document.getElementById('url-form'); + const urlBox = document.getElementById('url_box'); + + urlForm.addEventListener('submit', function(event) { + event.preventDefault(); // Prevent form from submitting the traditional way + const url = urlBox.value.trim(); if (!url) { - response_area.innerText = 'Please enter a URL.'; + responseArea.innerText = 'Please enter a URL.'; return; } - else { - document.getElementById('url_box').value = ""; - } - - // First, process the URL + + // Clear the input and update UI + urlBox.value = ""; + submitButton.disabled = true; + responseArea.innerText = 'Processing...'; + + // Process the URL fetch('/process_url', { method: 'POST', headers: { @@ -24,57 +31,58 @@ document.addEventListener("DOMContentLoaded", (event) => { if (!response.ok) { throw new Error('Network response was not ok'); } - // Extract the text from the response body - return response.text(); // Use .json() if the response is JSON + return response.text(); }) .then(text => { - submit_button.style.display = "none"; if (text === "Processing started. Check /stream_output for updates.") { - streamOutput(response_area); + streamOutput(responseArea); } else { - response_area.innerText = text; // Show any other response message - submit_button.style.display = "flex"; + responseArea.innerText = text; + submitButton.disabled = false; } }) .catch(error => { console.error('Error processing URL:', error); - response_area.innerText = 'Error processing URL: ' + error.message; - submit_button.style.display = "flex"; + responseArea.innerText = 'Error processing URL: ' + error.message; + submitButton.disabled = false; }); }); + + function streamOutput(responseArea) { + // Fetch the streaming output + fetch('/stream_output') + .then(response => { + if (!response.ok) { + throw new Error('Network response was not ok'); + } + const reader = response.body.getReader(); + const decoder = new TextDecoder("utf-8"); + + responseArea.innerHTML = ""; + + function readStream() { + reader.read().then(({ done, value }) => { + if (done) { + submitButton.disabled = false; + return; + } + const chunk = decoder.decode(value, { stream: true }); + responseArea.innerHTML += chunk; + responseSection.scrollTop = responseSection.scrollHeight + readStream(); + }).catch(error => { + console.error('Error reading stream:', error); + responseArea.innerText = 'Error reading stream: ' + error.message; + submitButton.disabled = false; + }); + } + + readStream(); + }) + .catch(error => { + console.error('Error fetching stream:', error); + responseArea.innerText = 'Error fetching stream: ' + error.message; + submitButton.disabled = false; + }); + } }); - -function streamOutput(response_area) { - // Fetch the streaming output - const streamResponsePromise = fetch('/stream_output'); - response_area.innerHTML = "" - - streamResponsePromise - .then(response => { - const reader = response.body.getReader(); - const decoder = new TextDecoder("utf-8"); - - function readStream() { - reader.read().then(({ done, value }) => { - if(done) { - document.getElementById('submit').style.display = "flex"; - return - } - // Decode and process the chunk - const chunk = decoder.decode(value, { stream: true }); - response_area.innerHTML += chunk; - response_area.scrollTop = response_area.scrollHeight - - // Continue reading - readStream(); - }); - } - - // Start reading the stream - readStream(); - }) - .catch(error => { - console.error('Error fetching stream:', error); - response_area.innerText = 'Error fetching stream: ' + error.message; - }); -} diff --git a/app/website/static/style.css b/app/website/static/style.css index eacb116..2db8550 100644 --- a/app/website/static/style.css +++ b/app/website/static/style.css @@ -1,7 +1,5 @@ - - @font-face { - font-family: 'nimbus_sans_d_otlight'; + font-family: 'NimbusSansD'; src: url('font-files/nimbus-sans-d-ot-light.woff2') format('woff2'), url('font-files/nimbus-sans-d-ot-light.woff') format('woff'); font-weight: normal; @@ -9,70 +7,103 @@ } * { - font-family: 'nimbus_sans_d_otlight'; - color: white; + box-sizing: border-box; + margin: 0; + padding: 0; + font-family: 'NimbusSansD', sans-serif; + color: #FFFFFF; } body { display: flex; - flex-direction: column; - width: 100%; - max-width: 100vw; - height: 100%; - min-height: 100vh; - max-height: 100vh; - margin: 0; - background-color: rgb(31, 31, 31); + justify-content: center; + align-items: center; + height: 100vh; + background-color: #1F1F1F; } -body .content { +.container { display: flex; flex-direction: column; - align-self: center; - width: 75%; - max-width: 65vw; - height: 100%; - min-height: 100vh; - max-height: 100vh; + width: 85vw; + height: 90vh; + background-color: #2E2E2E; + border-radius: 10px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); + overflow: hidden; +} + +#response-section { + flex: 1; + padding: 20px; + background-color: #1E1E1E; + overflow-y: auto; + font-size: 1rem; + line-height: 1.5; + scroll-behavior: smooth; +} + +.form-section { + padding: 15px 20px; + background-color: #3A3A3A; } #response-area { - display: block; - height: 90%; - min-height: 90vh; - text-wrap: wrap; - flex-wrap: wrap; - align-content: flex-end; - overflow-y: auto; + white-space: pre-wrap; } -.form_box { +#url-form { display: flex; - width: 100%; - justify-content: space-between; - align-content: space-around; + gap: 10px; } #url_box { - display: flex; - height: 5%; - min-height: 5vh; - width: 90%; - min-width: 80vh; - background-color: rgb(31, 31, 31); + flex: 1; + padding: 10px 15px; + border: none; + border-radius: 5px; + background-color: #4A4A4A; + color: #FFFFFF; + font-size: 1rem; + outline: none; +} + +#url_box::placeholder { + color: #B0B0B0; } #submit { - display: flex; - width: 5%; - min-width: 3vw; - background-color: rgb(49, 49, 49); -} -#submit:hover { + padding: 10px 20px; + border: none; + border-radius: 5px; + background-color: #5A5A5A; + color: #FFFFFF; + font-size: 1rem; cursor: pointer; - background-color: rgb(31, 31, 31); + transition: background-color 0.3s ease; } -input { - border-radius: 15px; +#submit:hover { + background-color: #7A7A7A; } + +#submit:disabled { + background-color: #3A3A3A; + cursor: not-allowed; +} + +/* Responsive Adjustments */ +@media (max-width: 600px) { + .container { + height: 95vh; + } + + #url_box { + font-size: 0.9rem; + } + + #submit { + font-size: 0.9rem; + padding: 10px; + } +} \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 1569171..11688ce 100644 Binary files a/requirements.txt and b/requirements.txt differ