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),
)