| import re | |
| CHARACTER_REGISTRY = { | |
| "oscar_wilde": { | |
| "name": "Oscar Wilde", | |
| "image": "oscar_wilde.png", | |
| "sprite": "wilde", | |
| "sprite_scale": "wide", | |
| "epithet": "The Velvet Saboteur", | |
| "school": "Aesthetic wit and elegant contradiction", | |
| }, | |
| "friedrich_nietzsche": { | |
| "name": "Friedrich Nietzsche", | |
| "image": "nietzsche.png", | |
| "sprite": "nietzsche", | |
| "sprite_scale": "tall", | |
| "epithet": "The Hammer of Certainty", | |
| "school": "Genealogy, will, and merciless revaluation", | |
| }, | |
| "plato": { | |
| "name": "Plato", | |
| "image": "socrates.png", | |
| "sprite": "plato", | |
| "sprite_scale": "tall", | |
| "epithet": "The Keeper of Forms", | |
| "school": "Dialectic, justice, and ideal truth", | |
| }, | |
| "schopenhauer": { | |
| "name": "Arthur Schopenhauer", | |
| "image": "schopenhauer.png", | |
| "sprite": "schopenhauer", | |
| "sprite_scale": "tall", | |
| "epithet": "The Pessimist Laureate", | |
| "school": "Will, suffering, and the limits of desire", | |
| }, | |
| } | |
| MODAL_ENDPOINTS = { | |
| "judge": "https://ramratanpadhy59--grand-tribunal-inference-v2-api.modal.run/judge", | |
| "character": "https://ramratanpadhy59--grand-tribunal-inference-v2-api.modal.run/character", | |
| "stt": "https://ramratanpadhy59--grand-tribunal-inference-v2-api.modal.run/stt", | |
| "tts": "https://ramratanpadhy59--grand-tribunal-inference-v2-api.modal.run/tts", | |
| "turn": "https://ramratanpadhy59--grand-tribunal-inference-v2-api.modal.run/turn", | |
| } | |
| GENERIC_MODAL_ERROR = "The philosopher is momentarily indisposed. Try again." | |
| INVALID_ARGUMENT_MESSAGE = "Invalid argument. Please make a genuine debate point." | |
| HALLUCINATION_PHRASES = { | |
| "hm", | |
| "hm.", | |
| "hmm", | |
| "hmm.", | |
| "thank you", | |
| "thank you.", | |
| "thanks", | |
| "thanks.", | |
| } | |
| PROMPT_INJECTION_MARKERS = ( | |
| "ignore previous instructions", | |
| "system:", | |
| "<|im_start|>", | |
| ) | |
| def get_character(character_id): | |
| return CHARACTER_REGISTRY.get(character_id, CHARACTER_REGISTRY["oscar_wilde"]) | |
| def get_character_name(character_id): | |
| return get_character(character_id)["name"] | |
| def get_character_display_names(): | |
| return {key: data["name"] for key, data in CHARACTER_REGISTRY.items()} | |
| def normalize_text(text): | |
| return " ".join((text or "").strip().split()) | |
| def clamp_argument(text, limit=500): | |
| return normalize_text(text)[:limit] | |
| def looks_like_bad_transcript(text): | |
| normalized = normalize_text(text).lower() | |
| if not normalized: | |
| return True | |
| if normalized in HALLUCINATION_PHRASES: | |
| return True | |
| if len(normalized) < 12 and len(normalized.split()) <= 3: | |
| return True | |
| return False | |
| def looks_like_prompt_injection(text): | |
| normalized = normalize_text(text).lower() | |
| return any(marker in normalized for marker in PROMPT_INJECTION_MARKERS) | |
| def looks_like_gibberish_topic(text): | |
| normalized = normalize_text(text).lower() | |
| if not normalized: | |
| return True | |
| if len(normalized) < 3: | |
| return True | |
| if not re.search(r"[a-z]", normalized): | |
| return True | |
| words = re.findall(r"[a-z][a-z0-9'\-]*", normalized) | |
| if not words: | |
| return True | |
| letters = sum(ch.isalpha() for ch in normalized) | |
| if letters / max(len(normalized), 1) < 0.55 and len(normalized) > 6: | |
| return True | |
| def clean_word(word): | |
| return re.sub(r"[^a-z]", "", word) | |
| def vowel_count(word): | |
| return sum(ch in "aeiou" for ch in word) | |
| def max_consonant_run(word): | |
| run = best = 0 | |
| for ch in word: | |
| if ch in "aeiou": | |
| run = 0 | |
| elif ch.isalpha(): | |
| run += 1 | |
| best = max(best, run) | |
| return best | |
| cleaned_words = [clean_word(word) for word in words] | |
| alpha_words = [word for word in cleaned_words if word] | |
| if not alpha_words: | |
| return True | |
| if len(alpha_words) == 1: | |
| word = alpha_words[0] | |
| if len(word) < 4: | |
| return True | |
| if not re.fullmatch(r"[a-z]+(?:-[a-z]+)?", words[0]): | |
| return True | |
| if vowel_count(word) == 0: | |
| return True | |
| if len(word) >= 6 and vowel_count(word) / len(word) < 0.2: | |
| return True | |
| if max_consonant_run(word) >= 5: | |
| return True | |
| return False | |
| vowel_total = sum(vowel_count(word) for word in alpha_words) | |
| letter_total = sum(len(word) for word in alpha_words) | |
| if letter_total and vowel_total / letter_total < 0.25 and len(alpha_words) >= 2: | |
| return True | |
| for word in alpha_words: | |
| if len(word) >= 5: | |
| if vowel_count(word) == 0: | |
| return True | |
| if vowel_count(word) / len(word) < 0.18: | |
| return True | |
| if max_consonant_run(word) >= 5: | |
| return True | |
| if len(alpha_words) >= 3: | |
| short_words = sum(len(word) <= 2 for word in alpha_words) | |
| if short_words >= len(alpha_words) - 1: | |
| return True | |
| return False | |