local-deep-research 0.5.0__py3-none-any.whl → 0.5.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- __version__ = "0.5.0"
1
+ __version__ = "0.5.2"
@@ -2,7 +2,14 @@ from loguru import logger
2
2
  from sqlalchemy import inspect
3
3
 
4
4
  from ..services.settings_manager import SettingsManager
5
- from .models import Base, Journal, Setting, ResearchLog
5
+ from .models import (
6
+ Base,
7
+ Journal,
8
+ Setting,
9
+ ResearchLog,
10
+ Research,
11
+ ResearchHistory,
12
+ )
6
13
 
7
14
 
8
15
  def import_default_settings_file(db_session):
@@ -52,6 +59,14 @@ def run_migrations(engine, db_session=None):
52
59
  logger.info("Creating research logs table.")
53
60
  Base.metadata.create_all(engine, tables=[ResearchLog.__table__])
54
61
 
62
+ if not inspector.has_table(Research.__tablename__):
63
+ logger.info("Creating research table.")
64
+ Base.metadata.create_all(engine, tables=[Research.__table__])
65
+
66
+ if not inspector.has_table(ResearchHistory.__tablename__):
67
+ logger.info("Creating research table.")
68
+ Base.metadata.create_all(engine, tables=[ResearchHistory.__table__])
69
+
55
70
  # Import existing settings from files
56
71
  if db_session:
57
72
  import_default_settings_file(db_session)
@@ -34,6 +34,37 @@ class ResearchStatus(enum.Enum):
34
34
  CANCELLED = "cancelled"
35
35
 
36
36
 
37
+ class ResearchHistory(Base):
38
+ """Represents the research table."""
39
+
40
+ __tablename__ = "research_history"
41
+
42
+ # Unique identifier for each record.
43
+ id = Column(Integer, primary_key=True, autoincrement=True)
44
+ # The search query.
45
+ query = Column(Text, nullable=False)
46
+ # The mode of research (e.g., 'quick_summary', 'detailed_report').
47
+ mode = Column(Text, nullable=False)
48
+ # Current status of the research.
49
+ status = Column(Text, nullable=False)
50
+ # The timestamp when the research started.
51
+ created_at = Column(Text, nullable=False)
52
+ # The timestamp when the research was completed.
53
+ completed_at = Column(Text)
54
+ # Duration of the research in seconds.
55
+ duration_seconds = Column(Integer)
56
+ # Path to the generated report.
57
+ report_path = Column(Text)
58
+ # Additional metadata about the research.
59
+ research_meta = Column(JSON)
60
+ # Latest progress log message.
61
+ progress_log = Column(JSON)
62
+ # Current progress of the research (as a percentage).
63
+ progress = Column(Integer)
64
+ # Title of the research report.
65
+ title = Column(Text)
66
+
67
+
37
68
  class Research(Base):
38
69
  __tablename__ = "research"
39
70
 
@@ -54,14 +85,6 @@ class Research(Base):
54
85
  end_time = Column(DateTime, nullable=True)
55
86
  error_message = Column(Text, nullable=True)
56
87
 
57
- # Relationships
58
- report = relationship(
59
- "ResearchReport",
60
- back_populates="research",
61
- uselist=False,
62
- cascade="all, delete-orphan",
63
- )
64
-
65
88
 
66
89
  class ResearchLog(Base):
67
90
  __tablename__ = "app_logs"
@@ -88,29 +111,6 @@ class ResearchLog(Base):
88
111
  )
89
112
 
90
113
 
91
- class ResearchReport(Base):
92
- __tablename__ = "research_report"
93
-
94
- id = Column(Integer, primary_key=True, index=True)
95
- research_id = Column(
96
- Integer,
97
- ForeignKey("research.id", ondelete="CASCADE"),
98
- nullable=False,
99
- unique=True,
100
- )
101
- content = Column(Text, nullable=True)
102
- created_at = Column(DateTime, server_default=func.now(), nullable=False)
103
- updated_at = Column(
104
- DateTime, server_default=func.now(), onupdate=func.now(), nullable=False
105
- )
106
- report_metadata = Column(
107
- JSON, nullable=True
108
- ) # Additional metadata about the report
109
-
110
- # Relationships
111
- research = relationship("Research", back_populates="report")
112
-
113
-
114
114
  class SettingType(enum.Enum):
115
115
  APP = "app"
116
116
  LLM = "llm"
@@ -43,26 +43,6 @@ def init_db():
43
43
  conn = get_db_connection()
44
44
  cursor = conn.cursor()
45
45
 
46
- # Create the table if it doesn't exist
47
- cursor.execute(
48
- """
49
- CREATE TABLE IF NOT EXISTS research_history (
50
- id INTEGER PRIMARY KEY AUTOINCREMENT,
51
- query TEXT NOT NULL,
52
- mode TEXT NOT NULL,
53
- status TEXT NOT NULL,
54
- created_at TEXT NOT NULL,
55
- completed_at TEXT,
56
- duration_seconds INTEGER,
57
- report_path TEXT,
58
- metadata TEXT,
59
- progress_log TEXT,
60
- progress INTEGER,
61
- title TEXT
62
- )
63
- """
64
- )
65
-
66
46
  # Create a dedicated table for research logs
67
47
  cursor.execute(
68
48
  """
@@ -101,7 +81,7 @@ def init_db():
101
81
  columns = [column[1] for column in cursor.fetchall()]
102
82
 
103
83
  if "duration_seconds" not in columns:
104
- print(
84
+ logger.info(
105
85
  "Adding missing 'duration_seconds' column to research_history table"
106
86
  )
107
87
  cursor.execute(
@@ -110,16 +90,26 @@ def init_db():
110
90
 
111
91
  # Check if the progress column exists, add it if missing
112
92
  if "progress" not in columns:
113
- print("Adding missing 'progress' column to research_history table")
93
+ logger.info(
94
+ "Adding missing 'progress' column to research_history table"
95
+ )
114
96
  cursor.execute(
115
97
  "ALTER TABLE research_history ADD COLUMN progress INTEGER"
116
98
  )
117
99
 
118
100
  # Check if the title column exists, add it if missing
119
101
  if "title" not in columns:
120
- print("Adding missing 'title' column to research_history table")
102
+ logger.info("Adding missing 'title' column to research_history table")
121
103
  cursor.execute("ALTER TABLE research_history ADD COLUMN title TEXT")
122
104
 
105
+ # Check if the metadata column exists, and rename it to "research_meta"
106
+ # if it does.
107
+ if "metadata" in columns:
108
+ logger.info("Renaming 'metadata' column to 'research_meta'")
109
+ cursor.execute(
110
+ "ALTER TABLE research_history RENAME COLUMN metadata TO research_meta"
111
+ )
112
+
123
113
  # Enable foreign key support
124
114
  cursor.execute("PRAGMA foreign_keys = ON")
125
115
 
@@ -15,13 +15,18 @@ from flask import (
15
15
  )
16
16
  from loguru import logger
17
17
 
18
- from ..models.database import calculate_duration, get_db_connection
18
+ from ..models.database import (
19
+ calculate_duration,
20
+ get_db_connection,
21
+ )
19
22
  from ..services.research_service import (
20
23
  run_research_process,
21
24
  start_research_process,
22
25
  )
23
26
  from ..utils.templates import render_template_with_defaults
24
27
  from .globals import active_research, termination_flags
28
+ from ..database.models import ResearchHistory, ResearchLog
29
+ from ...utilities.db_utils import get_db_session
25
30
 
26
31
  # Create a Blueprint for the research application
27
32
  research_bp = Blueprint("research", __name__, url_prefix="/research")
@@ -220,20 +225,19 @@ def start_research():
220
225
  "questions_per_iteration": questions_per_iteration,
221
226
  }
222
227
 
223
- cursor.execute(
224
- "INSERT INTO research_history (query, mode, status, created_at, progress_log, metadata) VALUES (?, ?, ?, ?, ?, ?)",
225
- (
226
- query,
227
- mode,
228
- "in_progress",
229
- created_at,
230
- json.dumps([{"time": created_at, "progress": 0}]),
231
- json.dumps(research_settings),
232
- ),
233
- )
234
- research_id = cursor.lastrowid
235
- conn.commit()
236
- conn.close()
228
+ db_session = get_db_session()
229
+ with db_session:
230
+ research = ResearchHistory(
231
+ query=query,
232
+ mode=mode,
233
+ status="in_progress",
234
+ created_at=created_at,
235
+ progress_log=[{"time": created_at, "progress": 0}],
236
+ research_meta=research_settings,
237
+ )
238
+ db_session.add(research)
239
+ db_session.commit()
240
+ research_id = research.id
237
241
 
238
242
  # Start the research process with the selected parameters
239
243
  research_thread = start_research_process(
@@ -612,59 +616,30 @@ def get_history():
612
616
 
613
617
  @research_bp.route("/api/research/<int:research_id>")
614
618
  def get_research_details(research_id):
615
- """Get full details of a research"""
619
+ """Get full details of a research using ORM"""
616
620
  try:
617
- conn = get_db_connection()
618
- cursor = conn.cursor()
619
- cursor.execute(
620
- """SELECT id, query, status, progress, mode,
621
- created_at, completed_at, report_path, metadata
622
- FROM research_history WHERE id = ?""",
623
- (research_id,),
621
+ db_session = get_db_session()
622
+ research = (
623
+ db_session.query(ResearchHistory)
624
+ .filter(ResearchHistory.id == research_id)
625
+ .first()
624
626
  )
625
- result = cursor.fetchone()
626
627
 
627
- if result is None:
628
- conn.close()
628
+ if not research:
629
629
  return jsonify({"error": "Research not found"}), 404
630
630
 
631
- # Unpack the result
632
- (
633
- research_id,
634
- query,
635
- status,
636
- progress,
637
- mode,
638
- created_at,
639
- completed_at,
640
- report_path,
641
- metadata_str,
642
- ) = result
643
-
644
- # Parse metadata if it exists
645
- metadata = {}
646
- if metadata_str:
647
- try:
648
- metadata = json.loads(metadata_str)
649
- except json.JSONDecodeError:
650
- logger.warning(
651
- f"Invalid JSON in metadata for research {research_id}"
652
- )
653
-
654
- conn.close()
655
-
656
631
  return jsonify(
657
632
  {
658
- "id": research_id,
659
- "query": query,
660
- "status": status,
661
- "progress": progress,
662
- "progress_percentage": progress or 0,
663
- "mode": mode,
664
- "created_at": created_at,
665
- "completed_at": completed_at,
666
- "report_path": report_path,
667
- "metadata": metadata,
633
+ "id": research.id,
634
+ "query": research.query,
635
+ "status": research.status,
636
+ "progress": research.progress,
637
+ "progress_percentage": research.progress or 0,
638
+ "mode": research.mode,
639
+ "created_at": research.created_at,
640
+ "completed_at": research.completed_at,
641
+ "report_path": research.report_path,
642
+ "metadata": research.research_meta,
668
643
  }
669
644
  )
670
645
  except Exception as e:
@@ -676,50 +651,36 @@ def get_research_details(research_id):
676
651
  def get_research_logs(research_id):
677
652
  """Get logs for a specific research"""
678
653
  try:
679
- conn = get_db_connection()
680
- cursor = conn.cursor()
681
-
682
654
  # First check if the research exists
683
- cursor.execute(
684
- "SELECT id FROM research_history WHERE id = ?", (research_id,)
685
- )
686
- if cursor.fetchone() is None:
687
- conn.close()
688
- return jsonify({"error": "Research not found"}), 404
689
-
690
- # Get logs from research_logs table
691
- cursor.execute(
692
- """SELECT id, message, timestamp, log_type, progress, metadata
693
- FROM research_logs
694
- WHERE research_id = ?
695
- ORDER BY timestamp ASC""",
696
- (research_id,),
697
- )
655
+ db_session = get_db_session()
656
+ with db_session:
657
+ research = (
658
+ db_session.query(ResearchHistory)
659
+ .filter_by(id=research_id)
660
+ .first()
661
+ )
662
+ if not research:
663
+ return jsonify({"error": "Research not found"}), 404
664
+
665
+ # Get logs from research_logs table
666
+ log_results = (
667
+ db_session.query(ResearchLog)
668
+ .filter_by(research_id=research_id)
669
+ .order_by(ResearchLog.timestamp)
670
+ .all()
671
+ )
698
672
 
699
673
  logs = []
700
- for row in cursor.fetchall():
701
- log_id, message, timestamp, log_type, progress, metadata_str = row
702
-
703
- # Parse metadata if it exists
704
- metadata = {}
705
- if metadata_str:
706
- try:
707
- metadata = json.loads(metadata_str)
708
- except json.JSONDecodeError:
709
- pass
710
-
674
+ for row in log_results:
711
675
  logs.append(
712
676
  {
713
- "id": log_id,
714
- "message": message,
715
- "timestamp": timestamp,
716
- "log_type": log_type,
717
- "progress": progress,
718
- "metadata": metadata,
677
+ "id": row.id,
678
+ "message": row.message,
679
+ "timestamp": row.timestamp,
680
+ "log_type": row.level,
719
681
  }
720
682
  )
721
683
 
722
- conn.close()
723
684
  return jsonify(logs)
724
685
 
725
686
  except Exception as e:
@@ -730,27 +691,18 @@ def get_research_logs(research_id):
730
691
  @research_bp.route("/api/report/<int:research_id>")
731
692
  def get_research_report(research_id):
732
693
  """Get the research report content"""
733
- from ..database.models import Research
734
- from ...utilities.db_utils import get_db_session
735
-
736
694
  session = get_db_session()
737
695
  try:
738
696
  # Query using ORM
739
- research = session.query(Research).filter_by(id=research_id).first()
697
+ research = (
698
+ session.query(ResearchHistory).filter_by(id=research_id).first()
699
+ )
740
700
 
741
701
  if research is None:
742
702
  return jsonify({"error": "Research not found"}), 404
743
703
 
744
704
  # Parse metadata if it exists
745
- metadata = {}
746
- if research.metadata:
747
- try:
748
- metadata = json.loads(research.metadata)
749
- except json.JSONDecodeError:
750
- logger.warning(
751
- f"Invalid JSON in metadata for research {research_id}"
752
- )
753
-
705
+ metadata = research.research_meta
754
706
  # Check if report file exists
755
707
  if not research.report_path or not os.path.exists(research.report_path):
756
708
  return jsonify({"error": "Report file not found"}), 404
@@ -771,11 +723,11 @@ def get_research_report(research_id):
771
723
  "content": content,
772
724
  "metadata": {
773
725
  "query": research.query,
774
- "mode": research.mode.value if research.mode else None,
775
- "created_at": research.created_at.isoformat()
726
+ "mode": research.mode if research.mode else None,
727
+ "created_at": research.created_at
776
728
  if research.created_at
777
729
  else None,
778
- "completed_at": research.completed_at.isoformat()
730
+ "completed_at": research.completed_at
779
731
  if research.completed_at
780
732
  else None,
781
733
  "report_path": research.report_path,
@@ -1,5 +1,4 @@
1
1
  import hashlib
2
- import json
3
2
  import threading
4
3
  from datetime import datetime
5
4
  from pathlib import Path
@@ -15,8 +14,8 @@ from ...utilities.log_utils import log_for_research
15
14
  from ...utilities.search_utilities import extract_links_from_search_results
16
15
  from ...utilities.db_utils import get_db_session
17
16
  from ...utilities.threading_utils import thread_context, thread_with_app_context
18
- from ..database.models import ResearchStrategy
19
- from ..models.database import calculate_duration, get_db_connection
17
+ from ..database.models import ResearchStrategy, ResearchHistory
18
+ from ..models.database import calculate_duration
20
19
  from .socket_service import SocketIOService
21
20
 
22
21
  # Output directory for research results
@@ -336,36 +335,19 @@ def run_research_process(
336
335
  active_research[research_id]["progress"] = adjusted_progress
337
336
 
338
337
  # Update progress in the research_history table (for backward compatibility)
339
- conn = get_db_connection()
340
- cursor = conn.cursor()
338
+ db_session = get_db_session()
341
339
 
342
340
  # Update the progress and log separately to avoid race conditions
343
- if adjusted_progress is not None:
344
- cursor.execute(
345
- "UPDATE research_history SET progress = ? WHERE id = ?",
346
- (adjusted_progress, research_id),
347
- )
348
-
349
- # Add the log entry to the progress_log
350
- cursor.execute(
351
- "SELECT progress_log FROM research_history WHERE id = ?",
352
- (research_id,),
353
- )
354
- log_result = cursor.fetchone()
355
-
356
- if log_result:
357
- try:
358
- current_log = json.loads(log_result[0])
359
- except Exception:
360
- current_log = []
361
-
362
- cursor.execute(
363
- "UPDATE research_history SET progress_log = ? WHERE id = ?",
364
- (json.dumps(current_log), research_id),
365
- )
366
-
367
- conn.commit()
368
- conn.close()
341
+ with db_session:
342
+ if adjusted_progress is not None:
343
+ research = (
344
+ db_session.query(ResearchHistory)
345
+ .filter(ResearchHistory.id == research_id)
346
+ .first()
347
+ )
348
+ if research:
349
+ research.progress = adjusted_progress
350
+ db_session.commit()
369
351
 
370
352
  # Emit a socket event
371
353
  try:
@@ -743,32 +725,28 @@ def run_research_process(
743
725
  logger.info(
744
726
  "Updating database for research_id: %s", research_id
745
727
  )
746
- # Get the start time from the database
747
- conn = get_db_connection()
748
- cursor = conn.cursor()
749
- cursor.execute(
750
- "SELECT created_at FROM research_history WHERE id = ?",
751
- (research_id,),
752
- )
753
- result = cursor.fetchone()
754
-
755
- # Use the helper function for consistent duration calculation
756
- duration_seconds = calculate_duration(result[0])
757
-
758
- # Update the record
759
- cursor.execute(
760
- "UPDATE research_history SET status = ?, completed_at = ?, duration_seconds = ?, report_path = ?, metadata = ? WHERE id = ?",
761
- (
762
- "completed",
763
- completed_at,
764
- duration_seconds,
765
- str(report_path),
766
- json.dumps(metadata),
767
- research_id,
768
- ),
769
- )
770
- conn.commit()
771
- conn.close()
728
+
729
+ db_session = get_db_session()
730
+ with db_session:
731
+ research = (
732
+ db_session.query(ResearchHistory)
733
+ .filter_by(id=research_id)
734
+ .first()
735
+ )
736
+
737
+ # Use the helper function for consistent duration calculation
738
+ duration_seconds = calculate_duration(
739
+ research.created_at, research.completed_at
740
+ )
741
+
742
+ research.status = "completed"
743
+ research.completed_at = completed_at
744
+ research.duration_seconds = duration_seconds
745
+ research.report_path = str(report_path)
746
+ research.research_meta = metadata
747
+
748
+ db_session.commit()
749
+
772
750
  logger.info(
773
751
  f"Database updated successfully for research_id: {research_id}"
774
752
  )
@@ -837,31 +815,26 @@ def run_research_process(
837
815
  now = datetime.utcnow()
838
816
  completed_at = now.isoformat()
839
817
 
840
- # Get the start time from the database
841
- conn = get_db_connection()
842
- cursor = conn.cursor()
843
- cursor.execute(
844
- "SELECT created_at FROM research_history WHERE id = ?",
845
- (research_id,),
846
- )
847
- result = cursor.fetchone()
848
-
849
- # Use the helper function for consistent duration calculation
850
- duration_seconds = calculate_duration(result[0])
851
-
852
- cursor.execute(
853
- "UPDATE research_history SET status = ?, completed_at = ?, duration_seconds = ?, report_path = ?, metadata = ? WHERE id = ?",
854
- (
855
- "completed",
856
- completed_at,
857
- duration_seconds,
858
- str(report_path),
859
- json.dumps(metadata),
860
- research_id,
861
- ),
862
- )
863
- conn.commit()
864
- conn.close()
818
+ db_session = get_db_session()
819
+ with db_session:
820
+ research = (
821
+ db_session.query(ResearchHistory)
822
+ .filter_by(id=research_id)
823
+ .first()
824
+ )
825
+
826
+ # Use the helper function for consistent duration calculation
827
+ duration_seconds = calculate_duration(
828
+ research.created_at, research.completed_at
829
+ )
830
+
831
+ research.status = "completed"
832
+ research.completed_at = completed_at
833
+ research.duration_seconds = duration_seconds
834
+ research.report_path = str(report_path)
835
+ research.research_meta = metadata
836
+
837
+ db_session.commit()
865
838
 
866
839
  progress_callback(
867
840
  "Research completed successfully",
@@ -914,9 +887,6 @@ def run_research_process(
914
887
  if research_id in active_research:
915
888
  progress_callback(user_friendly_error, None, metadata)
916
889
 
917
- conn = get_db_connection()
918
- cursor = conn.cursor()
919
-
920
890
  # If termination was requested, mark as suspended instead of failed
921
891
  status = (
922
892
  "suspended"
@@ -938,28 +908,33 @@ def run_research_process(
938
908
 
939
909
  # Get the start time from the database
940
910
  duration_seconds = None
941
- cursor.execute(
942
- "SELECT created_at FROM research_history WHERE id = ?",
943
- (research_id,),
944
- )
945
- result = cursor.fetchone()
946
-
947
- # Use the helper function for consistent duration calculation
948
- if result and result[0]:
949
- duration_seconds = calculate_duration(result[0])
950
-
951
- cursor.execute(
952
- "UPDATE research_history SET status = ?, completed_at = ?, duration_seconds = ?, metadata = ? WHERE id = ?",
953
- (
954
- status,
955
- completed_at,
956
- duration_seconds,
957
- json.dumps(metadata),
958
- research_id,
959
- ),
960
- )
961
- conn.commit()
962
- conn.close()
911
+ db_session = get_db_session()
912
+ with db_session:
913
+ research = (
914
+ db_session.query(ResearchHistory)
915
+ .filter_by(id=research_id)
916
+ .first()
917
+ )
918
+ assert research is not None, "Research not in database"
919
+
920
+ duration_seconds = calculate_duration(research.created_at)
921
+
922
+ db_session = get_db_session()
923
+ with db_session:
924
+ research = (
925
+ db_session.query(ResearchHistory)
926
+ .filter_by(id=research_id)
927
+ .first()
928
+ )
929
+ assert research is not None, "Research not in database"
930
+
931
+ # Update the ResearchHistory object with the new status and completion time
932
+ research.status = status
933
+ research.completed_at = completed_at
934
+ research.duration_seconds = duration_seconds
935
+ research.metadata = metadata
936
+
937
+ db_session.commit()
963
938
 
964
939
  try:
965
940
  SocketIOService().emit_to_subscribers(
@@ -993,15 +968,17 @@ def cleanup_research_resources(research_id, active_research, termination_flags):
993
968
  # Get the current status from the database to determine the final status message
994
969
  current_status = "completed" # Default
995
970
  try:
996
- conn = get_db_connection()
997
- cursor = conn.cursor()
998
- cursor.execute(
999
- "SELECT status FROM research_history WHERE id = ?", (research_id,)
1000
- )
1001
- result = cursor.fetchone()
1002
- if result and result[0]:
1003
- current_status = result[0]
1004
- conn.close()
971
+ db_session = get_db_session()
972
+ with db_session:
973
+ research = (
974
+ db_session.query(ResearchHistory)
975
+ .filter(ResearchHistory.id == research_id)
976
+ .first()
977
+ )
978
+ if research:
979
+ current_status = research.status
980
+ else:
981
+ logger.error("Research with ID %s not found", research_id)
1005
982
  except Exception:
1006
983
  logger.exception("Error retrieving research status during cleanup")
1007
984
 
@@ -1063,33 +1040,23 @@ def handle_termination(research_id, active_research, termination_flags):
1063
1040
  active_research: Dictionary of active research processes
1064
1041
  termination_flags: Dictionary of termination flags
1065
1042
  """
1066
- # Explicitly set the status to suspended in the database
1067
- conn = get_db_connection()
1068
- cursor = conn.cursor()
1069
-
1070
- # Calculate duration up to termination point - using UTC consistently
1071
1043
  now = datetime.utcnow()
1072
1044
  completed_at = now.isoformat()
1073
1045
 
1074
- # Get the start time from the database
1075
- cursor.execute(
1076
- "SELECT created_at FROM research_history WHERE id = ?",
1077
- (research_id,),
1078
- )
1079
- result = cursor.fetchone()
1046
+ # Fetch the start time from the database using the ORM
1047
+ session = get_db_session()
1048
+ research = session.query(ResearchHistory).filter_by(id=research_id).first()
1080
1049
 
1081
- # Calculate the duration
1082
- duration_seconds = (
1083
- calculate_duration(result[0]) if result and result[0] else None
1084
- )
1050
+ if research:
1051
+ duration_seconds = calculate_duration(research.created_at)
1085
1052
 
1086
- # Update the database with suspended status
1087
- cursor.execute(
1088
- "UPDATE research_history SET status = ?, completed_at = ?, duration_seconds = ? WHERE id = ?",
1089
- ("suspended", completed_at, duration_seconds, research_id),
1090
- )
1091
- conn.commit()
1092
- conn.close()
1053
+ # Update the database with suspended status using the ORM
1054
+ research.status = "suspended"
1055
+ research.completed_at = completed_at
1056
+ research.duration_seconds = duration_seconds
1057
+ session.commit()
1058
+ else:
1059
+ logger.error(f"Research with ID {research_id} not found.")
1093
1060
 
1094
1061
  # Clean up resources
1095
1062
  cleanup_research_resources(research_id, active_research, termination_flags)
@@ -1097,7 +1064,7 @@ def handle_termination(research_id, active_research, termination_flags):
1097
1064
 
1098
1065
  def cancel_research(research_id):
1099
1066
  """
1100
- Cancel/terminate a research process
1067
+ Cancel/terminate a research process using ORM.
1101
1068
 
1102
1069
  Args:
1103
1070
  research_id: The ID of the research to cancel
@@ -1122,27 +1089,14 @@ def cancel_research(research_id):
1122
1089
  return True
1123
1090
  else:
1124
1091
  # Update database directly if not found in active_research
1125
- from ..models.database import get_db_connection
1126
-
1127
- conn = get_db_connection()
1128
- cursor = conn.cursor()
1129
-
1130
- # First check if the research exists
1131
- cursor.execute(
1132
- "SELECT status FROM research_history WHERE id = ?", (research_id,)
1092
+ session = get_db_session()
1093
+ research = (
1094
+ session.query(ResearchHistory).filter_by(id=research_id).first()
1133
1095
  )
1134
- result = cursor.fetchone()
1135
-
1136
- if not result:
1137
- conn.close()
1096
+ if not research:
1138
1097
  return False
1139
1098
 
1140
1099
  # If it exists but isn't in active_research, still update status
1141
- cursor.execute(
1142
- "UPDATE research_history SET status = ? WHERE id = ?",
1143
- ("suspended", research_id),
1144
- )
1145
- conn.commit()
1146
- conn.close()
1147
-
1100
+ research.status = "suspended"
1101
+ session.commit()
1148
1102
  return True
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: local-deep-research
3
- Version: 0.5.0
3
+ Version: 0.5.2
4
4
  Summary: AI-powered research assistant with deep, iterative analysis using LLMs and web searches
5
5
  Author-Email: LearningCircuit <185559241+LearningCircuit@users.noreply.github.com>, HashedViking <6432677+HashedViking@users.noreply.github.com>
6
6
  License: MIT License
@@ -1,9 +1,9 @@
1
- local_deep_research-0.5.0.dist-info/METADATA,sha256=MHzkyEprOZlD9pJTHHXdxuucEcKweXxPJRJ2qgAtIWk,17676
2
- local_deep_research-0.5.0.dist-info/WHEEL,sha256=tSfRZzRHthuv7vxpI4aehrdN9scLjk-dCJkPLzkHxGg,90
3
- local_deep_research-0.5.0.dist-info/entry_points.txt,sha256=GcXS501Rjh-P80S8db7hnrQ23mS_Jg27PwpVQVO77as,113
4
- local_deep_research-0.5.0.dist-info/licenses/LICENSE,sha256=Qg2CaTdu6SWnSqk1_JtgBPp_Da-LdqJDhT1Vt1MUc5s,1072
1
+ local_deep_research-0.5.2.dist-info/METADATA,sha256=kAVbaFqnR6nls4PM_vMyRtVnZuj4z1LthPtEz3YREJk,17676
2
+ local_deep_research-0.5.2.dist-info/WHEEL,sha256=tSfRZzRHthuv7vxpI4aehrdN9scLjk-dCJkPLzkHxGg,90
3
+ local_deep_research-0.5.2.dist-info/entry_points.txt,sha256=GcXS501Rjh-P80S8db7hnrQ23mS_Jg27PwpVQVO77as,113
4
+ local_deep_research-0.5.2.dist-info/licenses/LICENSE,sha256=Qg2CaTdu6SWnSqk1_JtgBPp_Da-LdqJDhT1Vt1MUc5s,1072
5
5
  local_deep_research/__init__.py,sha256=j1ktf_e9HeXPe86NHibY5aINtZfTSGRTvLNtz9BJZa4,1071
6
- local_deep_research/__version__.py,sha256=LBK46heutvn3KmsCrKIYu8RQikbfnjZaj2xFrXaeCzQ,22
6
+ local_deep_research/__version__.py,sha256=isJrmDBLRag7Zc2UK9ZovWGOv7ji1Oh-zJtJMNJFkXw,22
7
7
  local_deep_research/advanced_search_system/__init__.py,sha256=sGusMj4eFIrhXR6QbOM16UDKB6aI-iS4IFivKWpMlh0,234
8
8
  local_deep_research/advanced_search_system/answer_decoding/__init__.py,sha256=BmmbIPQnouYyboFD61CDq71fW5On555w7dbt42s9gV4,148
9
9
  local_deep_research/advanced_search_system/answer_decoding/browsecomp_answer_decoder.py,sha256=4FDMP4n_z5DOzVIisH3_kexRqNm1AO3MDe-Md3WtgE0,12856
@@ -177,19 +177,19 @@ local_deep_research/web/app.py,sha256=tzxKLJU1m8aG6HXIiDbHaGkd8seX_A3pfgr9wK7w6Z
177
177
  local_deep_research/web/app_factory.py,sha256=7o5GM8PUwVzB1sz7PywXLLqw3QkzhZZ18mBPbvZXKbM,9153
178
178
  local_deep_research/web/database/README.md,sha256=eEDLqLIfOBRvc0TFh3J1HrtFbZceYmVgpjS3-oyZ5nI,2861
179
179
  local_deep_research/web/database/migrate_to_ldr_db.py,sha256=RRzITerhjUjlHPTf6GoNgrwa4BpKuj_RAohzcS9ttG0,9919
180
- local_deep_research/web/database/migrations.py,sha256=KuqYMPuyz3J_VWTfd_53r1BfXt6P30BKJzj6KlrEh8I,2048
181
- local_deep_research/web/database/models.py,sha256=fikYpV7iHfTmIvM0sfDjm3X3fPhp6XIlmgCiQYmkq14,5521
180
+ local_deep_research/web/database/migrations.py,sha256=BKUDckR98Yz8cMtd473lMxLcVqWTtFyHVFWFPs9PgfY,2468
181
+ local_deep_research/web/database/models.py,sha256=k5YSyhw_vjBDdI-_gIdoTboPHWhX_O_el0SZ0SVKJrM,5747
182
182
  local_deep_research/web/database/schema_upgrade.py,sha256=x3zwZNTR2kpfqOdg573fyX6X4bH1yF5pc_it_a44CR4,3525
183
- local_deep_research/web/models/database.py,sha256=9ORuGX4WoVSiGXijL-YF4-dg1e2avfCByZrUBZ8pKIg,8566
183
+ local_deep_research/web/models/database.py,sha256=uBecKQpZd9cXdt2ZPUr0FOpHFbDZd40kQNKYSZOxnEI,8422
184
184
  local_deep_research/web/models/settings.py,sha256=rXBI9vY5k3ndR8dPd3fZJy-6HwYltQihhSBRq-sZutw,2314
185
185
  local_deep_research/web/routes/api_routes.py,sha256=7hOFqF7Y3Qc5PwugXRoSi1CEVIcXUeW6nSwWayLB0P4,20052
186
186
  local_deep_research/web/routes/benchmark_routes.py,sha256=bXiURrmWoaGtbOQ3jhgXgxjs3Qa440Jqk1CPZ1QMyO0,15725
187
187
  local_deep_research/web/routes/globals.py,sha256=cCCSW0MPxHKNXDy0kGG5HqOz197i1iyqJdWRI72DCWw,454
188
188
  local_deep_research/web/routes/history_routes.py,sha256=ZKuLJRahQfSzyxVDTm02QqrLgPHNZ1OsUv1kaO-6cbc,12839
189
189
  local_deep_research/web/routes/metrics_routes.py,sha256=b0fYdhH_OvyvMRjdswA41qPNV4DIWrZjoOnlysEBcAE,41619
190
- local_deep_research/web/routes/research_routes.py,sha256=l2V-wMMC0_x0jx5tpwu7XOurK13vR7v3ri5jqPveqYY,29333
190
+ local_deep_research/web/routes/research_routes.py,sha256=fVMV9WDO2T6XVVH1oU_bCXR1ekg6XNbnvkc61NB-DQs,27964
191
191
  local_deep_research/web/routes/settings_routes.py,sha256=0VZPWSZ9-yQNFNdid_rC4jzqKI2sE0AjucuIstZx548,60600
192
- local_deep_research/web/services/research_service.py,sha256=WdbdwkfBCL_Ve8C1w-VxhwIWQYAeDiGxP7OlRWBvsJA,44684
192
+ local_deep_research/web/services/research_service.py,sha256=w0eNd_Q21Ky2dk7GxpFE84zpScDLwjSm_QMLKCEyx1E,43426
193
193
  local_deep_research/web/services/resource_service.py,sha256=aU7SDADxcIAAuIymL_TOxUV_HvEMAfL8gZSB5gVSzFM,4674
194
194
  local_deep_research/web/services/settings_manager.py,sha256=EsAqZ5LTFFJcrWdCEheQYUqMWCwh7AUGe6Jefi8-Df4,19744
195
195
  local_deep_research/web/services/settings_service.py,sha256=W3TdSb1_WTBdKVQEvi4swXBP99dMm2IT7H-5Y1weEH4,3535
@@ -262,4 +262,4 @@ local_deep_research/web_search_engines/engines/search_engine_wikipedia.py,sha256
262
262
  local_deep_research/web_search_engines/search_engine_base.py,sha256=sRgtszDM9RqNw_oVdmGk8CmKS_9EJYR-LyE1as53cp8,12401
263
263
  local_deep_research/web_search_engines/search_engine_factory.py,sha256=eMaFup2p4u1nP4fTmjzfLUAl_mUZkoE1mUABBIvNzDM,12095
264
264
  local_deep_research/web_search_engines/search_engines_config.py,sha256=oJ5GL9BhFvWFgmFtvwJ7AZ9o-uPLEfTNhJJouHF40iA,5296
265
- local_deep_research-0.5.0.dist-info/RECORD,,
265
+ local_deep_research-0.5.2.dist-info/RECORD,,