Merge commit '14b320bbdea91c3614bef5721dedf4ac6c8bdac5'

This commit is contained in:
ForeverPyrite
2025-01-07 22:31:51 -05:00
6 changed files with 206 additions and 165 deletions

View File

@@ -4,7 +4,6 @@ __pycache__
*.pyd *.pyd
*.env *.env
*venv/ *venv/
.env
*.git *.git
.gitignore .gitignore
Dockerfile Dockerfile

View File

@@ -1,63 +1,68 @@
from flask import Flask, render_template, Response, request import logging
from main import get_auto_transcript, get_video_id, create_and_stream, log, output_stream, awaiter import os
from asyncio import sleep from flask import Flask, render_template, Response, request, session
from datetime import datetime from main import yoink, process, user_streams, stream_lock
import pytz import uuid # Import UUID
import threading
app = Flask(__name__, static_folder="website/static", template_folder="website") 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('/') @app.route('/')
def home(): 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']) @app.route('/process_url', methods=['POST'])
def process_url(): def process_url():
global thread session_id = session.get('id')
log(f"\n\n\n## New Entry at {datetime.now(pytz.timezone('America/New_York')).strftime('%Y-%m-%d %H:%M:%S')}\n\n") 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'] url = request.form['url']
log(f"URL: {url}\n") logging.info(f"Received URL for processing from session {session_id}: {url}")
# Extract the video ID from the URL success, msg, status_code, = process(url, session_id)
video_id = get_video_id(url) # Modify this function to accept the URL
if not video_id: if success:
log(f"Could not parse video id from URL: {url}") logging.info(f"Processing started successfully for session {session_id}.")
return "Couldn't parse video ID from URL. (Are you sure you entered a valid YouTube.com or YouTu.be URL?)" return Response("Processing started. Check /stream_output for updates.", content_type='text/plain', status=200)
log(f"Video ID: {video_id}\n\n") else:
logging.error(f"Processing failed for session {session_id}: {msg}")
# Get the transcript for that video ID return Response(msg, content_type='text/plain', status=status_code)
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
@app.route('/stream_output') @app.route('/stream_output')
def stream_output(): def stream_output():
def yoink(): session_id = session.get('id')
log("<details>\n<summary>Starting stream thread...</summary>\n\n") if not session_id or session_id not in user_streams:
thread.start() logging.warning(f"Stream requested without a valid session ID: {session_id}")
# Start streaming output from output_stream return Response("No active stream for this session.", content_type='text/plain', status=400)
log("Starting to stream output.") logging.info(f"Streaming output requested for session {session_id}.")
while not output_stream.done: return Response(yoink(session_id), content_type='text/plain', status=200)
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</details>\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)
if __name__ == '__main__':
logging.info("Starting Flask application.")
app.run(debug=True, threaded=True) # Enable threaded to handle multiple requests
if __name__ == '__main__':
app.run(debug=True)

View File

@@ -2,29 +2,27 @@
<html lang="en-us"> <html lang="en-us">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>Screw You Bardo</title> <title>Screw You Bardo</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}"> <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
<link rel="icon" type="image/x-icon" href="https://www.foreverpyrite.com/favicon.ico"> <link rel="icon" type="image/x-icon" href="https://www.foreverpyrite.com/favicon.ico">
<script src="{{ url_for('static', filename='script.js')}}"></script> <script defer src="{{ url_for('static', filename='script.js') }}"></script>
</head> </head>
<body> <body>
<main class="container">
<section id="response-section">
<pre id="response-area">Response will appear here.</pre>
</section>
<div class="content"> <section class="form-section">
<pre id="response-area">Response will appear here.</pre> <form id="url-form">
<div class="form_box"> <input type="url" id="url_box" name="url" placeholder="Paste the lecture URL here." required autofocus>
<input id="url_box" placeholder="Paste the lecture URL here." autofocus></input> <button type="submit" id="submit">Submit</button>
<input id="submit" type="submit" onclick=""></input> </form>
</div> </section>
</main>
</div>
</body> </body>
</html> </html>

View File

@@ -1,18 +1,25 @@
document.addEventListener("DOMContentLoaded", (event) => { document.addEventListener("DOMContentLoaded", () => {
const response_area = document.getElementById('response-area'); const responseArea = document.getElementById('response-area');
const submit_button = document.getElementById('submit') const responseSection = document.getElementById('response-section');
submit_button.addEventListener('click', function() { const submitButton = document.getElementById('submit');
var url = document.getElementById('url_box').value; 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) { if (!url) {
response_area.innerText = 'Please enter a URL.'; responseArea.innerText = 'Please enter a URL.';
return; return;
} }
else {
document.getElementById('url_box').value = ""; // Clear the input and update UI
} urlBox.value = "";
submitButton.disabled = true;
// First, process the URL responseArea.innerText = 'Processing...';
// Process the URL
fetch('/process_url', { fetch('/process_url', {
method: 'POST', method: 'POST',
headers: { headers: {
@@ -24,57 +31,58 @@ document.addEventListener("DOMContentLoaded", (event) => {
if (!response.ok) { if (!response.ok) {
throw new Error('Network response was not ok'); throw new Error('Network response was not ok');
} }
// Extract the text from the response body return response.text();
return response.text(); // Use .json() if the response is JSON
}) })
.then(text => { .then(text => {
submit_button.style.display = "none";
if (text === "Processing started. Check /stream_output for updates.") { if (text === "Processing started. Check /stream_output for updates.") {
streamOutput(response_area); streamOutput(responseArea);
} else { } else {
response_area.innerText = text; // Show any other response message responseArea.innerText = text;
submit_button.style.display = "flex"; submitButton.disabled = false;
} }
}) })
.catch(error => { .catch(error => {
console.error('Error processing URL:', error); console.error('Error processing URL:', error);
response_area.innerText = 'Error processing URL: ' + error.message; responseArea.innerText = 'Error processing URL: ' + error.message;
submit_button.style.display = "flex"; 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;
});
}

View File

@@ -1,7 +1,5 @@
@font-face { @font-face {
font-family: 'nimbus_sans_d_otlight'; font-family: 'NimbusSansD';
src: url('font-files/nimbus-sans-d-ot-light.woff2') format('woff2'), src: url('font-files/nimbus-sans-d-ot-light.woff2') format('woff2'),
url('font-files/nimbus-sans-d-ot-light.woff') format('woff'); url('font-files/nimbus-sans-d-ot-light.woff') format('woff');
font-weight: normal; font-weight: normal;
@@ -9,70 +7,103 @@
} }
* { * {
font-family: 'nimbus_sans_d_otlight'; box-sizing: border-box;
color: white; margin: 0;
padding: 0;
font-family: 'NimbusSansD', sans-serif;
color: #FFFFFF;
} }
body { body {
display: flex; display: flex;
flex-direction: column; justify-content: center;
width: 100%; align-items: center;
max-width: 100vw; height: 100vh;
height: 100%; background-color: #1F1F1F;
min-height: 100vh;
max-height: 100vh;
margin: 0;
background-color: rgb(31, 31, 31);
} }
body .content { .container {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-self: center; width: 85vw;
width: 75%; height: 90vh;
max-width: 65vw; background-color: #2E2E2E;
height: 100%; border-radius: 10px;
min-height: 100vh; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
max-height: 100vh; 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 { #response-area {
display: block; white-space: pre-wrap;
height: 90%;
min-height: 90vh;
text-wrap: wrap;
flex-wrap: wrap;
align-content: flex-end;
overflow-y: auto;
} }
.form_box { #url-form {
display: flex; display: flex;
width: 100%; gap: 10px;
justify-content: space-between;
align-content: space-around;
} }
#url_box { #url_box {
display: flex; flex: 1;
height: 5%; padding: 10px 15px;
min-height: 5vh; border: none;
width: 90%; border-radius: 5px;
min-width: 80vh; background-color: #4A4A4A;
background-color: rgb(31, 31, 31); color: #FFFFFF;
font-size: 1rem;
outline: none;
}
#url_box::placeholder {
color: #B0B0B0;
} }
#submit { #submit {
display: flex; padding: 10px 20px;
width: 5%; border: none;
min-width: 3vw; border-radius: 5px;
background-color: rgb(49, 49, 49); background-color: #5A5A5A;
} color: #FFFFFF;
#submit:hover { font-size: 1rem;
cursor: pointer; cursor: pointer;
background-color: rgb(31, 31, 31); transition: background-color 0.3s ease;
} }
input { #submit:hover {
border-radius: 15px; 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;
}
}

Binary file not shown.