|
# retoor <retoor@molodetz.nl>
|
|
|
|
import logging
|
|
from datetime import datetime, timezone
|
|
|
|
from devplacepy.database import (
|
|
db,
|
|
get_follow_counts,
|
|
get_table,
|
|
get_user_post_count,
|
|
get_user_rank,
|
|
)
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
MAX_BIO = 280
|
|
MAX_EXCERPT = 400
|
|
MAX_SOURCE = 1500
|
|
MAX_THREAD_MESSAGES = 6
|
|
MAX_MESSAGE = 240
|
|
|
|
TARGET_TABLES = {
|
|
"post": "posts",
|
|
"project": "projects",
|
|
"gist": "gists",
|
|
"news": "news",
|
|
}
|
|
|
|
|
|
def _truncate(text: str, limit: int) -> str:
|
|
text = " ".join((text or "").split())
|
|
if len(text) <= limit:
|
|
return text
|
|
return text[:limit].rstrip() + "..."
|
|
|
|
|
|
def _today() -> str:
|
|
return datetime.now(timezone.utc).strftime("%d/%m/%Y")
|
|
|
|
|
|
def _author_block(user_uid: str) -> str:
|
|
user = get_table("users").find_one(uid=user_uid)
|
|
if not user:
|
|
return ""
|
|
stats = [
|
|
f"role {user.get('role') or 'Member'}",
|
|
f"level {user.get('level', 1)}",
|
|
f"{user.get('stars', 0)} stars",
|
|
]
|
|
try:
|
|
stats.append(f"{get_user_post_count(user_uid)} posts")
|
|
except Exception:
|
|
pass
|
|
try:
|
|
rank = get_user_rank(user_uid)
|
|
if rank:
|
|
stats.append(f"rank #{rank}")
|
|
except Exception:
|
|
pass
|
|
try:
|
|
follows = get_follow_counts(user_uid)
|
|
stats.append(f"{follows.get('followers', 0)} followers")
|
|
except Exception:
|
|
pass
|
|
joined = (user.get("created_at") or "")[:10]
|
|
if joined:
|
|
stats.append(f"member since {joined}")
|
|
line = f"Author: {user.get('username', '')} ({', '.join(stats)})."
|
|
bio = _truncate(user.get("bio") or "", MAX_BIO)
|
|
if bio:
|
|
line += f' Bio: "{bio}".'
|
|
return line
|
|
|
|
|
|
def _target_excerpt(target_type: str, target_uid: str) -> str:
|
|
table = TARGET_TABLES.get(target_type)
|
|
if not table or table not in db.tables or not target_uid:
|
|
return ""
|
|
row = get_table(table).find_one(uid=target_uid)
|
|
if not row:
|
|
return ""
|
|
title = (row.get("title") or "").strip()
|
|
body = _truncate(row.get("content") or row.get("description") or "", MAX_EXCERPT)
|
|
label = f'the {target_type} "{title}"' if title else f"a {target_type}"
|
|
text = f"This is a comment on {label}."
|
|
if body:
|
|
text += f' Excerpt: "{body}".'
|
|
return text
|
|
|
|
|
|
def _comment_block(row: dict) -> str:
|
|
target_type = row.get("target_type") or "post"
|
|
target_uid = row.get("target_uid") or row.get("post_uid") or ""
|
|
text = _target_excerpt(target_type, target_uid)
|
|
parent_uid = row.get("parent_uid")
|
|
if parent_uid and "comments" in db.tables:
|
|
parent = get_table("comments").find_one(uid=parent_uid)
|
|
if parent:
|
|
text += f' In reply to: "{_truncate(parent.get("content") or "", MAX_EXCERPT)}".'
|
|
return text
|
|
|
|
|
|
def _post_block(row: dict) -> str:
|
|
topic = row.get("topic") or "general"
|
|
text = f"This is a post (topic: {topic})."
|
|
project_uid = row.get("project_uid")
|
|
if project_uid and "projects" in db.tables:
|
|
project = get_table("projects").find_one(uid=project_uid)
|
|
if project and project.get("title"):
|
|
text += f' Attached to the project "{project["title"]}".'
|
|
return text
|
|
|
|
|
|
def _item_block(table: str, row: dict) -> str:
|
|
kind = "project" if table == "projects" else "gist"
|
|
title = (row.get("title") or "").strip()
|
|
text = f'This is a {kind}' + (f' titled "{title}".' if title else ".")
|
|
description = _truncate(row.get("description") or "", MAX_EXCERPT)
|
|
if description:
|
|
text += f' Description: "{description}".'
|
|
if table == "gists":
|
|
language = row.get("language") or "plaintext"
|
|
source = _truncate(row.get("source_code") or "", MAX_SOURCE)
|
|
text += f" Language: {language}."
|
|
if source:
|
|
text += f" Source code (reference only, do not include unless asked): {source}"
|
|
return text
|
|
|
|
|
|
def _message_block(row: dict) -> str:
|
|
sender_uid = row.get("sender_uid")
|
|
receiver_uid = row.get("receiver_uid")
|
|
receiver = get_table("users").find_one(uid=receiver_uid) if receiver_uid else None
|
|
receiver_name = (receiver or {}).get("username") or "the recipient"
|
|
text = f"This is a direct message to {receiver_name}."
|
|
if "messages" not in db.tables or not sender_uid or not receiver_uid:
|
|
return text
|
|
rows = list(
|
|
db.query(
|
|
"SELECT sender_uid, content FROM messages "
|
|
"WHERE ((sender_uid = :a AND receiver_uid = :b) "
|
|
"OR (sender_uid = :b AND receiver_uid = :a)) "
|
|
"AND uid != :current AND deleted_at IS NULL "
|
|
"ORDER BY id DESC LIMIT :lim",
|
|
a=sender_uid,
|
|
b=receiver_uid,
|
|
current=row.get("uid") or "",
|
|
lim=MAX_THREAD_MESSAGES,
|
|
)
|
|
)
|
|
if not rows:
|
|
return text
|
|
lines = []
|
|
for entry in reversed(rows):
|
|
who = "you" if entry["sender_uid"] == sender_uid else receiver_name
|
|
lines.append(f"{who}: {_truncate(entry['content'] or '', MAX_MESSAGE)}")
|
|
return text + " Recent conversation:\n" + "\n".join(lines)
|
|
|
|
|
|
def _location_block(table: str, row: dict) -> str:
|
|
if table == "comments":
|
|
return _comment_block(row)
|
|
if table == "posts":
|
|
return _post_block(row)
|
|
if table in ("projects", "gists"):
|
|
return _item_block(table, row)
|
|
if table == "messages":
|
|
return _message_block(row)
|
|
return ""
|
|
|
|
|
|
def build_context(table: str, uid: str, row: dict, user_uid: str) -> str:
|
|
parts = [f"Today is {_today()} on the DevPlace developer network."]
|
|
try:
|
|
author = _author_block(user_uid)
|
|
if author:
|
|
parts.append(author)
|
|
except Exception as exc:
|
|
logger.warning("ai context author block failed: %s", exc)
|
|
try:
|
|
location = _location_block(table, row)
|
|
if location:
|
|
parts.append(location)
|
|
except Exception as exc:
|
|
logger.warning("ai context location block failed: %s", exc)
|
|
return "\n".join(parts)
|