oceansweep commited on
Commit
0215147
1 Parent(s): 6815452

Delete App_Function_Libraries/SQLite_DB.py

Browse files
Files changed (1) hide show
  1. App_Function_Libraries/SQLite_DB.py +0 -2016
App_Function_Libraries/SQLite_DB.py DELETED
@@ -1,2016 +0,0 @@
1
- # SQLite_DB.py
2
- #########################################
3
- # SQLite_DB Library
4
- # This library is used to perform any/all DB operations related to SQLite.
5
- #
6
- ####
7
-
8
- ####################
9
- # Function List
10
- # FIXME - UPDATE Function Arguments
11
- # 1. get_connection(self)
12
- # 2. execute_query(self, query: str, params: Tuple = ())
13
- # 3. create_tables()
14
- # 4. add_keyword(keyword: str)
15
- # 5. delete_keyword(keyword: str)
16
- # 6. add_media_with_keywords(url, title, media_type, content, keywords, prompt, summary, transcription_model, author, ingestion_date)
17
- # 7. fetch_all_keywords()
18
- # 8. keywords_browser_interface()
19
- # 9. display_keywords()
20
- # 10. export_keywords_to_csv()
21
- # 11. browse_items(search_query, search_type)
22
- # 12. fetch_item_details(media_id: int)
23
- # 13. add_media_version(media_id: int, prompt: str, summary: str)
24
- # 14. search_db(search_query: str, search_fields: List[str], keywords: str, page: int = 1, results_per_page: int = 10)
25
- # 15. search_and_display(search_query, search_fields, keywords, page)
26
- # 16. display_details(index, results)
27
- # 17. get_details(index, dataframe)
28
- # 18. format_results(results)
29
- # 19. export_to_csv(search_query: str, search_fields: List[str], keyword: str, page: int = 1, results_per_file: int = 1000)
30
- # 20. is_valid_url(url: str) -> bool
31
- # 21. is_valid_date(date_string: str) -> bool
32
- # 22. add_media_to_database(url, info_dict, segments, summary, keywords, custom_prompt_input, whisper_model)
33
- # 23. create_prompts_db()
34
- # 24. add_prompt(name, details, system, user=None)
35
- # 25. fetch_prompt_details(name)
36
- # 26. list_prompts()
37
- # 27. insert_prompt_to_db(title, description, system_prompt, user_prompt)
38
- # 28. update_media_content(media_id: int, content: str, prompt: str, summary: str)
39
- # 29. search_media_database(query: str) -> List[Tuple[int, str, str]]
40
- # 30. load_media_content(media_id: int)
41
- # 31.
42
- # 32.
43
- #
44
- #
45
- #####################
46
- #
47
- # Import necessary libraries
48
- import csv
49
- import html
50
- import logging
51
- import os
52
- import re
53
- import shutil
54
- import sqlite3
55
- import time
56
- import traceback
57
- from contextlib import contextmanager
58
- from datetime import datetime, timedelta
59
- from typing import List, Tuple, Dict, Any
60
- # Local Libraries
61
- #from App_Function_Libraries.ChromaDB_Library import auto_update_chroma_embeddings
62
- from App_Function_Libraries.Utils import is_valid_url
63
- # Third-Party Libraries
64
- import gradio as gr
65
- import pandas as pd
66
- import yaml
67
-
68
-
69
- # Import Local Libraries
70
- #
71
- #######################################################################################################################
72
- # Function Definitions
73
- #
74
-
75
-
76
- # Set up logging
77
- #logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
78
- #logging.basicConfig(level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(message)s')
79
- logger = logging.getLogger(__name__)
80
-
81
-
82
- #
83
- # Backup-related functions
84
-
85
- def create_incremental_backup(db_path, backup_dir):
86
- conn = sqlite3.connect(db_path)
87
- cursor = conn.cursor()
88
-
89
- # Get the page count of the database
90
- cursor.execute("PRAGMA page_count")
91
- page_count = cursor.fetchone()[0]
92
-
93
- # Create a new backup file
94
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
95
- backup_file = os.path.join(backup_dir, f"incremental_backup_{timestamp}.sqlib")
96
-
97
- # Perform the incremental backup
98
- conn.execute(f"VACUUM INTO '{backup_file}'")
99
-
100
- conn.close()
101
- print(f"Incremental backup created: {backup_file}")
102
- return backup_file
103
-
104
-
105
- def create_automated_backup(db_path, backup_dir):
106
- # Ensure backup directory exists
107
- os.makedirs(backup_dir, exist_ok=True)
108
-
109
- # Create a timestamped backup file name
110
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
111
- backup_file = os.path.join(backup_dir, f"backup_{timestamp}.db")
112
-
113
- # Copy the database file
114
- shutil.copy2(db_path, backup_file)
115
-
116
- print(f"Backup created: {backup_file}")
117
- return backup_file
118
-
119
- # FIXME - boto3 aint getting installed by default....
120
- # def upload_to_s3(file_path, bucket_name, s3_key):
121
- # import boto3
122
- # s3 = boto3.client('s3')
123
- # try:
124
- # s3.upload_file(file_path, bucket_name, s3_key)
125
- # print(f"File uploaded to S3: {s3_key}")
126
- # except Exception as e:
127
- # print(f"Error uploading to S3: {str(e)}")
128
-
129
-
130
- def rotate_backups(backup_dir, max_backups=10):
131
- backups = sorted(
132
- [f for f in os.listdir(backup_dir) if f.endswith('.db')],
133
- key=lambda x: os.path.getmtime(os.path.join(backup_dir, x)),
134
- reverse=True
135
- )
136
-
137
- while len(backups) > max_backups:
138
- old_backup = backups.pop()
139
- os.remove(os.path.join(backup_dir, old_backup))
140
- print(f"Removed old backup: {old_backup}")
141
-
142
-
143
- # FIXME - Setup properly and test/add documentation for its existence...
144
- db_path = "path/to/your/database.db"
145
- backup_dir = "path/to/backup/directory"
146
- #create_automated_backup(db_path, backup_dir)
147
-
148
- # FIXME - Setup properly and test/add documentation for its existence...
149
- #backup_file = create_automated_backup(db_path, backup_dir)
150
- #upload_to_s3(backup_file, 'your-s3-bucket-name', f"database_backups/{os.path.basename(backup_file)}")
151
-
152
- # FIXME - Setup properly and test/add documentation for its existence...
153
- #create_incremental_backup(db_path, backup_dir)
154
-
155
- # FIXME - Setup properly and test/add documentation for its existence...
156
- #rotate_backups(backup_dir)
157
-
158
- #
159
- #
160
- #######################################################################################################################
161
- #
162
- # DB-Integrity Check Functions
163
-
164
- def check_database_integrity(db_path):
165
- conn = sqlite3.connect(db_path)
166
- cursor = conn.cursor()
167
-
168
- cursor.execute("PRAGMA integrity_check")
169
- result = cursor.fetchone()
170
-
171
- conn.close()
172
-
173
- if result[0] == "ok":
174
- print("Database integrity check passed.")
175
- return True
176
- else:
177
- print("Database integrity check failed:", result[0])
178
- return False
179
-
180
- #check_database_integrity(db_path)
181
-
182
- #
183
- # End of DB-Integrity Check functions
184
- #######################################################################################################################
185
- #
186
- # Media-related Functions
187
-
188
- # Custom exceptions
189
- class DatabaseError(Exception):
190
- pass
191
-
192
-
193
- class InputError(Exception):
194
- pass
195
-
196
-
197
- # Database connection function with connection pooling
198
- class Database:
199
- def __init__(self, db_name=None):
200
- self.db_name = db_name or os.getenv('DB_NAME', 'media_summary.db')
201
- self.pool = []
202
- self.pool_size = 10
203
-
204
- @contextmanager
205
- def get_connection(self):
206
- retry_count = 5
207
- retry_delay = 1
208
- conn = None
209
- while retry_count > 0:
210
- try:
211
- conn = self.pool.pop() if self.pool else sqlite3.connect(self.db_name, check_same_thread=False)
212
- yield conn
213
- self.pool.append(conn)
214
- return
215
- except sqlite3.OperationalError as e:
216
- if 'database is locked' in str(e):
217
- logging.warning(f"Database is locked, retrying in {retry_delay} seconds...")
218
- retry_count -= 1
219
- time.sleep(retry_delay)
220
- else:
221
- raise DatabaseError(f"Database error: {e}")
222
- except Exception as e:
223
- raise DatabaseError(f"Unexpected error: {e}")
224
- finally:
225
- # Ensure the connection is returned to the pool even on failure
226
- if conn:
227
- self.pool.append(conn)
228
- raise DatabaseError("Database is locked and retries have been exhausted")
229
-
230
- def execute_query(self, query: str, params: Tuple = ()) -> None:
231
- with self.get_connection() as conn:
232
- try:
233
- cursor = conn.cursor()
234
- cursor.execute(query, params)
235
- conn.commit()
236
- except sqlite3.Error as e:
237
- raise DatabaseError(f"Database error: {e}, Query: {query}")
238
-
239
- db = Database()
240
-
241
- def instantiate_SQLite_db():
242
- global sqlite_db
243
- sqlite_db = Database()
244
-
245
-
246
- # Function to create tables with the new media schema
247
- def create_tables(db) -> None:
248
- table_queries = [
249
- # CREATE TABLE statements
250
- '''
251
- CREATE TABLE IF NOT EXISTS Media (
252
- id INTEGER PRIMARY KEY AUTOINCREMENT,
253
- url TEXT,
254
- title TEXT NOT NULL,
255
- type TEXT NOT NULL,
256
- content TEXT,
257
- author TEXT,
258
- ingestion_date TEXT,
259
- prompt TEXT,
260
- summary TEXT,
261
- transcription_model TEXT,
262
- is_trash BOOLEAN DEFAULT 0,
263
- trash_date DATETIME,
264
- vector_embedding BLOB
265
- )
266
- ''',
267
- '''
268
- CREATE TABLE IF NOT EXISTS Keywords (
269
- id INTEGER PRIMARY KEY AUTOINCREMENT,
270
- keyword TEXT NOT NULL UNIQUE
271
- )
272
- ''',
273
- '''
274
- CREATE TABLE IF NOT EXISTS MediaKeywords (
275
- id INTEGER PRIMARY KEY AUTOINCREMENT,
276
- media_id INTEGER NOT NULL,
277
- keyword_id INTEGER NOT NULL,
278
- FOREIGN KEY (media_id) REFERENCES Media(id),
279
- FOREIGN KEY (keyword_id) REFERENCES Keywords(id)
280
- )
281
- ''',
282
- '''
283
- CREATE TABLE IF NOT EXISTS MediaVersion (
284
- id INTEGER PRIMARY KEY AUTOINCREMENT,
285
- media_id INTEGER NOT NULL,
286
- version INTEGER NOT NULL,
287
- prompt TEXT,
288
- summary TEXT,
289
- created_at TEXT NOT NULL,
290
- FOREIGN KEY (media_id) REFERENCES Media(id)
291
- )
292
- ''',
293
- '''
294
- CREATE TABLE IF NOT EXISTS MediaModifications (
295
- id INTEGER PRIMARY KEY AUTOINCREMENT,
296
- media_id INTEGER NOT NULL,
297
- prompt TEXT,
298
- summary TEXT,
299
- modification_date TEXT,
300
- FOREIGN KEY (media_id) REFERENCES Media(id)
301
- )
302
- ''',
303
- '''
304
- CREATE TABLE IF NOT EXISTS ChatConversations (
305
- id INTEGER PRIMARY KEY AUTOINCREMENT,
306
- media_id INTEGER,
307
- media_name TEXT,
308
- conversation_name TEXT,
309
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
310
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
311
- FOREIGN KEY (media_id) REFERENCES Media(id)
312
- )
313
- ''',
314
- '''
315
- CREATE TABLE IF NOT EXISTS ChatMessages (
316
- id INTEGER PRIMARY KEY AUTOINCREMENT,
317
- conversation_id INTEGER,
318
- sender TEXT,
319
- message TEXT,
320
- timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
321
- FOREIGN KEY (conversation_id) REFERENCES ChatConversations(id)
322
- )
323
- ''',
324
- '''
325
- CREATE TABLE IF NOT EXISTS Transcripts (
326
- id INTEGER PRIMARY KEY AUTOINCREMENT,
327
- media_id INTEGER,
328
- whisper_model TEXT,
329
- transcription TEXT,
330
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
331
- FOREIGN KEY (media_id) REFERENCES Media(id)
332
- )
333
- ''',
334
- '''
335
- CREATE TABLE IF NOT EXISTS MediaChunks (
336
- id INTEGER PRIMARY KEY AUTOINCREMENT,
337
- media_id INTEGER,
338
- chunk_text TEXT,
339
- start_index INTEGER,
340
- end_index INTEGER,
341
- vector_embedding BLOB,
342
- FOREIGN KEY (media_id) REFERENCES Media(id)
343
- )
344
- ''',
345
- '''
346
- CREATE TABLE IF NOT EXISTS UnvectorizedMediaChunks (
347
- id INTEGER PRIMARY KEY AUTOINCREMENT,
348
- media_id INTEGER NOT NULL,
349
- chunk_text TEXT NOT NULL,
350
- chunk_index INTEGER NOT NULL,
351
- start_char INTEGER NOT NULL,
352
- end_char INTEGER NOT NULL,
353
- chunk_type TEXT,
354
- creation_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
355
- last_modified TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
356
- is_processed BOOLEAN DEFAULT FALSE,
357
- metadata TEXT,
358
- FOREIGN KEY (media_id) REFERENCES Media(id)
359
- )
360
- '''
361
- ]
362
-
363
- index_queries = [
364
- # CREATE INDEX statements
365
- 'CREATE INDEX IF NOT EXISTS idx_media_title ON Media(title)',
366
- 'CREATE INDEX IF NOT EXISTS idx_media_type ON Media(type)',
367
- 'CREATE INDEX IF NOT EXISTS idx_media_author ON Media(author)',
368
- 'CREATE INDEX IF NOT EXISTS idx_media_ingestion_date ON Media(ingestion_date)',
369
- 'CREATE INDEX IF NOT EXISTS idx_keywords_keyword ON Keywords(keyword)',
370
- 'CREATE INDEX IF NOT EXISTS idx_mediakeywords_media_id ON MediaKeywords(media_id)',
371
- 'CREATE INDEX IF NOT EXISTS idx_mediakeywords_keyword_id ON MediaKeywords(keyword_id)',
372
- 'CREATE INDEX IF NOT EXISTS idx_media_version_media_id ON MediaVersion(media_id)',
373
- 'CREATE INDEX IF NOT EXISTS idx_mediamodifications_media_id ON MediaModifications(media_id)',
374
- 'CREATE INDEX IF NOT EXISTS idx_chatconversations_media_id ON ChatConversations(media_id)',
375
- 'CREATE INDEX IF NOT EXISTS idx_chatmessages_conversation_id ON ChatMessages(conversation_id)',
376
- 'CREATE INDEX IF NOT EXISTS idx_media_is_trash ON Media(is_trash)',
377
- 'CREATE INDEX IF NOT EXISTS idx_mediachunks_media_id ON MediaChunks(media_id)',
378
- 'CREATE INDEX IF NOT EXISTS idx_unvectorized_media_chunks_media_id ON UnvectorizedMediaChunks(media_id)',
379
- 'CREATE INDEX IF NOT EXISTS idx_unvectorized_media_chunks_is_processed ON UnvectorizedMediaChunks(is_processed)',
380
- 'CREATE INDEX IF NOT EXISTS idx_unvectorized_media_chunks_chunk_type ON UnvectorizedMediaChunks(chunk_type)',
381
- # CREATE UNIQUE INDEX statements
382
- 'CREATE UNIQUE INDEX IF NOT EXISTS idx_unique_media_url ON Media(url)',
383
- 'CREATE UNIQUE INDEX IF NOT EXISTS idx_unique_media_keyword ON MediaKeywords(media_id, keyword_id)'
384
- ]
385
-
386
- virtual_table_queries = [
387
- # CREATE VIRTUAL TABLE statements
388
- 'CREATE VIRTUAL TABLE IF NOT EXISTS media_fts USING fts5(title, content)',
389
- 'CREATE VIRTUAL TABLE IF NOT EXISTS keyword_fts USING fts5(keyword)'
390
- ]
391
-
392
- all_queries = table_queries + index_queries + virtual_table_queries
393
-
394
- for query in all_queries:
395
- try:
396
- db.execute_query(query)
397
- except Exception as e:
398
- logging.error(f"Error executing query: {query}")
399
- logging.error(f"Error details: {str(e)}")
400
- raise
401
-
402
- logging.info("All tables, indexes, and virtual tables created successfully.")
403
-
404
- create_tables(db)
405
-
406
-
407
- def check_media_exists(title, url):
408
- """Check if media with the given title or URL exists in the database."""
409
- with db.get_connection() as conn:
410
- cursor = conn.cursor()
411
- cursor.execute("SELECT id FROM Media WHERE title = ? OR url = ?", (title, url))
412
- result = cursor.fetchone()
413
- return result is not None
414
-
415
-
416
- def check_media_and_whisper_model(title=None, url=None, current_whisper_model=None):
417
- """
418
- Check if media exists in the database and compare the whisper model used.
419
-
420
- :param title: Title of the media (optional)
421
- :param url: URL of the media (optional)
422
- :param current_whisper_model: The whisper model currently selected for use
423
- :return: Tuple (bool, str) - (should_download, reason)
424
- """
425
- if not title and not url:
426
- return True, "No title or URL provided"
427
-
428
- with db.get_connection() as conn:
429
- cursor = conn.cursor()
430
-
431
- # First, find the media_id
432
- query = "SELECT id FROM Media WHERE "
433
- params = []
434
-
435
- if title:
436
- query += "title = ?"
437
- params.append(title)
438
-
439
- if url:
440
- if params:
441
- query += " OR "
442
- query += "url = ?"
443
- params.append(url)
444
-
445
- cursor.execute(query, tuple(params))
446
- result = cursor.fetchone()
447
-
448
- if not result:
449
- return True, "Media not found in database"
450
-
451
- media_id = result[0]
452
-
453
- # Now, get the latest transcript for this media
454
- cursor.execute("""
455
- SELECT transcription
456
- FROM Transcripts
457
- WHERE media_id = ?
458
- ORDER BY created_at DESC
459
- LIMIT 1
460
- """, (media_id,))
461
-
462
- transcript_result = cursor.fetchone()
463
-
464
- if not transcript_result:
465
- return True, f"No transcript found for media (ID: {media_id})"
466
-
467
- transcription = transcript_result[0]
468
-
469
- # Extract the whisper model from the transcription
470
- match = re.search(r"This text was transcribed using whisper model: (.+)$", transcription, re.MULTILINE)
471
- if not match:
472
- return True, f"Whisper model information not found in transcript (Media ID: {media_id})"
473
-
474
- db_whisper_model = match.group(1).strip()
475
-
476
- if not current_whisper_model:
477
- return False, f"Media found in database (ID: {media_id})"
478
-
479
- if db_whisper_model != current_whisper_model:
480
- return True, f"Different whisper model (DB: {db_whisper_model}, Current: {current_whisper_model})"
481
-
482
- return False, f"Media found with same whisper model (ID: {media_id})"
483
-
484
-
485
- #######################################################################################################################
486
- # Keyword-related Functions
487
- #
488
-
489
- # Function to add a keyword
490
- def add_keyword(keyword: str) -> int:
491
- keyword = keyword.strip().lower()
492
- with db.get_connection() as conn:
493
- cursor = conn.cursor()
494
- try:
495
- cursor.execute('INSERT OR IGNORE INTO Keywords (keyword) VALUES (?)', (keyword,))
496
- cursor.execute('SELECT id FROM Keywords WHERE keyword = ?', (keyword,))
497
- keyword_id = cursor.fetchone()[0]
498
- cursor.execute('INSERT OR IGNORE INTO keyword_fts (rowid, keyword) VALUES (?, ?)', (keyword_id, keyword))
499
- logging.info(f"Keyword '{keyword}' added to keyword_fts with ID: {keyword_id}")
500
- conn.commit()
501
- return keyword_id
502
- except sqlite3.IntegrityError as e:
503
- logging.error(f"Integrity error adding keyword: {e}")
504
- raise DatabaseError(f"Integrity error adding keyword: {e}")
505
- except sqlite3.Error as e:
506
- logging.error(f"Error adding keyword: {e}")
507
- raise DatabaseError(f"Error adding keyword: {e}")
508
-
509
-
510
- # Function to delete a keyword
511
- def delete_keyword(keyword: str) -> str:
512
- keyword = keyword.strip().lower()
513
- with db.get_connection() as conn:
514
- cursor = conn.cursor()
515
- try:
516
- cursor.execute('SELECT id FROM Keywords WHERE keyword = ?', (keyword,))
517
- keyword_id = cursor.fetchone()
518
- if keyword_id:
519
- cursor.execute('DELETE FROM Keywords WHERE keyword = ?', (keyword,))
520
- cursor.execute('DELETE FROM keyword_fts WHERE rowid = ?', (keyword_id[0],))
521
- conn.commit()
522
- return f"Keyword '{keyword}' deleted successfully."
523
- else:
524
- return f"Keyword '{keyword}' not found."
525
- except sqlite3.Error as e:
526
- raise DatabaseError(f"Error deleting keyword: {e}")
527
-
528
-
529
-
530
- # Function to add media with keywords
531
- def add_media_with_keywords(url, title, media_type, content, keywords, prompt, summary, transcription_model, author,
532
- ingestion_date):
533
- # Set default values for missing fields
534
- url = url or 'Unknown'
535
- title = title or 'Untitled'
536
- media_type = media_type or 'Unknown'
537
- content = content or 'No content available'
538
- keywords = keywords or 'default'
539
- prompt = prompt or 'No prompt available'
540
- summary = summary or 'No summary available'
541
- transcription_model = transcription_model or 'Unknown'
542
- author = author or 'Unknown'
543
- ingestion_date = ingestion_date or datetime.now().strftime('%Y-%m-%d')
544
-
545
- # Ensure URL is valid
546
- if not is_valid_url(url):
547
- url = 'localhost'
548
-
549
- if media_type not in ['article', 'audio', 'document', 'obsidian_note', 'podcast', 'text', 'video', 'unknown']:
550
- raise InputError("Invalid media type. Allowed types: article, audio file, document, obsidian_note podcast, text, video, unknown.")
551
-
552
- if ingestion_date and not is_valid_date(ingestion_date):
553
- raise InputError("Invalid ingestion date format. Use YYYY-MM-DD.")
554
-
555
- # Handle keywords as either string or list
556
- if isinstance(keywords, str):
557
- keyword_list = [keyword.strip().lower() for keyword in keywords.split(',')]
558
- elif isinstance(keywords, list):
559
- keyword_list = [keyword.strip().lower() for keyword in keywords]
560
- else:
561
- keyword_list = ['default']
562
-
563
- logging.info(f"Adding/updating media: URL={url}, Title={title}, Type={media_type}")
564
- logging.debug(f"Content (first 500 chars): {content[:500]}...")
565
- logging.debug(f"Keywords: {keyword_list}")
566
- logging.info(f"Prompt: {prompt}")
567
- logging.info(f"Summary: {summary}")
568
- logging.info(f"Author: {author}")
569
- logging.info(f"Ingestion Date: {ingestion_date}")
570
- logging.info(f"Transcription Model: {transcription_model}")
571
-
572
- try:
573
- with db.get_connection() as conn:
574
- conn.execute("BEGIN TRANSACTION")
575
- cursor = conn.cursor()
576
-
577
- # Check if media already exists
578
- cursor.execute('SELECT id FROM Media WHERE url = ?', (url,))
579
- existing_media = cursor.fetchone()
580
-
581
- if existing_media:
582
- media_id = existing_media[0]
583
- logging.info(f"Updating existing media with ID: {media_id}")
584
-
585
- cursor.execute('''
586
- UPDATE Media
587
- SET content = ?, transcription_model = ?, title = ?, type = ?, author = ?, ingestion_date = ?
588
- WHERE id = ?
589
- ''', (content, transcription_model, title, media_type, author, ingestion_date, media_id))
590
- else:
591
- logging.info("Creating new media entry")
592
-
593
- cursor.execute('''
594
- INSERT INTO Media (url, title, type, content, author, ingestion_date, transcription_model)
595
- VALUES (?, ?, ?, ?, ?, ?, ?)
596
- ''', (url, title, media_type, content, author, ingestion_date, transcription_model))
597
- media_id = cursor.lastrowid
598
-
599
- logging.info(f"Adding new modification to MediaModifications for media ID: {media_id}")
600
- cursor.execute('''
601
- INSERT INTO MediaModifications (media_id, prompt, summary, modification_date)
602
- VALUES (?, ?, ?, ?)
603
- ''', (media_id, prompt, summary, ingestion_date))
604
- logger.info("New modification added to MediaModifications")
605
-
606
- # Insert keywords and associate with media item
607
- logging.info("Processing keywords")
608
- for keyword in keyword_list:
609
- keyword = keyword.strip().lower()
610
- cursor.execute('INSERT OR IGNORE INTO Keywords (keyword) VALUES (?)', (keyword,))
611
- cursor.execute('SELECT id FROM Keywords WHERE keyword = ?', (keyword,))
612
- keyword_id = cursor.fetchone()[0]
613
- cursor.execute('INSERT OR IGNORE INTO MediaKeywords (media_id, keyword_id) VALUES (?, ?)',
614
- (media_id, keyword_id))
615
-
616
- # Update full-text search index
617
- logging.info("Updating full-text search index")
618
- cursor.execute('INSERT OR REPLACE INTO media_fts (rowid, title, content) VALUES (?, ?, ?)',
619
- (media_id, title, content))
620
-
621
- logging.info("Adding new media version")
622
- add_media_version(media_id, prompt, summary)
623
-
624
- conn.commit()
625
- logging.info(f"Media '{title}' successfully added/updated with ID: {media_id}")
626
-
627
- return f"Media '{title}' added/updated successfully with keywords: {', '.join(keyword_list)}"
628
-
629
- except sqlite3.Error as e:
630
- conn.rollback()
631
- logging.error(f"SQL Error: {e}")
632
- raise DatabaseError(f"Error adding media with keywords: {e}")
633
- except Exception as e:
634
- conn.rollback()
635
- logging.error(f"Unexpected Error: {e}")
636
- raise DatabaseError(f"Unexpected error: {e}")
637
-
638
-
639
- def ingest_article_to_db(url, title, author, content, keywords, summary, ingestion_date, custom_prompt):
640
- try:
641
- # Check if content is not empty or whitespace
642
- if not content.strip():
643
- raise ValueError("Content is empty.")
644
-
645
- keyword_list = keywords.split(',') if keywords else ["default"]
646
- keyword_str = ', '.join(keyword_list)
647
-
648
- # Set default values for missing fields
649
- url = url or 'Unknown'
650
- title = title or 'Unknown'
651
- author = author or 'Unknown'
652
- keywords = keywords or 'default'
653
- summary = summary or 'No summary available'
654
- ingestion_date = ingestion_date or datetime.now().strftime('%Y-%m-%d')
655
-
656
- # Log the values of all fields before calling add_media_with_keywords
657
- logging.debug(f"URL: {url}")
658
- logging.debug(f"Title: {title}")
659
- logging.debug(f"Author: {author}")
660
- logging.debug(f"Content: {content[:50]}... (length: {len(content)})") # Log first 50 characters of content
661
- logging.debug(f"Keywords: {keywords}")
662
- logging.debug(f"Summary: {summary}")
663
- logging.debug(f"Ingestion Date: {ingestion_date}")
664
- logging.debug(f"Custom Prompt: {custom_prompt}")
665
-
666
- # Check if any required field is empty and log the specific missing field
667
- if not url:
668
- logging.error("URL is missing.")
669
- raise ValueError("URL is missing.")
670
- if not title:
671
- logging.error("Title is missing.")
672
- raise ValueError("Title is missing.")
673
- if not content:
674
- logging.error("Content is missing.")
675
- raise ValueError("Content is missing.")
676
- if not keywords:
677
- logging.error("Keywords are missing.")
678
- raise ValueError("Keywords are missing.")
679
- if not summary:
680
- logging.error("Summary is missing.")
681
- raise ValueError("Summary is missing.")
682
- if not ingestion_date:
683
- logging.error("Ingestion date is missing.")
684
- raise ValueError("Ingestion date is missing.")
685
- if not custom_prompt:
686
- logging.error("Custom prompt is missing.")
687
- raise ValueError("Custom prompt is missing.")
688
-
689
- # Add media with keywords to the database
690
- result = add_media_with_keywords(
691
- url=url,
692
- title=title,
693
- media_type='article',
694
- content=content,
695
- keywords=keyword_str or "article_default",
696
- prompt=custom_prompt or None,
697
- summary=summary or "No summary generated",
698
- transcription_model=None, # or some default value if applicable
699
- author=author or 'Unknown',
700
- ingestion_date=ingestion_date
701
- )
702
- return result
703
- except Exception as e:
704
- logging.error(f"Failed to ingest article to the database: {e}")
705
- return str(e)
706
-
707
-
708
- def fetch_all_keywords() -> List[str]:
709
- try:
710
- with db.get_connection() as conn:
711
- cursor = conn.cursor()
712
- cursor.execute('SELECT keyword FROM Keywords')
713
- keywords = [row[0] for row in cursor.fetchall()]
714
- return keywords
715
- except sqlite3.Error as e:
716
- raise DatabaseError(f"Error fetching keywords: {e}")
717
-
718
- def keywords_browser_interface():
719
- keywords = fetch_all_keywords()
720
- return gr.Markdown("\n".join(f"- {keyword}" for keyword in keywords))
721
-
722
- def display_keywords():
723
- try:
724
- keywords = fetch_all_keywords()
725
- return "\n".join(keywords) if keywords else "No keywords found."
726
- except DatabaseError as e:
727
- return str(e)
728
-
729
-
730
- def export_keywords_to_csv():
731
- try:
732
- keywords = fetch_all_keywords()
733
- if not keywords:
734
- return None, "No keywords found in the database."
735
-
736
- filename = "keywords.csv"
737
- with open(filename, 'w', newline='', encoding='utf-8') as file:
738
- writer = csv.writer(file)
739
- writer.writerow(["Keyword"])
740
- for keyword in keywords:
741
- writer.writerow([keyword])
742
-
743
- return filename, f"Keywords exported to {filename}"
744
- except Exception as e:
745
- logger.error(f"Error exporting keywords to CSV: {e}")
746
- return None, f"Error exporting keywords: {e}"
747
-
748
-
749
- # Function to fetch items based on search query and type
750
- def browse_items(search_query, search_type):
751
- try:
752
- with db.get_connection() as conn:
753
- cursor = conn.cursor()
754
- if search_type == 'Title':
755
- cursor.execute("SELECT id, title, url FROM Media WHERE title LIKE ?", (f'%{search_query}%',))
756
- elif search_type == 'URL':
757
- cursor.execute("SELECT id, title, url FROM Media WHERE url LIKE ?", (f'%{search_query}%',))
758
- elif search_type == 'Keyword':
759
- return fetch_items_by_keyword(search_query)
760
- elif search_type == 'Content':
761
- cursor.execute("SELECT id, title, url FROM Media WHERE content LIKE ?", (f'%{search_query}%',))
762
- else:
763
- raise ValueError(f"Invalid search type: {search_type}")
764
-
765
- results = cursor.fetchall()
766
- return results
767
- except sqlite3.Error as e:
768
- logger.error(f"Error fetching items by {search_type}: {e}")
769
- raise DatabaseError(f"Error fetching items by {search_type}: {e}")
770
-
771
-
772
- # Function to fetch item details
773
- def fetch_item_details(media_id: int):
774
- try:
775
- with db.get_connection() as conn:
776
- cursor = conn.cursor()
777
- cursor.execute("""
778
- SELECT prompt, summary
779
- FROM MediaModifications
780
- WHERE media_id = ?
781
- ORDER BY modification_date DESC
782
- LIMIT 1
783
- """, (media_id,))
784
- prompt_summary_result = cursor.fetchone()
785
- cursor.execute("SELECT content FROM Media WHERE id = ?", (media_id,))
786
- content_result = cursor.fetchone()
787
-
788
- prompt = prompt_summary_result[0] if prompt_summary_result else ""
789
- summary = prompt_summary_result[1] if prompt_summary_result else ""
790
- content = content_result[0] if content_result else ""
791
-
792
- return content, prompt, summary
793
- except sqlite3.Error as e:
794
- logging.error(f"Error fetching item details: {e}")
795
- # Return empty strings if there's an error
796
- return "", "", ""
797
-
798
- #
799
- #
800
- #######################################################################################################################
801
- #
802
- # Media-related Functions
803
-
804
-
805
-
806
- # Function to add a version of a prompt and summary
807
- def add_media_version(media_id: int, prompt: str, summary: str) -> None:
808
- try:
809
- with db.get_connection() as conn:
810
- cursor = conn.cursor()
811
-
812
- # Get the current version number
813
- cursor.execute('SELECT MAX(version) FROM MediaVersion WHERE media_id = ?', (media_id,))
814
- current_version = cursor.fetchone()[0] or 0
815
-
816
- # Insert the new version
817
- cursor.execute('''
818
- INSERT INTO MediaVersion (media_id, version, prompt, summary, created_at)
819
- VALUES (?, ?, ?, ?, ?)
820
- ''', (media_id, current_version + 1, prompt, summary, datetime.now().strftime('%Y-%m-%d %H:%M:%S')))
821
- conn.commit()
822
- except sqlite3.Error as e:
823
- raise DatabaseError(f"Error adding media version: {e}")
824
-
825
-
826
- # Function to search the database with advanced options, including keyword search and full-text search
827
- def search_db(search_query: str, search_fields: List[str], keywords: str, page: int = 1, results_per_page: int = 10):
828
- if page < 1:
829
- raise ValueError("Page number must be 1 or greater.")
830
-
831
- # Prepare keywords by splitting and trimming
832
- keywords = [keyword.strip().lower() for keyword in keywords.split(',') if keyword.strip()]
833
-
834
- with db.get_connection() as conn:
835
- cursor = conn.cursor()
836
- offset = (page - 1) * results_per_page
837
-
838
- # Prepare the search conditions for general fields
839
- search_conditions = []
840
- params = []
841
-
842
- for field in search_fields:
843
- if search_query: # Ensure there's a search query before adding this condition
844
- search_conditions.append(f"Media.{field} LIKE ?")
845
- params.append(f'%{search_query}%')
846
-
847
- # Prepare the conditions for keywords filtering
848
- keyword_conditions = []
849
- for keyword in keywords:
850
- keyword_conditions.append(
851
- f"EXISTS (SELECT 1 FROM MediaKeywords mk JOIN Keywords k ON mk.keyword_id = k.id WHERE mk.media_id = Media.id AND k.keyword LIKE ?)")
852
- params.append(f'%{keyword}%')
853
-
854
- # Combine all conditions
855
- where_clause = " AND ".join(
856
- search_conditions + keyword_conditions) if search_conditions or keyword_conditions else "1=1"
857
-
858
- # Complete the query
859
- query = f'''
860
- SELECT DISTINCT Media.id, Media.url, Media.title, Media.type, Media.content, Media.author, Media.ingestion_date,
861
- MediaModifications.prompt, MediaModifications.summary
862
- FROM Media
863
- LEFT JOIN MediaModifications ON Media.id = MediaModifications.media_id
864
- WHERE {where_clause}
865
- ORDER BY Media.ingestion_date DESC
866
- LIMIT ? OFFSET ?
867
- '''
868
- params.extend([results_per_page, offset])
869
-
870
- cursor.execute(query, params)
871
- results = cursor.fetchall()
872
-
873
- return results
874
-
875
-
876
- # Gradio function to handle user input and display results with pagination, with better feedback
877
- def search_and_display(search_query, search_fields, keywords, page):
878
- results = search_db(search_query, search_fields, keywords, page)
879
-
880
- if isinstance(results, pd.DataFrame):
881
- # Convert DataFrame to a list of tuples or lists
882
- processed_results = results.values.tolist() # This converts DataFrame rows to lists
883
- elif isinstance(results, list):
884
- # Ensure that each element in the list is itself a list or tuple (not a dictionary)
885
- processed_results = [list(item.values()) if isinstance(item, dict) else item for item in results]
886
- else:
887
- raise TypeError("Unsupported data type for results")
888
-
889
- return processed_results
890
-
891
-
892
- def display_details(index, results):
893
- if index is None or results is None:
894
- return "Please select a result to view details."
895
-
896
- try:
897
- # Ensure the index is an integer and access the row properly
898
- index = int(index)
899
- if isinstance(results, pd.DataFrame):
900
- if index >= len(results):
901
- return "Index out of range. Please select a valid index."
902
- selected_row = results.iloc[index]
903
- else:
904
- # If results is not a DataFrame, but a list (assuming list of dicts)
905
- selected_row = results[index]
906
- except ValueError:
907
- return "Index must be an integer."
908
- except IndexError:
909
- return "Index out of range. Please select a valid index."
910
-
911
- # Build HTML output safely
912
- details_html = f"""
913
- <h3>{selected_row.get('Title', 'No Title')}</h3>
914
- <p><strong>URL:</strong> {selected_row.get('URL', 'No URL')}</p>
915
- <p><strong>Type:</strong> {selected_row.get('Type', 'No Type')}</p>
916
- <p><strong>Author:</strong> {selected_row.get('Author', 'No Author')}</p>
917
- <p><strong>Ingestion Date:</strong> {selected_row.get('Ingestion Date', 'No Date')}</p>
918
- <p><strong>Prompt:</strong> {selected_row.get('Prompt', 'No Prompt')}</p>
919
- <p><strong>Summary:</strong> {selected_row.get('Summary', 'No Summary')}</p>
920
- <p><strong>Content:</strong> {selected_row.get('Content', 'No Content')}</p>
921
- """
922
- return details_html
923
-
924
-
925
- def get_details(index, dataframe):
926
- if index is None or dataframe is None or index >= len(dataframe):
927
- return "Please select a result to view details."
928
- row = dataframe.iloc[index]
929
- details = f"""
930
- <h3>{row['Title']}</h3>
931
- <p><strong>URL:</strong> {row['URL']}</p>
932
- <p><strong>Type:</strong> {row['Type']}</p>
933
- <p><strong>Author:</strong> {row['Author']}</p>
934
- <p><strong>Ingestion Date:</strong> {row['Ingestion Date']}</p>
935
- <p><strong>Prompt:</strong> {row['Prompt']}</p>
936
- <p><strong>Summary:</strong> {row['Summary']}</p>
937
- <p><strong>Content:</strong></p>
938
- <pre>{row['Content']}</pre>
939
- """
940
- return details
941
-
942
-
943
- def format_results(results):
944
- if not results:
945
- return pd.DataFrame(columns=['URL', 'Title', 'Type', 'Content', 'Author', 'Ingestion Date', 'Prompt', 'Summary'])
946
-
947
- df = pd.DataFrame(results, columns=['URL', 'Title', 'Type', 'Content', 'Author', 'Ingestion Date', 'Prompt', 'Summary'])
948
- logging.debug(f"Formatted DataFrame: {df}")
949
-
950
- return df
951
-
952
-
953
- # Function to export search results to CSV or markdown with pagination
954
- def export_to_file(search_query: str, search_fields: List[str], keyword: str, page: int = 1, results_per_file: int = 1000, export_format: str = 'csv'):
955
- try:
956
- results = search_db(search_query, search_fields, keyword, page, results_per_file)
957
- if not results:
958
- return "No results found to export."
959
-
960
- # Create an 'exports' directory if it doesn't exist
961
- if not os.path.exists('exports'):
962
- os.makedirs('exports')
963
-
964
- if export_format == 'csv':
965
- filename = f'exports/search_results_page_{page}.csv'
966
- with open(filename, 'w', newline='', encoding='utf-8') as file:
967
- writer = csv.writer(file)
968
- writer.writerow(['URL', 'Title', 'Type', 'Content', 'Author', 'Ingestion Date', 'Prompt', 'Summary'])
969
- for row in results:
970
- writer.writerow(row)
971
- elif export_format == 'markdown':
972
- filename = f'exports/search_results_page_{page}.md'
973
- with open(filename, 'w', encoding='utf-8') as file:
974
- for item in results:
975
- markdown_content = convert_to_markdown({
976
- 'title': item[1],
977
- 'url': item[0],
978
- 'type': item[2],
979
- 'content': item[3],
980
- 'author': item[4],
981
- 'ingestion_date': item[5],
982
- 'summary': item[7],
983
- 'keywords': item[8].split(',') if item[8] else []
984
- })
985
- file.write(markdown_content)
986
- file.write("\n---\n\n") # Separator between items
987
- else:
988
- return f"Unsupported export format: {export_format}"
989
-
990
- return f"Results exported to {filename}"
991
- except (DatabaseError, InputError) as e:
992
- return str(e)
993
-
994
-
995
- # Helper function to validate date format
996
- def is_valid_date(date_string: str) -> bool:
997
- try:
998
- datetime.strptime(date_string, '%Y-%m-%d')
999
- return True
1000
- except ValueError:
1001
- return False
1002
-
1003
- # Add ingested media to DB
1004
- def add_media_to_database(url, info_dict, segments, summary, keywords, custom_prompt_input, whisper_model, media_type='video'):
1005
- try:
1006
- # Extract content from segments
1007
- if isinstance(segments, list):
1008
- content = ' '.join([segment.get('Text', '') for segment in segments if 'Text' in segment])
1009
- elif isinstance(segments, dict):
1010
- content = segments.get('text', '') or segments.get('content', '')
1011
- else:
1012
- content = str(segments)
1013
-
1014
- logging.debug(f"Extracted content (first 500 chars): {content[:500]}")
1015
-
1016
- # Set default custom prompt if not provided
1017
- if custom_prompt_input is None:
1018
- custom_prompt_input = """No Custom Prompt Provided or Was Used."""
1019
-
1020
- logging.info(f"Adding media to database: URL={url}, Title={info_dict.get('title', 'Untitled')}, Type={media_type}")
1021
-
1022
- # Process keywords
1023
- if isinstance(keywords, str):
1024
- keyword_list = [keyword.strip().lower() for keyword in keywords.split(',')]
1025
- elif isinstance(keywords, (list, tuple)):
1026
- keyword_list = [keyword.strip().lower() for keyword in keywords]
1027
- else:
1028
- keyword_list = ['default']
1029
-
1030
- with db.get_connection() as conn:
1031
- cursor = conn.cursor()
1032
-
1033
- # Check if media already exists
1034
- cursor.execute('SELECT id FROM Media WHERE url = ?', (url,))
1035
- existing_media = cursor.fetchone()
1036
-
1037
- if existing_media:
1038
- media_id = existing_media[0]
1039
- logging.info(f"Updating existing media with ID: {media_id}")
1040
-
1041
- cursor.execute('''
1042
- UPDATE Media
1043
- SET content = ?, transcription_model = ?, title = ?, type = ?, author = ?, ingestion_date = ?
1044
- WHERE id = ?
1045
- ''', (content, whisper_model, info_dict.get('title', 'Untitled'), media_type,
1046
- info_dict.get('uploader', 'Unknown'), datetime.now().strftime('%Y-%m-%d'), media_id))
1047
- else:
1048
- logging.info("Creating new media entry")
1049
-
1050
- cursor.execute('''
1051
- INSERT INTO Media (url, title, type, content, author, ingestion_date, transcription_model)
1052
- VALUES (?, ?, ?, ?, ?, ?, ?)
1053
- ''', (url, info_dict.get('title', 'Untitled'), media_type, content,
1054
- info_dict.get('uploader', 'Unknown'), datetime.now().strftime('%Y-%m-%d'), whisper_model))
1055
- media_id = cursor.lastrowid
1056
-
1057
- logging.info(f"Adding new modification to MediaModifications for media ID: {media_id}")
1058
- cursor.execute('''
1059
- INSERT INTO MediaModifications (media_id, prompt, summary, modification_date)
1060
- VALUES (?, ?, ?, ?)
1061
- ''', (media_id, custom_prompt_input, summary, datetime.now().strftime('%Y-%m-%d')))
1062
-
1063
- # Insert keywords and associate with media item
1064
- logging.info("Processing keywords")
1065
- for keyword in keyword_list:
1066
- cursor.execute('INSERT OR IGNORE INTO Keywords (keyword) VALUES (?)', (keyword,))
1067
- cursor.execute('SELECT id FROM Keywords WHERE keyword = ?', (keyword,))
1068
- keyword_id = cursor.fetchone()[0]
1069
- cursor.execute('INSERT OR IGNORE INTO MediaKeywords (media_id, keyword_id) VALUES (?, ?)',
1070
- (media_id, keyword_id))
1071
-
1072
- # Update full-text search index
1073
- logging.info("Updating full-text search index")
1074
- cursor.execute('INSERT OR REPLACE INTO media_fts (rowid, title, content) VALUES (?, ?, ?)',
1075
- (media_id, info_dict.get('title', 'Untitled'), content))
1076
-
1077
- logging.info("Adding new media version")
1078
- add_media_version(media_id, custom_prompt_input, summary)
1079
-
1080
- conn.commit()
1081
-
1082
- logging.info(f"Media '{info_dict.get('title', 'Untitled')}' successfully added/updated with ID: {media_id}")
1083
-
1084
- return f"Media '{info_dict.get('title', 'Untitled')}' added/updated successfully with keywords: {', '.join(keyword_list)}"
1085
-
1086
- except sqlite3.Error as e:
1087
- logging.error(f"SQL Error: {e}")
1088
- raise DatabaseError(f"Error adding media with keywords: {e}")
1089
- except Exception as e:
1090
- logging.error(f"Unexpected Error: {e}")
1091
- raise DatabaseError(f"Unexpected error: {e}")
1092
- # def add_media_to_database(url, info_dict, segments, summary, keywords, custom_prompt_input, whisper_model, media_type='video'):
1093
- # try:
1094
- # # Extract content from segments
1095
- # if isinstance(segments, list):
1096
- # content = ' '.join([segment.get('Text', '') for segment in segments if 'Text' in segment])
1097
- # elif isinstance(segments, dict):
1098
- # content = segments.get('text', '') or segments.get('content', '')
1099
- # else:
1100
- # content = str(segments)
1101
- #
1102
- # logging.debug(f"Extracted content (first 500 chars): {content[:500]}")
1103
- #
1104
- # # Set default custom prompt if not provided
1105
- # if custom_prompt_input is None:
1106
- # custom_prompt_input = """No Custom Prompt Provided or Was Used."""
1107
- #
1108
- # logging.info(f"Adding media to database: URL={url}, Title={info_dict.get('title', 'Untitled')}, Type={media_type}")
1109
- #
1110
- # result = add_media_with_keywords(
1111
- # url=url,
1112
- # title=info_dict.get('title', 'Untitled'),
1113
- # media_type=media_type,
1114
- # content=content,
1115
- # keywords=','.join(keywords) if isinstance(keywords, list) else keywords,
1116
- # prompt=custom_prompt_input or 'No prompt provided',
1117
- # summary=summary or 'No summary provided',
1118
- # transcription_model=whisper_model,
1119
- # author=info_dict.get('uploader', 'Unknown'),
1120
- # ingestion_date=datetime.now().strftime('%Y-%m-%d')
1121
- # )
1122
- #
1123
- # logging.info(f"Media added successfully: {result}")
1124
- # return result
1125
- #
1126
- # except Exception as e:
1127
- # logging.error(f"Error in add_media_to_database: {str(e)}")
1128
- # raise
1129
-
1130
-
1131
- #
1132
- # End of ....
1133
- #######################################################################################################################
1134
- #
1135
- # Functions to manage prompts DB
1136
-
1137
- def create_prompts_db():
1138
- with sqlite3.connect('prompts.db') as conn:
1139
- cursor = conn.cursor()
1140
- cursor.executescript('''
1141
- CREATE TABLE IF NOT EXISTS Prompts (
1142
- id INTEGER PRIMARY KEY AUTOINCREMENT,
1143
- name TEXT NOT NULL UNIQUE,
1144
- details TEXT,
1145
- system TEXT,
1146
- user TEXT
1147
- );
1148
- CREATE TABLE IF NOT EXISTS Keywords (
1149
- id INTEGER PRIMARY KEY AUTOINCREMENT,
1150
- keyword TEXT NOT NULL UNIQUE COLLATE NOCASE
1151
- );
1152
- CREATE TABLE IF NOT EXISTS PromptKeywords (
1153
- prompt_id INTEGER,
1154
- keyword_id INTEGER,
1155
- FOREIGN KEY (prompt_id) REFERENCES Prompts (id),
1156
- FOREIGN KEY (keyword_id) REFERENCES Keywords (id),
1157
- PRIMARY KEY (prompt_id, keyword_id)
1158
- );
1159
- CREATE INDEX IF NOT EXISTS idx_keywords_keyword ON Keywords(keyword);
1160
- CREATE INDEX IF NOT EXISTS idx_promptkeywords_prompt_id ON PromptKeywords(prompt_id);
1161
- CREATE INDEX IF NOT EXISTS idx_promptkeywords_keyword_id ON PromptKeywords(keyword_id);
1162
- ''')
1163
-
1164
-
1165
- def normalize_keyword(keyword):
1166
- return re.sub(r'\s+', ' ', keyword.strip().lower())
1167
-
1168
-
1169
- def add_prompt(name, details, system, user=None, keywords=None):
1170
- if not name or not system:
1171
- return "Name and system prompt are required."
1172
-
1173
- try:
1174
- with sqlite3.connect('prompts.db') as conn:
1175
- cursor = conn.cursor()
1176
- cursor.execute('''
1177
- INSERT INTO Prompts (name, details, system, user)
1178
- VALUES (?, ?, ?, ?)
1179
- ''', (name, details, system, user))
1180
- prompt_id = cursor.lastrowid
1181
-
1182
- if keywords:
1183
- normalized_keywords = [normalize_keyword(k) for k in keywords if k.strip()]
1184
- for keyword in set(normalized_keywords): # Use set to remove duplicates
1185
- cursor.execute('''
1186
- INSERT OR IGNORE INTO Keywords (keyword) VALUES (?)
1187
- ''', (keyword,))
1188
- cursor.execute('SELECT id FROM Keywords WHERE keyword = ?', (keyword,))
1189
- keyword_id = cursor.fetchone()[0]
1190
- cursor.execute('''
1191
- INSERT OR IGNORE INTO PromptKeywords (prompt_id, keyword_id) VALUES (?, ?)
1192
- ''', (prompt_id, keyword_id))
1193
- return "Prompt added successfully."
1194
- except sqlite3.IntegrityError:
1195
- return "Prompt with this name already exists."
1196
- except sqlite3.Error as e:
1197
- return f"Database error: {e}"
1198
-
1199
-
1200
- def fetch_prompt_details(name):
1201
- with sqlite3.connect('prompts.db') as conn:
1202
- cursor = conn.cursor()
1203
- cursor.execute('''
1204
- SELECT p.name, p.details, p.system, p.user, GROUP_CONCAT(k.keyword, ', ') as keywords
1205
- FROM Prompts p
1206
- LEFT JOIN PromptKeywords pk ON p.id = pk.prompt_id
1207
- LEFT JOIN Keywords k ON pk.keyword_id = k.id
1208
- WHERE p.name = ?
1209
- GROUP BY p.id
1210
- ''', (name,))
1211
- return cursor.fetchone()
1212
-
1213
-
1214
- def list_prompts(page=1, per_page=10):
1215
- offset = (page - 1) * per_page
1216
- with sqlite3.connect('prompts.db') as conn:
1217
- cursor = conn.cursor()
1218
- cursor.execute('SELECT name FROM Prompts LIMIT ? OFFSET ?', (per_page, offset))
1219
- prompts = [row[0] for row in cursor.fetchall()]
1220
-
1221
- # Get total count of prompts
1222
- cursor.execute('SELECT COUNT(*) FROM Prompts')
1223
- total_count = cursor.fetchone()[0]
1224
-
1225
- total_pages = (total_count + per_page - 1) // per_page
1226
- return prompts, total_pages, page
1227
-
1228
- # This will not scale. For a large number of prompts, use a more efficient method.
1229
- # FIXME - see above statement.
1230
- def load_preset_prompts():
1231
- try:
1232
- with sqlite3.connect('prompts.db') as conn:
1233
- cursor = conn.cursor()
1234
- cursor.execute('SELECT name FROM Prompts ORDER BY name ASC')
1235
- prompts = [row[0] for row in cursor.fetchall()]
1236
- return prompts
1237
- except sqlite3.Error as e:
1238
- print(f"Database error: {e}")
1239
- return []
1240
-
1241
-
1242
- def insert_prompt_to_db(title, description, system_prompt, user_prompt, keywords=None):
1243
- return add_prompt(title, description, system_prompt, user_prompt, keywords)
1244
-
1245
-
1246
- def search_prompts_by_keyword(keyword, page=1, per_page=10):
1247
- normalized_keyword = normalize_keyword(keyword)
1248
- offset = (page - 1) * per_page
1249
- with sqlite3.connect('prompts.db') as conn:
1250
- cursor = conn.cursor()
1251
- cursor.execute('''
1252
- SELECT DISTINCT p.name
1253
- FROM Prompts p
1254
- JOIN PromptKeywords pk ON p.id = pk.prompt_id
1255
- JOIN Keywords k ON pk.keyword_id = k.id
1256
- WHERE k.keyword LIKE ?
1257
- LIMIT ? OFFSET ?
1258
- ''', ('%' + normalized_keyword + '%', per_page, offset))
1259
- prompts = [row[0] for row in cursor.fetchall()]
1260
-
1261
- # Get total count of matching prompts
1262
- cursor.execute('''
1263
- SELECT COUNT(DISTINCT p.id)
1264
- FROM Prompts p
1265
- JOIN PromptKeywords pk ON p.id = pk.prompt_id
1266
- JOIN Keywords k ON pk.keyword_id = k.id
1267
- WHERE k.keyword LIKE ?
1268
- ''', ('%' + normalized_keyword + '%',))
1269
- total_count = cursor.fetchone()[0]
1270
-
1271
- total_pages = (total_count + per_page - 1) // per_page
1272
- return prompts, total_pages, page
1273
-
1274
-
1275
- def update_prompt_keywords(prompt_name, new_keywords):
1276
- try:
1277
- with sqlite3.connect('prompts.db') as conn:
1278
- cursor = conn.cursor()
1279
-
1280
- cursor.execute('SELECT id FROM Prompts WHERE name = ?', (prompt_name,))
1281
- prompt_id = cursor.fetchone()
1282
- if not prompt_id:
1283
- return "Prompt not found."
1284
- prompt_id = prompt_id[0]
1285
-
1286
- cursor.execute('DELETE FROM PromptKeywords WHERE prompt_id = ?', (prompt_id,))
1287
-
1288
- normalized_keywords = [normalize_keyword(k) for k in new_keywords if k.strip()]
1289
- for keyword in set(normalized_keywords): # Use set to remove duplicates
1290
- cursor.execute('INSERT OR IGNORE INTO Keywords (keyword) VALUES (?)', (keyword,))
1291
- cursor.execute('SELECT id FROM Keywords WHERE keyword = ?', (keyword,))
1292
- keyword_id = cursor.fetchone()[0]
1293
- cursor.execute('INSERT INTO PromptKeywords (prompt_id, keyword_id) VALUES (?, ?)',
1294
- (prompt_id, keyword_id))
1295
-
1296
- # Remove unused keywords
1297
- cursor.execute('''
1298
- DELETE FROM Keywords
1299
- WHERE id NOT IN (SELECT DISTINCT keyword_id FROM PromptKeywords)
1300
- ''')
1301
- return "Keywords updated successfully."
1302
- except sqlite3.Error as e:
1303
- return f"Database error: {e}"
1304
-
1305
-
1306
- def add_or_update_prompt(title, description, system_prompt, user_prompt, keywords=None):
1307
- if not title:
1308
- return "Error: Title is required."
1309
-
1310
- existing_prompt = fetch_prompt_details(title)
1311
- if existing_prompt:
1312
- # Update existing prompt
1313
- result = update_prompt_in_db(title, description, system_prompt, user_prompt)
1314
- if "successfully" in result:
1315
- # Update keywords if the prompt update was successful
1316
- keyword_result = update_prompt_keywords(title, keywords or [])
1317
- result += f" {keyword_result}"
1318
- else:
1319
- # Insert new prompt
1320
- result = insert_prompt_to_db(title, description, system_prompt, user_prompt, keywords)
1321
-
1322
- return result
1323
-
1324
-
1325
- def load_prompt_details(selected_prompt):
1326
- if selected_prompt:
1327
- details = fetch_prompt_details(selected_prompt)
1328
- if details:
1329
- return details[0], details[1], details[2], details[3], details[4] # Include keywords
1330
- return "", "", "", "", ""
1331
-
1332
-
1333
- def update_prompt_in_db(title, description, system_prompt, user_prompt):
1334
- try:
1335
- with sqlite3.connect('prompts.db') as conn:
1336
- cursor = conn.cursor()
1337
- cursor.execute(
1338
- "UPDATE Prompts SET details = ?, system = ?, user = ? WHERE name = ?",
1339
- (description, system_prompt, user_prompt, title)
1340
- )
1341
- if cursor.rowcount == 0:
1342
- return "No prompt found with the given title."
1343
- return "Prompt updated successfully!"
1344
- except sqlite3.Error as e:
1345
- return f"Error updating prompt: {e}"
1346
-
1347
-
1348
- create_prompts_db()
1349
-
1350
- def delete_prompt(prompt_id):
1351
- try:
1352
- with sqlite3.connect('prompts.db') as conn:
1353
- cursor = conn.cursor()
1354
-
1355
- # Delete associated keywords
1356
- cursor.execute("DELETE FROM PromptKeywords WHERE prompt_id = ?", (prompt_id,))
1357
-
1358
- # Delete the prompt
1359
- cursor.execute("DELETE FROM Prompts WHERE id = ?", (prompt_id,))
1360
-
1361
- if cursor.rowcount == 0:
1362
- return f"No prompt found with ID {prompt_id}"
1363
- else:
1364
- conn.commit()
1365
- return f"Prompt with ID {prompt_id} has been successfully deleted"
1366
- except sqlite3.Error as e:
1367
- return f"An error occurred: {e}"
1368
-
1369
- #
1370
- #
1371
- #######################################################################################################################
1372
- #
1373
- # Function to fetch/update media content
1374
-
1375
- def update_media_content(selected_item, item_mapping, content_input, prompt_input, summary_input):
1376
- try:
1377
- if selected_item and item_mapping and selected_item in item_mapping:
1378
- media_id = item_mapping[selected_item]
1379
-
1380
- with db.get_connection() as conn:
1381
- cursor = conn.cursor()
1382
-
1383
- # Update the main content in the Media table
1384
- cursor.execute("UPDATE Media SET content = ? WHERE id = ?", (content_input, media_id))
1385
-
1386
- # Check if a row already exists in MediaModifications for this media_id
1387
- cursor.execute("SELECT COUNT(*) FROM MediaModifications WHERE media_id = ?", (media_id,))
1388
- exists = cursor.fetchone()[0] > 0
1389
-
1390
- if exists:
1391
- # Update existing row
1392
- cursor.execute("""
1393
- UPDATE MediaModifications
1394
- SET prompt = ?, summary = ?, modification_date = CURRENT_TIMESTAMP
1395
- WHERE media_id = ?
1396
- """, (prompt_input, summary_input, media_id))
1397
- else:
1398
- # Insert new row
1399
- cursor.execute("""
1400
- INSERT INTO MediaModifications (media_id, prompt, summary, modification_date)
1401
- VALUES (?, ?, ?, CURRENT_TIMESTAMP)
1402
- """, (media_id, prompt_input, summary_input))
1403
-
1404
- conn.commit()
1405
-
1406
- return f"Content updated successfully for media ID: {media_id}"
1407
- else:
1408
- return "No item selected or invalid selection"
1409
- except Exception as e:
1410
- logging.error(f"Error updating media content: {e}")
1411
- return f"Error updating content: {str(e)}"
1412
-
1413
- def search_media_database(query: str) -> List[Tuple[int, str, str]]:
1414
- try:
1415
- with db.get_connection() as conn:
1416
- cursor = conn.cursor()
1417
- cursor.execute("SELECT id, title, url FROM Media WHERE title LIKE ?", (f'%{query}%',))
1418
- results = cursor.fetchall()
1419
- return results
1420
- except sqlite3.Error as e:
1421
- raise Exception(f"Error searching media database: {e}")
1422
-
1423
- def load_media_content(media_id: int) -> dict:
1424
- try:
1425
- with db.get_connection() as conn:
1426
- cursor = conn.cursor()
1427
- cursor.execute("SELECT content, prompt, summary FROM Media WHERE id = ?", (media_id,))
1428
- result = cursor.fetchone()
1429
- if result:
1430
- return {
1431
- "content": result[0],
1432
- "prompt": result[1],
1433
- "summary": result[2]
1434
- }
1435
- return {"content": "", "prompt": "", "summary": ""}
1436
- except sqlite3.Error as e:
1437
- raise Exception(f"Error loading media content: {e}")
1438
-
1439
-
1440
- def fetch_items_by_title_or_url(search_query: str, search_type: str):
1441
- try:
1442
- with db.get_connection() as conn:
1443
- cursor = conn.cursor()
1444
- if search_type == 'Title':
1445
- cursor.execute("SELECT id, title, url FROM Media WHERE title LIKE ?", (f'%{search_query}%',))
1446
- elif search_type == 'URL':
1447
- cursor.execute("SELECT id, title, url FROM Media WHERE url LIKE ?", (f'%{search_query}%',))
1448
- results = cursor.fetchall()
1449
- return results
1450
- except sqlite3.Error as e:
1451
- raise DatabaseError(f"Error fetching items by {search_type}: {e}")
1452
-
1453
-
1454
- def fetch_items_by_keyword(search_query: str):
1455
- try:
1456
- with db.get_connection() as conn:
1457
- cursor = conn.cursor()
1458
- cursor.execute("""
1459
- SELECT m.id, m.title, m.url
1460
- FROM Media m
1461
- JOIN MediaKeywords mk ON m.id = mk.media_id
1462
- JOIN Keywords k ON mk.keyword_id = k.id
1463
- WHERE k.keyword LIKE ?
1464
- """, (f'%{search_query}%',))
1465
- results = cursor.fetchall()
1466
- return results
1467
- except sqlite3.Error as e:
1468
- raise DatabaseError(f"Error fetching items by keyword: {e}")
1469
-
1470
-
1471
- def fetch_items_by_content(search_query: str):
1472
- try:
1473
- with db.get_connection() as conn:
1474
- cursor = conn.cursor()
1475
- cursor.execute("SELECT id, title, url FROM Media WHERE content LIKE ?", (f'%{search_query}%',))
1476
- results = cursor.fetchall()
1477
- return results
1478
- except sqlite3.Error as e:
1479
- raise DatabaseError(f"Error fetching items by content: {e}")
1480
-
1481
-
1482
- def fetch_item_details_single(media_id: int):
1483
- try:
1484
- with db.get_connection() as conn:
1485
- cursor = conn.cursor()
1486
- cursor.execute("""
1487
- SELECT prompt, summary
1488
- FROM MediaModifications
1489
- WHERE media_id = ?
1490
- ORDER BY modification_date DESC
1491
- LIMIT 1
1492
- """, (media_id,))
1493
- prompt_summary_result = cursor.fetchone()
1494
- cursor.execute("SELECT content FROM Media WHERE id = ?", (media_id,))
1495
- content_result = cursor.fetchone()
1496
-
1497
- prompt = prompt_summary_result[0] if prompt_summary_result else ""
1498
- summary = prompt_summary_result[1] if prompt_summary_result else ""
1499
- content = content_result[0] if content_result else ""
1500
-
1501
- return prompt, summary, content
1502
- except sqlite3.Error as e:
1503
- raise Exception(f"Error fetching item details: {e}")
1504
-
1505
-
1506
-
1507
- def convert_to_markdown(item):
1508
- markdown = f"# {item['title']}\n\n"
1509
- markdown += f"**URL:** {item['url']}\n\n"
1510
- markdown += f"**Author:** {item['author']}\n\n"
1511
- markdown += f"**Ingestion Date:** {item['ingestion_date']}\n\n"
1512
- markdown += f"**Type:** {item['type']}\n\n"
1513
- markdown += f"**Keywords:** {', '.join(item['keywords'])}\n\n"
1514
- markdown += "## Summary\n\n"
1515
- markdown += f"{item['summary']}\n\n"
1516
- markdown += "## Content\n\n"
1517
- markdown += f"{item['content']}\n\n"
1518
- return markdown
1519
-
1520
- # Gradio function to handle user input and display results with pagination for displaying entries in the DB
1521
- def fetch_paginated_data(page: int, results_per_page: int) -> Tuple[List[Tuple], int]:
1522
- try:
1523
- offset = (page - 1) * results_per_page
1524
- with db.get_connection() as conn:
1525
- cursor = conn.cursor()
1526
- cursor.execute("SELECT COUNT(*) FROM Media")
1527
- total_entries = cursor.fetchone()[0]
1528
-
1529
- cursor.execute("SELECT id, title, url FROM Media LIMIT ? OFFSET ?", (results_per_page, offset))
1530
- results = cursor.fetchall()
1531
-
1532
- return results, total_entries
1533
- except sqlite3.Error as e:
1534
- raise Exception(f"Error fetching paginated data: {e}")
1535
-
1536
- def format_results_as_html(results: List[Tuple]) -> str:
1537
- html = "<table class='table table-striped'>"
1538
- html += "<tr><th>ID</th><th>Title</th><th>URL</th></tr>"
1539
- for row in results:
1540
- html += f"<tr><td>{row[0]}</td><td>{row[1]}</td><td>{row[2]}</td></tr>"
1541
- html += "</table>"
1542
- return html
1543
-
1544
- def view_database(page: int, results_per_page: int) -> Tuple[str, str, int]:
1545
- results, total_entries = fetch_paginated_data(page, results_per_page)
1546
- formatted_results = format_results_as_html(results)
1547
- # Calculate total pages
1548
- total_pages = (total_entries + results_per_page - 1) // results_per_page
1549
- return formatted_results, f"Page {page} of {total_pages}", total_pages
1550
-
1551
-
1552
- def search_and_display_items(query, search_type, page, entries_per_page,char_count):
1553
- offset = (page - 1) * entries_per_page
1554
- try:
1555
- with sqlite3.connect('media_summary.db') as conn:
1556
- cursor = conn.cursor()
1557
-
1558
- # Adjust the SQL query based on the search type
1559
- if search_type == "Title":
1560
- where_clause = "WHERE m.title LIKE ?"
1561
- elif search_type == "URL":
1562
- where_clause = "WHERE m.url LIKE ?"
1563
- elif search_type == "Keyword":
1564
- where_clause = "WHERE k.keyword LIKE ?"
1565
- elif search_type == "Content":
1566
- where_clause = "WHERE m.content LIKE ?"
1567
- else:
1568
- raise ValueError("Invalid search type")
1569
-
1570
- cursor.execute(f'''
1571
- SELECT m.id, m.title, m.url, m.content, mm.summary, GROUP_CONCAT(k.keyword, ', ') as keywords
1572
- FROM Media m
1573
- LEFT JOIN MediaModifications mm ON m.id = mm.media_id
1574
- LEFT JOIN MediaKeywords mk ON m.id = mk.media_id
1575
- LEFT JOIN Keywords k ON mk.keyword_id = k.id
1576
- {where_clause}
1577
- GROUP BY m.id
1578
- ORDER BY m.ingestion_date DESC
1579
- LIMIT ? OFFSET ?
1580
- ''', (f'%{query}%', entries_per_page, offset))
1581
- items = cursor.fetchall()
1582
-
1583
- cursor.execute(f'''
1584
- SELECT COUNT(DISTINCT m.id)
1585
- FROM Media m
1586
- LEFT JOIN MediaKeywords mk ON m.id = mk.media_id
1587
- LEFT JOIN Keywords k ON mk.keyword_id = k.id
1588
- {where_clause}
1589
- ''', (f'%{query}%',))
1590
- total_items = cursor.fetchone()[0]
1591
-
1592
- results = ""
1593
- for item in items:
1594
- title = html.escape(item[1]).replace('\n', '<br>')
1595
- url = html.escape(item[2]).replace('\n', '<br>')
1596
- # First X amount of characters of the content
1597
- content = html.escape(item[3] or '')[:char_count] + '...'
1598
- summary = html.escape(item[4] or '').replace('\n', '<br>')
1599
- keywords = html.escape(item[5] or '').replace('\n', '<br>')
1600
-
1601
- results += f"""
1602
- <div style="border: 1px solid #ddd; padding: 10px; margin-bottom: 20px;">
1603
- <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
1604
- <div><strong>Title:</strong> {title}</div>
1605
- <div><strong>URL:</strong> {url}</div>
1606
- </div>
1607
- <div style="margin-top: 10px;">
1608
- <strong>Content (first {char_count} characters):</strong>
1609
- <pre style="white-space: pre-wrap; word-wrap: break-word;">{content}</pre>
1610
- </div>
1611
- <div style="margin-top: 10px;">
1612
- <strong>Summary:</strong>
1613
- <pre style="white-space: pre-wrap; word-wrap: break-word;">{summary}</pre>
1614
- </div>
1615
- <div style="margin-top: 10px;">
1616
- <strong>Keywords:</strong> {keywords}
1617
- </div>
1618
- </div>
1619
- """
1620
-
1621
- total_pages = (total_items + entries_per_page - 1) // entries_per_page
1622
- pagination = f"Page {page} of {total_pages} (Total items: {total_items})"
1623
-
1624
- return results, pagination, total_pages
1625
- except sqlite3.Error as e:
1626
- return f"<p>Error searching items: {e}</p>", "Error", 0
1627
-
1628
-
1629
- #
1630
- # End of Functions to manage prompts DB / Fetch and update media content
1631
- #######################################################################################################################
1632
- #
1633
- # Obsidian-related Functions
1634
-
1635
- def import_obsidian_note_to_db(note_data):
1636
- try:
1637
- with db.get_connection() as conn:
1638
- cursor = conn.cursor()
1639
-
1640
- cursor.execute("SELECT id FROM Media WHERE title = ? AND type = 'obsidian_note'", (note_data['title'],))
1641
- existing_note = cursor.fetchone()
1642
-
1643
- # Generate a relative path or meaningful identifier instead of using the temporary file path
1644
- relative_path = os.path.relpath(note_data['file_path'], start=os.path.dirname(note_data['file_path']))
1645
-
1646
- if existing_note:
1647
- media_id = existing_note[0]
1648
- cursor.execute("""
1649
- UPDATE Media
1650
- SET content = ?, author = ?, ingestion_date = CURRENT_TIMESTAMP, url = ?
1651
- WHERE id = ?
1652
- """, (note_data['content'], note_data['frontmatter'].get('author', 'Unknown'), relative_path, media_id))
1653
-
1654
- cursor.execute("DELETE FROM MediaKeywords WHERE media_id = ?", (media_id,))
1655
- else:
1656
- cursor.execute("""
1657
- INSERT INTO Media (title, content, type, author, ingestion_date, url)
1658
- VALUES (?, ?, 'obsidian_note', ?, CURRENT_TIMESTAMP, ?)
1659
- """, (note_data['title'], note_data['content'], note_data['frontmatter'].get('author', 'Unknown'),
1660
- relative_path))
1661
-
1662
- media_id = cursor.lastrowid
1663
-
1664
- for tag in note_data['tags']:
1665
- cursor.execute("INSERT OR IGNORE INTO Keywords (keyword) VALUES (?)", (tag,))
1666
- cursor.execute("SELECT id FROM Keywords WHERE keyword = ?", (tag,))
1667
- keyword_id = cursor.fetchone()[0]
1668
- cursor.execute("INSERT OR IGNORE INTO MediaKeywords (media_id, keyword_id) VALUES (?, ?)",
1669
- (media_id, keyword_id))
1670
-
1671
- frontmatter_str = yaml.dump(note_data['frontmatter'])
1672
- cursor.execute("""
1673
- INSERT INTO MediaModifications (media_id, prompt, summary, modification_date)
1674
- VALUES (?, 'Obsidian Frontmatter', ?, CURRENT_TIMESTAMP)
1675
- """, (media_id, frontmatter_str))
1676
-
1677
- # Update full-text search index
1678
- cursor.execute('INSERT OR REPLACE INTO media_fts (rowid, title, content) VALUES (?, ?, ?)',
1679
- (media_id, note_data['title'], note_data['content']))
1680
-
1681
- action = "Updated" if existing_note else "Imported"
1682
- logger.info(f"{action} Obsidian note: {note_data['title']}")
1683
- return True, None
1684
- except sqlite3.Error as e:
1685
- error_msg = f"Database error {'updating' if existing_note else 'importing'} note {note_data['title']}: {str(e)}"
1686
- logger.error(error_msg)
1687
- return False, error_msg
1688
- except Exception as e:
1689
- error_msg = f"Unexpected error {'updating' if existing_note else 'importing'} note {note_data['title']}: {str(e)}\n{traceback.format_exc()}"
1690
- logger.error(error_msg)
1691
- return False, error_msg
1692
-
1693
-
1694
- #
1695
- # End of Obsidian-related Functions
1696
- #######################################################################################################################
1697
- #
1698
- # Chat-related Functions
1699
-
1700
-
1701
-
1702
- def create_chat_conversation(media_id, conversation_name):
1703
- try:
1704
- with db.get_connection() as conn:
1705
- cursor = conn.cursor()
1706
- cursor.execute('''
1707
- INSERT INTO ChatConversations (media_id, conversation_name, created_at, updated_at)
1708
- VALUES (?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
1709
- ''', (media_id, conversation_name))
1710
- conn.commit()
1711
- return cursor.lastrowid
1712
- except sqlite3.Error as e:
1713
- logging.error(f"Error creating chat conversation: {e}")
1714
- raise DatabaseError(f"Error creating chat conversation: {e}")
1715
-
1716
-
1717
- def add_chat_message(conversation_id: int, sender: str, message: str) -> int:
1718
- try:
1719
- with db.get_connection() as conn:
1720
- cursor = conn.cursor()
1721
- cursor.execute('''
1722
- INSERT INTO ChatMessages (conversation_id, sender, message)
1723
- VALUES (?, ?, ?)
1724
- ''', (conversation_id, sender, message))
1725
- conn.commit()
1726
- return cursor.lastrowid
1727
- except sqlite3.Error as e:
1728
- logging.error(f"Error adding chat message: {e}")
1729
- raise DatabaseError(f"Error adding chat message: {e}")
1730
-
1731
-
1732
- def get_chat_messages(conversation_id: int) -> List[Dict[str, Any]]:
1733
- try:
1734
- with db.get_connection() as conn:
1735
- cursor = conn.cursor()
1736
- cursor.execute('''
1737
- SELECT id, sender, message, timestamp
1738
- FROM ChatMessages
1739
- WHERE conversation_id = ?
1740
- ORDER BY timestamp ASC
1741
- ''', (conversation_id,))
1742
- messages = cursor.fetchall()
1743
- return [
1744
- {
1745
- 'id': msg[0],
1746
- 'sender': msg[1],
1747
- 'message': msg[2],
1748
- 'timestamp': msg[3]
1749
- }
1750
- for msg in messages
1751
- ]
1752
- except sqlite3.Error as e:
1753
- logging.error(f"Error retrieving chat messages: {e}")
1754
- raise DatabaseError(f"Error retrieving chat messages: {e}")
1755
-
1756
-
1757
- def search_chat_conversations(search_query: str) -> List[Dict[str, Any]]:
1758
- try:
1759
- with db.get_connection() as conn:
1760
- cursor = conn.cursor()
1761
- cursor.execute('''
1762
- SELECT cc.id, cc.media_id, cc.conversation_name, cc.created_at, m.title as media_title
1763
- FROM ChatConversations cc
1764
- LEFT JOIN Media m ON cc.media_id = m.id
1765
- WHERE cc.conversation_name LIKE ? OR m.title LIKE ?
1766
- ORDER BY cc.updated_at DESC
1767
- ''', (f'%{search_query}%', f'%{search_query}%'))
1768
- conversations = cursor.fetchall()
1769
- return [
1770
- {
1771
- 'id': conv[0],
1772
- 'media_id': conv[1],
1773
- 'conversation_name': conv[2],
1774
- 'created_at': conv[3],
1775
- 'media_title': conv[4] or "Unknown Media"
1776
- }
1777
- for conv in conversations
1778
- ]
1779
- except sqlite3.Error as e:
1780
- logging.error(f"Error searching chat conversations: {e}")
1781
- return []
1782
-
1783
-
1784
- def update_chat_message(message_id: int, new_message: str) -> None:
1785
- try:
1786
- with db.get_connection() as conn:
1787
- cursor = conn.cursor()
1788
- cursor.execute('''
1789
- UPDATE ChatMessages
1790
- SET message = ?, timestamp = CURRENT_TIMESTAMP
1791
- WHERE id = ?
1792
- ''', (new_message, message_id))
1793
- conn.commit()
1794
- except sqlite3.Error as e:
1795
- logging.error(f"Error updating chat message: {e}")
1796
- raise DatabaseError(f"Error updating chat message: {e}")
1797
-
1798
-
1799
- def delete_chat_message(message_id: int) -> None:
1800
- try:
1801
- with db.get_connection() as conn:
1802
- cursor = conn.cursor()
1803
- cursor.execute('DELETE FROM ChatMessages WHERE id = ?', (message_id,))
1804
- conn.commit()
1805
- except sqlite3.Error as e:
1806
- logging.error(f"Error deleting chat message: {e}")
1807
- raise DatabaseError(f"Error deleting chat message: {e}")
1808
-
1809
-
1810
- def save_chat_history_to_database(chatbot, conversation_id, media_id, media_name, conversation_name):
1811
- try:
1812
- with db.get_connection() as conn:
1813
- cursor = conn.cursor()
1814
-
1815
- # If conversation_id is None, create a new conversation
1816
- if conversation_id is None:
1817
- cursor.execute('''
1818
- INSERT INTO ChatConversations (media_id, media_name, conversation_name, created_at, updated_at)
1819
- VALUES (?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
1820
- ''', (media_id, media_name, conversation_name))
1821
- conversation_id = cursor.lastrowid
1822
- else:
1823
- # If conversation exists, update the media_name
1824
- cursor.execute('''
1825
- UPDATE ChatConversations
1826
- SET media_name = ?, updated_at = CURRENT_TIMESTAMP
1827
- WHERE id = ?
1828
- ''', (media_name, conversation_id))
1829
-
1830
- # Save each message in the chatbot history
1831
- for i, (user_msg, ai_msg) in enumerate(chatbot):
1832
- cursor.execute('''
1833
- INSERT INTO ChatMessages (conversation_id, sender, message, timestamp)
1834
- VALUES (?, ?, ?, CURRENT_TIMESTAMP)
1835
- ''', (conversation_id, 'user', user_msg))
1836
-
1837
- cursor.execute('''
1838
- INSERT INTO ChatMessages (conversation_id, sender, message, timestamp)
1839
- VALUES (?, ?, ?, CURRENT_TIMESTAMP)
1840
- ''', (conversation_id, 'ai', ai_msg))
1841
-
1842
- # Update the conversation's updated_at timestamp
1843
- cursor.execute('''
1844
- UPDATE ChatConversations
1845
- SET updated_at = CURRENT_TIMESTAMP
1846
- WHERE id = ?
1847
- ''', (conversation_id,))
1848
-
1849
- conn.commit()
1850
-
1851
- return conversation_id
1852
- except Exception as e:
1853
- logging.error(f"Error saving chat history to database: {str(e)}")
1854
- raise
1855
-
1856
-
1857
- def get_conversation_name(conversation_id):
1858
- if conversation_id is None:
1859
- return None
1860
-
1861
- try:
1862
- with sqlite3.connect('media_summary.db') as conn: # Replace with your actual database name
1863
- cursor = conn.cursor()
1864
-
1865
- query = """
1866
- SELECT conversation_name, media_name
1867
- FROM ChatConversations
1868
- WHERE id = ?
1869
- """
1870
-
1871
- cursor.execute(query, (conversation_id,))
1872
- result = cursor.fetchone()
1873
-
1874
- if result:
1875
- conversation_name, media_name = result
1876
- if conversation_name:
1877
- return conversation_name
1878
- elif media_name:
1879
- return f"{media_name}-chat"
1880
-
1881
- return None # Return None if no result found
1882
- except sqlite3.Error as e:
1883
- logging.error(f"Database error in get_conversation_name: {e}")
1884
- return None
1885
- except Exception as e:
1886
- logging.error(f"Unexpected error in get_conversation_name: {e}")
1887
- return None
1888
-
1889
- #
1890
- # End of Chat-related Functions
1891
- #######################################################################################################################
1892
-
1893
-
1894
- #######################################################################################################################
1895
- #
1896
- # Functions to Compare Transcripts
1897
-
1898
- # Fetch Transcripts
1899
- def get_transcripts(media_id):
1900
- try:
1901
- with db.get_connection() as conn:
1902
- cursor = conn.cursor()
1903
- cursor.execute('''
1904
- SELECT id, whisper_model, transcription, created_at
1905
- FROM Transcripts
1906
- WHERE media_id = ?
1907
- ORDER BY created_at DESC
1908
- ''', (media_id,))
1909
- return cursor.fetchall()
1910
- except Exception as e:
1911
- logging.error(f"Error in get_transcripts: {str(e)}")
1912
- return []
1913
-
1914
-
1915
- #
1916
- # End of Functions to Compare Transcripts
1917
- #######################################################################################################################
1918
-
1919
-
1920
- #######################################################################################################################
1921
- #
1922
- # Functions to handle deletion of media items
1923
-
1924
-
1925
- def mark_as_trash(media_id: int) -> None:
1926
- with db.get_connection() as conn:
1927
- cursor = conn.cursor()
1928
- cursor.execute("""
1929
- UPDATE Media
1930
- SET is_trash = 1, trash_date = ?
1931
- WHERE id = ?
1932
- """, (datetime.now(), media_id))
1933
- conn.commit()
1934
-
1935
-
1936
- def restore_from_trash(media_id: int) -> None:
1937
- with db.get_connection() as conn:
1938
- cursor = conn.cursor()
1939
- cursor.execute("""
1940
- UPDATE Media
1941
- SET is_trash = 0, trash_date = NULL
1942
- WHERE id = ?
1943
- """, (media_id,))
1944
- conn.commit()
1945
-
1946
-
1947
- def get_trashed_items() -> List[Dict]:
1948
- with db.get_connection() as conn:
1949
- cursor = conn.cursor()
1950
- cursor.execute("""
1951
- SELECT id, title, trash_date
1952
- FROM Media
1953
- WHERE is_trash = 1
1954
- ORDER BY trash_date DESC
1955
- """)
1956
- return [{'id': row[0], 'title': row[1], 'trash_date': row[2]} for row in cursor.fetchall()]
1957
-
1958
-
1959
- def permanently_delete_item(media_id: int) -> None:
1960
- with db.get_connection() as conn:
1961
- cursor = conn.cursor()
1962
- cursor.execute("DELETE FROM Media WHERE id = ?", (media_id,))
1963
- cursor.execute("DELETE FROM MediaKeywords WHERE media_id = ?", (media_id,))
1964
- cursor.execute("DELETE FROM MediaVersion WHERE media_id = ?", (media_id,))
1965
- cursor.execute("DELETE FROM MediaModifications WHERE media_id = ?", (media_id,))
1966
- cursor.execute("DELETE FROM media_fts WHERE rowid = ?", (media_id,))
1967
- conn.commit()
1968
-
1969
-
1970
- def empty_trash(days_threshold: int) -> Tuple[int, int]:
1971
- threshold_date = datetime.now() - timedelta(days=days_threshold)
1972
- with db.get_connection() as conn:
1973
- cursor = conn.cursor()
1974
- cursor.execute("""
1975
- SELECT id FROM Media
1976
- WHERE is_trash = 1 AND trash_date <= ?
1977
- """, (threshold_date,))
1978
- old_items = cursor.fetchall()
1979
-
1980
- for item in old_items:
1981
- permanently_delete_item(item[0])
1982
-
1983
- cursor.execute("""
1984
- SELECT COUNT(*) FROM Media
1985
- WHERE is_trash = 1 AND trash_date > ?
1986
- """, (threshold_date,))
1987
- remaining_items = cursor.fetchone()[0]
1988
-
1989
- return len(old_items), remaining_items
1990
-
1991
-
1992
- def user_delete_item(media_id: int, force: bool = False) -> str:
1993
- with db.get_connection() as conn:
1994
- cursor = conn.cursor()
1995
- cursor.execute("SELECT is_trash, trash_date FROM Media WHERE id = ?", (media_id,))
1996
- result = cursor.fetchone()
1997
-
1998
- if not result:
1999
- return "Item not found."
2000
-
2001
- is_trash, trash_date = result
2002
-
2003
- if not is_trash:
2004
- mark_as_trash(media_id)
2005
- return "Item moved to trash."
2006
-
2007
- if force or (trash_date and (datetime.now() - trash_date).days >= 30):
2008
- permanently_delete_item(media_id)
2009
- return "Item permanently deleted."
2010
- else:
2011
- return "Item is already in trash. Use force=True to delete permanently before 30 days."
2012
-
2013
- #
2014
- # End of Functions to handle deletion of media items
2015
- #######################################################################################################################
2016
-