diff options
| author | salmonik <raisersoft@proton.me> | 2026-05-21 17:10:23 +0200 |
|---|---|---|
| committer | salmonik <raisersoft@proton.me> | 2026-05-21 17:10:23 +0200 |
| commit | 2e614665e0c62fe572b5af53a2a6be1116d66720 (patch) | |
| tree | dbb48ad5d665a3f3d8c793db30a523264f17bcde /cogs/assistant.py | |
| parent | fix naming not resolving things like %20 (diff) | |
| parent | Fix voice reconnect race: reuse VoiceClient instead of destroying it (diff) | |
| download | senator-sperma-2e614665e0c62fe572b5af53a2a6be1116d66720.tar.gz senator-sperma-2e614665e0c62fe572b5af53a2a6be1116d66720.zip | |
Merge pull request 'Voice related fixes, alongside simplifying assistant' (#5) from dev into main
Reviewed-on: https://codeberg.org/salmonik/senator-sperma/pulls/5
Diffstat (limited to '')
| -rw-r--r-- | cogs/assistant.py | 112 |
1 files changed, 20 insertions, 92 deletions
diff --git a/cogs/assistant.py b/cogs/assistant.py index a91d027..fed7825 100644 --- a/cogs/assistant.py +++ b/cogs/assistant.py | |||
| @@ -3,16 +3,11 @@ | |||
| 3 | # Copyright (C) 2026 Salmonik @ salmonik@tuta.io | 3 | # Copyright (C) 2026 Salmonik @ salmonik@tuta.io |
| 4 | # https://codeberg.org/salmonik/senator-sperma | 4 | # https://codeberg.org/salmonik/senator-sperma |
| 5 | 5 | ||
| 6 | import re | ||
| 7 | import discord | 6 | import discord |
| 8 | from discord.ext import commands | 7 | from discord.ext import commands |
| 9 | import httpx | 8 | import httpx |
| 10 | import json | ||
| 11 | import logging | 9 | import logging |
| 12 | import shutil | ||
| 13 | import os | 10 | import os |
| 14 | from tempfile import NamedTemporaryFile | ||
| 15 | from cogs.downloader import Downloader | ||
| 16 | 11 | ||
| 17 | logger = logging.getLogger(__name__) | 12 | logger = logging.getLogger(__name__) |
| 18 | 13 | ||
| @@ -46,15 +41,16 @@ class Jarvis(commands.Cog): | |||
| 46 | messages = Jarvis._build_messages(message, chain) | 41 | messages = Jarvis._build_messages(message, chain) |
| 47 | payload = Jarvis._build_payload(messages) | 42 | payload = Jarvis._build_payload(messages) |
| 48 | 43 | ||
| 49 | response_data = await Jarvis._make_api_request(payload, headers) | 44 | response_data = await Jarvis._safe_api_call(payload, headers) |
| 50 | response_message, action = Jarvis._parse_response(response_data) | 45 | if response_data is None: |
| 46 | await message.reply("Przepraszam, nie mogę teraz odpowiedzieć. Spróbuj ponownie później.") | ||
| 47 | return | ||
| 48 | |||
| 49 | response_message = Jarvis._parse_response(response_data) | ||
| 51 | 50 | ||
| 52 | channel = message.channel | 51 | channel = message.channel |
| 53 | await message.reply(response_message) | 52 | await message.reply(response_message) |
| 54 | 53 | ||
| 55 | if action == "download": | ||
| 56 | await Jarvis._handle_download_action(message, channel) | ||
| 57 | |||
| 58 | @staticmethod | 54 | @staticmethod |
| 59 | async def _build_message_chain(message): | 55 | async def _build_message_chain(message): |
| 60 | chain = [] | 56 | chain = [] |
| @@ -80,10 +76,10 @@ class Jarvis(commands.Cog): | |||
| 80 | messages = [{ | 76 | messages = [{ |
| 81 | "role": "system", | 77 | "role": "system", |
| 82 | "content": "Jesteś Senator Sperma Armostrong, także znany jako Jarvis." | 78 | "content": "Jesteś Senator Sperma Armostrong, także znany jako Jarvis." |
| 83 | "Twoim zadaniem, po za asystowaniem użytkownika, jest mówienie programowi nadrzędnemu jak sie zachować" | 79 | "Twoim zadaniem jest asystowanie użytkownika" |
| 84 | "pole 'message' może być tylko wypełnione w języku Polskim." | ||
| 85 | "zachowuj sie jak Jarvis, bądź osobistym asystentem użytkownika, nie używaj emotikon" | 80 | "zachowuj sie jak Jarvis, bądź osobistym asystentem użytkownika, nie używaj emotikon" |
| 86 | "nie pytaj sie użytkownika czy chcesz abyś coś zrobił, użytkownik ci sam powie abyś coś wykonał" | 81 | "nie pytaj sie użytkownika czy chce abyś coś zrobił, użytkownik ci sam powie abyś coś wykonał" |
| 82 | "Nie pozostawiaj wiadomości pustej oraz nie dawaj swojej wiadomości w pole myślenia" | ||
| 87 | }] | 83 | }] |
| 88 | 84 | ||
| 89 | if chain is None: | 85 | if chain is None: |
| @@ -92,7 +88,7 @@ class Jarvis(commands.Cog): | |||
| 92 | for msg in chain: | 88 | for msg in chain: |
| 93 | messages.append({ | 89 | messages.append({ |
| 94 | "role": "user", | 90 | "role": "user", |
| 95 | "content": f"{msg.author.display_name}: {msg.content}" | 91 | "content": f"USER: {msg.author.display_name}\nMESSAGE: {msg.content}" |
| 96 | }) | 92 | }) |
| 97 | 93 | ||
| 98 | return messages | 94 | return messages |
| @@ -102,38 +98,12 @@ class Jarvis(commands.Cog): | |||
| 102 | return { | 98 | return { |
| 103 | "model": os.getenv("OPENROUTER_MODEL"), | 99 | "model": os.getenv("OPENROUTER_MODEL"), |
| 104 | "messages": messages, | 100 | "messages": messages, |
| 105 | "response_format": Jarvis._get_response_schema(), | ||
| 106 | "plugins": [{"id": "response-healing"}] | 101 | "plugins": [{"id": "response-healing"}] |
| 107 | } | 102 | } |
| 108 | 103 | ||
| 109 | @staticmethod | 104 | @staticmethod |
| 110 | def _get_response_schema(): | ||
| 111 | return { | ||
| 112 | "type": "json_schema", | ||
| 113 | "json_schema": { | ||
| 114 | "name": "response", | ||
| 115 | "strict": True, | ||
| 116 | "schema": { | ||
| 117 | "type": "object", | ||
| 118 | "properties": { | ||
| 119 | "message": { | ||
| 120 | "type": "string", | ||
| 121 | "description": "the message that user will receive", | ||
| 122 | }, | ||
| 123 | "action": { | ||
| 124 | "type": "string", | ||
| 125 | "description": "the action to do, either download, compress or none" | ||
| 126 | } | ||
| 127 | }, | ||
| 128 | "required": ["message", "action"], | ||
| 129 | "additionalProperties": False, | ||
| 130 | } | ||
| 131 | } | ||
| 132 | } | ||
| 133 | |||
| 134 | @staticmethod | ||
| 135 | async def _make_api_request(payload, headers): | 105 | async def _make_api_request(payload, headers): |
| 136 | async with httpx.AsyncClient() as client: | 106 | async with httpx.AsyncClient(timeout=30.0) as client: |
| 137 | url = "https://openrouter.ai/api/v1/chat/completions" | 107 | url = "https://openrouter.ai/api/v1/chat/completions" |
| 138 | response = await client.post(url=url, headers=headers, json=payload) | 108 | response = await client.post(url=url, headers=headers, json=payload) |
| 139 | response_data = response.json() | 109 | response_data = response.json() |
| @@ -141,62 +111,20 @@ class Jarvis(commands.Cog): | |||
| 141 | return response_data | 111 | return response_data |
| 142 | 112 | ||
| 143 | @staticmethod | 113 | @staticmethod |
| 144 | def _parse_response(response_data): | 114 | async def _safe_api_call(payload, headers): |
| 145 | content = response_data['choices'][0]['message']['content'] | ||
| 146 | inner_json_str = content.strip('```json\n').strip('```').strip() | ||
| 147 | try: | 115 | try: |
| 148 | inner_data = json.loads(inner_json_str) | 116 | return await Jarvis._make_api_request(payload, headers) |
| 149 | response_message = inner_data['message'] | 117 | except httpx.HTTPError as e: |
| 150 | action = inner_data['action'] | 118 | logger.error(f"API request failed: {e}") |
| 151 | except (json.JSONDecodeError, KeyError): | 119 | return None |
| 152 | response_message = content | ||
| 153 | action = "none" | ||
| 154 | |||
| 155 | logger.info(response_message) | ||
| 156 | logger.info(action) | ||
| 157 | |||
| 158 | return response_message, action | ||
| 159 | 120 | ||
| 160 | @staticmethod | 121 | @staticmethod |
| 161 | async def _handle_download_action(message, channel): | 122 | def _parse_response(response_data): |
| 162 | url_match = Jarvis._extract_url(message.content) | 123 | content = response_data['choices'][0]['message']['content'] |
| 163 | if not url_match: | ||
| 164 | await channel.send("Nie znaleziono poprawnego linku w odpowiedzi.") | ||
| 165 | return | ||
| 166 | |||
| 167 | response = await Downloader.download_standalone(url=url_match) | ||
| 168 | await Jarvis._process_download_response(response, channel) | ||
| 169 | |||
| 170 | @staticmethod | ||
| 171 | def _extract_url(content): | ||
| 172 | match = re.search(r'https://(?:[A-Za-z0-9-]+\.)*[A-Za-z0-9-]{1,63}\.[A-Za-z]{2,63}/\S*', content) | ||
| 173 | return match.group(0) if match else None | ||
| 174 | |||
| 175 | @staticmethod | ||
| 176 | async def _process_download_response(response, channel): | ||
| 177 | if not isinstance(response, dict): | ||
| 178 | return | ||
| 179 | 124 | ||
| 180 | if response["status"] == "success": | 125 | logger.info(content) |
| 181 | await Jarvis._handle_successful_download(response, channel) | ||
| 182 | else: | ||
| 183 | await Jarvis._handle_failed_download(response) | ||
| 184 | 126 | ||
| 185 | @staticmethod | 127 | return content |
| 186 | async def _handle_successful_download(response, channel): | ||
| 187 | if response["type"] == "url": | ||
| 188 | await channel.send(f"pobioron i wysyłon: {response['content']}") | ||
| 189 | elif response["type"] == "file": | ||
| 190 | file_data = response["content"] | ||
| 191 | await channel.send("wysyłon:", file=discord.File(file_data)) | ||
| 192 | shutil.rmtree(response["temp_dir"]) | ||
| 193 | |||
| 194 | @staticmethod | ||
| 195 | async def _handle_failed_download(response): | ||
| 196 | logger.error(f"Error: {response['content']}") | ||
| 197 | if 'details' in response: | ||
| 198 | logger.error(f"Details: {response['details']}") | ||
| 199 | shutil.rmtree(response["temp_dir"]) | ||
| 200 | 128 | ||
| 201 | def setup(bot): | 129 | def setup(bot): |
| 202 | bot.add_cog(Jarvis(bot)) | 130 | bot.add_cog(Jarvis(bot)) |
