26dae1d101
The bot could talk to OpenAI or Anthropic; this adds Ollama as a third provider so it can run against models hosted on our own box, and extends the switch command to pick WHICH model - not just which backend. Provider: Ollama exposes an OpenAI-compatible /v1 surface, so the client is just openai.AsyncOpenAI(base_url=OLLAMA_URL + "/v1"). That reuses the existing message format and the whole _map_openai_error mapping instead of forking a second error taxonomy. There is no API key - the endpoint IS the configuration, so the backend stays dormant (and refuses to be selected, with a message naming the variable) until CONJURER_OLLAMA_URL is set, the same way the Conan bridge behaves. Model selection: * list_provider_models() asks the SERVER for Ollama (/v1/models), so the picker shows what is actually pulled on the box rather than a hardcoded list. Hosted providers just report what they are wired to. * set_active_model() pins the config's latest_model and persists it; cheap_model is left alone so the MUSIC path keeps its cheaper backend. * $gadaj_teraz now takes "<config> [model]", and a new read-only $modele_ai lists what is available. Pinning an id Ollama does not have is rejected up front with the real list - otherwise the typo only surfaces later as a failed reply. Two fixes this exposed: * AI_CONFIGS now merges built-in defaults with the settings-file block instead of letting the file win outright. Every provider switch persists a "configs" block, so a file written by an older build would have permanently hidden ollama from the picker after an upgrade. * _persist_active_ai_config assigns "configs" instead of setdefault, so a pinned model actually survives a restart. * the hardcoded 120s response timeout is now CONJURER_AI_TIMEOUT_SECONDS - a self-hosted model on a modest GPU can legitimately need longer. Tests cover: ollama appears in the picker, select_model maps the legacy gpt-4o default instead of leaking it, model listing (server-queried, sorted, de-duplicated, failure -> AIError, unconfigured -> auth), pinning (latest only, blank/unknown rejected), and that provider_generate routes to the new path. Suite: 68 unit + 70 integration green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
614 lines
28 KiB
Python
614 lines
28 KiB
Python
# ai command cogs
|
|
import logging
|
|
import re
|
|
import sys
|
|
from datetime import datetime
|
|
from enum import Enum
|
|
from typing import Optional
|
|
from pathlib import Path
|
|
import discord
|
|
import openai
|
|
import requests
|
|
from queue import Empty
|
|
from discord.ext import commands, tasks
|
|
from other_functions import discord_friendly_send, discord_friendly_reply
|
|
from communication_subroutine import AI_QUERY_Q
|
|
|
|
|
|
import ai_functions
|
|
from constants import (
|
|
ASSISTANTS,
|
|
DATA,
|
|
GRAPHICS_PATH,
|
|
INITIAL_TIME_WAIT,
|
|
MASTER_TIMEOUT,
|
|
MESSAGE_TABLE,
|
|
OPENAICLIENT,
|
|
SPECJALNE_ZIEMNIACZKI,
|
|
WORD_REACTIONS,
|
|
)
|
|
|
|
|
|
class Dm_Mode(Enum):
|
|
ARMIA_HAMMERA = (1,)
|
|
SPECJALNY_ZIEMNIACZEK = (2,)
|
|
SEKRETNY_SEKSRET = (3,)
|
|
ECHO_ECHO = (4,)
|
|
|
|
|
|
class Events(commands.Cog):
|
|
def __init__(self, bot):
|
|
self.bot = bot
|
|
self.logger = logging.getLogger("discord")
|
|
self.armia = {}
|
|
for superfryta in SPECJALNE_ZIEMNIACZKI.values():
|
|
self.armia[superfryta[0]] = Dm_Mode.SPECJALNY_ZIEMNIACZEK
|
|
self.logger.info(self.armia)
|
|
|
|
@tasks.loop(seconds=2)
|
|
async def ai_query_worker(self):
|
|
"""Drain AI_QUERY_Q one prompt at a time and answer with the active backend.
|
|
|
|
This is the bot-side half of the AI query interface: prompts arrive over
|
|
HTTP (POST /ai_query) or in-process (submit_ai_query), get queued, and are
|
|
answered here with handle_response - so they automatically use whichever
|
|
provider $gadaj_teraz currently selects. The answer is posted to the
|
|
channel the request named.
|
|
"""
|
|
try:
|
|
item = AI_QUERY_Q.get(block=False)
|
|
except Empty:
|
|
return
|
|
prompt = item.get("prompt", "")
|
|
request_type = item.get("request_type", "NONE")
|
|
channel_id = item.get("channel_id")
|
|
username = item.get("username", "conjurer")
|
|
self.logger.info(
|
|
"AI query from %s (%s) -> channel %s", username, request_type, channel_id
|
|
)
|
|
global MESSAGE_TABLE # pylint: disable=global-statement
|
|
try:
|
|
if request_type == "NONE":
|
|
# Clean one-shot: no persona system prompt, no memory write.
|
|
result, _ = await ai_functions.handle_response(
|
|
"", True, True, [], username, "NONE", none_request=prompt
|
|
)
|
|
else:
|
|
result, MESSAGE_TABLE = await ai_functions.handle_response(
|
|
prompt, True, True, MESSAGE_TABLE, username, request_type
|
|
)
|
|
except Exception as exc: # pylint: disable=broad-except
|
|
self.logger.exception("AI query failed: %s", exc)
|
|
result = "*Kondziu drapie się po głowie* Coś się zjebało przy pytaniu do AI."
|
|
if channel_id is None:
|
|
self.logger.warning("AI query had no channel_id - answer dropped")
|
|
return
|
|
channel = self.bot.get_channel(channel_id)
|
|
if channel is None:
|
|
self.logger.warning("AI query channel %s not found - answer dropped", channel_id)
|
|
return
|
|
await self._send_chunked(channel, result)
|
|
|
|
@ai_query_worker.before_loop
|
|
async def _before_ai_query_worker(self):
|
|
await self.bot.wait_until_ready()
|
|
|
|
async def _send_chunked(self, channel, text):
|
|
"""Send text in <=1900-char pieces (Discord caps messages at 2000)."""
|
|
text = text or ""
|
|
while text:
|
|
await discord_friendly_send(channel, text[:1900])
|
|
text = text[1900:]
|
|
|
|
async def cog_load(self):
|
|
# The AI query worker must run regardless of the OpenAI guard below - it
|
|
# answers via handle_response, which works on Claude too. Start it first.
|
|
if not self.ai_query_worker.is_running():
|
|
self.ai_query_worker.start()
|
|
self.logger.info("Starting personal assistants")
|
|
# Personal assistants use the OpenAI Assistants API (threads/runs), which
|
|
# has no Anthropic equivalent - skip cleanly when OpenAI isn't wired up
|
|
# (e.g. a Claude-only deployment) instead of crashing the cog load.
|
|
if OPENAICLIENT is None:
|
|
self.logger.warning(
|
|
"OPENAICLIENT niedostępny - osobiści asystenci (OpenAI Assistants API) wyłączeni"
|
|
)
|
|
return
|
|
for superfryta_id, superfryta in SPECJALNE_ZIEMNIACZKI.items():
|
|
|
|
if superfryta[4] != "":
|
|
self.logger.info(
|
|
"Personal assistant for user: %s, exists id: %s,name: %s, owner: %s, special instructions: %s assistant id: %s ",
|
|
superfryta_id,
|
|
superfryta[0],
|
|
superfryta[1],
|
|
superfryta[2],
|
|
superfryta[3],
|
|
superfryta[4],
|
|
)
|
|
thread = await OPENAICLIENT.beta.threads.create()
|
|
self.logger.info("Thread id: %s", thread.id)
|
|
ASSISTANTS[superfryta[1]] = (
|
|
superfryta[2],
|
|
superfryta[4],
|
|
superfryta[0],
|
|
thread,
|
|
)
|
|
else:
|
|
self.logger.info(
|
|
"Creating personal assistant for user: %s, id: %s,name: %s, owner: %s, special instructions: %s",
|
|
superfryta_id,
|
|
superfryta[0],
|
|
superfryta[1],
|
|
superfryta[2],
|
|
superfryta[3],
|
|
)
|
|
await ai_functions.create_chat_assistant(
|
|
superfryta_id, superfryta[0], superfryta[1], superfryta[2], superfryta[3]
|
|
)
|
|
self.logger.info("Started personal assistants")
|
|
|
|
async def cog_unload(self):
|
|
self.ai_query_worker.cancel()
|
|
|
|
@commands.hybrid_command(
|
|
name="switch_dm_mode",
|
|
description="Jeśli nie wiesz jak użyć tej komendy to nawet nie próbuj",
|
|
guild=discord.Object(id=664789470779932693),
|
|
)
|
|
async def switch_dm_mode(self, ctx, dm_mode_arg: Dm_Mode):
|
|
async with ctx.channel.typing():
|
|
if isinstance(ctx.channel, discord.DMChannel):
|
|
for superfryta in SPECJALNE_ZIEMNIACZKI.values():
|
|
self.logger.info(ctx.message.author.id)
|
|
self.logger.info(superfryta)
|
|
if ctx.message.author.id == superfryta[0]:
|
|
self.armia[ctx.message.author.id] = dm_mode_arg
|
|
self.logger.info(self.armia)
|
|
await ctx.reply("Weszlo")
|
|
return
|
|
await ctx.reply(
|
|
"Tylko członkowie Armii Hammera mogą używać. Nie jesteś jednym z nich"
|
|
)
|
|
else:
|
|
await ctx.reply("Nope. Nie wiesz jak użyć")
|
|
|
|
@commands.hybrid_command(
|
|
name="modele_ai",
|
|
description="Pokaż modele dostępne dla danego backendu (Ollamę pyta na żywo).",
|
|
)
|
|
async def modele_ai(self, ctx, nazwa_konfigu: Optional[str] = None):
|
|
"""Read-only model listing. For Ollama this queries the server, so it
|
|
shows exactly what is pulled on the box right now."""
|
|
async with ctx.channel.typing():
|
|
target = nazwa_konfigu or ai_functions.get_active_ai_config()
|
|
if target not in ai_functions.list_ai_configs():
|
|
await discord_friendly_reply(
|
|
ctx,
|
|
f"Nie znam configu '{target}'. Dostępne: "
|
|
f"{', '.join(ai_functions.list_ai_configs())}",
|
|
)
|
|
return
|
|
try:
|
|
models = await ai_functions.list_provider_models(target)
|
|
except ai_functions.AIError as exc:
|
|
await discord_friendly_reply(
|
|
ctx, f"Nie mogę pobrać modeli dla '{target}': {exc}"
|
|
)
|
|
return
|
|
if not models:
|
|
await discord_friendly_reply(ctx, f"Brak modeli dla '{target}'.")
|
|
return
|
|
await discord_friendly_reply(
|
|
ctx,
|
|
f"Modele dla **{target}**: {', '.join(models)}\n"
|
|
f"Wepniesz przez `$gadaj_teraz {target} <model>` (tylko Vykidailo).",
|
|
)
|
|
|
|
@commands.hybrid_command(
|
|
name="gadaj_teraz",
|
|
description="Pokaż/przełącz backend AI i model (bez argumentu = status). Przełączanie: Vykidailo.",
|
|
)
|
|
async def gadaj_teraz(
|
|
self, ctx, nazwa_konfigu: Optional[str] = None, model: Optional[str] = None
|
|
):
|
|
async with ctx.channel.typing():
|
|
available = ai_functions.list_ai_configs()
|
|
# No argument -> report the active backend (read-only, open to all).
|
|
if not nazwa_konfigu:
|
|
active = ai_functions.get_active_ai_config()
|
|
active_cfg = ai_functions.AI_CONFIGS.get(active, {})
|
|
await discord_friendly_reply(
|
|
ctx,
|
|
f"Teraz gadam przez **{active}** "
|
|
f"({active_cfg.get('provider')} / {active_cfg.get('latest_model')}). "
|
|
f"Dostępne: {', '.join(available)}. "
|
|
"Przełączysz przez `$gadaj_teraz <config> [model]` (tylko Vykidailo), "
|
|
"modele zobaczysz przez `$modele_ai`.",
|
|
)
|
|
return
|
|
is_admin = isinstance(ctx.author, discord.Member) and any(
|
|
role.name == "Vykidailo" for role in ctx.author.roles
|
|
)
|
|
if not is_admin:
|
|
await discord_friendly_reply(ctx, "Tylko Vykidailo może przełączać AI.")
|
|
return
|
|
if nazwa_konfigu not in available:
|
|
await discord_friendly_reply(
|
|
ctx,
|
|
f"Nie znam configu '{nazwa_konfigu}'. Dostępne: {', '.join(available)}",
|
|
)
|
|
return
|
|
try:
|
|
cfg = ai_functions.set_active_ai_config(nazwa_konfigu)
|
|
except (KeyError, RuntimeError) as exc:
|
|
await discord_friendly_reply(
|
|
ctx, f"Nie mogę przełączyć na '{nazwa_konfigu}': {exc}"
|
|
)
|
|
return
|
|
|
|
# Optional second argument pins the model. For a backend we can
|
|
# enumerate (Ollama), reject an unknown id up front with the list -
|
|
# otherwise the typo only surfaces later as a failed reply.
|
|
if model:
|
|
try:
|
|
known = await ai_functions.list_provider_models(nazwa_konfigu)
|
|
except ai_functions.AIError:
|
|
known = [] # cannot enumerate -> accept verbatim
|
|
if known and cfg.get("provider") == "ollama" and model not in known:
|
|
await discord_friendly_reply(
|
|
ctx,
|
|
f"Model '{model}' nie jest wgrany na Ollamie. "
|
|
f"Dostępne: {', '.join(known)}",
|
|
)
|
|
return
|
|
try:
|
|
cfg = ai_functions.set_active_model(model, nazwa_konfigu)
|
|
except (KeyError, ValueError) as exc:
|
|
await discord_friendly_reply(
|
|
ctx, f"Nie mogę wpiąć modelu '{model}': {exc}"
|
|
)
|
|
return
|
|
|
|
self.logger.info(
|
|
"Przełączono AI na config %s (%s / %s)",
|
|
nazwa_konfigu, cfg.get("provider"), cfg.get("latest_model"),
|
|
)
|
|
message = (
|
|
f"Teraz gadam przez **{nazwa_konfigu}** — "
|
|
f"{cfg.get('provider')} / {cfg.get('latest_model')}."
|
|
)
|
|
# Switched without pinning a model: show what else is on offer.
|
|
if not model:
|
|
try:
|
|
others = await ai_functions.list_provider_models(nazwa_konfigu)
|
|
except ai_functions.AIError:
|
|
others = []
|
|
if len(others) > 1:
|
|
message += (
|
|
f"\nDostępne modele: {', '.join(others)} "
|
|
f"(`$gadaj_teraz {nazwa_konfigu} <model>`)."
|
|
)
|
|
await discord_friendly_reply(ctx, message)
|
|
|
|
@commands.hybrid_command(
|
|
name="armia_hammera",
|
|
description="Jeśli nie wiesz jak użyć tej komendy to nawet nie próbuj",
|
|
)
|
|
async def armia_hammera(
|
|
self, ctx, message_txt: str, recipient: Optional[discord.User]
|
|
):
|
|
for superfryta in SPECJALNE_ZIEMNIACZKI.values():
|
|
if ctx.message.author.id == superfryta[0]:
|
|
await self.armia_hammera_back(ctx, message_txt, recipient)
|
|
return
|
|
await ctx.reply("Tylko członkowie Armii Hammera mogą używać. Nie jesteś jednym z nich")
|
|
|
|
async def armia_hammera_back(self, ctx, message_txt, recipient=None):
|
|
self.logger.info("Armia Hammera")
|
|
recipients = []
|
|
if recipient:
|
|
recipients.append(recipient.id)
|
|
else:
|
|
for superfryta in SPECJALNE_ZIEMNIACZKI.values():
|
|
recipients.append(superfryta[0])
|
|
self.logger.info(recipients)
|
|
for item in recipients:
|
|
user = await self.bot.fetch_user(item)
|
|
channel = await user.create_dm()
|
|
self.logger.info(
|
|
"User %s -> %s: %s", ctx.message.author, user, message_txt
|
|
)
|
|
await discord_friendly_send(channel, message_txt)
|
|
await discord_friendly_reply(ctx, "Poszło")
|
|
|
|
#TODO: NOT IMPLEMENTED YET
|
|
@commands.Cog.listener(name="dodaj_do_bazy_wiedzy")
|
|
async def dodaj_do_bazy_wiedzy(self, ctx):
|
|
for superfryta in SPECJALNE_ZIEMNIACZKI.values():
|
|
if ctx.message.author.id == superfryta[0]:
|
|
#logic here
|
|
return
|
|
await ctx.reply("Tylko członkowie Armii Hammera mogą używać. Nie jesteś jednym z nich")
|
|
|
|
#TODO: NOT IMPLEMENTED YET
|
|
@commands.Cog.listener(name="listuj_baze_wiedzy")
|
|
async def listuj_baze_wiedzy(self, ctx):
|
|
for superfryta in SPECJALNE_ZIEMNIACZKI.values():
|
|
if ctx.message.author.id == superfryta[0]:
|
|
#logic here
|
|
return
|
|
await ctx.reply("Tylko członkowie Armii Hammera mogą używać. Nie jesteś jednym z nich")
|
|
|
|
#TODO: NOT IMPLEMENTED YET
|
|
@commands.Cog.listener(name="usun_z_bazy_wiedzy")
|
|
async def usun_z_bazy_wiedzy(self, ctx):
|
|
for superfryta in SPECJALNE_ZIEMNIACZKI.values():
|
|
if ctx.message.author.id == superfryta[0]:
|
|
#logic here
|
|
return
|
|
await ctx.reply("Tylko członkowie Armii Hammera mogą używać. Nie jesteś jednym z nich")
|
|
|
|
#TODO: NOT IMPLEMENTED YET
|
|
@commands.Cog.listener(name="przetworz_plik_linia_po_linii")
|
|
async def przetworz_plik_linia_po_linii(self, ctx):
|
|
for superfryta in SPECJALNE_ZIEMNIACZKI.values():
|
|
if ctx.message.author.id == superfryta[0]:
|
|
#logic here
|
|
return
|
|
await ctx.reply("Tylko członkowie Armii Hammera mogą używać. Nie jesteś jednym z nich")
|
|
|
|
@commands.Cog.listener()
|
|
async def on_message(self, message):
|
|
"""
|
|
Handle incoming messages in a Discord server, perform various
|
|
checks and actions based on the content and context of the message, and respond accordingly.
|
|
|
|
:param message: The message object that is received when a user sends a message in a Discord server
|
|
or DM. The code is checking various conditions and performing actions based on the content of the
|
|
message and the context in which it was sent. It also includes TODOs for future improvements
|
|
:return: The function `on_message` is being returned.
|
|
"""
|
|
vykidailo = False
|
|
channel = None
|
|
if message.author == self.bot.user:
|
|
return
|
|
|
|
if isinstance(message.author, discord.Member):
|
|
for role in message.author.roles:
|
|
if role.name == "Vykidailo":
|
|
vykidailo = True
|
|
|
|
if ("Conjurer Śpij Słodko Aniołku" in message.content) and vykidailo:
|
|
sys.exit()
|
|
# kanal bez bota
|
|
if message.channel.id == 1095985579147141202:
|
|
return
|
|
# wentylacja
|
|
if message.channel.id == 1083804024173764739:
|
|
return
|
|
# legendy
|
|
if message.channel.id == 1084448332841230388:
|
|
return
|
|
# interrogation booth
|
|
if message.channel.id == 1111625221171052595:
|
|
return
|
|
if isinstance(message.channel, discord.DMChannel):
|
|
self.logger.info(message.author.id)
|
|
for superfryta in SPECJALNE_ZIEMNIACZKI.values():
|
|
self.logger.info(superfryta[0])
|
|
|
|
if message.author.id == superfryta[0]:
|
|
self.logger.info("Specjalny ziemniak")
|
|
if self.armia[message.author.id] == Dm_Mode.SPECJALNY_ZIEMNIACZEK:
|
|
#await self.bot.process_commands(message)
|
|
await ai_functions.chat_with_assistant(message, superfryta[1])
|
|
return
|
|
elif self.armia[message.author.id] == Dm_Mode.ECHO_ECHO:
|
|
await ai_functions.echo(message)
|
|
return
|
|
elif self.armia[message.author.id] == Dm_Mode.ARMIA_HAMMERA:
|
|
self.logger.info("Armia Hammera get context")
|
|
ctx = await self.bot.get_context(message)
|
|
self.logger.info("Armia Hammera function call")
|
|
await self.armia_hammera_back(ctx=ctx, message_txt=message.content)
|
|
return
|
|
elif self.armia[message.author.id] == Dm_Mode.SEKRETNY_SEKSRET:
|
|
pass
|
|
else:
|
|
await discord_friendly_send(message.channel,"Coś się wyebao. Wołaj Hammera")
|
|
return
|
|
channel = self.bot.get_channel(1064888712565100614)
|
|
await discord_friendly_send(channel, "Słyszałem ja żem że: " + message.content)
|
|
return
|
|
channel = message.channel
|
|
message_content_lower = message.content.lower()
|
|
|
|
tdelta = datetime.now() - MASTER_TIMEOUT
|
|
tdelta = tdelta.total_seconds()
|
|
if "opowiedz o fabryczce" in message_content_lower:
|
|
await message.reply(DATA["fabryczka"])
|
|
if "opowiedz mi o fabryczce" in message_content_lower:
|
|
await message.reply(DATA["fabryczka"])
|
|
|
|
if tdelta > INITIAL_TIME_WAIT:
|
|
for word in WORD_REACTIONS:
|
|
if re.search(r"\b" + word + r"\b", message_content_lower):
|
|
tdelta = datetime.now() - WORD_REACTIONS[word][2]
|
|
tdelta = tdelta.total_seconds()
|
|
security_clearance = WORD_REACTIONS[word][4]
|
|
reaction = WORD_REACTIONS[word][3]
|
|
if tdelta > WORD_REACTIONS[word][1]:
|
|
# TODO: to zrobic reactiony
|
|
self.logger.info("Ping z procedury reakcji")
|
|
if reaction:
|
|
emoji = self.bot.get_emoji(WORD_REACTIONS[word][0])
|
|
await message.add_reaction(emoji)
|
|
elif security_clearance and vykidailo:
|
|
await message.reply(WORD_REACTIONS[word][0])
|
|
elif not security_clearance:
|
|
await message.reply(WORD_REACTIONS[word][0])
|
|
WORD_REACTIONS[word][2] = datetime.now()
|
|
|
|
# TODO: drobne literówki, mentiony, spacja przed dwukropkiem. napraw.
|
|
kondziu_mentioned = False
|
|
for mention in message.mentions:
|
|
if mention == self.bot.user:
|
|
kondziu_mentioned = True
|
|
|
|
if kondziu_mentioned or "conjurer:" in message_content_lower:
|
|
async with channel.typing():
|
|
self.logger.info("Procedura chatu")
|
|
|
|
message_content_lower = message_content_lower.replace("conjurer: ", "")
|
|
if message.author.nick:
|
|
username = message.author.nick
|
|
else:
|
|
username = message.author.name
|
|
vykidailo = False
|
|
bartender = False
|
|
if kondziu_mentioned:
|
|
prompt = message.clean_content
|
|
else:
|
|
prompt = message.content
|
|
for role in message.author.roles:
|
|
if role.name == "Vykidailo":
|
|
vykidailo = True
|
|
if role.name == "Bartender":
|
|
bartender = True
|
|
global MESSAGE_TABLE # pylint: disable=global-statement
|
|
|
|
result, MESSAGE_TABLE = await ai_functions.handle_response(
|
|
prompt,
|
|
vykidailo,
|
|
bartender,
|
|
MESSAGE_TABLE,
|
|
username,
|
|
"GENERAL",
|
|
)
|
|
|
|
await discord_friendly_reply(message, result)
|
|
if "imaginuje sobie:" in message.content:
|
|
async with channel.typing():
|
|
self.logger.info("Poczatek procedury obrazkowej")
|
|
# Image generation is DALL-E (OpenAI); there is no Anthropic
|
|
# equivalent, so it stays on OpenAI regardless of the chat
|
|
# backend. Degrade gracefully when OpenAI isn't configured.
|
|
if OPENAICLIENT is None:
|
|
await discord_friendly_reply(
|
|
message,
|
|
"*Kondziu rozkłada łapska* — malowanie obrazków jest teraz wyłączone (brak OpenAI).",
|
|
)
|
|
return
|
|
message_content_lower = message_content_lower.replace("imaginuje sobie: ", "")
|
|
self.logger.debug("Wywolanie obrazka: %s", message_content_lower)
|
|
# Every error branch below must RETURN: otherwise control falls
|
|
# through to `if response:` with `response` unbound (the call
|
|
# raised) -> UnboundLocalError, crashing the handler right after
|
|
# the friendly message was already sent.
|
|
response = None
|
|
try:
|
|
response = await OPENAICLIENT.images.generate(
|
|
model="dall-e-3",
|
|
prompt=message.content,
|
|
size="1024x1024",
|
|
quality="standard",
|
|
n=1,
|
|
)
|
|
except openai.APITimeoutError as e:
|
|
# Handle timeout error, e.g. retry or log
|
|
await discord_friendly_reply(
|
|
message, 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}"
|
|
)
|
|
return
|
|
except openai.APIConnectionError as e:
|
|
await discord_friendly_reply(
|
|
message, 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}"
|
|
)
|
|
return
|
|
except openai.BadRequestError as e:
|
|
# Handle invalid request error, e.g. validate parameters or log
|
|
if message.author.nick:
|
|
username = message.author.nick
|
|
else:
|
|
username = message.author.name
|
|
resp, _ = await ai_functions.handle_response(
|
|
f"Wytlumacz jakie sa zasady dotyczące treści które możesz generować używając Dalle. Wytłumacz błąd {e} prostym językiem. Przeproś za nadmierną cenzurę. Wytłumacz co mogło być nie tak w prompcie '{message.content}'",
|
|
True,
|
|
True,
|
|
MESSAGE_TABLE,
|
|
username,
|
|
"GENERAL",
|
|
)
|
|
await discord_friendly_reply(
|
|
message, f"Sorki, cenzura: {resp}. Jak chcesz to są kanały na nudle #sexy-foteczky i #kanal-do-fapania *Na ekranie pojawia się: {e}"
|
|
)
|
|
return
|
|
except openai.AuthenticationError as e:
|
|
# Handle authentication error, e.g. check credentials or log
|
|
await discord_friendly_reply(
|
|
message, 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}"
|
|
)
|
|
return
|
|
except openai.PermissionDeniedError as e:
|
|
# Handle permission error, e.g. check scope or log
|
|
# (was accidentally passing a (message, text) TUPLE as one arg)
|
|
await discord_friendly_reply(
|
|
message, 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}"
|
|
)
|
|
return
|
|
except openai.RateLimitError as e:
|
|
await discord_friendly_reply(
|
|
message, f"*Kondziu patrzy na terminal* Wołaj szefa. Zapłacić rachunki za AI trzeba. Jak chcesz to się na #zebranie dorzuć. {e}"
|
|
)
|
|
return
|
|
except openai.APIError as e:
|
|
# Handle API error, e.g. retry or log
|
|
await discord_friendly_reply(
|
|
message, f"*Kondziu nurkuje za bar, terminal wybucha. Przed tobą ląduje pergamin zapisany pięknym gotykiem a na nim*: {e}"
|
|
)
|
|
return
|
|
if response:
|
|
self.logger.info(response)
|
|
image_url = response.data[0].url
|
|
image_desc = response.data[0].revised_prompt
|
|
self.logger.debug("Wynikowy obrazek pod url: %s", image_url)
|
|
response = requests.get(image_url, timeout=360)
|
|
temp_file_name = message.content + ".png"
|
|
temp_file_name = GRAPHICS_PATH + message.content + ".png"
|
|
num = 0
|
|
while (Path(temp_file_name)).exists():
|
|
temp_file_name = GRAPHICS_PATH + message.content + str(num) + ".png"
|
|
num += 1
|
|
try:
|
|
with open(temp_file_name, "wb") as dalle_file:
|
|
dalle_file.write(response.content)
|
|
except OSError:
|
|
temp_file_name = "/home/pi/oserror.png"
|
|
with open(temp_file_name, "wb") as dalle_file:
|
|
dalle_file.write(response.content)
|
|
except FileNotFoundError:
|
|
temp_file_name = "/home/pi/fnferror.png"
|
|
with open(temp_file_name, "wb") as dalle_file:
|
|
dalle_file.write(response.content)
|
|
except Exception as e:
|
|
self.logger.error("Nieznany błąd: %s", e)
|
|
temp_file_name = "/home/pi/error.png"
|
|
with open(temp_file_name, "wb") as dalle_file:
|
|
dalle_file.write(response.content)
|
|
finally:
|
|
self.logger.info("Koniec procedury obrazkowej.")
|
|
fnord = discord.File(
|
|
temp_file_name, spoiler=False, description=message.content
|
|
)
|
|
|
|
#await message.reply(f"{image_desc}", file=fnord)
|
|
await discord_friendly_reply(message,f"{image_desc}", file = fnord)
|
|
# *=========================================== Define Functions
|
|
|
|
|
|
async def setup(bot):
|
|
logger = logging.getLogger("discord")
|
|
await bot.add_cog(Events(bot))
|
|
logger.info("Loading ai events module done")
|