diff --git a/app/main.py b/app/main.py
index c1f5d58..1628239 100644
--- a/app/main.py
+++ b/app/main.py
@@ -1,102 +1,134 @@
-import os
+# To parse video ids
import re
-import threading
-import pytz
-from datetime import datetime
-from dotenv import load_dotenv
-from youtube_transcript_api import YouTubeTranscriptApi, _errors
+
+# Youtube Transcript stuff import
+import youtube_transcript_api._errors
+from youtube_transcript_api import YouTubeTranscriptApi
from youtube_transcript_api.formatters import TextFormatter
-from openai import AssistantEventHandler, OpenAI
+
+# OpenAI API stuff import
+from openai import AssistantEventHandler
+from openai import OpenAI
+
+### For streaming
from typing_extensions import override
import asyncio
+awaiter = asyncio.run
+
+# The StreamOutput class to handle streaming
+class StreamOutput:
+
+ def __init__(self):
+ self.delta: str = ""
+ self.response: str = ""
+ self.done: bool = False
+ self.buffer: list = []
+
+ def reset(self):
+ self.delta = ""
+ self.response = ""
+ self.done = False
+ self.buffer: list = []
+
+ async def send_delta(self, delta):
+ self.delta = delta
+ self.response += delta
+ def get_index(list):
+ if len(list) == 0:
+ return 0
+ else:
+ return len(list)-1
+ if self.buffer != []:
+ try:
+ if self.delta != self.buffer[get_index(self.buffer)]:
+ self.buffer.append(delta)
+ except IndexError as index_error:
+ log(f"\nCaught IndexError: {str(index_error)}")
+ self.buffer.append(delta)
+ else: self.buffer.append(delta)
+
+# To get the env var
+from dotenv import load_dotenv
+import os
-# Load environment variables
load_dotenv()
# For logging
-def log(message: str):
- timestamp = datetime.now(pytz.timezone('America/New_York')).strftime('%Y-%m-%d %H:%M:%S')
+import pytz
+from datetime import datetime
+
+def log(message):
+ try:
with open("logs/log.md", "a") as file:
- file.write(f"{timestamp} - {message}\n")
+ file.write(message)
+ except FileNotFoundError:
+ with open("logs/log.md", "x+"):
+ log(message)
-# StreamOutput class to handle streaming
-class StreamOutput:
- def __init__(self):
- self.response = ""
- self.done = False
- self.buffer = []
- self.lock = threading.Lock()
+### OpenAI Config
- def reset(self):
- with self.lock:
- self.response = ""
- self.done = False
- self.buffer = []
+# Setting up OpenAI Client with API Key
+client = OpenAI(
+ organization='org-7ANUFsqOVIXLLNju8Rvmxu3h',
+ project="proj_NGz8Kux8CSka7DRJucAlDCz6",
+ api_key=os.getenv("OPENAI_API_KEY")
+)
- def add_to_buffer(self, delta: str):
- with self.lock:
- self.response += delta
- self.buffer.append(delta)
+# screw bardo assistant that is configured to make notes and 5Q&A based on any given YouTube Transcript
+asst_screw_bardo_id = "asst_JGFaX6uOIotqy5mIJnu3Yyp7"
+
+# This is copy and pasted straight up from the quickstart guide, just appending to an output buffer instead of directly printing:
+class EventHandler(AssistantEventHandler):
+ @override
+ def on_text_created(self, text) -> None:
+ awaiter(output_stream.send_delta("Response Recieved:\n\nScrew-Bardo:\n\n"))
+
+ @override
+ def on_text_delta(self, delta, snapshot):
+ awaiter(output_stream.send_delta(delta.value))
+
+ def on_tool_call_created(self, tool_call):
+ raise Exception("Assistant shouldn't be calling tools.")
+
+def create_and_stream(transcript):
+ with client.beta.threads.create_and_run_stream(
+ assistant_id=asst_screw_bardo_id,
+ thread={
+ "messages": [{"role": "user", "content": transcript}]
+ },
+ event_handler=EventHandler()
+ ) as stream:
+ stream.until_done()
+ output_stream.done = True
+
+def get_video_id(url):
+ youtu_be = r'(?<=youtu.be/)([A-Za-z0-9_-]{11})'
+ youtube_com = r'(?<=youtube\.com\/watch\?v=)([A-Za-z0-9_-]{11})'
+
+ id = re.search(youtu_be, url)
+ if not id:
+ id = re.search(youtube_com, url)
+
+ if not id:
+ # Couldn't parse video ID from URL
+ return None
+
+ return id.group(1)
+
+# Takes the transcript and formats it in basic text before writing it to auto-transcript.txt
+def get_auto_transcript(video_id):
+ trans_api_errors = youtube_transcript_api._errors
+ try:
+ transcript = YouTubeTranscriptApi.get_transcript(video_id, languages=['en'], proxies=None, cookies=None, preserve_formatting=False)
+ except trans_api_errors.TranscriptsDisabled as e:
+ log(f'\n\n# Exception while fetching transcript:\n \n{e}\n')
+ return None
+
+ formatter = TextFormatter() # Ensure that you create an instance of TextFormatter
+
+ txt_transcript = formatter.format_transcript(transcript)
+ return txt_transcript
output_stream = StreamOutput()
-# OpenAI Client Configuration
-client = OpenAI(
- organization='org-7ANUFsqOVIXLLNju8Rvmxu3h',
- project="proj_NGz8Kux8CSka7DRJucAlDCz6",
- api_key=os.getenv("OPENAI_API_KEY")
-)
-
-asst_screw_bardo_id = "asst_JGFaX6uOIotqy5mIJnu3Yyp7"
-
-# Async helper
-def awaiter(coro):
- asyncio.run(coro)
-
-# EventHandler for OpenAI Assistant
-class EventHandler(AssistantEventHandler):
- @override
- def on_text_created(self, text) -> None:
- awaiter(output_stream.send_delta("Response Received:\n\nScrew-Bardo:\n\n"))
-
- @override
- def on_text_delta(self, delta, snapshot):
- awaiter(output_stream.send_delta(delta.value))
-
- def on_tool_call_created(self, tool_call):
- raise Exception("Assistant shouldn't be calling tools.")
-
-def create_and_stream(transcript: str):
- try:
- with client.beta.threads.create_and_run_stream(
- assistant_id=asst_screw_bardo_id,
- thread={
- "messages": [{"role": "user", "content": transcript}]
- },
- event_handler=EventHandler()
- ) as stream:
- stream.until_done()
- output_stream.done = True
- except Exception as e:
- log(f"Error in create_and_stream: {e}")
- output_stream.done = True
-
-def get_video_id(url: str) -> str:
- youtu_be = r'(?<=youtu.be/)([A-Za-z0-9_-]{11})'
- youtube_com = r'(?<=youtube\.com\/watch\?v=)([A-Za-z0-9_-]{11})'
-
- match = re.search(youtu_be, url) or re.search(youtube_com, url)
- if match:
- return match.group(1)
- return None
-
-def get_auto_transcript(video_id: str) -> str:
- try:
- transcript = YouTubeTranscriptApi.get_transcript(video_id, languages=['en'])
- formatter = TextFormatter()
- return formatter.format_transcript(transcript)
- except _errors.TranscriptsDisabled as e:
- log(f'Exception while fetching transcript: {e}')
- except Exception as e:
- log(f'Unexpected error while fetching transcript: {e}')
- return None
\ No newline at end of file
+log(f"\n\n# Main initilized at {datetime.now(pytz.timezone('America/New_York')).strftime('%Y-%m-%d %H:%M:%S')}. Presumeably application starting.\n")
\ No newline at end of file
diff --git a/app/website/index.html b/app/website/index.html
index f283176..a03369a 100644
--- a/app/website/index.html
+++ b/app/website/index.html
@@ -2,29 +2,27 @@
-
-
Screw You Bardo
-
-
-
+
+
+
+ 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..ec2793a 100644
--- a/app/website/static/script.js
+++ b/app/website/static/script.js
@@ -1,18 +1,24 @@
-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 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 +30,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;
+ responseArea.scrollTop = responseArea.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..541c0d4 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,102 @@
}
* {
- 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;
+}
+
+.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