Implement business plans

This commit is contained in:
2026-03-02 00:00:53 +00:00
parent d22924f6aa
commit b3e183eb0a
26 changed files with 4109 additions and 39 deletions

View File

145
core/translation/engine.py Normal file
View File

@@ -0,0 +1,145 @@
from __future__ import annotations
import hashlib
import time
from asgiref.sync import sync_to_async
from core.clients import transport
from core.messaging import ai as ai_runner
from core.messaging.reply_sync import apply_sync_origin, is_mirrored_origin
from core.models import AI, Message, TranslationBridge, TranslationEventLog
from core.util import logs
log = logs.get_logger("translation_engine")
def _direction_allowed(bridge: TranslationBridge, source_side: str) -> bool:
if bridge.direction == "bidirectional":
return True
if source_side == "a" and bridge.direction == "a_to_b":
return True
if source_side == "b" and bridge.direction == "b_to_a":
return True
return False
def _target_for_side(bridge: TranslationBridge, source_side: str):
if source_side == "a":
return ("b", bridge.b_service, bridge.b_channel_identifier, bridge.b_language)
return ("a", bridge.a_service, bridge.a_channel_identifier, bridge.a_language)
def _source_language(bridge: TranslationBridge, source_side: str):
return bridge.a_language if source_side == "a" else bridge.b_language
async def _translate_text(user, text: str, source_lang: str, target_lang: str) -> str:
ai_obj = await sync_to_async(lambda: AI.objects.filter(user=user).first())()
if ai_obj is None:
return text
prompt = [
{
"role": "system",
"content": (
"Translate the user text exactly for meaning and tone. "
"Do not add commentary. Return only translated text."
),
},
{
"role": "user",
"content": (
f"Source language: {source_lang}\n"
f"Target language: {target_lang}\n"
f"Text:\n{text}"
),
},
]
return str(await ai_runner.run_prompt(prompt, ai_obj) or "").strip()
async def process_inbound_translation(message: Message):
if message is None or not str(message.text or "").strip():
return
if is_mirrored_origin(message.message_meta):
return
source_service = str(message.source_service or "").strip().lower()
source_channel = str(message.source_chat_id or "").strip()
if not source_service or not source_channel:
return
bridges = await sync_to_async(list)(
TranslationBridge.objects.filter(
user=message.user,
enabled=True,
)
)
for bridge in bridges:
side = None
if (
bridge.a_service == source_service
and str(bridge.a_channel_identifier or "").strip() == source_channel
):
side = "a"
elif (
bridge.b_service == source_service
and str(bridge.b_channel_identifier or "").strip() == source_channel
):
side = "b"
if side is None or not _direction_allowed(bridge, side):
continue
_, target_service, target_channel, target_lang = _target_for_side(bridge, side)
source_lang = _source_language(bridge, side)
origin_tag = f"translation:{bridge.id}:{message.id}"
content_hash = hashlib.sha1(
f"{source_service}|{source_channel}|{message.text}".encode("utf-8")
).hexdigest()
log_row = await sync_to_async(TranslationEventLog.objects.create)(
bridge=bridge,
source_message=message,
target_service=target_service,
target_channel=target_channel,
status="pending",
origin_tag=origin_tag,
content_hash=content_hash,
)
try:
translated = await _translate_text(
message.user,
str(message.text or ""),
source_lang=source_lang,
target_lang=target_lang,
)
if target_service != "web":
await transport.send_message_raw(
target_service,
target_channel,
text=translated,
attachments=[],
metadata={"origin_tag": origin_tag},
)
log_row.status = "ok"
log_row.error = ""
except Exception as exc:
log_row.status = "failed"
log_row.error = str(exc)
log.warning("translation forward failed bridge=%s: %s", bridge.id, exc)
await sync_to_async(log_row.save)(update_fields=["status", "error", "updated_at"])
def apply_translation_origin(meta: dict | None, origin_tag: str) -> dict:
return apply_sync_origin(meta, origin_tag)
def parse_quick_mode_title(raw_title: str) -> dict:
title = str(raw_title or "").strip()
parts = [part.strip() for part in title.split("|") if part.strip()]
if len(parts) < 2:
return {}
return {
"a_language": parts[0].lower(),
"b_language": parts[1].lower(),
}