File size: 9,021 Bytes
b387e01 155974e b387e01 155974e 01cbc60 155974e 39cfcd1 b387e01 39cfcd1 b387e01 4c5fda9 39cfcd1 48d657f 39cfcd1 48d657f 7063659 27d974a 39cfcd1 27d974a 39cfcd1 27d974a 7063659 3b361f1 39cfcd1 3b361f1 f82f941 39cfcd1 f82f941 2f3072b 8f6d79d 312c142 0435b8d 8f6d79d 74087c2 312c142 ffb5352 0435b8d d06306b bf1d8e6 74087c2 d06306b 74087c2 d06306b 74087c2 0435b8d 74087c2 c212805 a2ed062 74087c2 67f284e 31bce5e 0435b8d 67f284e 0435b8d 24a79a8 0435b8d 24a79a8 b543594 0435b8d 24a79a8 0435b8d b543594 0435b8d f0b5a65 24a79a8 f0b5a65 b543594 24a79a8 b543594 aeab924 c1447cb c29fb5e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 | """Application config."""
from __future__ import annotations
import os
from pathlib import Path
from dotenv import load_dotenv
ROOT_DIR = Path(__file__).resolve().parent.parent
load_dotenv(ROOT_DIR / ".env")
APP_DIR = Path(__file__).resolve().parent
APP_TITLE = os.environ.get("APP_TITLE", "ZuZu Writer")
HOST = os.environ.get("HOST", "0.0.0.0")
PORT = int(os.environ.get("PORT", "7860") or "7860")
SPACY_MODEL = os.environ.get("SPACY_MODEL", "en_core_web_sm")
MAX_CHARS = max(
100_000,
int(os.environ.get("MAX_CHARS", "1000000") or "1000000"),
)
# Optional Supabase authentication and UI quotas.
SUPABASE_URL = (os.environ.get("SUPABASE_URL") or "").rstrip("/")
SUPABASE_ANON_KEY = os.environ.get("SUPABASE_ANON_KEY") or ""
SUPABASE_SERVICE_ROLE_KEY = os.environ.get("SUPABASE_SERVICE_ROLE_KEY") or ""
SUPABASE_JWT_SECRET = os.environ.get("SUPABASE_JWT_SECRET") or ""
AUTH_ENABLED = bool(SUPABASE_URL and SUPABASE_ANON_KEY and SUPABASE_SERVICE_ROLE_KEY)
GUEST_DAILY_REWRITES = max(
1, min(int(os.environ.get("GUEST_DAILY_REWRITES", "1000") or "1000"), 1000)
)
GUEST_MAX_WORDS = max(
20, min(int(os.environ.get("GUEST_MAX_WORDS", "100") or "100"), 5000)
)
GUEST_DAILY_WORD_CAP = max(
GUEST_MAX_WORDS,
min(
int(
os.environ.get("GUEST_DAILY_WORD_CAP", str(GUEST_MAX_WORDS))
or str(GUEST_MAX_WORDS)
),
5000,
),
)
SESSION_IDLE_MINUTES = int(
os.environ.get("SESSION_IDLE_MINUTES", "30") or "30"
)
# Self-hosted LanguageTool (ZuZu Grammar)
# Example compose service: http://languagetool:8010 | local: http://127.0.0.1:8010
LANGUAGE_TOOL_URL = (os.environ.get("LANGUAGE_TOOL_URL") or "").rstrip("/")
LANGUAGE_TOOL_LANGUAGE = os.environ.get("LANGUAGE_TOOL_LANGUAGE") or "en-US"
LANGUAGE_TOOL_TIMEOUT = float(os.environ.get("LANGUAGE_TOOL_TIMEOUT", "90") or "90")
LANGUAGE_TOOL_CHUNK_CHARS = max(
500,
min(int(os.environ.get("LANGUAGE_TOOL_CHUNK_CHARS", "1800") or "1800"), 8000),
)
# Standalone grammar endpoint cap. Rewrite grammar runs sentence-by-sentence.
GRAMMAR_MAX_CHARS = max(
1000,
min(int(os.environ.get("GRAMMAR_MAX_CHARS", "50000") or "50000"), MAX_CHARS),
)
_lt_flag = (os.environ.get("LANGUAGE_TOOL_ENABLED") or "true").strip().lower()
LANGUAGE_TOOL_ENABLED = _lt_flag not in {"0", "false", "no", "off"} and bool(LANGUAGE_TOOL_URL)
# Optional lightweight semantic safety model. It is never used for generation.
MINILM_MODEL = (
os.environ.get("MINILM_MODEL") or "sentence-transformers/all-MiniLM-L6-v2"
).strip()
# Grammar cleanup is applied only after a candidate rewrite. Skipped sentences stay exact.
_gfo = (os.environ.get("GRAMMAR_FIX_OUTPUT") or "true").strip().lower()
GRAMMAR_FIX_OUTPUT = _gfo not in {"0", "false", "no", "off"}
ENGINE_BATCH_PARAS = max(
1, min(int(os.environ.get("ENGINE_BATCH_PARAS", "20") or "20"), 100)
)
ENGINE_MIN_CONFIDENCE = max(
0.0, min(float(os.environ.get("ENGINE_MIN_CONFIDENCE", "0.55") or "0.55"), 1.0)
)
ENGINE_SAFETY_MIN = max(
0.0, min(float(os.environ.get("ENGINE_SAFETY_MIN", "0.80") or "0.80"), 1.0)
)
# MiniLM safety gate (optional). When off, phrase/lexical use classical
# collocation brakes instead of embedding similarity.
_ems = (os.environ.get("ENGINE_USE_MINILM_SAFETY") or "true").strip().lower()
ENGINE_USE_MINILM_SAFETY = _ems in {"1", "true", "yes", "on"}
# When MiniLM is off: aggressive classical wording (option 2) maximizes
# surface change per pass while keeping hard VO/sense blockers. Set false
# to restore tight anti-drift mode (options 1/3 later).
_eca = (os.environ.get("ENGINE_CLASSICAL_AGGRESSIVE") or "true").strip().lower()
ENGINE_CLASSICAL_AGGRESSIVE = _eca in {"1", "true", "yes", "on"}
# Rotate between equally-ranked structural options so repeating a request
# returns a different valid rewrite instead of the identical one.
_esv = (os.environ.get("ENGINE_STRUCTURAL_VARIATION") or "true").strip().lower()
ENGINE_STRUCTURAL_VARIATION = _esv in {"1", "true", "yes", "on"}
# Context-aware vocabulary refinement after structural/paraphrase rewrites.
# Synonym count is chosen dynamically from sentence length (and polish).
_elr = (os.environ.get("ENGINE_LEXICAL_REFINEMENT") or "true").strip().lower()
ENGINE_LEXICAL_REFINEMENT = _elr in {"1", "true", "yes", "on"}
# 0 = fully dynamic (recommended). Positive values only hard-cap that budget.
ENGINE_LEXICAL_MAX_CHANGES = max(
0,
min(int(os.environ.get("ENGINE_LEXICAL_MAX_CHANGES", "0") or "0"), 15),
)
ENGINE_LEXICAL_MIN_WSD = max(
0.0,
min(float(os.environ.get("ENGINE_LEXICAL_MIN_WSD", "0.14") or "0.14"), 1.0),
)
ENGINE_LEXICAL_MIN_ZIPF = max(
0.0,
min(float(os.environ.get("ENGINE_LEXICAL_MIN_ZIPF", "4.0") or "4.0"), 8.0),
)
ENGINE_LEXICAL_MAX_FREQUENCY_GAP = max(
0.0,
min(
float(
os.environ.get("ENGINE_LEXICAL_MAX_FREQUENCY_GAP", "0.60")
or "0.60"
),
8.0,
),
)
# Prefer everyday synonyms: allow a larger jump toward more-common words,
# but only a tiny step toward rarer/advanced wording.
ENGINE_LEXICAL_MAX_SIMPLER_GAP = max(
0.0,
min(
float(
os.environ.get("ENGINE_LEXICAL_MAX_SIMPLER_GAP", "2.5") or "2.5"
),
8.0,
),
)
ENGINE_LEXICAL_MAX_HARDER_GAP = max(
0.0,
min(
float(
os.environ.get("ENGINE_LEXICAL_MAX_HARDER_GAP", "0.15") or "0.15"
),
2.0,
),
)
_els = (os.environ.get("ENGINE_LEXICAL_PREFER_SIMPLER") or "true").strip().lower()
ENGINE_LEXICAL_PREFER_SIMPLER = _els in {"1", "true", "yes", "on"}
ENGINE_WORDNET_LEXICON = (
os.environ.get("ENGINE_WORDNET_LEXICON") or "oewn:2025"
).strip()
# Require every rewriteable sentence to change wording when a safe option exists.
_erw = (os.environ.get("ENGINE_REQUIRE_WORDING_CHANGE") or "true").strip().lower()
ENGINE_REQUIRE_WORDING_CHANGE = _erw in {"1", "true", "yes", "on"}
# Fixed cleft fallback is off by default; prefer local paraphrase instead.
_efr = (os.environ.get("ENGINE_FORCE_REWRITE") or "false").strip().lower()
ENGINE_FORCE_REWRITE = _efr in {"1", "true", "yes", "on"}
# Local CPU T5 paraphraser (primary rewrite path when the model is available).
_ep = (os.environ.get("ENGINE_PARAPHRASE") or "true").strip().lower()
ENGINE_PARAPHRASE = _ep in {"1", "true", "yes", "on"}
_epp = (os.environ.get("ENGINE_PARAPHRASE_PRIMARY") or "true").strip().lower()
ENGINE_PARAPHRASE_PRIMARY = _epp in {"1", "true", "yes", "on"}
ENGINE_PARAPHRASE_MODEL = (
os.environ.get("ENGINE_PARAPHRASE_MODEL")
or "Vamsi/T5_Paraphrase_Paws"
).strip()
ENGINE_PARAPHRASE_MIN_SIM = max(
0.0,
min(
float(os.environ.get("ENGINE_PARAPHRASE_MIN_SIM", "0.72") or "0.72"),
1.0,
),
)
ENGINE_PARAPHRASE_NUM_RETURN = max(
1,
min(int(os.environ.get("ENGINE_PARAPHRASE_NUM_RETURN", "6") or "6"), 8),
)
ENGINE_PARAPHRASE_MAX_NEW_TOKENS = max(
16,
min(
int(os.environ.get("ENGINE_PARAPHRASE_MAX_NEW_TOKENS", "72") or "72"),
128,
),
)
# Reject paraphrases whose surface form is still nearly identical to the source.
ENGINE_PARAPHRASE_MAX_SURFACE = max(
0.5,
min(
float(os.environ.get("ENGINE_PARAPHRASE_MAX_SURFACE", "0.70") or "0.70"),
0.99,
),
)
# When primary paraphrase is on, require at least this much surface distance.
ENGINE_PARAPHRASE_MIN_DIVERGENCE = max(
0.0,
min(
float(
os.environ.get("ENGINE_PARAPHRASE_MIN_DIVERGENCE", "0.30") or "0.30"
),
0.6,
),
)
# Phrase-level rewrite: verb–object / modifier–noun spans via WordNet + optional T5.
_ephr = (os.environ.get("ENGINE_PHRASE_REWRITE") or "true").strip().lower()
ENGINE_PHRASE_REWRITE = _ephr in {"1", "true", "yes", "on"}
# 0 = dynamic (1 normally, 2 with polish). Positive values hard-cap changes.
ENGINE_PHRASE_MAX_CHANGES = max(
0,
min(int(os.environ.get("ENGINE_PHRASE_MAX_CHANGES", "0") or "0"), 4),
)
ENGINE_PHRASE_MIN_SIM = max(
0.0,
min(float(os.environ.get("ENGINE_PHRASE_MIN_SIM", "0.74") or "0.74"), 1.0),
)
# T5 span paraphrase is optional; WordNet phrase swaps are the default wording path.
_ephr_t5 = (os.environ.get("ENGINE_PHRASE_USE_T5") or "false").strip().lower()
ENGINE_PHRASE_USE_T5 = _ephr_t5 in {"1", "true", "yes", "on"}
# Keep rewritten length near the source (trims only when clearly bloated).
_epl = (os.environ.get("ENGINE_PRESERVE_LENGTH") or "true").strip().lower()
ENGINE_PRESERVE_LENGTH = _epl in {"1", "true", "yes", "on"}
# Split long clauses during structural fallback for more surface change.
_esl = (os.environ.get("ENGINE_SPLIT_LONG") or "true").strip().lower()
ENGINE_SPLIT_LONG = _esl in {"1", "true", "yes", "on"}
ENGINE_SPLIT_MIN_WORDS = max(
10,
min(int(os.environ.get("ENGINE_SPLIT_MIN_WORDS", "16") or "16"), 40),
)
|