Compare commits

...

5 Commits

6 changed files with 49 additions and 9 deletions

5
.gitignore vendored
View File

@@ -58,3 +58,8 @@ htmlcov/
!.yarn/releases !.yarn/releases
!.yarn/sdks !.yarn/sdks
!.yarn/versions !.yarn/versions
backend-rust/owlynews.sqlite3
backend-rust/target
/backend-rust/config.toml
/backend-rust/owlynews.sqlite3-shm
/backend-rust/owlynews.sqlite3-wal

View File

@@ -2,7 +2,7 @@ import logging
import os import os
from pathlib import Path from pathlib import Path
DB_PATH = Path(os.getenv("DB_NAME", "owlynews.sqlite3")) DB_PATH = Path(os.getenv("DB_NAME", os.path.join(os.path.dirname(os.path.dirname(__file__)), "owlynews.sqlite3")))
OLLAMA_HOST = os.getenv("OLLAMA_HOST", "http://localhost:11434") OLLAMA_HOST = os.getenv("OLLAMA_HOST", "http://localhost:11434")
MIN_CRON_HOURS = float(os.getenv("MIN_CRON_HOURS", 0.5)) MIN_CRON_HOURS = float(os.getenv("MIN_CRON_HOURS", 0.5))
DEFAULT_CRON_HOURS = float(os.getenv("CRON_HOURS", MIN_CRON_HOURS)) DEFAULT_CRON_HOURS = float(os.getenv("CRON_HOURS", MIN_CRON_HOURS))
@@ -12,7 +12,7 @@ LLM_MODEL = os.getenv("LLM_MODEL", "mistral-nemo:12b")
LLM_TIMEOUT_SECONDS = int(os.getenv("LLM_TIMEOUT_SECONDS", 180)) LLM_TIMEOUT_SECONDS = int(os.getenv("LLM_TIMEOUT_SECONDS", 180))
OLLAMA_API_TIMEOUT_SECONDS = int(os.getenv("OLLAMA_API_TIMEOUT_SECONDS", 10)) OLLAMA_API_TIMEOUT_SECONDS = int(os.getenv("OLLAMA_API_TIMEOUT_SECONDS", 10))
ARTICLE_FETCH_TIMEOUT = int(os.getenv("ARTICLE_FETCH_TIMEOUT", 30)) ARTICLE_FETCH_TIMEOUT = int(os.getenv("ARTICLE_FETCH_TIMEOUT", 30))
MAX_ARTICLE_LENGTH = int(os.getenv("MAX_ARTICLE_LENGTH", 5000)) MAX_ARTICLE_LENGTH = int(os.getenv("MAX_ARTICLE_LENGTH", 10_000))
frontend_path = os.path.join( frontend_path = os.path.join(
os.path.dirname(os.path.dirname(os.path.dirname(__file__))), os.path.dirname(os.path.dirname(os.path.dirname(__file__))),

View File

@@ -15,6 +15,8 @@ import sqlite3
import time import time
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
from typing import Any, Dict, List, Union from typing import Any, Dict, List, Union
import subprocess
import threading
# Third-party imports # Third-party imports
import httpx import httpx
@@ -62,6 +64,19 @@ scheduler.add_job(
scheduler.start() scheduler.start()
def start_frontend_build():
try:
subprocess.Popen(
["yarn", "build"],
cwd="../frontend",
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
print("Frontend build started successfully")
except Exception as e:
print(f"Failed to start frontend build: {e}")
# API endpoints # API endpoints
@app.get("/news", response_model=List[Dict[str, Any]]) @app.get("/news", response_model=List[Dict[str, Any]])
async def get_news( async def get_news(
@@ -200,8 +215,6 @@ async def list_feeds(db: sqlite3.Cursor = Depends(get_db)):
) )
@app.post("/feeds", response_model=SuccessResponse) @app.post("/feeds", response_model=SuccessResponse)
async def add_feed( async def add_feed(
feed: FeedData, feed: FeedData,
@@ -290,7 +303,7 @@ async def get_model_status():
@app.post("/sync", response_model=None) @app.post("/sync", response_model=None)
async def manual_sync(db: sqlite3.Cursor = Depends(get_db)): async def manual_sync(db: sqlite3.Cursor = Depends(get_db_write)): # Note: changed to get_db_write
""" """
Manually trigger a feed synchronization. Manually trigger a feed synchronization.
@@ -302,7 +315,14 @@ async def manual_sync(db: sqlite3.Cursor = Depends(get_db)):
""" """
db.execute("SELECT val FROM meta WHERE key='last_sync'") db.execute("SELECT val FROM meta WHERE key='last_sync'")
row = db.fetchone() row = db.fetchone()
last_sync_ts = int(row["val"])
if row is None:
# Initialize the last_sync key if it doesn't exist
import time
last_sync_ts = int(time.time()) - (SYNC_COOLDOWN_MINUTES * 60 + 1) # Set to a time that allows sync
db.execute("INSERT INTO meta (key, val) VALUES ('last_sync', ?)", (str(last_sync_ts),))
else:
last_sync_ts = int(row["val"])
now = datetime.now(timezone.utc) now = datetime.now(timezone.utc)
last_sync_time = datetime.fromtimestamp(last_sync_ts, timezone.utc) last_sync_time = datetime.fromtimestamp(last_sync_ts, timezone.utc)
@@ -314,8 +334,12 @@ async def manual_sync(db: sqlite3.Cursor = Depends(get_db)):
try: try:
task = asyncio.create_task(NewsFetcher.harvest_feeds()) task = asyncio.create_task(NewsFetcher.harvest_feeds())
# Update the last_sync timestamp after triggering the sync
current_ts = int(time.time())
db.execute("UPDATE meta SET val=? WHERE key='last_sync'", (str(current_ts),))
return {"status": "triggered", "task_id": id(task)} return {"status": "triggered", "task_id": id(task)}
except Exception as e: except Exception as e:
logger.error(f"❌ Failed to trigger sync: {e}")
raise HTTPException( raise HTTPException(
500, f"Failed to trigger sync: {str(e)}" 500, f"Failed to trigger sync: {str(e)}"
) )
@@ -394,5 +418,11 @@ async def update_cron_schedule(
return {"hours": hours} return {"hours": hours}
# Mount static frontend
app.mount("/", StaticFiles(directory=frontend_path, html=True), name="static") app.mount("/", StaticFiles(directory=frontend_path, html=True), name="static")
if __name__ == "__main__":
threading.Thread(target=start_frontend_build, daemon=True).start()
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)

View File

@@ -209,7 +209,7 @@ class NewsFetcher:
"format": "json", "format": "json",
"options": { "options": {
"num_gpu": 1, # Force GPU usage "num_gpu": 1, # Force GPU usage
"num_ctx": 8192, # Context size "num_ctx": 128_000, # Context size
} }
} }

View File

@@ -14,6 +14,7 @@ SYNC_COOLDOWN_MINUTES=30
LLM_MODEL=qwen2:7b-instruct-q4_K_M LLM_MODEL=qwen2:7b-instruct-q4_K_M
LLM_MODEL=phi3:3.8b-mini-128k-instruct-q4_0 LLM_MODEL=phi3:3.8b-mini-128k-instruct-q4_0
LLM_MODEL=mistral-nemo:12b LLM_MODEL=mistral-nemo:12b
LLM_MODEL=cnjack/mistral-samll-3.1:24b-it-q4_K_S
# Timeout in seconds for LLM requests # Timeout in seconds for LLM requests
LLM_TIMEOUT_SECONDS=180 LLM_TIMEOUT_SECONDS=180

View File

@@ -23,7 +23,11 @@ export default defineConfig({
server: { server: {
proxy: { proxy: {
'/news': 'http://localhost:8000', '/news': 'http://localhost:8000',
'/meta': 'http://localhost:8000' '/meta': 'http://localhost:8000',
'/feeds': 'http://localhost:8000',
'/model': 'http://localhost:8000',
'/sync': 'http://localhost:8000',
'/settings': 'http://localhost:8000'
} }
}, },
}); });