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.
- local_deep_research/__version__.py +1 -1
- local_deep_research/web/database/migrations.py +16 -1
- local_deep_research/web/database/models.py +31 -31
- local_deep_research/web/models/database.py +13 -23
- local_deep_research/web/routes/research_routes.py +65 -113
- local_deep_research/web/services/research_service.py +112 -158
- {local_deep_research-0.5.0.dist-info → local_deep_research-0.5.2.dist-info}/METADATA +1 -1
- {local_deep_research-0.5.0.dist-info → local_deep_research-0.5.2.dist-info}/RECORD +11 -11
- {local_deep_research-0.5.0.dist-info → local_deep_research-0.5.2.dist-info}/WHEEL +0 -0
- {local_deep_research-0.5.0.dist-info → local_deep_research-0.5.2.dist-info}/entry_points.txt +0 -0
- {local_deep_research-0.5.0.dist-info → local_deep_research-0.5.2.dist-info}/licenses/LICENSE +0 -0
@@ -1 +1 @@
|
|
1
|
-
__version__ = "0.5.
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
224
|
-
|
225
|
-
(
|
226
|
-
query,
|
227
|
-
mode,
|
228
|
-
"in_progress",
|
229
|
-
created_at,
|
230
|
-
|
231
|
-
|
232
|
-
)
|
233
|
-
|
234
|
-
|
235
|
-
|
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
|
-
|
618
|
-
|
619
|
-
|
620
|
-
|
621
|
-
|
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
|
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":
|
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":
|
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
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
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
|
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":
|
714
|
-
"message": message,
|
715
|
-
"timestamp": timestamp,
|
716
|
-
"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 =
|
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
|
775
|
-
"created_at": research.created_at
|
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
|
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
|
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
|
-
|
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
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
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
|
-
|
747
|
-
|
748
|
-
|
749
|
-
|
750
|
-
|
751
|
-
|
752
|
-
|
753
|
-
|
754
|
-
|
755
|
-
|
756
|
-
|
757
|
-
|
758
|
-
|
759
|
-
|
760
|
-
|
761
|
-
|
762
|
-
|
763
|
-
|
764
|
-
|
765
|
-
|
766
|
-
|
767
|
-
|
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
|
-
|
841
|
-
|
842
|
-
|
843
|
-
|
844
|
-
|
845
|
-
|
846
|
-
|
847
|
-
|
848
|
-
|
849
|
-
|
850
|
-
|
851
|
-
|
852
|
-
|
853
|
-
|
854
|
-
|
855
|
-
|
856
|
-
|
857
|
-
|
858
|
-
|
859
|
-
|
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
|
-
|
942
|
-
|
943
|
-
(
|
944
|
-
|
945
|
-
|
946
|
-
|
947
|
-
|
948
|
-
|
949
|
-
|
950
|
-
|
951
|
-
|
952
|
-
|
953
|
-
|
954
|
-
|
955
|
-
|
956
|
-
|
957
|
-
|
958
|
-
|
959
|
-
|
960
|
-
|
961
|
-
|
962
|
-
|
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
|
-
|
997
|
-
|
998
|
-
|
999
|
-
|
1000
|
-
|
1001
|
-
|
1002
|
-
|
1003
|
-
|
1004
|
-
|
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
|
-
#
|
1075
|
-
|
1076
|
-
|
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
|
-
|
1082
|
-
|
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
|
-
|
1087
|
-
|
1088
|
-
|
1089
|
-
|
1090
|
-
|
1091
|
-
|
1092
|
-
|
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
|
-
|
1126
|
-
|
1127
|
-
|
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
|
-
|
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
|
-
|
1142
|
-
|
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.
|
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.
|
2
|
-
local_deep_research-0.5.
|
3
|
-
local_deep_research-0.5.
|
4
|
-
local_deep_research-0.5.
|
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=
|
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=
|
181
|
-
local_deep_research/web/database/models.py,sha256=
|
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=
|
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=
|
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=
|
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.
|
265
|
+
local_deep_research-0.5.2.dist-info/RECORD,,
|
File without changes
|
{local_deep_research-0.5.0.dist-info → local_deep_research-0.5.2.dist-info}/entry_points.txt
RENAMED
File without changes
|
{local_deep_research-0.5.0.dist-info → local_deep_research-0.5.2.dist-info}/licenses/LICENSE
RENAMED
File without changes
|