Trololindo commited on
Commit
7f03076
·
2 Parent(s): c56dba2a4eee2b

Merge branch 'main' into hackaton-delenda-est

Browse files
backend/app/api/routes.py CHANGED
@@ -16,7 +16,7 @@ from app.services.image_analyzer import analyze_image
16
  from app.core.config import get_settings
17
  from app.utils.exceptions import DeepfakeDetectionError, SetupRequiredError
18
  from app.core.limiter import limiter
19
- from app.config_manager import save_guild_config
20
 
21
  logger = logging.getLogger(__name__)
22
 
@@ -67,6 +67,7 @@ async def save_discord_guild_setup(guild_id: str, payload: GuildConfigSchema):
67
  # Walidacja modeli z pliku ustawień
68
  settings = get_settings()
69
  allowed_text_models = settings.AVAILABLE_MODELS.get("text", [])
 
70
 
71
  # Walidujemy tylko wtedy, gdy model nie jest ustawiony na "none"
72
  if payload.active_text_model and payload.active_text_model.lower() != "none":
@@ -76,6 +77,13 @@ async def save_discord_guild_setup(guild_id: str, payload: GuildConfigSchema):
76
  detail=f"Model '{payload.active_text_model}' nie jest dozwolony. Wybierz z: {allowed_text_models}"
77
  )
78
 
 
 
 
 
 
 
 
79
  # Zapis konfiguracji przez config_manager
80
  config_dict = payload.dict()
81
  save_guild_config(guild_id, config_dict)
@@ -87,6 +95,18 @@ async def save_discord_guild_setup(guild_id: str, payload: GuildConfigSchema):
87
  "config": config_dict
88
  }
89
 
 
 
 
 
 
 
 
 
 
 
 
 
90
  @router.post(
91
  "/analyze",
92
  response_model=AnalysisResponse,
@@ -102,6 +122,8 @@ async def save_discord_guild_setup(guild_id: str, payload: GuildConfigSchema):
102
  )
103
  @limiter.limit("1/5seconds")
104
  async def analyze(request: Request, payload: AnalysisRequest) -> AnalysisResponse:
 
 
105
  if isinstance(payload, TextAnalysisRequest):
106
  content_type = "text"
107
  elif isinstance(payload, ImageAnalysisRequest):
@@ -113,12 +135,6 @@ async def analyze(request: Request, payload: AnalysisRequest) -> AnalysisRespons
113
  )
114
 
115
  settings = get_settings()
116
- models = settings.AVAILABLE_MODELS.get(content_type)
117
- if not models:
118
- raise HTTPException(status_code=400, detail=f"No model available for {content_type} analysis")
119
-
120
- model = models[0]
121
- logger.info(f"Received {content_type} analysis request, model: {model}")
122
 
123
  try:
124
  if content_type == "text":
@@ -127,7 +143,7 @@ async def analyze(request: Request, payload: AnalysisRequest) -> AnalysisRespons
127
  if len(payload.text) < 50:
128
  raise ValueError("Text content must be at least 50 characters")
129
 
130
- analysis_result = await analyze_text(payload.text)
131
 
132
  elif content_type == "image":
133
  image_bytes = await download_file(str(payload.image_url))
@@ -136,10 +152,12 @@ async def analyze(request: Request, payload: AnalysisRequest) -> AnalysisRespons
136
  if len(image_bytes) > settings.MAX_CONTENT_SIZES["image"]:
137
  raise ValueError(f"Image size exceeds maximum of {settings.MAX_CONTENT_SIZES['image']} bytes")
138
 
139
- analysis_result = await analyze_image(image_bytes)
140
 
141
  except ValueError as e:
142
  raise HTTPException(status_code=400, detail=str(e))
 
 
143
  except DeepfakeDetectionError as e:
144
  raise HTTPException(status_code=e.status_code, detail=e.message)
145
  except Exception as e:
@@ -147,12 +165,13 @@ async def analyze(request: Request, payload: AnalysisRequest) -> AnalysisRespons
147
  raise HTTPException(status_code=500, detail=f"Failed to analyze {content_type}")
148
 
149
  logger.info(f"{content_type.capitalize()} analysis completed. Result: {analysis_result}")
 
150
 
151
  return AnalysisResponse(
152
  is_deepfake=analysis_result["is_deepfake"],
153
  confidence=analysis_result["confidence"],
154
  analysis_time=analysis_result["analysis_time"],
155
- used_model=model,
156
  content_type=content_type,
157
  )
158
 
 
16
  from app.core.config import get_settings
17
  from app.utils.exceptions import DeepfakeDetectionError, SetupRequiredError
18
  from app.core.limiter import limiter
19
+ from app.config_manager import _load_all_configs, save_guild_config
20
 
21
  logger = logging.getLogger(__name__)
22
 
 
67
  # Walidacja modeli z pliku ustawień
68
  settings = get_settings()
69
  allowed_text_models = settings.AVAILABLE_MODELS.get("text", [])
70
+ allowed_image_models = settings.AVAILABLE_MODELS.get("image", [])
71
 
72
  # Walidujemy tylko wtedy, gdy model nie jest ustawiony na "none"
73
  if payload.active_text_model and payload.active_text_model.lower() != "none":
 
77
  detail=f"Model '{payload.active_text_model}' nie jest dozwolony. Wybierz z: {allowed_text_models}"
78
  )
79
 
80
+ if payload.active_image_model and payload.active_image_model.lower() != "none":
81
+ if payload.active_image_model not in allowed_image_models:
82
+ raise HTTPException(
83
+ status_code=400,
84
+ detail=f"Model '{payload.active_image_model}' nie jest dozwolony. Wybierz z: {allowed_image_models}"
85
+ )
86
+
87
  # Zapis konfiguracji przez config_manager
88
  config_dict = payload.dict()
89
  save_guild_config(guild_id, config_dict)
 
95
  "config": config_dict
96
  }
97
 
98
+ @router.get("/guilds/{guild_id}/config", tags=["Setup"])
99
+ async def get_discord_guild_config(guild_id: str):
100
+ """Zwraca zapisaną konfigurację dla konkretnego serwera Discord."""
101
+ configs = _load_all_configs()
102
+ guild_config = configs.get(guild_id, {})
103
+
104
+ return {
105
+ "active_text_model": guild_config.get("active_text_model", "none"),
106
+ "active_image_model": guild_config.get("active_image_model", "none"),
107
+ "log_channel_id": guild_config.get("log_channel_id", None)
108
+ }
109
+
110
  @router.post(
111
  "/analyze",
112
  response_model=AnalysisResponse,
 
122
  )
123
  @limiter.limit("1/5seconds")
124
  async def analyze(request: Request, payload: AnalysisRequest) -> AnalysisResponse:
125
+ guild_id = payload.guild_id
126
+
127
  if isinstance(payload, TextAnalysisRequest):
128
  content_type = "text"
129
  elif isinstance(payload, ImageAnalysisRequest):
 
135
  )
136
 
137
  settings = get_settings()
 
 
 
 
 
 
138
 
139
  try:
140
  if content_type == "text":
 
143
  if len(payload.text) < 50:
144
  raise ValueError("Text content must be at least 50 characters")
145
 
146
+ analysis_result = await analyze_text(payload.text, guild_id)
147
 
148
  elif content_type == "image":
149
  image_bytes = await download_file(str(payload.image_url))
 
152
  if len(image_bytes) > settings.MAX_CONTENT_SIZES["image"]:
153
  raise ValueError(f"Image size exceeds maximum of {settings.MAX_CONTENT_SIZES['image']} bytes")
154
 
155
+ analysis_result = await analyze_image(image_bytes, guild_id)
156
 
157
  except ValueError as e:
158
  raise HTTPException(status_code=400, detail=str(e))
159
+ except SetupRequiredError as e:
160
+ raise HTTPException(status_code=400, detail=str(e))
161
  except DeepfakeDetectionError as e:
162
  raise HTTPException(status_code=e.status_code, detail=e.message)
163
  except Exception as e:
 
165
  raise HTTPException(status_code=500, detail=f"Failed to analyze {content_type}")
166
 
167
  logger.info(f"{content_type.capitalize()} analysis completed. Result: {analysis_result}")
168
+ used_model = analysis_result.get("used_model", settings.AVAILABLE_MODELS.get(content_type)[0])
169
 
170
  return AnalysisResponse(
171
  is_deepfake=analysis_result["is_deepfake"],
172
  confidence=analysis_result["confidence"],
173
  analysis_time=analysis_result["analysis_time"],
174
+ used_model=used_model,
175
  content_type=content_type,
176
  )
177
 
backend/app/config_manager.py CHANGED
@@ -39,6 +39,16 @@ def get_active_text_model(guild_id: str) -> Optional[str]:
39
  guild_config = configs.get(guild_id, {})
40
  model = guild_config.get("active_text_model", "none")
41
 
 
 
 
 
 
 
 
 
 
 
42
  if not model or model.lower() == "none":
43
  return None
44
  return model
 
39
  guild_config = configs.get(guild_id, {})
40
  model = guild_config.get("active_text_model", "none")
41
 
42
+ if not model or model.lower() == "none":
43
+ return None
44
+ return model
45
+
46
+ def get_active_image_model(guild_id: str) -> Optional[str]:
47
+ """Zwraca aktywny model obrazu dla serwera. Jeśli brak konfiguracji, zwraca None."""
48
+ configs = _load_all_configs()
49
+ guild_config = configs.get(guild_id, {})
50
+ model = guild_config.get("active_image_model", "none")
51
+
52
  if not model or model.lower() == "none":
53
  return None
54
  return model
backend/app/models/schemas.py CHANGED
@@ -5,6 +5,7 @@ from typing import Union, Literal, Optional, Dict, List
5
  class TextAnalysisRequest(BaseModel):
6
  content_type: Literal["text"]
7
  text: str = Field(..., description="Text content to analyze for deepfake detection")
 
8
 
9
  class Config:
10
  json_schema_extra = {
@@ -18,6 +19,7 @@ class TextAnalysisRequest(BaseModel):
18
  class ImageAnalysisRequest(BaseModel):
19
  content_type: Literal["image"]
20
  image_url: HttpUrl = Field(..., description="URL of the image to analyze")
 
21
 
22
  class Config:
23
  json_schema_extra = {
@@ -28,37 +30,10 @@ class ImageAnalysisRequest(BaseModel):
28
  }
29
 
30
 
31
- class VideoAnalysisRequest(BaseModel):
32
- content_type: Literal["video"]
33
- video_url: HttpUrl = Field(..., description="URL of the video to analyze")
34
-
35
- class Config:
36
- json_schema_extra = {
37
- "example": {
38
- "content_type": "video",
39
- "video_url": "https://example.com/video.mp4"
40
- }
41
- }
42
-
43
-
44
- class FileAnalysisRequest(BaseModel):
45
- content_type: Literal["file"]
46
- file_url: HttpUrl = Field(..., description="URL of the file to analyze")
47
-
48
- class Config:
49
- json_schema_extra = {
50
- "example": {
51
- "content_type": "file",
52
- "file_url": "https://example.com/video.mp4"
53
- }
54
- }
55
-
56
 
57
  AnalysisRequest = Union[
58
  TextAnalysisRequest,
59
- ImageAnalysisRequest,
60
- VideoAnalysisRequest,
61
- FileAnalysisRequest,
62
  ]
63
 
64
 
@@ -112,5 +87,5 @@ class HealthResponse(BaseModel):
112
 
113
  class GuildConfigSchema(BaseModel):
114
  active_text_model: Optional[str] = "none"
115
- # Tutaj możesz dodać inne parametry, które bot zbiera w sesji setup (np. log_channel_id)
116
  log_channel_id: Optional[str] = None
 
5
  class TextAnalysisRequest(BaseModel):
6
  content_type: Literal["text"]
7
  text: str = Field(..., description="Text content to analyze for deepfake detection")
8
+ guild_id: str = Field(..., description="ID serwera Discord, z którego pochodzi żądanie")
9
 
10
  class Config:
11
  json_schema_extra = {
 
19
  class ImageAnalysisRequest(BaseModel):
20
  content_type: Literal["image"]
21
  image_url: HttpUrl = Field(..., description="URL of the image to analyze")
22
+ guild_id: str = Field(..., description="ID serwera Discord, z którego pochodzi żądanie")
23
 
24
  class Config:
25
  json_schema_extra = {
 
30
  }
31
 
32
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
 
34
  AnalysisRequest = Union[
35
  TextAnalysisRequest,
36
+ ImageAnalysisRequest
 
 
37
  ]
38
 
39
 
 
87
 
88
  class GuildConfigSchema(BaseModel):
89
  active_text_model: Optional[str] = "none"
90
+ active_image_model: Optional[str] = "none"
91
  log_channel_id: Optional[str] = None
backend/app/services/image_analyzer.py CHANGED
@@ -1,30 +1,60 @@
1
  import io
2
  import logging
3
  import time
 
4
  from typing import Dict, Any
5
  from PIL import Image
6
  from transformers import pipeline
7
 
 
 
 
 
8
  logger = logging.getLogger(__name__)
9
 
 
 
10
  _image_classifier = None
11
 
12
- def _load_model():
13
- global _image_classifier
14
- if _image_classifier is None:
15
- logger.info("Loading capcheck/ai-image-detection model...")
16
- _image_classifier = pipeline(
17
- "image-classification",
18
- model="capcheck/ai-image-detection",
19
- device=-1
20
- )
21
- logger.info("Image detector model loaded successfully")
 
 
 
 
 
 
 
 
 
 
 
 
22
  return _image_classifier
23
 
24
- async def analyze_image(image_bytes: bytes) -> Dict[str, Any]:
25
  start_time = time.time()
26
 
27
- logger.info(f"Starting image analysis, size: {len(image_bytes)} bytes")
 
 
 
 
 
 
 
 
 
 
 
28
 
29
  try:
30
  image = Image.open(io.BytesIO(image_bytes)).convert("RGB")
@@ -32,14 +62,15 @@ async def analyze_image(image_bytes: bytes) -> Dict[str, Any]:
32
  logger.error(f"Failed to parse image bytes: {str(e)}")
33
  raise ValueError("Invalid image format or corrupted bytes") from e
34
 
35
- classifier = _load_model()
36
-
37
  result = classifier(image)
38
 
39
  label = result[0]["label"]
40
  score = result[0]["score"]
41
 
42
- is_deepfake = label.lower() == "fake"
 
43
  confidence = score
44
 
45
  analysis_time = time.time() - start_time
@@ -48,6 +79,7 @@ async def analyze_image(image_bytes: bytes) -> Dict[str, Any]:
48
  "is_deepfake": is_deepfake,
49
  "confidence": round(confidence, 3),
50
  "analysis_time": round(analysis_time, 3),
 
51
  }
52
 
53
  logger.info(f"Image analysis completed. Result: {response}")
 
1
  import io
2
  import logging
3
  import time
4
+ import gc
5
  from typing import Dict, Any
6
  from PIL import Image
7
  from transformers import pipeline
8
 
9
+ # Importujemy helper do konfiguracji oraz wyjątek braku konfiguracji
10
+ from app.config_manager import get_active_image_model
11
+ from app.utils.exceptions import SetupRequiredError
12
+
13
  logger = logging.getLogger(__name__)
14
 
15
+ # Przechowujemy referencje do aktualnie załadowanego modelu obrazów
16
+ _loaded_model_name = None
17
  _image_classifier = None
18
 
19
+ def _load_model(target_model_name: str):
20
+ global _image_classifier, _loaded_model_name
21
+
22
+ # Jeśli model o tej nazwie jest już załadowany w pamięci, używamy go ponownie
23
+ if _image_classifier is not None and _loaded_model_name == target_model_name:
24
+ return _image_classifier
25
+
26
+ logger.info(f"Wymagana zmiana modelu obrazu. Obecny w RAM: {_loaded_model_name}, Nowy: {target_model_name}")
27
+
28
+ # Czyszczenie pamięci po poprzednim modelu obrazów
29
+ _image_classifier = None
30
+ gc.collect()
31
+
32
+ logger.info(f"Ładowanie modelu image detector: {target_model_name}...")
33
+ _image_classifier = pipeline(
34
+ "image-classification",
35
+ model=target_model_name,
36
+ device=-1 # -1 oznacza CPU
37
+ )
38
+ _loaded_model_name = target_model_name
39
+ logger.info(f"Model {target_model_name} został pomyślnie załadowany.")
40
+
41
  return _image_classifier
42
 
43
+ async def analyze_image(image_bytes: bytes, guild_id: str) -> Dict[str, Any]:
44
  start_time = time.time()
45
 
46
+ # 1. Sprawdzamy konfigurację modelu dla danego serwera Discord
47
+ active_model = get_active_image_model(guild_id)
48
+
49
+ # BLOKADA: Jeżeli model to 'none' lub brak konfiguracji, natychmiast przerywamy i zgłaszamy błąd
50
+ if not active_model:
51
+ logger.warning(f"Zablokowano zapytanie! Serwer {guild_id} nie ma skonfigurowanego modelu dla obrazów.")
52
+ raise SetupRequiredError(
53
+ f"Serwer o ID '{guild_id}' nie został jeszcze skonfigurowany pod kątem analizy obrazów. "
54
+ "Użyj komendy setup na Discordzie przed wykonaniem analizy."
55
+ )
56
+
57
+ logger.info(f"Starting image analysis for guild: {guild_id}, model: {active_model}, size: {len(image_bytes)} bytes")
58
 
59
  try:
60
  image = Image.open(io.BytesIO(image_bytes)).convert("RGB")
 
62
  logger.error(f"Failed to parse image bytes: {str(e)}")
63
  raise ValueError("Invalid image format or corrupted bytes") from e
64
 
65
+ # 2. Dynamicznie pobieramy/ładujemy wskazany model
66
+ classifier = _load_model(active_model)
67
  result = classifier(image)
68
 
69
  label = result[0]["label"]
70
  score = result[0]["score"]
71
 
72
+ # Dostosowanie do najczęstszych etykiet fałszywych obrazów (np. "fake", "ai", "synthetic")
73
+ is_deepfake = label.lower() in ["fake", "ai", "synthetic", "label_1"]
74
  confidence = score
75
 
76
  analysis_time = time.time() - start_time
 
79
  "is_deepfake": is_deepfake,
80
  "confidence": round(confidence, 3),
81
  "analysis_time": round(analysis_time, 3),
82
+ "used_model": active_model,
83
  }
84
 
85
  logger.info(f"Image analysis completed. Result: {response}")
backend/guild_configs.json ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ {
2
+ "1515307986963267595": {
3
+ "active_text_model": "almanach/xlmr-chatgptdetect-noisy",
4
+ "active_image_model": "capcheck/ai-image-detection",
5
+ "log_channel_id": "1515373138937123007"
6
+ }
7
+ }
configManager.js DELETED
@@ -1,44 +0,0 @@
1
- import fs from "fs";
2
- import path from "path";
3
-
4
- const filePath = path.resolve("./guildConfigs.json");
5
-
6
- // Domyślne ustawienia (teraz modele są zapisywane dynamicznie w obiekcie)
7
- export const DEFAULT_CONFIG = {
8
- logChannelId: null,
9
- models: {}
10
- };
11
-
12
- export function loadConfig(guildId) {
13
- if (!fs.existsSync(filePath)) {
14
- fs.writeFileSync(filePath, JSON.stringify({}));
15
- }
16
- try {
17
- const data = JSON.parse(fs.readFileSync(filePath, "utf-8"));
18
- const config = data[guildId] || { ...DEFAULT_CONFIG };
19
-
20
- // Upewniamy się, że obiekt "models" zawsze istnieje
21
- if (!config.models) {
22
- config.models = {};
23
- }
24
- return config;
25
- } catch (err) {
26
- console.error("Błąd podczas odczytu konfiguracji:", err);
27
- return { ...DEFAULT_CONFIG };
28
- }
29
- }
30
-
31
- export function saveConfig(guildId, newConfig) {
32
- if (!fs.existsSync(filePath)) {
33
- fs.writeFileSync(filePath, JSON.stringify({}));
34
- }
35
- try {
36
- const data = JSON.parse(fs.readFileSync(filePath, "utf-8"));
37
- data[guildId] = { ...DEFAULT_CONFIG, ...data[guildId], ...newConfig };
38
- fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
39
- return true;
40
- } catch (err) {
41
- console.error("Błąd podczas zapisu konfiguracji:", err);
42
- return false;
43
- }
44
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
index.js CHANGED
@@ -20,8 +20,6 @@ import {
20
  ChannelType
21
  } from "discord.js";
22
 
23
- import { loadConfig, saveConfig } from "./configManager.js";
24
-
25
  const client = new Client({
26
  intents: [
27
  GatewayIntentBits.Guilds,
@@ -81,6 +79,28 @@ async function fetchAvailableModels() {
81
  return null;
82
  }
83
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84
  function preparePayload(input, explicitContentType = null) {
85
  const trimmed = input.trim();
86
 
@@ -198,7 +218,7 @@ function generateSetupView(tempConfig, availableModels) {
198
  }
199
 
200
  async function sendLogToDiscord(guild, embedToSend) {
201
- const config = loadConfig(guild.id);
202
  if (!config.logChannelId) return;
203
 
204
  try {
@@ -214,16 +234,9 @@ async function sendLogToDiscord(guild, embedToSend) {
214
  async function handleAnalysis(interaction, userContent, targetMessage = null, explicitContentType = null) {
215
  await interaction.deferReply({ flags: [MessageFlags.Ephemeral] });
216
 
217
- const serverConfig = loadConfig(interaction.guildId);
218
-
219
  try {
220
  const { type, payload } = preparePayload(userContent, explicitContentType);
221
-
222
- // DYNAMICZNE POBIERANIE MODELU Z PLIKU KONFIGURACYJNEGO DLA DANEGO FORMATU (np. text, image, video)
223
- const chosenModel = serverConfig.models[type];
224
- if (chosenModel) {
225
- payload.model = chosenModel;
226
- }
227
 
228
  console.log(`Wysyłanie zapytania typu: ${type} do API z modelem: ${payload.model || "domyślny"}...`);
229
 
@@ -335,28 +348,25 @@ client.on(Events.InteractionCreate, async (interaction) => {
335
 
336
  if (interaction.commandName === "setup") {
337
  const guildId = interaction.guildId;
338
- const currentConfig = loadConfig(guildId);
339
-
340
  await interaction.deferReply({ flags: [MessageFlags.Ephemeral] });
341
 
342
- // Pobieramy aktywne modele bezpośrednio z FastAPI
 
343
  const availableModels = await fetchAvailableModels();
344
 
345
- // Jeśli backend nie działa, natychmiast przerywamy i wyświetlamy błąd
346
  if (!availableModels || Object.keys(availableModels).length === 0) {
347
  return interaction.editReply({
348
  content: "❌ **Błąd konfiguracji:** Nie udało się nawiązać połączenia z backendem (FastAPI). Uruchom swój backend w Pythonie i spróbuj ponownie!"
349
  });
350
  }
351
 
352
- // Inicjalizujemy domyślne modele w konfiguracji, jeśli nie były wcześniej ustawione
353
  for (const [contentType, models] of Object.entries(availableModels)) {
354
  if (!currentConfig.models[contentType] && models.length > 0) {
355
  currentConfig.models[contentType] = models[0];
356
  }
357
  }
358
 
359
- // Zapisujemy sesję z konfiguracją oraz pobranymi modelami
360
  activeSetupSessions.set(guildId, {
361
  config: { ...currentConfig },
362
  availableModels
@@ -447,14 +457,15 @@ client.on(Events.InteractionCreate, async (interaction) => {
447
  const tempSession = activeSetupSessions.get(guildId);
448
  if (tempSession) {
449
  try {
450
- const response = await fetch(`http://backend-api-url/guilds/${guildId}/setup`, {
451
  method: "POST",
452
  headers: {
453
  "Content-Type": "application/json"
454
  },
455
  body: JSON.stringify({
456
- active_text_model: tempSession.config.active_text_model || "none",
457
- log_channel_id: tempSession.config.log_channel_id || null
 
458
  })
459
  });
460
 
 
20
  ChannelType
21
  } from "discord.js";
22
 
 
 
23
  const client = new Client({
24
  intents: [
25
  GatewayIntentBits.Guilds,
 
79
  return null;
80
  }
81
 
82
+ async function fetchGuildConfig(guildId) {
83
+ try {
84
+ const response = await fetch(`${API_URL}/guilds/${guildId}/config`);
85
+ if (response.ok) {
86
+ const data = await response.json();
87
+ return {
88
+ logChannelId: data.log_channel_id,
89
+ models: {
90
+ text: data.active_text_model || "none",
91
+ image: data.active_image_model || "none"
92
+ }
93
+ };
94
+ }
95
+ } catch (err) {
96
+ console.error(`[CONFIG ERROR] Błąd pobierania konfiguracji dla gildii ${guildId}:`, err.message);
97
+ }
98
+ return {
99
+ logChannelId: null,
100
+ models: {}
101
+ };
102
+ }
103
+
104
  function preparePayload(input, explicitContentType = null) {
105
  const trimmed = input.trim();
106
 
 
218
  }
219
 
220
  async function sendLogToDiscord(guild, embedToSend) {
221
+ const config = await fetchGuildConfig(guild.id);
222
  if (!config.logChannelId) return;
223
 
224
  try {
 
234
  async function handleAnalysis(interaction, userContent, targetMessage = null, explicitContentType = null) {
235
  await interaction.deferReply({ flags: [MessageFlags.Ephemeral] });
236
 
 
 
237
  try {
238
  const { type, payload } = preparePayload(userContent, explicitContentType);
239
+ payload.guild_id = interaction.guildId;
 
 
 
 
 
240
 
241
  console.log(`Wysyłanie zapytania typu: ${type} do API z modelem: ${payload.model || "domyślny"}...`);
242
 
 
348
 
349
  if (interaction.commandName === "setup") {
350
  const guildId = interaction.guildId;
351
+
 
352
  await interaction.deferReply({ flags: [MessageFlags.Ephemeral] });
353
 
354
+ // Pobieramy konfigurację bezpośrednio z FastAPI
355
+ const currentConfig = await fetchGuildConfig(guildId);
356
  const availableModels = await fetchAvailableModels();
357
 
 
358
  if (!availableModels || Object.keys(availableModels).length === 0) {
359
  return interaction.editReply({
360
  content: "❌ **Błąd konfiguracji:** Nie udało się nawiązać połączenia z backendem (FastAPI). Uruchom swój backend w Pythonie i spróbuj ponownie!"
361
  });
362
  }
363
 
 
364
  for (const [contentType, models] of Object.entries(availableModels)) {
365
  if (!currentConfig.models[contentType] && models.length > 0) {
366
  currentConfig.models[contentType] = models[0];
367
  }
368
  }
369
 
 
370
  activeSetupSessions.set(guildId, {
371
  config: { ...currentConfig },
372
  availableModels
 
457
  const tempSession = activeSetupSessions.get(guildId);
458
  if (tempSession) {
459
  try {
460
+ const response = await fetch(`${API_URL}/guilds/${guildId}/setup`, {
461
  method: "POST",
462
  headers: {
463
  "Content-Type": "application/json"
464
  },
465
  body: JSON.stringify({
466
+ active_text_model: tempSession.config.models?.text || "none",
467
+ active_image_model: tempSession.config.models?.image || "none",
468
+ log_channel_id: tempSession.config.logChannelId || null
469
  })
470
  });
471