Tobiasz Kubiak commited on
Commit
1b1e7c5
·
unverified ·
2 Parent(s): 7b59e80944aa7f

Merge pull request #16 from Tobkubos/backend-setup

Browse files
backend/app/api/routes.py CHANGED
@@ -99,7 +99,7 @@ async def save_discord_guild_setup(guild_id: str, payload: GuildConfigSchema):
99
  return {
100
  "status": "success",
101
  "message": f"Konfiguracja dla serwera {guild_id} została zapisana.",
102
- "config": config_dict
103
  }
104
 
105
  @router.get("/guilds/{guild_id}/config", tags=["Setup"])
@@ -111,7 +111,8 @@ async def get_discord_guild_config(guild_id: str):
111
  return {
112
  "active_text_model": guild_config.get("active_text_model", "none"),
113
  "active_image_model": guild_config.get("active_image_model", "none"),
114
- "log_channel_id": guild_config.get("log_channel_id", None)
 
115
  }
116
 
117
  async def _execute_analysis(payload: AnalysisRequest, guild_id: str, settings) -> dict:
@@ -222,7 +223,8 @@ async def analyze(request: Request, payload: AnalysisRequest) -> AnalysisRespons
222
  analysis_time=analysis_result["analysis_time"],
223
  used_model=used_model,
224
  content_type=content_type,
 
225
  )
226
 
227
  from app.api.factcheck_router import router as factcheck_router
228
- router.include_router(factcheck_router) #kupczak tu był
 
99
  return {
100
  "status": "success",
101
  "message": f"Konfiguracja dla serwera {guild_id} została zapisana.",
102
+ "config": config_dict,
103
  }
104
 
105
  @router.get("/guilds/{guild_id}/config", tags=["Setup"])
 
111
  return {
112
  "active_text_model": guild_config.get("active_text_model", "none"),
113
  "active_image_model": guild_config.get("active_image_model", "none"),
114
+ "log_channel_id": guild_config.get("log_channel_id", None),
115
+ "multi_model_workflow": guild_config.get("multi_model_workflow", False)
116
  }
117
 
118
  async def _execute_analysis(payload: AnalysisRequest, guild_id: str, settings) -> dict:
 
223
  analysis_time=analysis_result["analysis_time"],
224
  used_model=used_model,
225
  content_type=content_type,
226
+ details=analysis_result.get("details"),
227
  )
228
 
229
  from app.api.factcheck_router import router as factcheck_router
230
+ router.include_router(factcheck_router) #kupczak tu był
backend/app/config_manager.py CHANGED
@@ -51,4 +51,10 @@ def get_active_image_model(guild_id: str) -> Optional[str]:
51
 
52
  if not model or model.lower() == "none":
53
  return None
54
- return model
 
 
 
 
 
 
 
51
 
52
  if not model or model.lower() == "none":
53
  return None
54
+ return model
55
+
56
+ def is_multi_model_enabled(guild_id: str) -> bool:
57
+ """Sprawdza, czy dla danej gildii włączony jest tryb wielomodelowy."""
58
+ configs = _load_all_configs()
59
+ guild_config = configs.get(guild_id, {})
60
+ return guild_config.get("multi_model_workflow", False)
backend/app/models/schemas.py CHANGED
@@ -38,13 +38,18 @@ AnalysisRequest = Union[
38
  ImageAnalysisRequest
39
  ]
40
 
41
-
 
 
 
 
42
  class AnalysisResponse(BaseModel):
43
  is_deepfake: bool = Field(..., description="Whether the content is detected as a deepfake")
44
  confidence: float = Field(..., ge=0.0, le=1.0, description="Confidence score between 0.0 and 1.0")
45
  analysis_time: float = Field(..., description="Time taken for analysis in seconds")
46
  used_model: str = Field(..., description="The detector model that was used")
47
  content_type: str = Field(..., description="Type of content analyzed (text/image/video/file)")
 
48
 
49
  class Config:
50
  json_schema_extra = {
@@ -53,7 +58,10 @@ class AnalysisResponse(BaseModel):
53
  "confidence": 0.847,
54
  "analysis_time": 1.234,
55
  "used_model": "mock",
56
- "content_type": "image"
 
 
 
57
  }
58
  }
59
 
@@ -91,3 +99,4 @@ class GuildConfigSchema(BaseModel):
91
  active_text_model: Optional[str] = "none"
92
  active_image_model: Optional[str] = "none"
93
  log_channel_id: Optional[str] = None
 
 
38
  ImageAnalysisRequest
39
  ]
40
 
41
+ class ModelDetail(BaseModel):
42
+ model: str
43
+ is_deepfake: bool
44
+ confidence: float
45
+
46
  class AnalysisResponse(BaseModel):
47
  is_deepfake: bool = Field(..., description="Whether the content is detected as a deepfake")
48
  confidence: float = Field(..., ge=0.0, le=1.0, description="Confidence score between 0.0 and 1.0")
49
  analysis_time: float = Field(..., description="Time taken for analysis in seconds")
50
  used_model: str = Field(..., description="The detector model that was used")
51
  content_type: str = Field(..., description="Type of content analyzed (text/image/video/file)")
52
+ details: Optional[List[ModelDetail]] = None
53
 
54
  class Config:
55
  json_schema_extra = {
 
58
  "confidence": 0.847,
59
  "analysis_time": 1.234,
60
  "used_model": "mock",
61
+ "content_type": "image",
62
+ "details": [
63
+ {"model": "mock", "is_deepfake": True, "confidence": 0.847}
64
+ ]
65
  }
66
  }
67
 
 
99
  active_text_model: Optional[str] = "none"
100
  active_image_model: Optional[str] = "none"
101
  log_channel_id: Optional[str] = None
102
+ multi_model_workflow: Optional[bool] = False
backend/app/services/image_analyzer.py CHANGED
@@ -6,81 +6,110 @@ 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")
61
  except Exception as e:
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
77
-
78
- response = {
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}")
86
- return response
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  from PIL import Image
7
  from transformers import pipeline
8
 
9
+ from app.config_manager import get_active_image_model, is_multi_model_enabled
10
+ from app.core.config import get_settings
11
  from app.utils.exceptions import SetupRequiredError
12
 
13
  logger = logging.getLogger(__name__)
14
 
15
+ _loaded_classifiers = {}
 
 
16
 
17
  def _load_model(target_model_name: str):
18
+ global _loaded_classifiers
19
 
20
+ if target_model_name in _loaded_classifiers:
21
+ return _loaded_classifiers[target_model_name]
 
22
 
23
+ logger.info(f"Model {target_model_name} nie jest załadowany. Ładowanie do RAM...")
24
 
 
 
25
  gc.collect()
26
+ _loaded_classifiers[target_model_name] = pipeline(
 
 
27
  "image-classification",
28
  model=target_model_name,
29
+ device=-1
30
  )
 
31
  logger.info(f"Model {target_model_name} został pomyślnie załadowany.")
32
 
33
+ return _loaded_classifiers[target_model_name]
34
 
35
  async def analyze_image(image_bytes: bytes, guild_id: str) -> Dict[str, Any]:
36
  start_time = time.time()
37
+ settings = get_settings()
38
 
39
+ multi_model_active = is_multi_model_enabled(guild_id)
 
 
 
 
 
 
 
 
 
 
 
40
 
41
  try:
42
  image = Image.open(io.BytesIO(image_bytes)).convert("RGB")
43
  except Exception as e:
44
  logger.error(f"Failed to parse image bytes: {str(e)}")
45
  raise ValueError("Invalid image format or corrupted bytes") from e
46
+
47
+ if multi_model_active:
48
+ models_to_run = settings.AVAILABLE_MODELS.get("image", [])
49
+ if not models_to_run:
50
+ raise ValueError("Brak zdefiniowanych modeli obrazów w ustawieniach systemu.")
51
+
52
+ logger.info(f"Wielomodelowa analiza obrazu dla serwera {guild_id} ({len(models_to_run)} modeli)")
53
+
54
+ individual_results = []
55
+ for m in models_to_run:
56
+ try:
57
+ classifier = _load_model(m)
58
+ result = classifier(image)
59
+ label = result[0]["label"]
60
+ score = result[0]["score"]
61
+ is_fake = label.lower() in ["fake", "ai", "synthetic", "label_1"]
62
+
63
+ individual_results.append({
64
+ "model": m,
65
+ "is_deepfake": is_fake,
66
+ "confidence": round(score, 3)
67
+ })
68
+ except Exception as e:
69
+ logger.error(f"Błąd modelu {m} podczas wielomodelowej analizy obrazu: {e}")
70
+
71
+ if not individual_results:
72
+ raise ValueError("Żaden z modeli obrazów nie dokonał pomyślnej analizy.")
73
+
74
+ fake_votes = sum(1 for r in individual_results if r["is_deepfake"])
75
+ is_deepfake = fake_votes > (len(individual_results) / 2)
76
+
77
+ confidence = sum(r["confidence"] for r in individual_results) / len(individual_results)
78
+ analysis_time = time.time() - start_time
79
+
80
+ return {
81
+ "is_deepfake": is_deepfake,
82
+ "confidence": round(confidence, 3),
83
+ "analysis_time": round(analysis_time, 3),
84
+ "used_model": "Multi-Model Workflow (Ensemble)",
85
+ "details": individual_results
86
+ }
87
+
88
+ else:
89
+ active_model = get_active_image_model(guild_id)
90
+ if not active_model:
91
+ logger.warning(f"Zablokowano zapytanie! Serwer {guild_id} nie ma skonfigurowanego modelu dla obrazów.")
92
+ raise SetupRequiredError(
93
+ f"Serwer o ID '{guild_id}' nie został jeszcze skonfigurowany pod kątem analizy obrazów. "
94
+ "Użyj komendy setup na Discordzie przed wykonaniem analizy."
95
+ )
96
+
97
+ logger.info(f"Starting image analysis for guild: {guild_id}, model: {active_model}, size: {len(image_bytes)} bytes")
98
+
99
+ classifier = _load_model(active_model)
100
+ result = classifier(image)
101
+
102
+ label = result[0]["label"]
103
+ score = result[0]["score"]
104
+
105
+ is_deepfake = label.lower() in ["fake", "ai", "synthetic", "label_1"]
106
+ confidence = score
107
+ analysis_time = time.time() - start_time
108
+
109
+ return {
110
+ "is_deepfake": is_deepfake,
111
+ "confidence": round(confidence, 3),
112
+ "analysis_time": round(analysis_time, 3),
113
+ "used_model": active_model,
114
+ "details": None
115
+ }
backend/app/services/text_analyzer.py CHANGED
@@ -3,74 +3,110 @@ import time
3
  import gc
4
  from typing import Dict, Any
5
 
6
- from app.config_manager import get_active_text_model
 
7
  from app.utils.exceptions import SetupRequiredError
8
  from transformers import pipeline
9
- # Importujesz helpery z Kroku 2:
10
- # from config_manager import get_active_text_model
11
 
12
  logger = logging.getLogger(__name__)
13
 
14
- # Przechowujemy nazwę aktualnie załadowanego modelu oraz sam obiekt klasyfikatora
15
- _loaded_model_name = None
16
- _text_classifier = None
17
 
18
  def _load_model(target_model_name: str):
19
- global _text_classifier, _loaded_model_name
20
 
21
- # Jeśli model w pamięci jest tym, którego potrzebujemy, po prostu go zwracamy
22
- if _text_classifier is not None and _loaded_model_name == target_model_name:
23
- return _text_classifier
24
 
25
- logger.info(f"Wymagana zmiana modelu. Obecny w RAM: {_loaded_model_name}, Nowy: {target_model_name}")
26
 
27
- # Zwalnianie pamięci po poprzednim modelu
28
- _text_classifier = None
29
  gc.collect()
30
-
31
- logger.info(f"Ładowanie modelu text detector: {target_model_name}...")
32
- _text_classifier = pipeline(
33
  "text-classification",
34
  model=target_model_name,
35
- device=-1 # -1 oznacza CPU, jeśli masz GPU ustaw np. 0
36
  )
37
- _loaded_model_name = target_model_name
38
  logger.info(f"Model {target_model_name} został pomyślnie załadowany.")
39
 
40
- return _text_classifier
41
 
42
  async def analyze_text(text: str, guild_id: str) -> Dict[str, Any]:
43
  start_time = time.time()
 
44
 
45
- # Pobranie aktywnego modelu dla danej gildii
46
- active_model = get_active_text_model(guild_id)
47
-
48
- # BLOKADA: Jeżeli model to 'none' lub brak konfiguracji, natychmiast wyrzucamy błąd
49
- if not active_model:
50
- logger.warning(f"Zablokowano zapytanie! Serwer {guild_id} nie ma skonfigurowanego modelu.")
51
- raise SetupRequiredError(
52
- f"Serwer o ID '{guild_id}' nie został jeszcze skonfigurowany. "
53
- "Użyj komendy setup na Discordzie przed wykonaniem analizy."
54
- )
55
-
56
- logger.info(f"Rozpoczęcie analizy tekstu dla serwera {guild_id} przy użyciu modelu: {active_model}")
57
-
58
- classifier = _load_model(active_model)
59
- result = classifier(text)
60
 
61
- label = result[0]["label"]
62
- score = result[0]["score"]
63
-
64
- is_deepfake = label.lower() in ["fake", "ai", "chatgpt", "label_1", "machine-generated"]
65
- confidence = score
66
- analysis_time = time.time() - start_time
67
-
68
- response = {
69
- "is_deepfake": is_deepfake,
70
- "confidence": round(confidence, 3),
71
- "analysis_time": round(analysis_time, 3),
72
- "used_model": active_model,
73
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
 
75
- logger.info(f"Analiza zakończona sukcesem dla serwera {guild_id}.")
76
- return response
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  import gc
4
  from typing import Dict, Any
5
 
6
+ from app.config_manager import get_active_text_model, is_multi_model_enabled
7
+ from app.core.config import get_settings
8
  from app.utils.exceptions import SetupRequiredError
9
  from transformers import pipeline
 
 
10
 
11
  logger = logging.getLogger(__name__)
12
 
13
+ # Słownik do keszowania klasyfikatorów w RAM (zapobiega ciągłemu przeładowywaniu przy multi-modelu)
14
+ _loaded_classifiers = {}
 
15
 
16
  def _load_model(target_model_name: str):
17
+ global _loaded_classifiers
18
 
19
+ if target_model_name in _loaded_classifiers:
20
+ return _loaded_classifiers[target_model_name]
 
21
 
22
+ logger.info(f"Model {target_model_name} nie jest załadowany. Ładowanie do RAM...")
23
 
 
 
24
  gc.collect()
25
+ _loaded_classifiers[target_model_name] = pipeline(
 
 
26
  "text-classification",
27
  model=target_model_name,
28
+ device=-1
29
  )
 
30
  logger.info(f"Model {target_model_name} został pomyślnie załadowany.")
31
 
32
+ return _loaded_classifiers[target_model_name]
33
 
34
  async def analyze_text(text: str, guild_id: str) -> Dict[str, Any]:
35
  start_time = time.time()
36
+ settings = get_settings()
37
 
38
+ # Sprawdzamy, czy włączony jest tryb wielomodelowy
39
+ multi_model_active = is_multi_model_enabled(guild_id)
 
 
 
 
 
 
 
 
 
 
 
 
 
40
 
41
+ if multi_model_active:
42
+ models_to_run = settings.AVAILABLE_MODELS.get("text", [])
43
+ if not models_to_run:
44
+ raise ValueError("Brak zdefiniowanych modeli tekstowych w ustawieniach systemu.")
45
+
46
+ logger.info(f"Rozpoczęcie wielomodelowej analizy tekstu dla serwera {guild_id} ({len(models_to_run)} modeli)")
47
+
48
+ individual_results = []
49
+ for m in models_to_run:
50
+ try:
51
+ classifier = _load_model(m)
52
+ result = classifier(text)
53
+ label = result[0]["label"]
54
+ score = result[0]["score"]
55
+ is_fake = label.lower() in ["fake", "ai", "chatgpt", "label_1", "machine-generated"]
56
+
57
+ individual_results.append({
58
+ "model": m,
59
+ "is_deepfake": is_fake,
60
+ "confidence": round(score, 3)
61
+ })
62
+ except Exception as e:
63
+ logger.error(f"Błąd modelu {m} podczas wielomodelowej analizy: {e}")
64
+
65
+ if not individual_results:
66
+ raise ValueError("Żaden z modeli tekstowych nie dokonał pomyślnej analizy.")
67
+
68
+ # Agregacja: Głosowanie większościowe
69
+ fake_votes = sum(1 for r in individual_results if r["is_deepfake"])
70
+ is_deepfake = fake_votes > (len(individual_results) / 2)
71
+
72
+ # Pewność: Średnia pewność wszystkich modeli
73
+ confidence = sum(r["confidence"] for r in individual_results) / len(individual_results)
74
+ analysis_time = time.time() - start_time
75
+
76
+ return {
77
+ "is_deepfake": is_deepfake,
78
+ "confidence": round(confidence, 3),
79
+ "analysis_time": round(analysis_time, 3),
80
+ "used_model": "Multi-Model Workflow (Ensemble)",
81
+ "details": individual_results # Przekazujemy szczegóły do bota
82
+ }
83
 
84
+ else:
85
+ # Tradycyjna analiza pojedynczego modelu
86
+ active_model = get_active_text_model(guild_id)
87
+ if not active_model:
88
+ logger.warning(f"Zablokowano zapytanie! Serwer {guild_id} nie ma skonfigurowanego modelu.")
89
+ raise SetupRequiredError(
90
+ f"Serwer o ID '{guild_id}' nie został jeszcze skonfigurowany. "
91
+ "Użyj komendy setup na Discordzie przed wykonaniem analizy."
92
+ )
93
+
94
+ logger.info(f"Rozpoczęcie analizy tekstu dla serwera {guild_id} przy użyciu modelu: {active_model}")
95
+
96
+ classifier = _load_model(active_model)
97
+ result = classifier(text)
98
+
99
+ label = result[0]["label"]
100
+ score = result[0]["score"]
101
+
102
+ is_deepfake = label.lower() in ["fake", "ai", "chatgpt", "label_1", "machine-generated"]
103
+ confidence = score
104
+ analysis_time = time.time() - start_time
105
+
106
+ return {
107
+ "is_deepfake": is_deepfake,
108
+ "confidence": round(confidence, 3),
109
+ "analysis_time": round(analysis_time, 3),
110
+ "used_model": active_model,
111
+ "details": None
112
+ }
backend/guild_configs.json CHANGED
@@ -2,6 +2,7 @@
2
  "1515307986963267595": {
3
  "active_text_model": "bibbbu/multilingual-ai-human-detector_xlm-roberta-base",
4
  "active_image_model": "Hemg/Deepfake-image",
5
- "log_channel_id": "1515373138937123007"
 
6
  }
7
  }
 
2
  "1515307986963267595": {
3
  "active_text_model": "bibbbu/multilingual-ai-human-detector_xlm-roberta-base",
4
  "active_image_model": "Hemg/Deepfake-image",
5
+ "log_channel_id": "1515373138937123007",
6
+ "multi_model_workflow": true
7
  }
8
  }
index.js CHANGED
@@ -90,6 +90,7 @@ async function fetchGuildConfig(guildId) {
90
  const data = await response.json();
91
  return {
92
  logChannelId: data.log_channel_id,
 
93
  models: {
94
  text: data.active_text_model || "none",
95
  image: data.active_image_model || "none",
@@ -104,7 +105,8 @@ async function fetchGuildConfig(guildId) {
104
  }
105
  return {
106
  logChannelId: null,
107
- models: {},
 
108
  };
109
  }
110
 
@@ -182,17 +184,18 @@ function generateSetupView(tempConfig, availableModels) {
182
  .setFooter({ text: "Wybierz opcje i kliknij Zapisz ustawienia" });
183
 
184
  embed.addFields({
185
- name: "📂 Kanał logów (Raporty)",
186
- value: tempConfig.logChannelId
187
- ? `<#${tempConfig.logChannelId}>`
188
- : "*Wysyłanie tylko do konsoli*",
189
- inline: false,
190
  });
191
 
192
- // Dynamicznie dodajemy pola dla każdego formatu zwróconego przez FastAPI
193
  for (const [contentType, models] of Object.entries(availableModels)) {
194
- const currentSelected =
195
- tempConfig.models[contentType] || models[0] || "Brak";
 
 
196
  embed.addFields({
197
  name: `⚙️ Model dla formatu: ${contentType.toUpperCase()}`,
198
  value: `\`${currentSelected}\``,
@@ -207,9 +210,8 @@ function generateSetupView(tempConfig, availableModels) {
207
 
208
  const components = [new ActionRowBuilder().addComponents(channelSelect)];
209
 
210
- // Dynamicznie generujemy menu rozwijane dla każdego formatu danych (tekst, obraz, wideo itp.)
211
  for (const [contentType, models] of Object.entries(availableModels)) {
212
- if (components.length >= 4) break; // Limit Discorda (max 5 rzędów komponentów na wiadomość)
213
 
214
  const currentSelected = tempConfig.models[contentType] || models[0];
215
 
@@ -222,12 +224,19 @@ function generateSetupView(tempConfig, availableModels) {
222
  const modelSelect = new StringSelectMenuBuilder()
223
  .setCustomId(`setup_model_${contentType}`)
224
  .setPlaceholder(`Wybierz model dla ${contentType}`)
225
- .addOptions(selectOptions);
 
 
226
 
227
  components.push(new ActionRowBuilder().addComponents(modelSelect));
228
  }
229
 
230
  const buttonsRow = new ActionRowBuilder().addComponents(
 
 
 
 
 
231
  new ButtonBuilder()
232
  .setCustomId("setup_save")
233
  .setLabel("Zapisz ustawienia")
@@ -397,40 +406,50 @@ async function handleAnalysis(
397
  }
398
  }
399
 
400
- const embedColor = data.is_deepfake ? 0xff0000 : 0x00ff00;
401
- const verdictText = data.is_deepfake
402
- ? "⚠️ Wykryto potencjalny Deepfake!"
403
- : "✅ Zawartość wydaje się oryginalna";
404
- const progressBar = getProgressBar(data.confidence, data.is_deepfake);
405
  const confidencePercent = (data.confidence * 100).toFixed(2);
406
 
407
  const embed = new EmbedBuilder()
408
  .setColor(embedColor)
409
  .setTitle("🛡️ Wynik Analizy Treści")
410
  .setDescription(`**Werdykt:** ${verdictText}`)
411
- .addFields(
412
- {
413
- name: "Pewność modelu",
414
- value: `\`${confidencePercent}%\` \n${progressBar}`,
415
- },
416
- {
417
- name: "Czas przetwarzania",
418
- value: `\`${data.analysis_time.toFixed(3)}s\``,
419
- inline: true,
420
- },
421
- { name: "Użyty model", value: `\`${data.used_model}\``, inline: true },
422
- {
423
- name: "Format danych",
424
- value: `\`${data.content_type.toUpperCase()}\``,
425
- inline: true,
426
- },
427
- )
428
  .setTimestamp()
429
  .setFooter({
430
  text: "Deepfake Detection Service",
431
  iconURL: client.user.displayAvatarURL(),
432
  });
433
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
434
  const buttonRow = new ActionRowBuilder().addComponents(
435
  new ButtonBuilder()
436
  .setCustomId("modelCorrect")
@@ -494,6 +513,10 @@ client.on(Events.InteractionCreate, async (interaction) => {
494
  });
495
  }
496
 
 
 
 
 
497
  for (const [contentType, models] of Object.entries(availableModels)) {
498
  if (!currentConfig.models[contentType] && models.length > 0) {
499
  currentConfig.models[contentType] = models[0];
@@ -609,7 +632,8 @@ client.on(Events.InteractionCreate, async (interaction) => {
609
  active_text_model: tempSession.config.models?.text || "none",
610
  active_image_model: tempSession.config.models?.image || "none",
611
  log_channel_id: tempSession.config.logChannelId || null,
612
- }),
 
613
  });
614
 
615
  if (!response.ok) {
@@ -736,6 +760,14 @@ client.on(Events.InteractionCreate, async (interaction) => {
736
  await sendLogToDiscord(interaction.guild, logEmbed);
737
  }
738
  }
 
 
 
 
 
 
 
 
739
  }
740
  });
741
 
 
90
  const data = await response.json();
91
  return {
92
  logChannelId: data.log_channel_id,
93
+ multiModelWorkflow: data.multi_model_workflow || false,
94
  models: {
95
  text: data.active_text_model || "none",
96
  image: data.active_image_model || "none",
 
105
  }
106
  return {
107
  logChannelId: null,
108
+ multiModelWorkflow: false,
109
+ models: {}
110
  };
111
  }
112
 
 
184
  .setFooter({ text: "Wybierz opcje i kliknij Zapisz ustawienia" });
185
 
186
  embed.addFields({
187
+ name: "🔗 Tryb wielomodelowy (Multi-Model Workflow)",
188
+ value: tempConfig.multiModelWorkflow
189
+ ? "🟢 **Włączony** (zostaną użyte wszystkie dostępne modele, indywidualny wybór jest zablokowany)"
190
+ : "🔴 **Wyłączony** (będzie używany tylko model wybrany poniżej)",
191
+ inline: false
192
  });
193
 
 
194
  for (const [contentType, models] of Object.entries(availableModels)) {
195
+ const currentSelected = tempConfig.multiModelWorkflow
196
+ ? "Wszystkie (Multi-Model Workflow)"
197
+ : (tempConfig.models[contentType] || models[0] || "Brak");
198
+
199
  embed.addFields({
200
  name: `⚙️ Model dla formatu: ${contentType.toUpperCase()}`,
201
  value: `\`${currentSelected}\``,
 
210
 
211
  const components = [new ActionRowBuilder().addComponents(channelSelect)];
212
 
 
213
  for (const [contentType, models] of Object.entries(availableModels)) {
214
+ if (components.length >= 4) break;
215
 
216
  const currentSelected = tempConfig.models[contentType] || models[0];
217
 
 
224
  const modelSelect = new StringSelectMenuBuilder()
225
  .setCustomId(`setup_model_${contentType}`)
226
  .setPlaceholder(`Wybierz model dla ${contentType}`)
227
+ .addOptions(selectOptions)
228
+ // WYszarzenie i zablokowanie wyboru, gdy włączony jest Multi-Model Workflow
229
+ .setDisabled(tempConfig.multiModelWorkflow);
230
 
231
  components.push(new ActionRowBuilder().addComponents(modelSelect));
232
  }
233
 
234
  const buttonsRow = new ActionRowBuilder().addComponents(
235
+ new ButtonBuilder()
236
+ .setCustomId("setup_toggle_multimodel")
237
+ .setLabel(tempConfig.multiModelWorkflow ? "Tryb Wielomodelowy: WŁ" : "Tryb Wielomodelowy: WYŁ")
238
+ .setStyle(tempConfig.multiModelWorkflow ? ButtonStyle.Primary : ButtonStyle.Secondary)
239
+ .setEmoji(tempConfig.multiModelWorkflow ? "🟢" : "⚫"),
240
  new ButtonBuilder()
241
  .setCustomId("setup_save")
242
  .setLabel("Zapisz ustawienia")
 
406
  }
407
  }
408
 
409
+ const embedColor = data.is_deepfake ? 0xFF0000 : 0x00FF00;
410
+ const verdictText = data.is_deepfake ? "⚠️ Wykryto potencjalny Deepfake!" : "✅ Zawartość wydaje się oryginalna";
 
 
 
411
  const confidencePercent = (data.confidence * 100).toFixed(2);
412
 
413
  const embed = new EmbedBuilder()
414
  .setColor(embedColor)
415
  .setTitle("🛡️ Wynik Analizy Treści")
416
  .setDescription(`**Werdykt:** ${verdictText}`)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
417
  .setTimestamp()
418
  .setFooter({
419
  text: "Deepfake Detection Service",
420
  iconURL: client.user.displayAvatarURL(),
421
  });
422
 
423
+ if (data.details && data.details.length > 0) {
424
+ // Widok dla Multi-Modelu: ładnie listujemy każdy model
425
+ embed.addFields({ name: "📊 Średnia pewność systemu", value: `\`${confidencePercent}%\``, inline: false });
426
+
427
+ for (const detail of data.details) {
428
+ const detailBar = getProgressBar(detail.confidence, detail.is_deepfake);
429
+ const statusText = detail.is_deepfake ? "🟥 FAKE" : "🟩 REAL";
430
+ const pct = (detail.confidence * 100).toFixed(1);
431
+
432
+ embed.addFields({
433
+ name: `🤖 Model: ${detail.model.split("/").pop()}`, // skracamy ścieżkę modelu
434
+ value: `Werdykt: **${statusText}** (Pewność: \`${pct}%\`)\n${detailBar}`,
435
+ inline: false
436
+ });
437
+ }
438
+ } else {
439
+ // Standardowy widok dla pojedynczego modelu (progressBar jest bezpiecznie zdefiniowany tutaj)
440
+ const progressBar = getProgressBar(data.confidence, data.is_deepfake);
441
+ embed.addFields(
442
+ { name: "Pewność modelu", value: `\`${confidencePercent}%\` \n${progressBar}` },
443
+ { name: "Użyty model", value: `\`${data.used_model}\``, inline: true }
444
+ );
445
+ }
446
+
447
+ // 3. Dodatkowe pola wspólne (dodawane tylko raz na samym końcu)
448
+ embed.addFields(
449
+ { name: "Czas przetwarzania", value: `\`${data.analysis_time.toFixed(3)}s\``, inline: true },
450
+ { name: "Format danych", value: `\`${data.content_type.toUpperCase()}\``, inline: true }
451
+ );
452
+
453
  const buttonRow = new ActionRowBuilder().addComponents(
454
  new ButtonBuilder()
455
  .setCustomId("modelCorrect")
 
513
  });
514
  }
515
 
516
+ if (currentConfig.multiModelWorkflow === undefined) {
517
+ currentConfig.multiModelWorkflow = false;
518
+ }
519
+
520
  for (const [contentType, models] of Object.entries(availableModels)) {
521
  if (!currentConfig.models[contentType] && models.length > 0) {
522
  currentConfig.models[contentType] = models[0];
 
632
  active_text_model: tempSession.config.models?.text || "none",
633
  active_image_model: tempSession.config.models?.image || "none",
634
  log_channel_id: tempSession.config.logChannelId || null,
635
+ multi_model_workflow: tempSession.config.multiModelWorkflow || false
636
+ })
637
  });
638
 
639
  if (!response.ok) {
 
760
  await sendLogToDiscord(interaction.guild, logEmbed);
761
  }
762
  }
763
+
764
+ if (interaction.customId === "setup_toggle_multimodel") {
765
+ const tempSession = activeSetupSessions.get(guildId);
766
+ if (tempSession) {
767
+ tempSession.config.multiModelWorkflow = !tempSession.config.multiModelWorkflow;
768
+ await interaction.update(generateSetupView(tempSession.config, tempSession.availableModels));
769
+ }
770
+ }
771
  }
772
  });
773