Latex incoming changes

This commit is contained in:
2025-08-16 17:54:06 +02:00
parent 6cc43ed16d
commit f0bd6c4015
4 changed files with 174 additions and 113 deletions
+63 -60
View File
@@ -133,7 +133,7 @@ def num_tokens_from_string(message, model):
async def handle_response(
prompt, vykidailo, bartender, history, username, request_type, algorithm="gpt-4o"
prompt, vykidailo, bartender, history, username, request_type, algorithm="gpt-4o", none_request=""
):
"""
Handle responses by appending them to a history, use OpenAI to
@@ -189,60 +189,63 @@ async def handle_response(
# convert back to json.
json.dump(file_data, file_memory, indent=4)
history = []
history.append(GPT_SETTINGS[0])
chat_gpt_config_request_size = num_tokens_from_string(GPT_SETTINGS[0], "gpt-4")
if request_type != "NONE":
history.append(GPT_SETTINGS[0])
chat_gpt_config_request_size = num_tokens_from_string(GPT_SETTINGS[0], "gpt-4")
for slowo, reakcja in WORD_REACTIONS.items():
if not reakcja[3]:
content = (
"Kiedy słyszysz "
+ slowo
+ " to reagujesz lub dzieje się to "
+ reakcja[0]
)
temp = {"role": "system", "content": content}
chat_gpt_config_request_size += num_tokens_from_string(temp, "gpt-4")
history.append(temp)
for slowo, reakcja in WORD_REACTIONS.items():
if not reakcja[3]:
content = (
"Kiedy słyszysz "
+ slowo
+ " to reagujesz lub dzieje się to "
+ reakcja[0]
)
temp = {"role": "system", "content": content}
chat_gpt_config_request_size += num_tokens_from_string(temp, "gpt-4")
history.append(temp)
final_prompt = username + ":" + prompt
logger.debug(
"Rozmiar zapytania przed dodaniem historii %s", chat_gpt_config_request_size
)
if request_type == "MUSIC":
table = MESSAGE_TABLE_MUZYKA
token_amount = 10700
elif request_type == "RANDOM":
table = MESSAGE_TABLE
token_amount = 10700
elif request_type == "GENERAL":
table = MESSAGE_TABLE
token_amount = 10700
else:
table = []
token_amount = 10000
prompt_gpt_request_size = num_tokens_from_string(
{"role": "user", "content": final_prompt}, "gpt-4"
)
temptable = []
for i in reversed(table):
temp_token = num_tokens_from_string(i, "gpt-4")
final_prompt = username + ":" + prompt
logger.debug(
"Rozmiar zapytania %s prompt %s temp %s",
chat_gpt_config_request_size,
prompt_gpt_request_size,
temp_token,
"Rozmiar zapytania przed dodaniem historii %s", chat_gpt_config_request_size
)
if (
chat_gpt_config_request_size
< token_amount + prompt_gpt_request_size + temp_token
):
temptable.insert(1, i)
chat_gpt_config_request_size += temp_token
history.extend(temptable)
temp = {"role": "user", "content": final_prompt}
history.append(temp)
if request_type == "MUSIC":
table = MESSAGE_TABLE_MUZYKA
token_amount = 10700
elif request_type == "RANDOM":
table = MESSAGE_TABLE
token_amount = 10700
elif request_type == "GENERAL":
table = MESSAGE_TABLE
token_amount = 10700
else:
table = []
token_amount = 10000
prompt_gpt_request_size = num_tokens_from_string(
{"role": "user", "content": final_prompt}, "gpt-4"
)
temptable = []
for i in reversed(table):
temp_token = num_tokens_from_string(i, "gpt-4")
logger.debug(
"Rozmiar zapytania %s prompt %s temp %s",
chat_gpt_config_request_size,
prompt_gpt_request_size,
temp_token,
)
if (
chat_gpt_config_request_size
< token_amount + prompt_gpt_request_size + temp_token
):
temptable.insert(1, i)
chat_gpt_config_request_size += temp_token
history.extend(temptable)
temp = {"role": "user", "content": final_prompt}
history.append(temp)
else:
history = none_request
logger.info("Rozmiar zapytania po wyslaniu %s", chat_gpt_config_request_size)
try:
# ...przygotowanie messages/system prompt/itp. jak masz...
@@ -256,9 +259,9 @@ async def handle_response(
except openai.APITimeoutError as e:
# Handle timeout error, e.g. retry or log
result = f"*Kondziu patrzy na terminal, czeka, czeka, czeka,.... Jeszcze chwile czeka Przypierdala w niego pięścią....* Nie mogę się połączyć z Openai spróbuj od nowa. *Na ekranie pojawia się*: {e}"
response = f"*Kondziu patrzy na terminal, czeka, czeka, czeka,.... Jeszcze chwile czeka Przypierdala w niego pięścią....* Nie mogę się połączyć z Openai spróbuj od nowa. *Na ekranie pojawia się*: {e}"
except openai.APIConnectionError as e:
result = f"*Kondziu patrzy na terminal, chwile się zastanawia. Przypierdala w niego pięścią....* Nie mogę się połączyć z Openai. *Na ekranie pojawia się*: {e}"
response = f"*Kondziu patrzy na terminal, chwile się zastanawia. Przypierdala w niego pięścią....* Nie mogę się połączyć z Openai. *Na ekranie pojawia się*: {e}"
except openai.BadRequestError as e:
# Handle invalid request error, e.g. validate parameters or log
resp, _ = await handle_response(
@@ -269,7 +272,7 @@ async def handle_response(
username,
"RANDOM",
)
result = f"Sorki, cenzura: {resp}. Jak chcesz to są kanały na nudle #sexy-foteczky i #kanal-do-fapania *Na ekranie pojawia się: {e}"
response = f"Sorki, cenzura: {resp}. Jak chcesz to są kanały na nudle #sexy-foteczky i #kanal-do-fapania *Na ekranie pojawia się: {e}"
except openai.APIResponseValidationError as e:
# Handle invalid request error, e.g. validate parameters or log
resp, _ = await handle_response(
@@ -280,20 +283,20 @@ async def handle_response(
username,
"RANDOM",
)
result = f"Sorki, cenzura: {resp}. Jak chcesz to są kanały na nudle #sexy-foteczky i #kanal-do-fapania *Na ekranie pojawia się: {e}"
response = f"Sorki, cenzura: {resp}. Jak chcesz to są kanały na nudle #sexy-foteczky i #kanal-do-fapania *Na ekranie pojawia się: {e}"
except openai.AuthenticationError as e:
# Handle authentication error, e.g. check credentials or log
result = f"*Kondziu patrzy na terminal, chwile się zastanawia. Przypierdala w niego pięścią....* Wołaj szefa - coś się z hasłem zjebało. *Na terminalu pojawia się:* {e}"
response = f"*Kondziu patrzy na terminal, chwile się zastanawia. Przypierdala w niego pięścią....* Wołaj szefa - coś się z hasłem zjebało. *Na terminalu pojawia się:* {e}"
except openai.PermissionDeniedError as e:
# Handle permission error, e.g. check scope or log
result = f"*Kondziu patrzy na terminal, chwile się zastanawia. Przypierdala w niego pięścią....* Wołaj szefa - coś się z uprawnieniami zjebało. *Na terminalu pojawia się:* {e}"
response = f"*Kondziu patrzy na terminal, chwile się zastanawia. Przypierdala w niego pięścią....* Wołaj szefa - coś się z uprawnieniami zjebało. *Na terminalu pojawia się:* {e}"
except openai.RateLimitError as e:
result = f"*Kondziu patrzy na terminal* Wołaj szefa. Zapłacić rachunki za AI trzeba. Jak chcesz to się na #zebranie dorzuć. {e}"
response = f"*Kondziu patrzy na terminal* Wołaj szefa. Zapłacić rachunki za AI trzeba. Jak chcesz to się na #zebranie dorzuć. {e}"
except openai.UnprocessableEntityError as e:
result = f"*Kondziu patrzy na terminal. Potem na to co każesz mu wysłać....* Ja wiem że jesteśmy w barze BDSM - ale nie da się włożyć TEGO w TO. *Za jego plecami na terminalu pojawia się:* {e}"
response = f"*Kondziu patrzy na terminal. Potem na to co każesz mu wysłać....* Ja wiem że jesteśmy w barze BDSM - ale nie da się włożyć TEGO w TO. *Za jego plecami na terminalu pojawia się:* {e}"
except openai.APIError as e:
# Handle API error, e.g. retry or log
result = f"*Kondziu nurkuje za bar, terminal wybucha. Przed tobą ląduje pergamin zapisany pięknym gotykiem a na nim*: {e}"
response = f"*Kondziu nurkuje za bar, terminal wybucha. Przed tobą ląduje pergamin zapisany pięknym gotykiem a na nim*: {e}"
logger.info("Historia wysłana:")
logger.info(history)
+4 -1
View File
@@ -3,6 +3,7 @@
from __future__ import annotations
import io
from typing import Optional, List
import logging
import discord
from discord import app_commands
@@ -128,5 +129,7 @@ class LatexModule(commands.Cog):
self.logger.debug("BATCH failed: %s", ", ".join(failed))
await interaction.followup.send("✅ Gotowe (ZIP w załączniku)." + (f" ❌ Błędy: {len(failed)}" if failed else ""), files=[discord.File(bio, filename=bio.name)])
async def setup(bot: commands.Bot):
async def setup(bot):
logger = logging.getLogger("discord")
await bot.add_cog(LatexModule(bot, logger_name="latex"))
logger.info("Loading kinky latex module done")
+106 -52
View File
@@ -1,76 +1,122 @@
# latex_functions.py
# -*- coding: utf-8 -*-
from __future__ import annotations
import asyncio, os, re, shlex, tempfile, zipfile
from pathlib import Path
from typing import Tuple, List, Dict, Optional
from openai import OpenAI
import asyncio
import re
import shlex
import tempfile
import zipfile
from pathlib import Path
from typing import Dict, List, Optional, Tuple
from ai_functions import handle_response
SAFE_TEX_NAME = re.compile(r"^[\w\-. ]+\.tex$", re.IGNORECASE)
def is_safe_tex_name(name: str) -> bool:
return bool(SAFE_TEX_NAME.match(name or "")) and name.lower().endswith(".tex")
def is_tex_attachment(att, max_mb: int) -> bool:
"""Filtruje discord.Attachment: tylko .tex + limit MB + nazwa bezpieczna."""
return bool(
getattr(att, "filename", "") and
is_safe_tex_name(att.filename) and
getattr(att, "size", 0) <= max_mb * 1024 * 1024
getattr(att, "filename", "")
and is_safe_tex_name(att.filename)
and getattr(att, "size", 0) <= max_mb * 1024 * 1024
)
async def run_latex_two_passes(tex_filename: str, workdir: Path, tex_engine: str, timeout_sec: int, logger=None) -> Tuple[bool, str, Path]:
cmd = f'{tex_engine} -interaction=nonstopmode -halt-on-error -file-line-error -no-shell-escape {shlex.quote(tex_filename)}'
if logger: logger.debug("LaTeX cmd: %s (cwd=%s)", cmd, workdir)
async def run_latex_two_passes(
tex_filename: str, workdir: Path, tex_engine: str, timeout_sec: int, logger=None
) -> Tuple[bool, str, Path]:
cmd = f"{tex_engine} -interaction=nonstopmode -halt-on-error -file-line-error -no-shell-escape {shlex.quote(tex_filename)}"
if logger:
logger.debug("LaTeX cmd: %s (cwd=%s)", cmd, workdir)
async def _run_once() -> Tuple[int, str]:
proc = await asyncio.create_subprocess_shell(
cmd, cwd=str(workdir),
stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.STDOUT
cmd,
cwd=str(workdir),
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.STDOUT,
)
try:
out_b, _ = await asyncio.wait_for(proc.communicate(), timeout=timeout_sec)
out_t = (out_b or b"").decode("utf-8", errors="replace")
return proc.returncode or 0, out_t
except asyncio.TimeoutError:
try: proc.kill()
except Exception: pass
try:
proc.kill()
except Exception:
pass
return 124, f"[Timeout] Compilation exceeded {timeout_sec}s.\n"
logs: List[str] = []
rc1, out1 = await _run_once(); logs.append(out1)
rc1, out1 = await _run_once()
logs.append(out1)
if rc1 != 0:
log_text = "".join(logs)
if logger: logger.debug("LaTeX pass1 FAIL %s\n%s", tex_filename, log_text[-2000:])
if logger:
logger.debug("LaTeX pass1 FAIL %s\n%s", tex_filename, log_text[-2000:])
return False, log_text, workdir / (Path(tex_filename).stem + ".pdf")
rc2, out2 = await _run_once(); logs.append(out2)
rc2, out2 = await _run_once()
logs.append(out2)
log_text = "".join(logs)
pdf = workdir / (Path(tex_filename).stem + ".pdf")
ok = (rc2 == 0) and pdf.exists()
if logger: logger.debug("LaTeX pass2 %s ok=%s\n%s", tex_filename, ok, log_text[-2000:])
if logger:
logger.debug("LaTeX pass2 %s ok=%s\n%s", tex_filename, ok, log_text[-2000:])
return ok, log_text, pdf
async def compile_single_tex_bytes(tex_bytes: bytes, filename: str, tex_engine: str, timeout_sec: int, logger=None) -> Dict[str, Optional[object]]:
async def compile_single_tex_bytes(
tex_bytes: bytes, filename: str, tex_engine: str, timeout_sec: int, logger=None
) -> Dict[str, Optional[object]]:
if not is_safe_tex_name(filename):
return {'ok': False, 'pdf_bytes': None, 'log_text': 'Invalid .tex filename', 'pdf_name': None}
return {
"ok": False,
"pdf_bytes": None,
"log_text": "Invalid .tex filename",
"pdf_name": None,
}
with tempfile.TemporaryDirectory(prefix="latex_one_") as td:
wd = Path(td)
tex_path = wd / filename
tex_path.write_bytes(tex_bytes)
ok, log_text, pdf_path = await run_latex_two_passes(filename, wd, tex_engine, timeout_sec, logger=logger)
ok, log_text, pdf_path = await run_latex_two_passes(
filename, wd, tex_engine, timeout_sec, logger=logger
)
if ok and pdf_path.exists():
return {'ok': True, 'pdf_bytes': pdf_path.read_bytes(), 'log_text': log_text, 'pdf_name': pdf_path.name}
return {'ok': False, 'pdf_bytes': None, 'log_text': log_text, 'pdf_name': pdf_path.name}
return {
"ok": True,
"pdf_bytes": pdf_path.read_bytes(),
"log_text": log_text,
"pdf_name": pdf_path.name,
}
return {
"ok": False,
"pdf_bytes": None,
"log_text": log_text,
"pdf_name": pdf_path.name,
}
async def compile_zip_to_zip(zip_bytes: bytes, tex_engine: str, timeout_sec: int, logger=None) -> Tuple[bytes, List[str]]:
failed: List[str] = []; out_pdf_paths: List[Path] = []
async def compile_zip_to_zip(
zip_bytes: bytes, tex_engine: str, timeout_sec: int, logger=None
) -> Tuple[bytes, List[str]]:
failed: List[str] = []
out_pdf_paths: List[Path] = []
with tempfile.TemporaryDirectory(prefix="latex_zip_") as td:
td_path = Path(td)
in_zip = td_path / "in.zip"; in_zip.write_bytes(zip_bytes)
extract_dir = td_path / "in"; extract_dir.mkdir(parents=True, exist_ok=True)
with zipfile.ZipFile(in_zip, 'r') as zf: zf.extractall(extract_dir)
in_zip = td_path / "in.zip"
in_zip.write_bytes(zip_bytes)
extract_dir = td_path / "in"
extract_dir.mkdir(parents=True, exist_ok=True)
with zipfile.ZipFile(in_zip, "r") as zf:
zf.extractall(extract_dir)
tex_files = [p for p in extract_dir.rglob("*.tex") if is_safe_tex_name(p.name)]
if not tex_files:
@@ -78,17 +124,23 @@ async def compile_zip_to_zip(zip_bytes: bytes, tex_engine: str, timeout_sec: int
for tex in tex_files:
try:
work = td_path / f"build_{tex.stem}"; work.mkdir(parents=True, exist_ok=True)
target_tex = work / tex.name; target_tex.write_bytes(tex.read_bytes())
ok, log_text, pdf_path = await run_latex_two_passes(target_tex.name, work, tex_engine, timeout_sec, logger=logger)
work = td_path / f"build_{tex.stem}"
work.mkdir(parents=True, exist_ok=True)
target_tex = work / tex.name
target_tex.write_bytes(tex.read_bytes())
ok, log_text, pdf_path = await run_latex_two_passes(
target_tex.name, work, tex_engine, timeout_sec, logger=logger
)
if ok and pdf_path.exists():
out_pdf_paths.append(pdf_path)
else:
failed.append(tex.name)
if logger: logger.debug("BATCH FAIL %s\n%s", tex.name, log_text[-1500:])
if logger:
logger.debug("BATCH FAIL %s\n%s", tex.name, log_text[-1500:])
except Exception as e:
failed.append(f"{tex.name} (exception: {e})")
if logger: logger.debug("BATCH EXC %s: %s", tex.name, e)
if logger:
logger.debug("BATCH EXC %s: %s", tex.name, e)
out_zip = td_path / "compiled_pdfs.zip"
with zipfile.ZipFile(out_zip, "w", compression=zipfile.ZIP_DEFLATED) as zf:
@@ -97,25 +149,27 @@ async def compile_zip_to_zip(zip_bytes: bytes, tex_engine: str, timeout_sec: int
return out_zip.read_bytes(), failed
async def ask_openai_diagnosis(log_text: str, tex_excerpt: str, api_key: str, model: str, logger=None) -> str:
if not api_key:
return "⚠️ Brak OPENAI_API_KEY — pomijam diagnozę."
client = OpenAI(api_key=api_key)
sys = ("You are a LaTeX build diagnostician. Analyze pdflatex log and return:\n"
"1) Root causes (bullets)\n2) Minimal working fix (code block)\n3) Alternative fix")
async def ask_openai_diagnosis(
log_text: str, tex_excerpt: str, api_key: str, model: str, logger=None
) -> str:
sys = (
"You are a LaTeX build diagnostician. Analyze pdflatex log and return:\n"
"1) Root causes (bullets)\n2) Minimal working fix (code block)\n3) Alternative fix"
)
usr = f"---LOG---\n{log_text[-9000:]}\n---END LOG---"
if tex_excerpt:
usr += f"\n---TEX EXCERPT---\n{tex_excerpt[:4000]}\n---END EXCERPT---"
try:
if model.startswith("gpt-3.5"):
def _legacy():
resp = client.chat.completions.create(model=model, messages=[{"role":"system","content":sys},{"role":"user","content":usr}], temperature=0.1)
return resp.choices[0].message.content.strip()
return await asyncio.to_thread(_legacy)
def _resp():
r = client.responses.create(model=model, temperature=0.1, input=[{"role":"system","content":sys},{"role":"user","content":usr}])
return (getattr(r,"output_text",None) or "").strip() or str(r)
return await asyncio.to_thread(_resp)
except Exception as e:
if logger: logger.debug("OpenAI error: %s", e)
return f"⚠️ OpenAI API error: {e}"
prompt = [{"role": sys, "content": "none"}, {"role": "user", "content": usr}]
response, _ = await handle_response(
prompt,
None,
None,
prompt,
"latex_diagnosis",
"NONE",
algorithm=model,
none_request=prompt,
)
return response
+1
View File
@@ -69,6 +69,7 @@ async def on_ready():
await client.load_extension("other_commands")
await client.load_extension("voice_recognition_commands")
await client.load_extension("file_search_commands")
await client.load_extension("latex_commands")
logger.info("Sensors: online")
logger.info(client.cogs)