added strtegy import and amployee strategy
This commit is contained in:
@@ -44,7 +44,9 @@ from src.services.storage_service import storage
|
||||
from src.services.link_extractor import LinkExtractor, LinkExtractionError
|
||||
from src.services.file_extractor import FileExtractor, FileExtractionError
|
||||
from src.agents.link_topic_builder import LinkTopicBuilderAgent
|
||||
from src.agents.strategy_importer import StrategyImporterAgent
|
||||
from src.services.post_insights_service import compute_post_insights, refresh_post_insights_for_account
|
||||
from src.services.insights_summary_service import generate_insights_summary
|
||||
|
||||
# Router for user frontend
|
||||
user_router = APIRouter(tags=["user"])
|
||||
@@ -3036,14 +3038,34 @@ async def refresh_post_insights(request: Request):
|
||||
metadata = profile.metadata or {}
|
||||
today = datetime.now(timezone.utc).date().isoformat()
|
||||
last_refresh = metadata.get("post_insights_manual_refresh_date")
|
||||
if last_refresh == today:
|
||||
if settings.insights_manual_refresh_limit_enabled and last_refresh == today:
|
||||
raise HTTPException(status_code=429, detail="Manual refresh already used today")
|
||||
|
||||
await refresh_post_insights_for_account(db, linkedin_account)
|
||||
# Mark refresh immediately to avoid concurrent spam
|
||||
metadata["post_insights_manual_refresh_date"] = today
|
||||
await db.update_profile(user_id, {"metadata": metadata})
|
||||
|
||||
return {"success": True, "refreshed_at": today}
|
||||
async def _bg_refresh_and_summary():
|
||||
try:
|
||||
await refresh_post_insights_for_account(db, linkedin_account)
|
||||
|
||||
from datetime import date, timedelta
|
||||
since = (date.today() - timedelta(days=90)).isoformat()
|
||||
insights_posts = await db.get_post_insights_posts(user_id)
|
||||
insights_daily = await db.get_post_insights_daily(user_id, since_date=since)
|
||||
post_insights = compute_post_insights(insights_posts, insights_daily)
|
||||
if post_insights and post_insights.get("has_data"):
|
||||
summary = await asyncio.to_thread(generate_insights_summary, post_insights)
|
||||
if summary:
|
||||
metadata["post_insights_summary"] = summary
|
||||
metadata["post_insights_summary_date"] = today
|
||||
await db.update_profile(user_id, {"metadata": metadata})
|
||||
except Exception as e:
|
||||
logger.warning(f"Manual insights refresh failed: {e}")
|
||||
|
||||
asyncio.create_task(_bg_refresh_and_summary())
|
||||
|
||||
return {"success": True, "refreshed_at": today, "queued": True}
|
||||
|
||||
except HTTPException:
|
||||
raise
|
||||
@@ -3122,6 +3144,63 @@ async def company_strategy_submit(request: Request):
|
||||
})
|
||||
|
||||
|
||||
@user_router.post("/company/strategy/import")
|
||||
async def company_strategy_import(request: Request):
|
||||
"""Import company strategy from a PDF document."""
|
||||
session = require_user_session(request)
|
||||
if not session:
|
||||
raise HTTPException(status_code=401, detail="Not authenticated")
|
||||
|
||||
if session.account_type != "company" or not session.company_id:
|
||||
raise HTTPException(status_code=403, detail="Company account required")
|
||||
|
||||
# Check token limit for companies/employees
|
||||
can_create, error_msg, _, _ = await db.check_company_token_limit(UUID(session.company_id))
|
||||
if not can_create:
|
||||
raise HTTPException(status_code=429, detail=error_msg)
|
||||
|
||||
try:
|
||||
form = await request.form()
|
||||
upload: UploadFile = form.get("file") # type: ignore[assignment]
|
||||
if not upload:
|
||||
raise HTTPException(status_code=400, detail="Keine Datei hochgeladen.")
|
||||
|
||||
filename = upload.filename or ""
|
||||
ext = Path(filename).suffix.lower()
|
||||
if ext != ".pdf":
|
||||
raise HTTPException(status_code=400, detail="Bitte eine PDF-Datei hochladen.")
|
||||
|
||||
file_bytes = await upload.read()
|
||||
max_bytes = 10 * 1024 * 1024 # 10 MB
|
||||
if len(file_bytes) > max_bytes:
|
||||
raise HTTPException(status_code=400, detail="Datei ist zu groß (max 10 MB).")
|
||||
|
||||
extractor = FileExtractor()
|
||||
try:
|
||||
text = extractor.extract_text(file_bytes, filename)
|
||||
except FileExtractionError as exc:
|
||||
raise HTTPException(status_code=400, detail=str(exc)) from exc
|
||||
|
||||
if len(text) > 50000:
|
||||
text = text[:50000]
|
||||
|
||||
company = await db.get_company(UUID(session.company_id))
|
||||
importer = StrategyImporterAgent()
|
||||
importer.set_tracking_context(
|
||||
operation="company_strategy_import",
|
||||
user_id=session.user_id,
|
||||
company_id=session.company_id
|
||||
)
|
||||
|
||||
strategy = await importer.process(text, company_name=company.name if company else None)
|
||||
return JSONResponse({"success": True, "strategy": strategy})
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.exception(f"Company strategy import failed: {e}")
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
@user_router.get("/company/accounts", response_class=HTMLResponse)
|
||||
async def company_accounts_page(request: Request):
|
||||
"""Company employee management page."""
|
||||
@@ -3589,8 +3668,8 @@ async def company_manage_post_types(request: Request, employee_id: str = None):
|
||||
# ==================== EMPLOYEE ROUTES ====================
|
||||
|
||||
@user_router.get("/employee/strategy", response_class=HTMLResponse)
|
||||
async def employee_strategy_page(request: Request):
|
||||
"""Read-only company strategy view for employees."""
|
||||
async def employee_strategy_page(request: Request, success: bool = False):
|
||||
"""Employee profile analysis (strategy) view."""
|
||||
session = require_user_session(request)
|
||||
if not session:
|
||||
return RedirectResponse(url="/login", status_code=302)
|
||||
@@ -3599,63 +3678,70 @@ async def employee_strategy_page(request: Request):
|
||||
if session.account_type != "employee" or not session.company_id:
|
||||
return RedirectResponse(url="/", status_code=302)
|
||||
|
||||
company = await db.get_company(UUID(session.company_id))
|
||||
strategy = company.company_strategy if company else {}
|
||||
|
||||
user_id = UUID(session.user_id)
|
||||
profile_picture = await get_user_avatar(session, user_id)
|
||||
profile_analysis = await db.get_profile_analysis(user_id)
|
||||
|
||||
analysis = profile_analysis.full_analysis if profile_analysis else {}
|
||||
if not isinstance(analysis, dict):
|
||||
analysis = {}
|
||||
|
||||
analysis_json = json.dumps(analysis, ensure_ascii=False, indent=2)
|
||||
|
||||
return templates.TemplateResponse("employee_strategy.html", {
|
||||
"request": request,
|
||||
"page": "strategy",
|
||||
"session": session,
|
||||
"company": company,
|
||||
"strategy": strategy,
|
||||
"profile_analysis": analysis,
|
||||
"analysis_json": analysis_json,
|
||||
"analysis_created_at": profile_analysis.created_at if profile_analysis else None,
|
||||
"success": success,
|
||||
"profile_picture": profile_picture
|
||||
})
|
||||
|
||||
|
||||
@user_router.get("/employee/insights", response_class=HTMLResponse)
|
||||
async def employee_insights_page(request: Request):
|
||||
"""Employee post insights page."""
|
||||
@user_router.post("/employee/strategy", response_class=HTMLResponse)
|
||||
async def employee_strategy_submit(request: Request):
|
||||
"""Save edited profile analysis for employees."""
|
||||
session = require_user_session(request)
|
||||
if not session:
|
||||
return RedirectResponse(url="/login", status_code=302)
|
||||
|
||||
if session.account_type != "employee":
|
||||
if session.account_type != "employee" or not session.company_id:
|
||||
return RedirectResponse(url="/", status_code=302)
|
||||
|
||||
form = await request.form()
|
||||
raw_json = (form.get("analysis_json") or "").strip()
|
||||
|
||||
try:
|
||||
analysis = json.loads(raw_json) if raw_json else {}
|
||||
if not isinstance(analysis, dict):
|
||||
raise ValueError("JSON muss ein Objekt sein.")
|
||||
|
||||
from src.database.models import ProfileAnalysis
|
||||
user_id = UUID(session.user_id)
|
||||
analysis_record = ProfileAnalysis(
|
||||
user_id=user_id,
|
||||
writing_style=analysis.get("writing_style", {}) or {},
|
||||
tone_analysis=analysis.get("tone_analysis", {}) or {},
|
||||
topic_patterns=analysis.get("topic_patterns", {}) or {},
|
||||
audience_insights=analysis.get("audience_insights", {}) or {},
|
||||
full_analysis=analysis
|
||||
)
|
||||
await db.save_profile_analysis(analysis_record)
|
||||
return RedirectResponse(url="/employee/strategy?success=true", status_code=302)
|
||||
except Exception as e:
|
||||
user_id = UUID(session.user_id)
|
||||
profile_picture = await get_user_avatar(session, user_id)
|
||||
linkedin_account = await db.get_linkedin_account(user_id)
|
||||
|
||||
post_insights = {"has_data": False}
|
||||
if linkedin_account:
|
||||
try:
|
||||
from datetime import date, timedelta
|
||||
since = (date.today() - timedelta(days=90)).isoformat()
|
||||
insights_posts = await db.get_post_insights_posts(user_id)
|
||||
insights_daily = await db.get_post_insights_daily(user_id, since_date=since)
|
||||
post_insights = compute_post_insights(insights_posts, insights_daily)
|
||||
except Exception as e:
|
||||
logger.error(f"Error computing post insights: {e}")
|
||||
|
||||
return templates.TemplateResponse("employee_insights.html", {
|
||||
return templates.TemplateResponse("employee_strategy.html", {
|
||||
"request": request,
|
||||
"page": "insights",
|
||||
"page": "strategy",
|
||||
"session": session,
|
||||
"profile_picture": profile_picture,
|
||||
"linkedin_account": linkedin_account,
|
||||
"post_insights": post_insights
|
||||
})
|
||||
except Exception as e:
|
||||
logger.error(f"Error loading insights: {e}")
|
||||
return templates.TemplateResponse("employee_insights.html", {
|
||||
"request": request,
|
||||
"page": "insights",
|
||||
"session": session,
|
||||
"error": str(e)
|
||||
"profile_analysis": {},
|
||||
"analysis_json": raw_json,
|
||||
"analysis_created_at": None,
|
||||
"error": f"Fehler beim Speichern: {e}",
|
||||
"profile_picture": profile_picture
|
||||
})
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user