Gradii commited on
Commit
da164cc
·
1 Parent(s): fda4b67

multi models workflow

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,4 +223,5 @@ 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
  )
 
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
  )
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
+ }
index.js CHANGED
@@ -82,6 +82,7 @@ async function fetchGuildConfig(guildId) {
82
  const data = await response.json();
83
  return {
84
  logChannelId: data.log_channel_id,
 
85
  models: {
86
  text: data.active_text_model || "none",
87
  image: data.active_image_model || "none"
@@ -93,6 +94,7 @@ async function fetchGuildConfig(guildId) {
93
  }
94
  return {
95
  logChannelId: null,
 
96
  models: {}
97
  };
98
  }
@@ -147,15 +149,19 @@ function generateSetupView(tempConfig, availableModels) {
147
  .setTimestamp()
148
  .setFooter({ text: "Wybierz opcje i kliknij Zapisz ustawienia" });
149
 
150
- embed.addFields({
151
- name: "📂 Kanał logów (Raporty)",
152
- value: tempConfig.logChannelId ? `<#${tempConfig.logChannelId}>` : "*Wysyłanie tylko do konsoli*",
153
- inline: false
 
 
154
  });
155
 
156
- // Dynamicznie dodajemy pola dla każdego formatu zwróconego przez FastAPI
157
  for (const [contentType, models] of Object.entries(availableModels)) {
158
- const currentSelected = tempConfig.models[contentType] || models[0] || "Brak";
 
 
 
159
  embed.addFields({
160
  name: `⚙️ Model dla formatu: ${contentType.toUpperCase()}`,
161
  value: `\`${currentSelected}\``,
@@ -172,9 +178,8 @@ function generateSetupView(tempConfig, availableModels) {
172
  new ActionRowBuilder().addComponents(channelSelect)
173
  ];
174
 
175
- // Dynamicznie generujemy menu rozwijane dla każdego formatu danych (tekst, obraz, wideo itp.)
176
  for (const [contentType, models] of Object.entries(availableModels)) {
177
- if (components.length >= 4) break; // Limit Discorda (max 5 rzędów komponentów na wiadomość)
178
 
179
  const currentSelected = tempConfig.models[contentType] || models[0];
180
 
@@ -187,12 +192,19 @@ function generateSetupView(tempConfig, availableModels) {
187
  const modelSelect = new StringSelectMenuBuilder()
188
  .setCustomId(`setup_model_${contentType}`)
189
  .setPlaceholder(`Wybierz model dla ${contentType}`)
190
- .addOptions(selectOptions);
 
 
191
 
192
  components.push(new ActionRowBuilder().addComponents(modelSelect));
193
  }
194
 
195
  const buttonsRow = new ActionRowBuilder().addComponents(
 
 
 
 
 
196
  new ButtonBuilder()
197
  .setCustomId("setup_save")
198
  .setLabel("Zapisz ustawienia")
@@ -278,22 +290,46 @@ async function handleAnalysis(interaction, userContent, targetMessage = null, ex
278
 
279
  const embedColor = data.is_deepfake ? 0xFF0000 : 0x00FF00;
280
  const verdictText = data.is_deepfake ? "⚠️ Wykryto potencjalny Deepfake!" : "✅ Zawartość wydaje się oryginalna";
281
- const progressBar = getProgressBar(data.confidence, data.is_deepfake);
282
  const confidencePercent = (data.confidence * 100).toFixed(2);
283
 
284
  const embed = new EmbedBuilder()
285
  .setColor(embedColor)
286
  .setTitle("🛡️ Wynik Analizy Treści")
287
  .setDescription(`**Werdykt:** ${verdictText}`)
288
- .addFields(
289
- { name: "Pewność modelu", value: `\`${confidencePercent}%\` \n${progressBar}` },
290
- { name: "Czas przetwarzania", value: `\`${data.analysis_time.toFixed(3)}s\``, inline: true },
291
- { name: "Użyty model", value: `\`${data.used_model}\``, inline: true },
292
- { name: "Format danych", value: `\`${data.content_type.toUpperCase()}\``, inline: true }
293
- )
294
  .setTimestamp()
295
  .setFooter({ text: "Deepfake Detection Service", iconURL: client.user.displayAvatarURL() });
296
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
297
  const buttonRow = new ActionRowBuilder().addComponents(
298
  new ButtonBuilder()
299
  .setCustomId("modelCorrect")
@@ -356,6 +392,10 @@ client.on(Events.InteractionCreate, async (interaction) => {
356
  });
357
  }
358
 
 
 
 
 
359
  for (const [contentType, models] of Object.entries(availableModels)) {
360
  if (!currentConfig.models[contentType] && models.length > 0) {
361
  currentConfig.models[contentType] = models[0];
@@ -445,7 +485,8 @@ client.on(Events.InteractionCreate, async (interaction) => {
445
  body: JSON.stringify({
446
  active_text_model: tempSession.config.models?.text || "none",
447
  active_image_model: tempSession.config.models?.image || "none",
448
- log_channel_id: tempSession.config.logChannelId || null
 
449
  })
450
  });
451
 
@@ -562,6 +603,14 @@ client.on(Events.InteractionCreate, async (interaction) => {
562
  await sendLogToDiscord(interaction.guild, logEmbed);
563
  }
564
  }
 
 
 
 
 
 
 
 
565
  }
566
  });
567
 
 
82
  const data = await response.json();
83
  return {
84
  logChannelId: data.log_channel_id,
85
+ multiModelWorkflow: data.multi_model_workflow || false,
86
  models: {
87
  text: data.active_text_model || "none",
88
  image: data.active_image_model || "none"
 
94
  }
95
  return {
96
  logChannelId: null,
97
+ multiModelWorkflow: false,
98
  models: {}
99
  };
100
  }
 
149
  .setTimestamp()
150
  .setFooter({ text: "Wybierz opcje i kliknij Zapisz ustawienia" });
151
 
152
+ embed.addFields({
153
+ name: "🔗 Tryb wielomodelowy (Multi-Model Workflow)",
154
+ value: tempConfig.multiModelWorkflow
155
+ ? "🟢 **Włączony** (zostaną użyte wszystkie dostępne modele, indywidualny wybór jest zablokowany)"
156
+ : "🔴 **Wyłączony** (będzie używany tylko model wybrany poniżej)",
157
+ inline: false
158
  });
159
 
 
160
  for (const [contentType, models] of Object.entries(availableModels)) {
161
+ const currentSelected = tempConfig.multiModelWorkflow
162
+ ? "Wszystkie (Multi-Model Workflow)"
163
+ : (tempConfig.models[contentType] || models[0] || "Brak");
164
+
165
  embed.addFields({
166
  name: `⚙️ Model dla formatu: ${contentType.toUpperCase()}`,
167
  value: `\`${currentSelected}\``,
 
178
  new ActionRowBuilder().addComponents(channelSelect)
179
  ];
180
 
 
181
  for (const [contentType, models] of Object.entries(availableModels)) {
182
+ if (components.length >= 4) break;
183
 
184
  const currentSelected = tempConfig.models[contentType] || models[0];
185
 
 
192
  const modelSelect = new StringSelectMenuBuilder()
193
  .setCustomId(`setup_model_${contentType}`)
194
  .setPlaceholder(`Wybierz model dla ${contentType}`)
195
+ .addOptions(selectOptions)
196
+ // WYszarzenie i zablokowanie wyboru, gdy włączony jest Multi-Model Workflow
197
+ .setDisabled(tempConfig.multiModelWorkflow);
198
 
199
  components.push(new ActionRowBuilder().addComponents(modelSelect));
200
  }
201
 
202
  const buttonsRow = new ActionRowBuilder().addComponents(
203
+ new ButtonBuilder()
204
+ .setCustomId("setup_toggle_multimodel")
205
+ .setLabel(tempConfig.multiModelWorkflow ? "Tryb Wielomodelowy: WŁ" : "Tryb Wielomodelowy: WYŁ")
206
+ .setStyle(tempConfig.multiModelWorkflow ? ButtonStyle.Primary : ButtonStyle.Secondary)
207
+ .setEmoji(tempConfig.multiModelWorkflow ? "🟢" : "⚫"),
208
  new ButtonBuilder()
209
  .setCustomId("setup_save")
210
  .setLabel("Zapisz ustawienia")
 
290
 
291
  const embedColor = data.is_deepfake ? 0xFF0000 : 0x00FF00;
292
  const verdictText = data.is_deepfake ? "⚠️ Wykryto potencjalny Deepfake!" : "✅ Zawartość wydaje się oryginalna";
 
293
  const confidencePercent = (data.confidence * 100).toFixed(2);
294
 
295
  const embed = new EmbedBuilder()
296
  .setColor(embedColor)
297
  .setTitle("🛡️ Wynik Analizy Treści")
298
  .setDescription(`**Werdykt:** ${verdictText}`)
 
 
 
 
 
 
299
  .setTimestamp()
300
  .setFooter({ text: "Deepfake Detection Service", iconURL: client.user.displayAvatarURL() });
301
 
302
+ // DYNAMICZNE RENDEROWANIE EMBEDA: Multi-Model vs Single-Model
303
+ if (data.details && data.details.length > 0) {
304
+ // Widok dla Multi-Modelu: ładnie listujemy każdy model
305
+ embed.addFields({ name: "📊 Średnia pewność systemu", value: `\`${confidencePercent}%\``, inline: false });
306
+
307
+ for (const detail of data.details) {
308
+ const detailBar = getProgressBar(detail.confidence, detail.is_deepfake);
309
+ const statusText = detail.is_deepfake ? "🟥 FAKE" : "🟩 REAL";
310
+ const pct = (detail.confidence * 100).toFixed(1);
311
+
312
+ embed.addFields({
313
+ name: `🤖 Model: ${detail.model.split("/").pop()}`, // skracamy ścieżkę modelu
314
+ value: `Werdykt: **${statusText}** (Pewność: \`${pct}%\`)\n${detailBar}`,
315
+ inline: false
316
+ });
317
+ }
318
+ } else {
319
+ // Standardowy widok dla pojedynczego modelu
320
+ const progressBar = getProgressBar(data.confidence, data.is_deepfake);
321
+ embed.addFields(
322
+ { name: "Pewność modelu", value: `\`${confidencePercent}%\` \n${progressBar}` },
323
+ { name: "Użyty model", value: `\`${data.used_model}\``, inline: true }
324
+ );
325
+ }
326
+
327
+ // Dodatkowe pola wspólne
328
+ embed.addFields(
329
+ { name: "Czas przetwarzania", value: `\`${data.analysis_time.toFixed(3)}s\``, inline: true },
330
+ { name: "Format danych", value: `\`${data.content_type.toUpperCase()}\``, inline: true }
331
+ );
332
+
333
  const buttonRow = new ActionRowBuilder().addComponents(
334
  new ButtonBuilder()
335
  .setCustomId("modelCorrect")
 
392
  });
393
  }
394
 
395
+ if (currentConfig.multiModelWorkflow === undefined) {
396
+ currentConfig.multiModelWorkflow = false;
397
+ }
398
+
399
  for (const [contentType, models] of Object.entries(availableModels)) {
400
  if (!currentConfig.models[contentType] && models.length > 0) {
401
  currentConfig.models[contentType] = models[0];
 
485
  body: JSON.stringify({
486
  active_text_model: tempSession.config.models?.text || "none",
487
  active_image_model: tempSession.config.models?.image || "none",
488
+ log_channel_id: tempSession.config.logChannelId || null,
489
+ multi_model_workflow: tempSession.config.multiModelWorkflow || false
490
  })
491
  });
492
 
 
603
  await sendLogToDiscord(interaction.guild, logEmbed);
604
  }
605
  }
606
+
607
+ if (interaction.customId === "setup_toggle_multimodel") {
608
+ const tempSession = activeSetupSessions.get(guildId);
609
+ if (tempSession) {
610
+ tempSession.config.multiModelWorkflow = !tempSession.config.multiModelWorkflow;
611
+ await interaction.update(generateSetupView(tempSession.config, tempSession.availableModels));
612
+ }
613
+ }
614
  }
615
  });
616