Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion frontend/src/main/background/task/screen-monitor-task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ class ScreenMonitorTask extends ScheduleNextTask {
const data = {
path: url,
window: type === 'screen' ? 'screen' : '',
create_time: createTime.format('YYYY-MM-DD HH:mm:ss'),
create_time: createTime.toISOString(),
app: type === 'window' ? 'window' : ''
}
const res = await axios.post(`http://127.0.0.1:${getBackendPort()}/api/add_screenshot`, data)
Expand Down
14 changes: 7 additions & 7 deletions opencontext/context_capture/vault_document_monitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@

import threading
import time
from datetime import datetime
from pathlib import Path
from typing import Any, Dict, List, Optional, Set

from opencontext.context_capture import BaseCaptureComponent
from opencontext.models.context import RawContextProperties
from opencontext.models.enums import ContentFormat, ContextSource
from opencontext.storage.global_storage import get_storage
from opencontext.utils.datetime_utils import now_local, parse_local_datetime
from opencontext.utils.logging_utils import get_logger

logger = get_logger(__name__)
Expand Down Expand Up @@ -63,7 +63,7 @@ def _initialize_impl(self, config: Dict[str, Any]) -> bool:
self._monitor_interval = config.get("monitor_interval", 5)

# Set initial scan time to current time
self._last_scan_time = datetime.now()
self._last_scan_time = now_local()

logger.info(
f"Vault document monitoring component initialized successfully, monitor interval: {self._monitor_interval}s"
Expand Down Expand Up @@ -164,7 +164,7 @@ def _scan_existing_documents(self):
"event_type": "existing",
"vault_id": doc["id"],
"document_data": doc,
"timestamp": datetime.now(),
"timestamp": now_local(),
}

with self._event_lock:
Expand All @@ -180,17 +180,17 @@ def _scan_vault_changes(self):
"""Scan changes in the vaults table"""
try:
# Get recent documents (based on created_at and updated_at)
current_time = datetime.now()
current_time = now_local()
documents = self._storage.get_vaults(limit=100, offset=0, is_deleted=False)

new_documents = []
updated_documents = []

for doc in documents:
vault_id = doc["id"]
created_at = datetime.fromisoformat(doc["created_at"].replace("Z", "+00:00"))
created_at = parse_local_datetime(doc["created_at"])
updated_at = (
datetime.fromisoformat(doc["updated_at"].replace("Z", "+00:00"))
parse_local_datetime(doc["updated_at"])
if doc.get("updated_at")
else created_at
)
Expand Down Expand Up @@ -267,7 +267,7 @@ def _create_context_from_event(self, event: Dict[str, Any]) -> Optional[RawConte
source=ContextSource.VAULT,
content_format=ContentFormat.TEXT,
content_text=doc.get("title", "") + doc.get("summary", "") + doc.get("content", ""),
create_time=datetime.fromisoformat(doc["created_at"].replace("Z", "+00:00")),
create_time=parse_local_datetime(doc["created_at"]),
filter_path=self._get_document_path(doc),
additional_info={
"vault_id": vault_id,
Expand Down
17 changes: 7 additions & 10 deletions opencontext/context_consumption/generation/smart_tip_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from opencontext.storage.base_storage import DocumentData
from opencontext.storage.global_storage import get_storage
from opencontext.tools.tool_definitions import ALL_TOOL_DEFINITIONS
from opencontext.utils.datetime_utils import now_local, parse_local_datetime
from opencontext.utils.logging_utils import get_logger

logger = get_logger(__name__)
Expand Down Expand Up @@ -90,7 +91,7 @@ def _analyze_activity_patterns(self, hours: int = 6) -> Dict[str, Any]:
"""Analyze activity patterns to find content that needs a reminder."""
try:
# Calculate the time range
end_time = datetime.datetime.now()
end_time = now_local()
start_time = end_time - datetime.timedelta(hours=hours)

# Query recent activity records
Expand Down Expand Up @@ -151,12 +152,8 @@ def _analyze_activity_patterns(self, hours: int = 6) -> Dict[str, Any]:
time_diffs = []
for i in range(1, len(activities)):
try:
prev_time = datetime.datetime.fromisoformat(
activities[i - 1]["end_time"].replace("Z", "+00:00")
)
curr_time = datetime.datetime.fromisoformat(
activities[i]["start_time"].replace("Z", "+00:00")
)
prev_time = parse_local_datetime(activities[i - 1]["end_time"])
curr_time = parse_local_datetime(activities[i]["start_time"])
diff = (curr_time - prev_time).total_seconds() / 60 # Convert to minutes
time_diffs.append(diff)
except Exception:
Expand All @@ -179,7 +176,7 @@ def _analyze_activity_patterns(self, hours: int = 6) -> Dict[str, Any]:
def _get_recent_tips(self, days: int = 1) -> List[Dict[str, Any]]:
"""Get recent tips to avoid repetition."""
try:
end_time = datetime.datetime.now()
end_time = now_local()
today_start = end_time.replace(hour=0, minute=0, second=0, microsecond=0)
start_time = today_start - datetime.timedelta(days=days - 1)

Expand Down Expand Up @@ -256,7 +253,7 @@ def _generate_intelligent_tip_with_patterns(
# Format time information
start_time_str = datetime.datetime.fromtimestamp(start_time).strftime("%H:%M:%S")
end_time_str = datetime.datetime.fromtimestamp(end_time).strftime("%H:%M:%S")
current_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
current_time = now_local().strftime("%Y-%m-%d %H:%M:%S")

# Build the user prompt
user_prompt = user_prompt_template.format(
Expand Down Expand Up @@ -345,7 +342,7 @@ def cleanup_old_tips(self, keep_hours: int = 48):
keep_hours: The number of hours to keep, default is 48 hours.
"""
try:
cutoff_time = datetime.datetime.now() - datetime.timedelta(hours=keep_hours)
cutoff_time = now_local() - datetime.timedelta(hours=keep_hours)
cutoff_timestamp = int(cutoff_time.timestamp())

# Get all tips
Expand Down
45 changes: 29 additions & 16 deletions opencontext/context_processing/processor/screenshot_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
from opencontext.monitoring.monitor import record_processing_error
from opencontext.storage.global_storage import get_storage
from opencontext.tools.tool_definitions import ALL_TOOL_DEFINITIONS
from opencontext.utils.datetime_utils import ensure_local_naive, now_local, parse_local_datetime
from opencontext.utils.image import calculate_phash, resize_image
from opencontext.utils.json_parser import parse_json_from_response
from opencontext.utils.logging_utils import get_logger
Expand Down Expand Up @@ -266,11 +267,11 @@ async def _process_vlm_single(self, raw_context: RawContextProperties) -> List[P
}
]

time_now = datetime.datetime.now()
time_now = now_local()
user_prompt = user_prompt_template.format(
current_date=time_now.isoformat(),
current_timestamp=int(time_now.timestamp()),
current_timezone=time_now.tzname(),
current_timezone=datetime.datetime.now().astimezone().tzname(),
)
content.insert(0, {"type": "text", "text": user_prompt})
system_prompt = system_prompt.format(
Expand Down Expand Up @@ -362,7 +363,7 @@ async def _merge_items_with_llm(self, context_type: ContextType, new_items: List

# Process results and build ProcessedContext objects
result_contexts = []
now = datetime.datetime.now()
now = now_local()
if context_type.value not in self._processed_cache:
self._processed_cache[context_type.value] = {}
need_to_del_ids = []
Expand All @@ -383,10 +384,24 @@ async def _merge_items_with_llm(self, context_type: ContextType, new_items: List
logger.error(f"No valid items for merged_ids: {merged_ids}")
continue

min_create_time = min((i.properties.create_time for i in items_to_merge if i.properties.create_time), default=now)
min_create_time = min(
(
ensure_local_naive(i.properties.create_time)
for i in items_to_merge
if i.properties.create_time
),
default=now,
)
event_time = self._parse_event_time_str(
data.get("event_time"),
max((i.properties.event_time for i in items_to_merge if i.properties.event_time), default=now)
max(
(
ensure_local_naive(i.properties.event_time)
for i in items_to_merge
if i.properties.event_time
),
default=now,
),
)

all_raw_props = []
Expand Down Expand Up @@ -458,23 +473,21 @@ async def _parse_single_context(self, item: ProcessedContext, entities: List[Dic
item.extracted_data.entities = entities_results
return item

def _parse_event_time_str(self, time_str: Optional[str], default: datetime.datetime) -> datetime.datetime:
def _parse_event_time_str(
self, time_str: Optional[str], default: datetime.datetime
) -> datetime.datetime:
"""Parse ISO time string, return default if invalid."""
if not time_str or time_str == "null":
return default
return ensure_local_naive(default)
try:
if any(
invalid_char in time_str
for invalid_char in ["xxxx", "XXXX", "TZ:TZ", "TZ", "????"]
):
event_time = default
elif time_str.endswith("Z"):
time_str = time_str[:-1] + "+00:00"
event_time = datetime.datetime.fromisoformat(time_str)
return event_time
return default
return ensure_local_naive(default)
return parse_local_datetime(time_str)
except (ValueError, TypeError):
return default
return ensure_local_naive(default)

def _safe_int(self, value, default=0) -> int:
"""Safely convert to int."""
Expand Down Expand Up @@ -531,7 +544,7 @@ async def batch_process(self, raw_contexts: List[RawContextProperties]) -> List[
return newly_processed_contexts

def _create_processed_context(self, analysis: Dict[str, Any], raw_context: RawContextProperties = None) -> ProcessedContext:
now = datetime.datetime.now()
now = now_local()
if not analysis:
logger.warning(f"Skipping incomplete item: {analysis}")
return None
Expand Down Expand Up @@ -565,7 +578,7 @@ def _create_processed_context(self, analysis: Dict[str, Any], raw_context: RawCo
properties=ContextProperties(
raw_properties=[raw_context] if raw_context else [],
source=ContextSource.SCREENSHOT,
create_time=raw_context.create_time if raw_context else now,
create_time=ensure_local_naive(raw_context.create_time) if raw_context else now,
update_time=now,
event_time=event_time,
enable_merge=True,
Expand Down
27 changes: 14 additions & 13 deletions opencontext/managers/consumption_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from opencontext.managers.event_manager import EventType, get_event_manager
from opencontext.models.enums import VaultType
from opencontext.storage.global_storage import get_storage
from opencontext.utils.datetime_utils import now_local, parse_local_datetime
from opencontext.utils.logging_utils import get_logger

logger = get_logger(__name__)
Expand Down Expand Up @@ -116,7 +117,7 @@ def _should_generate(self, task_type: str) -> bool:
if last_time is None:
return True

elapsed = (datetime.now() - last_time).total_seconds()
elapsed = (now_local() - last_time).total_seconds()
interval = self._task_intervals.get(task_type, 0)
should_generate = elapsed >= interval
return should_generate
Expand Down Expand Up @@ -171,7 +172,7 @@ def stop_scheduled_tasks(self):
def _calculate_seconds_until_daily_time(self, target_time_str: str) -> float:
try:
hour, minute = map(int, target_time_str.split(":"))
now = datetime.now()
now = now_local()
target = now.replace(hour=hour, minute=minute, second=0, microsecond=0)

if target <= now:
Expand All @@ -192,10 +193,10 @@ def _get_last_report_time(self) -> datetime:
if reports:
created_at_str = reports[0]["created_at"]
if created_at_str:
return datetime.fromisoformat(created_at_str.replace("Z", "+00:00"))
return datetime.now()
return parse_local_datetime(created_at_str)
return now_local()
except Exception:
return datetime.now()
return now_local()

def _start_report_timer(self):
"""Start daily report timer"""
Expand All @@ -213,7 +214,7 @@ def check_and_generate_daily_report():
if not self._activity_generator or not self._task_enabled.get("report", True):
return
try:
now = datetime.now()
now = now_local()
today = now.date()

hour, minute = map(int, self._daily_report_time.split(":"))
Expand Down Expand Up @@ -256,7 +257,7 @@ def generate_activity():

try:
if self._should_generate("activity"):
end_time = int(datetime.now().timestamp())
end_time = int(now_local().timestamp())
last_generation_time = self._last_generation_time("activity")
start_time = (
int(last_generation_time.timestamp())
Expand All @@ -266,7 +267,7 @@ def generate_activity():
self._real_activity_monitor.generate_realtime_activity_summary(
start_time, end_time
)
self._last_generation_times["activity"] = datetime.now()
self._last_generation_times["activity"] = now_local()
except Exception as e:
logger.exception(f"Failed to generate activity record: {e}")

Expand Down Expand Up @@ -295,15 +296,15 @@ def generate_tips():

try:
if self._should_generate("tips"):
end_time = int(datetime.now().timestamp())
end_time = int(now_local().timestamp())
last_generation_time = self._last_generation_time("tips")
start_time = (
int(last_generation_time.timestamp())
if last_generation_time
else end_time - self._task_intervals.get("tips", 60 * 60)
)
self._smart_tip_generator.generate_smart_tip(start_time, end_time)
self._last_generation_times["tips"] = datetime.now()
self._last_generation_times["tips"] = now_local()
except Exception as e:
logger.exception(f"Failed to generate smart tip: {e}")

Expand Down Expand Up @@ -332,7 +333,7 @@ def generate_todos():

try:
if self._should_generate("todos"):
end_time = int(datetime.now().timestamp())
end_time = int(now_local().timestamp())
last_generation_time = self._last_generation_time("todos")
start_time = (
int(last_generation_time.timestamp())
Expand All @@ -342,7 +343,7 @@ def generate_todos():
self._smart_todo_manager.generate_todo_tasks(
start_time=start_time, end_time=end_time
)
self._last_generation_times["todos"] = datetime.now()
self._last_generation_times["todos"] = now_local()
except Exception as e:
logger.exception(f"Failed to generate smart todo: {e}")

Expand Down Expand Up @@ -521,4 +522,4 @@ def reset_statistics(self) -> None:

self._statistics["total_queries"] = 0
self._statistics["total_contexts_consumed"] = 0
self._statistics["errors"] = 0
self._statistics["errors"] = 0
9 changes: 3 additions & 6 deletions opencontext/server/context_operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
get_context_type_options,
)
from opencontext.storage.global_storage import get_storage
from opencontext.utils.datetime_utils import now_local, parse_local_datetime
from opencontext.utils.logging_utils import get_logger

logger = get_logger(__name__)
Expand Down Expand Up @@ -87,14 +88,10 @@ def add_screenshot(

try:
screenshot_format = os.path.splitext(path)[1][1:]
# Handle ISO format time string, supports Z suffix
if create_time.endswith("Z"):
create_time = create_time[:-1] + "+00:00"

raw_context = RawContextProperties(
source=ContextSource.SCREENSHOT,
content_format=ContentFormat.IMAGE,
create_time=datetime.datetime.fromisoformat(create_time),
create_time=parse_local_datetime(create_time),
content_path=path,
additional_info={
"window": window,
Expand Down Expand Up @@ -135,7 +132,7 @@ def add_document(self, file_path: str, context_processor_callback) -> Optional[s
raw_context = RawContextProperties(
source=ContextSource.LOCAL_FILE,
content_format=ContentFormat.FILE,
create_time=datetime.datetime.now(),
create_time=now_local(),
object_id=object_id,
content_path=str(path),
additional_info={
Expand Down
11 changes: 10 additions & 1 deletion opencontext/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@
"""

from opencontext.utils.file_utils import ensure_dir, get_file_extension, is_binary_file
from opencontext.utils.datetime_utils import ensure_local_naive, now_local, parse_local_datetime
from opencontext.utils.logging_utils import setup_logging

__all__ = ["setup_logging", "ensure_dir", "get_file_extension", "is_binary_file"]
__all__ = [
"setup_logging",
"ensure_dir",
"get_file_extension",
"is_binary_file",
"ensure_local_naive",
"parse_local_datetime",
"now_local",
]
Loading