GitHub Actions commited on
Commit ·
307bbbc
1
Parent(s): 89be117
Deploy backend from GitHub 453f0e24ea5bcdd0ce6030af03ea7622ccc75dcb
Browse files- backend/app/api/routes.py +42 -22
- backend/app/db/firestore.py +8 -13
backend/app/api/routes.py
CHANGED
|
@@ -116,8 +116,8 @@ async def _analyze_text(text: str, user_id: str | None = None) -> dict:
|
|
| 116 |
)
|
| 117 |
saved = await save_document(COLLECTION, doc.id, doc.to_dict())
|
| 118 |
result["id"] = doc.id if saved else text_hash
|
| 119 |
-
except Exception
|
| 120 |
-
|
| 121 |
result["id"] = text_hash
|
| 122 |
|
| 123 |
return result
|
|
@@ -195,11 +195,11 @@ async def assist_text(request: AssistRequest):
|
|
| 195 |
"""Call Groq API to propose a rewrite of flagged text to reduce AI threat indicators.
|
| 196 |
|
| 197 |
The AI Fixer should:
|
| 198 |
-
1. Make text sound
|
| 199 |
-
2. Remove any harmful, offensive, or extreme language
|
| 200 |
-
3.
|
| 201 |
-
4.
|
| 202 |
-
5.
|
| 203 |
"""
|
| 204 |
if not settings.GROQ_API_KEY:
|
| 205 |
raise HTTPException(
|
|
@@ -214,18 +214,24 @@ async def assist_text(request: AssistRequest):
|
|
| 214 |
logs: list[str] = []
|
| 215 |
logs.append(f"Preparing rewrite request for Groq model: {settings.GROQ_MODEL}")
|
| 216 |
|
| 217 |
-
#
|
| 218 |
prompt = (
|
| 219 |
-
"You are
|
| 220 |
-
"
|
| 221 |
-
"
|
| 222 |
-
"
|
| 223 |
-
"
|
| 224 |
-
"
|
| 225 |
-
"
|
| 226 |
-
"
|
| 227 |
-
"
|
| 228 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 229 |
)
|
| 230 |
|
| 231 |
try:
|
|
@@ -241,19 +247,33 @@ async def assist_text(request: AssistRequest):
|
|
| 241 |
"model": settings.GROQ_MODEL,
|
| 242 |
"messages": [{"role": "user", "content": prompt}],
|
| 243 |
"max_tokens": 8192,
|
| 244 |
-
"temperature": 0.
|
|
|
|
|
|
|
|
|
|
| 245 |
},
|
| 246 |
)
|
| 247 |
response.raise_for_status()
|
| 248 |
data = response.json()
|
| 249 |
fixed_text = data["choices"][0]["message"]["content"].strip()
|
| 250 |
|
| 251 |
-
#
|
| 252 |
-
|
| 253 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 254 |
lines = fixed_text.split('\n')
|
| 255 |
if len(lines) > 1:
|
| 256 |
fixed_text = '\n'.join(lines[1:]).strip()
|
|
|
|
|
|
|
|
|
|
| 257 |
|
| 258 |
logs.append("Groq model returned rewritten text successfully.")
|
| 259 |
except httpx.TimeoutException:
|
|
|
|
| 116 |
)
|
| 117 |
saved = await save_document(COLLECTION, doc.id, doc.to_dict())
|
| 118 |
result["id"] = doc.id if saved else text_hash
|
| 119 |
+
except Exception:
|
| 120 |
+
# Silently skip Firestore - it's optional
|
| 121 |
result["id"] = text_hash
|
| 122 |
|
| 123 |
return result
|
|
|
|
| 195 |
"""Call Groq API to propose a rewrite of flagged text to reduce AI threat indicators.
|
| 196 |
|
| 197 |
The AI Fixer should:
|
| 198 |
+
1. Make text sound EXTREMELY human and natural with imperfections
|
| 199 |
+
2. Remove any harmful, offensive, or extreme language completely
|
| 200 |
+
3. Use casual, everyday language with natural variations
|
| 201 |
+
4. Add human quirks: contractions, filler words, casual tone
|
| 202 |
+
5. Maintain the original meaning and key information
|
| 203 |
"""
|
| 204 |
if not settings.GROQ_API_KEY:
|
| 205 |
raise HTTPException(
|
|
|
|
| 214 |
logs: list[str] = []
|
| 215 |
logs.append(f"Preparing rewrite request for Groq model: {settings.GROQ_MODEL}")
|
| 216 |
|
| 217 |
+
# Enhanced prompt specifically designed to evade AI detection
|
| 218 |
prompt = (
|
| 219 |
+
"You are helping someone rewrite text to sound completely natural and human-written. "
|
| 220 |
+
"Your goal is to make it indistinguishable from casual, authentic human writing.\n\n"
|
| 221 |
+
"CRITICAL RULES:\n"
|
| 222 |
+
"1. Write like a real person texting or chatting - use informal, conversational tone\n"
|
| 223 |
+
"2. Add natural imperfections: contractions (don't, it's, there's), filler words (just, really, kinda)\n"
|
| 224 |
+
"3. Remove ALL harmful, offensive, hateful, or toxic language - replace with friendly alternatives\n"
|
| 225 |
+
"4. Vary sentence length - mix short and long sentences naturally\n"
|
| 226 |
+
"5. Use simple everyday vocabulary - avoid fancy or technical words\n"
|
| 227 |
+
"6. Add casual expressions and natural flow (like, you know, I mean)\n"
|
| 228 |
+
"7. If it's a greeting, keep it super short and casual (max 10 words)\n"
|
| 229 |
+
"8. Don't be overly polite or formal - write like you're texting a friend\n"
|
| 230 |
+
"9. Include minor typos or informal grammar occasionally (but readable)\n"
|
| 231 |
+
"10. Keep the core message but make it sound spontaneous and unedited\n\n"
|
| 232 |
+
"IMPORTANT: Return ONLY the rewritten text. No explanations, no 'Here is', no meta-commentary. "
|
| 233 |
+
"Just the natural-sounding version.\n\n"
|
| 234 |
+
f"Text to make human-like:\n{request.text}"
|
| 235 |
)
|
| 236 |
|
| 237 |
try:
|
|
|
|
| 247 |
"model": settings.GROQ_MODEL,
|
| 248 |
"messages": [{"role": "user", "content": prompt}],
|
| 249 |
"max_tokens": 8192,
|
| 250 |
+
"temperature": 0.9, # Increased to 0.9 for more natural variation
|
| 251 |
+
"top_p": 0.95, # Added to increase diversity
|
| 252 |
+
"frequency_penalty": 0.3, # Reduce repetition
|
| 253 |
+
"presence_penalty": 0.3, # Encourage natural variation
|
| 254 |
},
|
| 255 |
)
|
| 256 |
response.raise_for_status()
|
| 257 |
data = response.json()
|
| 258 |
fixed_text = data["choices"][0]["message"]["content"].strip()
|
| 259 |
|
| 260 |
+
# Aggressively remove any meta-commentary
|
| 261 |
+
# Common AI patterns to strip out
|
| 262 |
+
meta_phrases = [
|
| 263 |
+
"here is", "here's", "sure", "certainly", "of course",
|
| 264 |
+
"i've rewritten", "i rewrote", "rewritten version",
|
| 265 |
+
"here you go", "this is", "below is"
|
| 266 |
+
]
|
| 267 |
+
|
| 268 |
+
first_line = fixed_text.split('\n')[0].lower()
|
| 269 |
+
if any(phrase in first_line for phrase in meta_phrases):
|
| 270 |
+
# Skip the meta-commentary line
|
| 271 |
lines = fixed_text.split('\n')
|
| 272 |
if len(lines) > 1:
|
| 273 |
fixed_text = '\n'.join(lines[1:]).strip()
|
| 274 |
+
# Remove quotes if the AI wrapped it
|
| 275 |
+
if fixed_text.startswith('"') and fixed_text.endswith('"'):
|
| 276 |
+
fixed_text = fixed_text[1:-1].strip()
|
| 277 |
|
| 278 |
logs.append("Groq model returned rewritten text successfully.")
|
| 279 |
except httpx.TimeoutException:
|
backend/app/db/firestore.py
CHANGED
|
@@ -44,7 +44,8 @@ def init_firebase() -> None:
|
|
| 44 |
global _db, _enabled
|
| 45 |
|
| 46 |
if not settings.FIREBASE_CREDENTIALS_JSON:
|
| 47 |
-
|
|
|
|
| 48 |
return
|
| 49 |
|
| 50 |
try:
|
|
@@ -60,10 +61,10 @@ def init_firebase() -> None:
|
|
| 60 |
_enabled = True
|
| 61 |
project = settings.FIREBASE_PROJECT_ID or cred_dict.get("project_id", "")
|
| 62 |
logger.info("Firebase + Firestore initialised", project=project)
|
| 63 |
-
except Exception
|
|
|
|
| 64 |
_db = None
|
| 65 |
_enabled = False
|
| 66 |
-
logger.warning("Firebase init failed – Firestore disabled", error=str(e))
|
| 67 |
|
| 68 |
|
| 69 |
# ---- Public helpers --------------------------------------------------------
|
|
@@ -75,11 +76,8 @@ async def save_document(collection: str, doc_id: str, data: dict) -> bool:
|
|
| 75 |
try:
|
| 76 |
_db.collection(collection).document(doc_id).set(data)
|
| 77 |
return True
|
| 78 |
-
except google.auth.exceptions.RefreshError
|
| 79 |
-
|
| 80 |
-
return False
|
| 81 |
-
except Exception as e:
|
| 82 |
-
logger.warning("Firestore save_document failed", collection=collection, doc_id=doc_id, error=str(e))
|
| 83 |
return False
|
| 84 |
|
| 85 |
|
|
@@ -90,11 +88,8 @@ async def get_document(collection: str, doc_id: str) -> dict | None:
|
|
| 90 |
try:
|
| 91 |
doc = _db.collection(collection).document(doc_id).get()
|
| 92 |
return doc.to_dict() if doc.exists else None
|
| 93 |
-
except google.auth.exceptions.RefreshError
|
| 94 |
-
|
| 95 |
-
return None
|
| 96 |
-
except Exception as e:
|
| 97 |
-
logger.warning("Firestore get_document failed", collection=collection, doc_id=doc_id, error=str(e))
|
| 98 |
return None
|
| 99 |
|
| 100 |
|
|
|
|
| 44 |
global _db, _enabled
|
| 45 |
|
| 46 |
if not settings.FIREBASE_CREDENTIALS_JSON:
|
| 47 |
+
# Silently disable if not configured - no need to spam logs
|
| 48 |
+
_enabled = False
|
| 49 |
return
|
| 50 |
|
| 51 |
try:
|
|
|
|
| 61 |
_enabled = True
|
| 62 |
project = settings.FIREBASE_PROJECT_ID or cred_dict.get("project_id", "")
|
| 63 |
logger.info("Firebase + Firestore initialised", project=project)
|
| 64 |
+
except Exception:
|
| 65 |
+
# Silently fail - Firestore is optional, no need to spam logs
|
| 66 |
_db = None
|
| 67 |
_enabled = False
|
|
|
|
| 68 |
|
| 69 |
|
| 70 |
# ---- Public helpers --------------------------------------------------------
|
|
|
|
| 76 |
try:
|
| 77 |
_db.collection(collection).document(doc_id).set(data)
|
| 78 |
return True
|
| 79 |
+
except (google.auth.exceptions.RefreshError, Exception):
|
| 80 |
+
# Silently fail - no logs, just return False
|
|
|
|
|
|
|
|
|
|
| 81 |
return False
|
| 82 |
|
| 83 |
|
|
|
|
| 88 |
try:
|
| 89 |
doc = _db.collection(collection).document(doc_id).get()
|
| 90 |
return doc.to_dict() if doc.exists else None
|
| 91 |
+
except (google.auth.exceptions.RefreshError, Exception):
|
| 92 |
+
# Silently fail - no logs, just return None
|
|
|
|
|
|
|
|
|
|
| 93 |
return None
|
| 94 |
|
| 95 |
|