Gradi02 commited on
Commit
8a8571b
·
unverified ·
2 Parent(s): 90c3e037365bef

Merge pull request #7 from Tobkubos/backend-setup

Browse files
backend/app/api/routes.py CHANGED
@@ -5,6 +5,7 @@ from app.models.schemas import (
5
  AnalysisRequest,
6
  AnalysisResponse,
7
  ErrorResponse,
 
8
  HealthResponse,
9
  TextAnalysisRequest,
10
  ImageAnalysisRequest,
@@ -13,8 +14,9 @@ from app.services.download import download_file
13
  from app.services.text_analyzer import analyze_text
14
  from app.services.image_analyzer import analyze_image
15
  from app.core.config import get_settings
16
- from app.utils.exceptions import DeepfakeDetectionError
17
  from app.core.limiter import limiter
 
18
 
19
  logger = logging.getLogger(__name__)
20
 
@@ -56,8 +58,35 @@ async def health_check() -> HealthResponse:
56
  version=settings.APP_VERSION,
57
  available_models=settings.AVAILABLE_MODELS,
58
  supported_types=list(settings.AVAILABLE_MODELS.keys()),
 
59
  )
60
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
  @router.post(
62
  "/analyze",
63
  response_model=AnalysisResponse,
 
5
  AnalysisRequest,
6
  AnalysisResponse,
7
  ErrorResponse,
8
+ GuildConfigSchema,
9
  HealthResponse,
10
  TextAnalysisRequest,
11
  ImageAnalysisRequest,
 
14
  from app.services.text_analyzer import analyze_text
15
  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
 
 
58
  version=settings.APP_VERSION,
59
  available_models=settings.AVAILABLE_MODELS,
60
  supported_types=list(settings.AVAILABLE_MODELS.keys()),
61
+ models_status=models_status,
62
  )
63
 
64
+ # Endpoint do zapisywania konfiguracji (wywoływany przez bota)
65
+ @router.post("/guilds/{guild_id}/setup", tags=["Setup"])
66
+ 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":
73
+ if payload.active_text_model not in allowed_text_models:
74
+ raise HTTPException(
75
+ status_code=400,
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)
82
+
83
+ logger.info(f"Zapisano nową konfigurację dla serwera Discord {guild_id}")
84
+ return {
85
+ "status": "success",
86
+ "message": f"Konfiguracja dla serwera {guild_id} została zapisana.",
87
+ "config": config_dict
88
+ }
89
+
90
  @router.post(
91
  "/analyze",
92
  response_model=AnalysisResponse,
backend/app/config_manager.py ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import os
3
+ import logging
4
+ from typing import Dict, Any, Optional
5
+
6
+ logger = logging.getLogger(__name__)
7
+ CONFIG_FILE = "guild_configs.json"
8
+
9
+ def _load_all_configs() -> Dict[str, Dict[str, Any]]:
10
+ """Odczytuje konfiguracje wszystkich serwerów z pliku."""
11
+ if os.path.exists(CONFIG_FILE):
12
+ try:
13
+ with open(CONFIG_FILE, "r", encoding="utf-8") as f:
14
+ return json.load(f)
15
+ except Exception as e:
16
+ logger.error(f"Błąd podczas odczytu pliku konfiguracji: {e}")
17
+ return {}
18
+
19
+ def _save_all_configs(configs: Dict[str, Dict[str, Any]]):
20
+ """Zapisuje konfiguracje na dysk."""
21
+ try:
22
+ with open(CONFIG_FILE, "w", encoding="utf-8") as f:
23
+ json.dump(configs, f, indent=4, ensure_ascii=False)
24
+ except Exception as e:
25
+ logger.error(f"Błąd podczas zapisu pliku konfiguracji: {e}")
26
+
27
+ def save_guild_config(guild_id: str, config_data: Dict[str, Any]):
28
+ """Zapisuje kompletną konfigurację dla danego serwera Discord."""
29
+ configs = _load_all_configs()
30
+ configs[guild_id] = config_data
31
+ _save_all_configs(configs)
32
+
33
+ def get_active_text_model(guild_id: str) -> Optional[str]:
34
+ """
35
+ Zwraca wybrany model dla serwera.
36
+ Zwraca None, jeśli konfiguracja nie istnieje lub model jest ustawiony na 'none'.
37
+ """
38
+ configs = _load_all_configs()
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
backend/app/core/config.py CHANGED
@@ -29,7 +29,8 @@ class Settings:
29
  LOG_FILE: Optional[str] = os.getenv("LOG_FILE", None)
30
 
31
  AVAILABLE_MODELS = {
32
- "text": ["yaya36095/xlm-roberta-text-detector"],
 
33
  "image": ["capcheck/ai-image-detection"],
34
  }
35
 
 
29
  LOG_FILE: Optional[str] = os.getenv("LOG_FILE", None)
30
 
31
  AVAILABLE_MODELS = {
32
+ "text": ["yaya36095/xlm-roberta-text-detector",
33
+ "almanach/xlmr-chatgptdetect-noisy"],
34
  "image": ["capcheck/ai-image-detection"],
35
  }
36
 
backend/app/models/schemas.py CHANGED
@@ -1,5 +1,5 @@
1
  from pydantic import BaseModel, HttpUrl, Field
2
- from typing import Union, Literal, Optional
3
 
4
 
5
  class TextAnalysisRequest(BaseModel):
@@ -100,5 +100,17 @@ class HealthResponse(BaseModel):
100
  status: str = Field(..., description="Service status")
101
  service: str = Field(..., description="Service name")
102
  version: str = Field(..., description="Service version")
103
- available_models: dict = Field(..., description="Available detector models per content type")
104
- supported_types: list = Field(..., description="Supported content types")
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  from pydantic import BaseModel, HttpUrl, Field
2
+ from typing import Union, Literal, Optional, Dict, List
3
 
4
 
5
  class TextAnalysisRequest(BaseModel):
 
100
  status: str = Field(..., description="Service status")
101
  service: str = Field(..., description="Service name")
102
  version: str = Field(..., description="Service version")
103
+ available_models: Dict[str, List[str]] = Field(
104
+ ..., description="Lista dostępnych modeli pogrupowana według typów"
105
+ )
106
+ supported_types: List[str] = Field(
107
+ ..., description="Obsługiwane typy danych"
108
+ )
109
+ models_status: Dict[str, str] = Field(
110
+ ..., description="Status gotowości handlerów dla poszczególnych typów"
111
+ )
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
backend/app/services/text_analyzer.py CHANGED
@@ -1,45 +1,76 @@
1
  import logging
2
  import time
 
3
  from typing import Dict, Any
 
 
 
4
  from transformers import pipeline
 
 
5
 
6
  logger = logging.getLogger(__name__)
7
 
 
 
8
  _text_classifier = None
9
 
10
- def _load_model():
11
- global _text_classifier
12
- if _text_classifier is None:
13
- logger.info("Loading XLM-RoBERTa text detector model...")
14
- _text_classifier = pipeline(
15
- "text-classification",
16
- model="yaya36095/xlm-roberta-text-detector",
17
- device=-1
18
- )
19
- logger.info("Text detector model loaded successfully")
 
 
 
 
 
 
 
 
 
 
 
 
20
  return _text_classifier
21
 
22
- async def analyze_text(text: str) -> Dict[str, Any]:
23
  start_time = time.time()
24
 
25
- logger.info(f"Starting text analysis, length: {len(text)} chars")
 
26
 
27
- classifier = _load_model()
 
 
 
 
 
 
 
 
 
 
28
  result = classifier(text)
29
 
30
  label = result[0]["label"]
31
  score = result[0]["score"]
32
 
33
- is_deepfake = label.lower() == "fake"
34
  confidence = score
35
-
36
  analysis_time = time.time() - start_time
37
 
38
  response = {
39
  "is_deepfake": is_deepfake,
40
  "confidence": round(confidence, 3),
41
  "analysis_time": round(analysis_time, 3),
 
42
  }
43
 
44
- logger.info(f"Text analysis completed. Result: {response}")
45
- return response
 
1
  import logging
2
  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
backend/app/utils/exceptions.py CHANGED
@@ -51,3 +51,9 @@ class UnsupportedModelError(DeepfakeDetectionError):
51
  def __init__(self, model_name: str):
52
  message = f"Detector model '{model_name}' is not supported"
53
  super().__init__(message, 400)
 
 
 
 
 
 
 
51
  def __init__(self, model_name: str):
52
  message = f"Detector model '{model_name}' is not supported"
53
  super().__init__(message, 400)
54
+
55
+ class SetupRequiredError(Exception):
56
+ """Wyjątek zgłaszany, gdy bot nie został jeszcze skonfigurowany na danym serwerze."""
57
+
58
+ def __init__(self, message: str = "Setup required"):
59
+ super().__init__(message, 500)
guildConfigs.json DELETED
@@ -1,9 +0,0 @@
1
- {
2
- "1515307986963267595": {
3
- "logChannelId": "1515373138937123007",
4
- "models": {
5
- "text": "yaya36095/xlm-roberta-text-detector",
6
- "image": "capcheck/ai-image-detection"
7
- }
8
- }
9
- }
 
 
 
 
 
 
 
 
 
 
index.js CHANGED
@@ -425,13 +425,37 @@ client.on(Events.InteractionCreate, async (interaction) => {
425
  if (interaction.customId === "setup_save") {
426
  const tempSession = activeSetupSessions.get(guildId);
427
  if (tempSession) {
428
- saveConfig(guildId, tempSession.config);
429
- activeSetupSessions.delete(guildId);
430
- await interaction.update({
431
- content: "✅ **Ustawienia zostały pomyślnie zapisane!**",
432
- embeds: [],
433
- components: []
434
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
435
  }
436
  }
437
 
 
425
  if (interaction.customId === "setup_save") {
426
  const tempSession = activeSetupSessions.get(guildId);
427
  if (tempSession) {
428
+ try {
429
+ const response = await fetch(`http://backend-api-url/guilds/${guildId}/setup`, {
430
+ method: "POST",
431
+ headers: {
432
+ "Content-Type": "application/json"
433
+ },
434
+ body: JSON.stringify({
435
+ active_text_model: tempSession.config.active_text_model || "none",
436
+ log_channel_id: tempSession.config.log_channel_id || null
437
+ })
438
+ });
439
+
440
+ if (!response.ok) {
441
+ const errData = await response.json();
442
+ throw new Error(errData.detail || "Błąd zapisu na backendzie");
443
+ }
444
+
445
+ activeSetupSessions.delete(guildId);
446
+ await interaction.update({
447
+ content: "✅ **Ustawienia zostały pomyślnie zapisane na backendzie!**",
448
+ embeds: [],
449
+ components: []
450
+ });
451
+ } catch (error) {
452
+ console.error("[SETUP ERROR]", error);
453
+ await interaction.update({
454
+ content: `❌ **Wystąpił błąd podczas zapisywania konfiguracji:** ${error.message}`,
455
+ embeds: [],
456
+ components: []
457
+ });
458
+ }
459
  }
460
  }
461