Reggie commited on
Commit
6aa994f
1 Parent(s): 6b13662

Using Flask

Browse files
README.md CHANGED
@@ -1,5 +1,5 @@
1
  ---
2
- title: Live Transcription
3
  colorFrom: blue
4
  colorTo: blue
5
  sdk: gradio
 
1
  ---
2
+ title: Utilities
3
  colorFrom: blue
4
  colorTo: blue
5
  sdk: gradio
app.py CHANGED
@@ -1,253 +1,88 @@
1
- from authlib.integrations.flask_client import OAuth
2
- from authlib.common.security import generate_token
3
- import ffmpeg
4
- from flask import Flask, render_template, request, jsonify, url_for, redirect, session
5
- from functools import wraps
6
- import os
7
- import streamlink
8
- import threading
9
- import time
10
- from faster_whisper import WhisperModel
11
- import subprocess
12
  from datetime import datetime as dt
13
- from datetime import timedelta, timezone
14
- from apiclient import discovery
15
- from google.oauth2 import service_account
16
- import json
17
-
18
- # Import secrets
19
- client_secret = json.loads(os.environ.get("client_secret"))
20
- gdoc_id = os.environ.get("gdoc_id")
21
- GOOGLE_CLIENT_SECRET = os.environ.get("GOOGLE_CLIENT_SECRET")
22
- GOOGLE_CLIENT_ID = os.environ.get("GOOGLE_CLIENT_ID")
23
- allowed_users = os.environ.get("allowed_users")
24
-
25
- # Faster Whisper setup
26
- model_size = 'small'
27
- beamsize = 2
28
- wmodel = WhisperModel(model_size, device="cpu", compute_type="int8")
29
-
30
- # Delete local_transcript if it exists
31
- if not os.path.exists('transcription_files'): os.makedirs('transcription_files')
32
- for f in os.listdir('transcription_files/'): os.remove(os.path.join('transcription_files/', f)) # clear any old files in transcription_files folder
33
-
34
- with open("client_secret.json", "w") as json_file: json.dump(client_secret, json_file, indent=4)
35
-
36
- scopes = ["https://www.googleapis.com/auth/documents", "https://www.googleapis.com/auth/drive.file"]
37
- credentials = service_account.Credentials.from_service_account_file('client_secret.json', scopes=scopes)
38
- service = discovery.build('docs', 'v1', credentials=credentials)
39
-
40
- local_tz = 5.5 # For timestamps
41
- local_transcript = 'transcription_files/tr.txt'
42
- pid_file = 'transcription_files/pid.txt'
43
-
44
- # Check if mp3 folder exists, and create it if it doesn't
45
- if not os.path.exists('mp3'): os.makedirs('mp3')
46
- # Delete any old files in mp3 folder
47
- for f in os.listdir('mp3/'): os.remove(os.path.join('mp3/', f))
48
-
49
- app = Flask(__name__, static_url_path='/static')
50
- app.secret_key = os.urandom(12)
51
-
52
- oauth = OAuth(app)
53
-
54
- # Store the streamlink process
55
- stream_process = None
56
- recording = False
57
- mp3_extraction_process = None
58
-
59
- def update_gdoc(text, gdoc_id): # Update contents of google doc
60
- print('Updating Google Doc', gdoc_id)
61
- doc = service.documents().get(documentId=gdoc_id).execute()
62
- endindex = [p['endIndex'] for p in doc['body']['content'] if 'paragraph' in p][-1]
63
-
64
- try:
65
- body = {'requests': [{'insertText': {'location': {'index': endindex-1,}, 'text': ' ' + text}}]}
66
- result = service.documents().batchUpdate(documentId=gdoc_id, body=body).execute()
67
- print(result)
68
-
69
- except Exception as e:
70
- print(e)
71
-
72
- def process_complete_callback(retcode, **kwargs):
73
- if retcode == 0:
74
- print("FFmpeg process completed successfully!")
75
- else:
76
- print("FFmpeg process encountered an error.")
77
-
78
- def transcribe_audio(latest_file, time_counter):
79
- print('transcribing ', latest_file)
80
- segments, info = wmodel.transcribe(f"{latest_file}", beam_size=beamsize) # beamsize is 2.
81
- text = ''
82
-
83
- for segment in segments:
84
- text += segment.text
85
- transcribed = text.replace('\n', ' ').replace(' ', ' ')
86
- if time_counter%5 == 0:
87
- transcribed_sents = transcribed.split('. ') # Get the first fullstop break and append to previous para, before adding time code
88
- transcribed = transcribed_sents[0] + '\nTime ' + str((dt.now(timezone.utc) + timedelta(hours=local_tz)).strftime('%H:%M:%S')) + '\n' + '. '.join(transcribed_sents[1:])
89
-
90
- time_counter += 1
91
- return transcribed, time_counter
92
-
93
- def save_audio(youtube_url):
94
- global stream_process, recording, mp3_extraction_process
95
- try:
96
- streams = streamlink.streams(youtube_url)
97
- #if "audio" not in streams:
98
- # raise Exception("No audio stream found.")
99
-
100
- stream_url = streams["144p"].url
101
- time_counter = 0
102
- while recording:
103
- # Save audio only into mp3 files
104
-
105
- saved_mp3 = f"mp3/audio_{int(time.time())}.mp3"
106
- mp3_extraction_process = (
107
- ffmpeg
108
- .input(stream_url, t=30)
109
- .audio
110
- # TODO - change destination url to relevant url
111
- .output(saved_mp3)
112
- .overwrite_output()
113
- .global_args('-loglevel', 'panic')
114
- .run_async()
115
- )
116
-
117
- print('pid', mp3_extraction_process.pid)
118
- # write the pid to pid_file
119
- with open(pid_file, 'w') as f: f.write(str(mp3_extraction_process.pid))
120
-
121
- # If there is more than one mp3 file in the folder, transcribe the one that is not being written to
122
- mp3files = [f for f in os.listdir('mp3') if f.endswith('.mp3')]
123
- if len(mp3files) < 2:
124
- print('Sleeping for 30s as only one mp3 file in folder')
125
- time.sleep(30)
126
- else:
127
- starttime = time.time()
128
- file_to_transcribe = [f for f in mp3files if f != os.path.basename(saved_mp3)][0]
129
- print('Working on ', file_to_transcribe)
130
- transcribed, time_counter = transcribe_audio(f'mp3/{file_to_transcribe}', time_counter)
131
- os.remove(f'mp3/{file_to_transcribe}')
132
-
133
- update_gdoc(transcribed, gdoc_id)
134
- with open(local_transcript, 'a', encoding='utf-8', errors='ignore') as f: f.write(transcribed)
135
-
136
- elapsed_time = time.time() - starttime
137
- print('Time to transcribe:', elapsed_time, 'seconds')
138
- if elapsed_time < 30:
139
- print(f'Sleeping for {30-elapsed_time} as there are more than one mp3 files in folder')
140
- time.sleep(30-elapsed_time)
141
- #time.sleep(30)
142
-
143
- except Exception as e:
144
- recording = False
145
- print('exception', str(e))
146
- return str(e)
147
-
148
- @app.route("/start_process", methods=["POST"])
149
- def start_process():
150
- if not os.path.isfile(local_transcript):
151
- global recording, stream_process
152
- with open(local_transcript, 'a', encoding='utf-8', errors='ignore') as f: f.write('') # Create the local transcript file, which is used as a check to prevent multiple recordings
153
-
154
- youtube_url = request.form.get("url")
155
- if not youtube_url:
156
- return jsonify({"message": "Please provide a valid YouTube URL."}), 400
157
-
158
- if recording:
159
- return jsonify({"message": "A recording is already in progress."}), 400
160
-
161
- print('In start process')
162
- recording = True
163
- stream_process = threading.Thread(target=save_audio, args=(youtube_url,))
164
- stream_process.start()
165
-
166
- return jsonify({"message": "Recording started."}), 200
167
 
168
- else: return jsonify({"message": "Recording is already in progress."}), 400
169
-
170
-
171
- @app.route("/stop_process", methods=["POST"])
172
- def stop_process():
173
- global recording, stream_process, mp3_extraction_process
174
-
175
- if not recording:
176
- return jsonify({"message": "No recording is currently in progress."}), 400
177
- print('In stop process')
178
- recording = False
179
- stream_process.join()
180
- stream_process = None
181
- mp3_extraction_process.terminate()
182
- mp3_extraction_process = None
183
- for f in os.listdir('mp3/'): os.remove(os.path.join('mp3/', f))
184
- if os.path.isfile(local_transcript): os.remove(local_transcript)
185
- # check if pid_file exists, get the pid inside it and convert to int, and use os.kill to kill it
186
- if os.path.isfile(pid_file):
187
- with open(pid_file, 'r') as f: pid = int(f.read())
188
- try:
189
- os.kill(pid, 9) # For linux
190
- print("Process terminated successfully in Linux.")
191
- except:
192
- try:
193
- process = subprocess.Popen(["taskkill", "/F", "/PID", str(pid)], stdout=subprocess.PIPE, stderr=subprocess.PIPE) # For Windows
194
- process.communicate()
195
- print("Process terminated successfully in Windows.")
196
- except Exception as e:
197
- print("Error:", e)
198
- os.remove(pid_file)
199
-
200
- return jsonify({"message": "Recording stopped."}), 200
201
-
202
- @app.route('/google/')
203
- def google():
204
- CONF_URL = 'https://accounts.google.com/.well-known/openid-configuration'
205
- oauth.register(
206
- name='google',
207
- client_id=GOOGLE_CLIENT_ID,
208
- client_secret=GOOGLE_CLIENT_SECRET,
209
- server_metadata_url=CONF_URL,
210
- client_kwargs={"scope": "openid email profile"}
211
- )
212
-
213
- # Redirect to google_auth function/page
214
- redirect_uri = url_for('google_auth', _external=True)
215
- session['nonce'] = generate_token()
216
- return oauth.google.authorize_redirect(redirect_uri, nonce=session['nonce'])
217
-
218
- @app.route('/google/auth/')
219
- def google_auth():
220
- token = oauth.google.authorize_access_token()
221
- user = oauth.google.parse_id_token(token, nonce=session['nonce'])
222
- session['user'] = user
223
- print('USER', user)
224
- # Redirect to home if login successful
225
- return redirect('/home')
226
-
227
- def is_not_logged_in():
228
- return session.get('user') is None or session.get('nonce') is None
229
-
230
- # decorator to check if user is logged in, used for protected URLs
231
- def login_required(f):
232
- @wraps(f)
233
- def decorated_function(*args, **kwargs):
234
- if is_not_logged_in():
235
- return redirect('/login')
236
- return f(*args, **kwargs)
237
- return decorated_function
238
-
239
- @app.route("/home")
240
- @login_required
241
- def home():
242
- return render_template("home.html")
243
-
244
- @app.route("/", methods=["GET"])
245
- @app.route("/login", methods=["GET"])
246
- def login():
247
- if not is_not_logged_in():
248
- return redirect("/home")
249
- #return render_template("login.html")
250
- return render_template("home.html")
251
 
252
  if __name__ == "__main__":
253
  app.run(host="0.0.0.0", debug=True, port=7860)
 
1
+ from flask import Flask, render_template, request, jsonify
2
+ from qdrant_client import QdrantClient
3
+ from qdrant_client import models
4
+ import torch.nn.functional as F
5
+ import torch
6
+ from torch import Tensor
7
+ from transformers import AutoTokenizer, AutoModel
8
+ from qdrant_client.models import Batch, PointStruct
9
+ from pickle import load, dump
10
+ import numpy as np
11
+ import os, time, sys
12
  from datetime import datetime as dt
13
+ from datetime import timedelta
14
+ from datetime import timezone
15
+
16
+ app = Flask(__name__)
17
+
18
+ # Initialize Qdrant Client and other required settings
19
+ qdrant_api_key = os.environ.get("qdrant_api_key")
20
+ qdrant_url = os.environ.get("qdrant_url")
21
+
22
+ client = QdrantClient(url=qdrant_url, port=443, api_key=qdrant_api_key, prefer_grpc=False)
23
+
24
+ device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
25
+
26
+ def average_pool(last_hidden_states: Tensor,
27
+ attention_mask: Tensor) -> Tensor:
28
+ last_hidden = last_hidden_states.masked_fill(~attention_mask[..., None].bool(), 0.0)
29
+ return last_hidden.sum(dim=1) / attention_mask.sum(dim=1)[..., None]
30
+
31
+ tokenizer = AutoTokenizer.from_pretrained('intfloat/e5-base-v2')
32
+ model = AutoModel.from_pretrained('intfloat/e5-base-v2').to(device)
33
+
34
+ def e5embed(query):
35
+ batch_dict = tokenizer(query, max_length=512, padding=True, truncation=True, return_tensors='pt')
36
+ batch_dict = {k: v.to(device) for k, v in batch_dict.items()}
37
+ outputs = model(**batch_dict)
38
+ embeddings = average_pool(outputs.last_hidden_state, batch_dict['attention_mask'])
39
+ embeddings = F.normalize(embeddings, p=2, dim=1)
40
+ embeddings = embeddings.cpu().detach().numpy().flatten().tolist()
41
+ return embeddings
42
+
43
+ @app.route("/")
44
+ def index():
45
+ return render_template("index.html")
46
+
47
+ @app.route("/search", methods=["POST"])
48
+ def search():
49
+ query = request.form["query"]
50
+ topN = 200 # Define your topN value
51
+
52
+
53
+ print('QUERY: ',query)
54
+ if query.strip().startswith('tilc:'):
55
+ collection_name = 'tils'
56
+ qvector = "context"
57
+ query = query.replace('tilc:', '')
58
+ elif query.strip().startswith('til:'):
59
+ collection_name = 'tils'
60
+ qvector = "title"
61
+ query = query.replace('til:', '')
62
+ else: collection_name = 'jks'
63
+
64
+ timh = time.time()
65
+ sq = e5embed(query)
66
+ print('EMBEDDING TIME: ', time.time() - timh)
67
+
68
+ timh = time.time()
69
+ if collection_name == "jks": results = client.search(collection_name=collection_name, query_vector=sq, with_payload=True, limit=topN)
70
+ else: results = client.search(collection_name=collection_name, query_vector=(qvector, sq), with_payload=True, limit=100)
71
+ print('SEARCH TIME: ', time.time() - timh)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
 
73
+ print(results[0].payload['text'].split('\n'))
74
+ try:
75
+ results = [{"text": x.payload['text'], "date": str(int(x.payload['date'])), "id": x.id} for x in results] # Implement your Qdrant search here
76
+ return jsonify(results)
77
+ except:
78
+ return jsonify([])
79
+
80
+ @app.route("/delete_joke", methods=["POST"])
81
+ def delete_joke():
82
+ joke_id = request.form["id"]
83
+ print('Deleting joke no', joke_id)
84
+ client.delete(collection_name="jks", points_selector=models.PointIdsList(points=[int(joke_id)],),)
85
+ return jsonify({"deleted": True})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
 
87
  if __name__ == "__main__":
88
  app.run(host="0.0.0.0", debug=True, port=7860)
modules/dataset.py DELETED
@@ -1,19 +0,0 @@
1
- from datasets import load_dataset
2
-
3
- dataset = load_dataset("go_emotions", split="train")
4
-
5
- emotions = dataset.info.features['labels'].feature.names
6
-
7
- def query_emotion(start, end):
8
- rows = dataset[start:end]
9
- texts, labels = [rows[k] for k in rows.keys()]
10
-
11
- observations = []
12
-
13
- for i, text in enumerate(texts):
14
- observations.append({
15
- "text": text,
16
- "emotion": emotions[labels[i]],
17
- })
18
-
19
- return observations
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
modules/inference.py DELETED
@@ -1,11 +0,0 @@
1
- from transformers import T5Tokenizer, T5ForConditionalGeneration
2
-
3
- tokenizer = T5Tokenizer.from_pretrained("t5-small")
4
- model = T5ForConditionalGeneration.from_pretrained("t5-small")
5
-
6
-
7
- def infer_t5(input):
8
- input_ids = tokenizer(input, return_tensors="pt").input_ids
9
- outputs = model.generate(input_ids)
10
-
11
- return tokenizer.decode(outputs[0], skip_special_tokens=True)
 
 
 
 
 
 
 
 
 
 
 
 
requirements.txt CHANGED
@@ -1,9 +1,5 @@
1
- google-api-python-client
2
- google-auth-oauthlib
3
- streamlink
4
- faster-whisper
5
- requests
6
- ffmpeg-python
7
- Authlib
8
  flask
9
- Werkzeug
 
1
+ transformers
2
+ torch
3
+ qdrant-client
 
 
 
 
4
  flask
5
+ Werkzeug
static/script.js DELETED
@@ -1,47 +0,0 @@
1
- $(document).ready(function () {
2
- let recording = false;
3
-
4
- $("#startBtn").click(function () {
5
- const youtubeUrl = $("#urlInput").val().trim();
6
- if (youtubeUrl === "") {
7
- showMessage("Please enter a valid YouTube Livestream URL.", "danger");
8
- return;
9
- }
10
-
11
- // Call the start_process route in Flask
12
- $.ajax({
13
- type: "POST",
14
- url: "/start_process",
15
- data: { url: youtubeUrl },
16
- success: function (data) {
17
- showMessage(data.message, "success");
18
- recording = true;
19
- },
20
- error: function (xhr, status, error) {
21
- showMessage("Error: " + xhr.responseText, "danger");
22
- },
23
- });
24
- });
25
-
26
- $("#stopBtn").click(function () {
27
- showMessage("Stopping transcription. This may take upto 30 sec.", "success");
28
- // Call the stop_process route in Flask
29
- $.ajax({
30
- type: "POST",
31
- url: "/stop_process",
32
- success: function (data) {
33
- showMessage(data.message, "success");
34
- recording = false;
35
- },
36
- error: function (xhr, status, error) {
37
- showMessage("Error: " + xhr.responseText, "danger");
38
- },
39
- });
40
- });
41
-
42
- function showMessage(message, type) {
43
- $("#message").html(
44
- `<div class="alert alert-${type}" role="alert">${message}</div>`
45
- );
46
- }
47
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
static/style.css DELETED
@@ -1,37 +0,0 @@
1
- #container {
2
- width: 600px;
3
- margin: 0 auto;
4
- text-align: center;
5
- }
6
-
7
- #urlInput {
8
- width: 500px;
9
- }
10
-
11
- button {
12
- margin: 10px;
13
- }
14
-
15
- #login {
16
- display: flex;
17
- justify-content: center;
18
- flex-direction: row;
19
- }
20
-
21
- #loginButton {
22
- display: flex;
23
- background-color: rgb(255, 255, 255);
24
- justify-content: center;
25
- align-items: center;
26
- width: fit-content;
27
- padding: 10px 30px;
28
- box-shadow: 1px 1px 1px 1px rgb(170, 170, 170);
29
- border-radius: 15px;
30
- border: 1px solid rgb(170, 170, 170);
31
- }
32
-
33
- #loginButtonText {
34
- justify-content: center;
35
- padding: 10px;
36
- color: rgb(85, 85, 85);
37
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
templates/home.html DELETED
@@ -1,85 +0,0 @@
1
- <!DOCTYPE html>
2
- <html>
3
-
4
- <head>
5
- <title>YouTube Livestream Audio Recorder</title>
6
- <!-- Add necessary CSS and jQuery libraries -->
7
- <style>
8
- body {
9
- font-family: Roboto, sans-serif;
10
- margin: 0;
11
- padding: 0;
12
- height: 100%;
13
- }
14
-
15
- #container {
16
- width: 600px;
17
- margin: 0 auto;
18
- text-align: center;
19
- }
20
-
21
- #url {
22
- width: 500px;
23
- }
24
-
25
- button {
26
- margin: 10px;
27
- }
28
-
29
- .row, h2 {
30
- display: flex;
31
- align-items: center;
32
- margin: 10px;
33
- }
34
-
35
- select, button {
36
- padding: 5px;
37
- border: 1px solid #ccc;
38
- border-radius: 5px;
39
- }
40
-
41
- input {
42
- padding: 5px;
43
- border: 1px solid #ccc;
44
- border-radius: 5px;
45
- width: 500px;
46
- }
47
-
48
- label, #message {
49
- margin: 0px 5px 0px 10px;
50
- }
51
-
52
- button {
53
- background-color: #2196f3;
54
- color: white;
55
- cursor: pointer;
56
- margin: 0px 0px 0px 3px;
57
- }
58
-
59
- button:hover {
60
- background-color: #1976d2;
61
- }
62
- </style>
63
- </head>
64
-
65
- <body>
66
- <div class="container">
67
- <h2>Search & Transcribing Utilities</h2>
68
- <div class="mb-3 row">
69
- <label for="urlInput" class="form-label">Enter YouTube Livestream URL:</label>
70
- <input type="text" class="form-control" id="urlInput"
71
- placeholder="e.g., https://www.youtube.com/watch?v=YOUR_STREAM_ID">
72
-
73
- <button class="btn btn-primary" id="startBtn">Start</button>
74
- <button class="btn btn-danger" id="stopBtn">Stop</button>
75
- <div id="message"></div>
76
- </div>
77
-
78
- <!-- Add necessary jQuery library -->
79
- <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
80
- <!-- Add custom script for handling button clicks -->
81
- <script src="{{ url_for('static', filename='script.js') }}"></script>
82
-
83
- </body>
84
-
85
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
templates/index.html CHANGED
@@ -1,85 +1,112 @@
1
  <!DOCTYPE html>
2
- <html>
3
-
4
  <head>
5
- <title>YouTube Livestream Audio Recorder</title>
6
- <!-- Add necessary CSS and jQuery libraries -->
7
- <style>
8
- body {
9
- font-family: Roboto, sans-serif;
10
- margin: 0;
11
- padding: 0;
12
- height: 100%;
13
- }
14
-
15
- #container {
16
- width: 600px;
17
- margin: 0 auto;
18
- text-align: center;
19
- }
20
-
21
- #url {
22
- width: 500px;
23
- }
24
-
25
- button {
26
- margin: 10px;
27
- }
28
-
29
- .row, h2 {
30
  display: flex;
 
31
  align-items: center;
32
- margin: 10px;
33
- }
34
 
35
- select, button {
36
- padding: 5px;
37
- border: 1px solid #ccc;
38
- border-radius: 5px;
39
- }
40
 
41
- input {
42
- padding: 5px;
43
- border: 1px solid #ccc;
44
- border-radius: 5px;
45
- width: 500px;
46
- }
 
 
47
 
48
- label, #message {
49
- margin: 0px 5px 0px 10px;
50
- }
51
 
52
- button {
53
- background-color: #2196f3;
 
 
 
 
 
 
54
  color: white;
 
 
55
  cursor: pointer;
56
- margin: 0px 0px 0px 3px;
57
- }
58
-
59
- button:hover {
60
- background-color: #1976d2;
61
- }
62
- </style>
63
  </head>
64
-
65
  <body>
66
- <div class="container">
67
- <h2>Search & Transcribing Utilities</h2>
68
- <div class="mb-3 row">
69
- <label for="urlInput" class="form-label">Enter YouTube Livestream URL:</label>
70
- <input type="text" class="form-control" id="urlInput"
71
- placeholder="e.g., https://www.youtube.com/watch?v=YOUR_STREAM_ID">
72
-
73
- <button class="btn btn-primary" id="startBtn">Start</button>
74
- <button class="btn btn-danger" id="stopBtn">Stop</button>
75
- <div id="message"></div>
76
- </div>
77
-
78
- <!-- Add necessary jQuery library -->
79
- <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
80
- <!-- Add custom script for handling button clicks -->
81
- <script src="{{ url_for('static', filename='script.js') }}"></script>
82
 
83
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  </html>
 
1
  <!DOCTYPE html>
2
+ <html lang="en">
 
3
  <head>
4
+ <meta charset="UTF-8">
5
+ <title>Joke Search</title>
6
+ <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
7
+ <style>
8
+ body {
9
+ font-family: Arial, sans-serif;
10
+ }
11
+
12
+ h1 {
13
+ text-align: center;
14
+ }
15
+
16
+ #search-container {
 
 
 
 
 
 
 
 
 
 
 
 
17
  display: flex;
18
+ justify-content: center;
19
  align-items: center;
20
+ }
 
21
 
22
+ #query {
23
+ width: 70%;
24
+ padding: 10px;
25
+ }
 
26
 
27
+ #search {
28
+ background-color: #4c72af;
29
+ color: white;
30
+ border: none;
31
+ padding: 10px 20px;
32
+ cursor: pointer;
33
+ margin-left: 10px; /* Add margin to separate the query bar and button */
34
+ }
35
 
36
+ #results {
37
+ margin: 20px;
38
+ }
39
 
40
+ .result {
41
+ border: 1px solid #ccc;
42
+ padding: 10px;
43
+ margin: 10px 0;
44
+ }
45
+
46
+ .delete {
47
+ background-color: #ff0000;
48
  color: white;
49
+ border: none;
50
+ padding: 5px 10px;
51
  cursor: pointer;
52
+ margin: 0 10px;
53
+ }
54
+ </style>
 
 
 
 
55
  </head>
 
56
  <body>
57
+ <h1>Joke Search</h1>
58
+ <div id="search-container">
59
+ <input type="text" id="query" placeholder="Search...">
60
+ <button id="search">Search</button>
61
+ </div>
62
+ <div id="results"></div>
 
 
 
 
 
 
 
 
 
 
63
 
64
+ <script>
65
+ $(document).ready(function () {
66
+ function performSearch() {
67
+ var query = $("#query").val();
68
+
69
+ $.post("/search", { query: query }, function (data) {
70
+ var results = $("#results");
71
+ results.empty();
72
+
73
+ data.forEach(function (result) {
74
+ var resultElement = $("<div class='result'>" +
75
+ "<p>" + result.text + "</p>" +
76
+ "<small>Date: " + result.date + "</small>" +
77
+ "<button class='delete' data-id='" + result.id + "'>Delete</button>" +
78
+ "</div>");
79
+
80
+ results.append(resultElement);
81
 
82
+ resultElement.find(".delete").on("click", function () {
83
+ var id = $(this).data("id");
84
+ var resultButton = $(this);
85
+
86
+ $.post("/delete_joke", { id: id }, function (data) {
87
+ // Handle the delete response if needed
88
+
89
+ if (data.deleted) {
90
+ // Replace the button with "DELETED!" text
91
+ resultButton.replaceWith("<p class='deleted-text'>DELETED!</p>");
92
+ }
93
+ });
94
+ });
95
+ });
96
+ });
97
+ }
98
+
99
+ $("#search").on("click", function () {
100
+ performSearch();
101
+ });
102
+
103
+ $("#query").on("keydown", function (event) {
104
+ if (event.key === "Enter") {
105
+ event.preventDefault(); // Prevent form submission
106
+ performSearch();
107
+ }
108
+ });
109
+ });
110
+ </script>
111
+ </body>
112
  </html>
templates/login.html DELETED
@@ -1,27 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
-
4
- <head>
5
- <meta charset="UTF-8">
6
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
- <link rel="preconnect" href="https://fonts.googleapis.com">
8
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
- <link href="https://fonts.googleapis.com/css2?family=Poppins&display=swap" rel="stylesheet">
10
- <link rel="stylesheet" type="text/css" href="{{ url_for('static',filename='style.css') }}">
11
- <title>Flask app</title>
12
- </head>
13
-
14
- <body>
15
- <div id="login">
16
- <a href="google/" id="loginButton" style="text-decoration:none;">
17
- <img id="google"
18
- src="https://upload.wikimedia.org/wikipedia/commons/thumb/5/53/Google_%22G%22_Logo.svg/2048px-Google_%22G%22_Logo.svg.png"
19
- alt="Google" style="width:50px;height:50px;">
20
- <div id="loginButtonText">
21
- Login with Google
22
- </div>
23
- </a>
24
- </div>
25
- </body>
26
-
27
- </html>