Update app.py
Browse files
app.py
CHANGED
@@ -1,24 +1,29 @@
|
|
1 |
import os, re, difflib
|
2 |
from typing import List
|
3 |
-
import
|
4 |
-
from
|
5 |
|
6 |
-
#
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
|
|
|
|
|
12 |
def load_model():
|
13 |
-
global
|
14 |
-
if
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
|
|
|
|
|
|
22 |
SENTINEL_OPEN, SENTINEL_CLOSE = "§§KEEP_OPEN§§", "§§KEEP_CLOSE§§"
|
23 |
URL_RE = re.compile(r'(https?://\S+)')
|
24 |
CODE_RE = re.compile(r'`{1,3}[\s\S]*?`{1,3}')
|
@@ -41,59 +46,64 @@ def restore(text: str, protected: List[str]):
|
|
41 |
text = re.sub(rf"{SENTINEL_OPEN}(\d+){SENTINEL_CLOSE}", unwrap, text)
|
42 |
return text.replace(SENTINEL_OPEN, "").replace(SENTINEL_CLOSE, "")
|
43 |
|
44 |
-
#
|
45 |
SYSTEM = (
|
46 |
-
"You are an expert editor. Humanize the user's text: vary sentence length,
|
47 |
-
"
|
48 |
-
"Do
|
49 |
-
"Keep tone
|
50 |
)
|
51 |
|
52 |
-
def
|
53 |
user = (
|
54 |
f"Tone: {tone}. Region: {region} English. Reading level: {level}. "
|
55 |
f"Humanization intensity: {intensity} (10 strongest).\n\n"
|
56 |
-
f"Rewrite this text. Keep
|
57 |
)
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
|
62 |
-
|
|
|
|
|
63 |
)
|
64 |
|
65 |
-
def
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
|
|
|
|
|
|
|
|
|
|
71 |
)
|
72 |
-
return
|
73 |
-
|
74 |
-
def diff_ratio(a, b): return difflib.SequenceMatcher(None, a, b).ratio()
|
75 |
|
76 |
-
|
|
|
77 |
protected_text, bag = protect(text)
|
78 |
-
|
79 |
-
prompt = apply_chat_template(load_model()[0], msgs)
|
80 |
|
81 |
-
# pass 1 (conservative), pass 2 (stronger) if
|
82 |
draft = generate_once(prompt, temperature=0.35)
|
83 |
if diff_ratio(protected_text, draft) > 0.97:
|
84 |
draft = generate_once(prompt, temperature=0.9)
|
85 |
|
86 |
-
draft = draft.replace("—","-")
|
87 |
final = restore(draft, bag)
|
88 |
|
89 |
-
# ensure protected spans survived
|
90 |
for i, span in enumerate(bag):
|
91 |
marker = f"{SENTINEL_OPEN}{i}{SENTINEL_CLOSE}"
|
92 |
if marker in protected_text and span not in final:
|
93 |
final = final.replace(marker, span)
|
94 |
return final
|
95 |
|
96 |
-
#
|
97 |
def ui_humanize(text, tone, region, level, intensity):
|
98 |
return humanize_core(text, tone, region, level, int(intensity))
|
99 |
|
@@ -107,9 +117,9 @@ demo = gr.Interface(
|
|
107 |
gr.Slider(1, 10, value=6, step=1, label="Humanization intensity"),
|
108 |
],
|
109 |
outputs=gr.Textbox(label="Humanized"),
|
110 |
-
title="NoteCraft Humanizer (
|
111 |
description="REST: POST /api/predict/ with { data: [text,tone,region,level,intensity] }",
|
112 |
).queue()
|
113 |
|
114 |
if __name__ == "__main__":
|
115 |
-
demo.launch()
|
|
|
1 |
import os, re, difflib
|
2 |
from typing import List
|
3 |
+
import gradio as gr
|
4 |
+
from ctransformers import AutoModelForCausalLM
|
5 |
|
6 |
+
# ---------------- Model (GGUF on CPU) ----------------
|
7 |
+
# These defaults work on HF free CPU Spaces.
|
8 |
+
REPO_ID = os.getenv("LLAMA_GGUF_REPO", "bartowski/Llama-3.2-3B-Instruct-GGUF")
|
9 |
+
FILENAME = os.getenv("LLAMA_GGUF_FILE", "Llama-3.2-3B-Instruct-Q5_0.gguf") # if not found, use Q8_0
|
10 |
+
MODEL_TYPE = "llama"
|
11 |
|
12 |
+
# lazy-load for fast startup
|
13 |
+
_llm = None
|
14 |
def load_model():
|
15 |
+
global _llm
|
16 |
+
if _llm is None:
|
17 |
+
_llm = AutoModelForCausalLM.from_pretrained(
|
18 |
+
REPO_ID,
|
19 |
+
model_file=FILENAME,
|
20 |
+
model_type=MODEL_TYPE,
|
21 |
+
gpu_layers=0,
|
22 |
+
context_length=8192,
|
23 |
+
)
|
24 |
+
return _llm
|
25 |
+
|
26 |
+
# ---------------- Protect / restore ----------------
|
27 |
SENTINEL_OPEN, SENTINEL_CLOSE = "§§KEEP_OPEN§§", "§§KEEP_CLOSE§§"
|
28 |
URL_RE = re.compile(r'(https?://\S+)')
|
29 |
CODE_RE = re.compile(r'`{1,3}[\s\S]*?`{1,3}')
|
|
|
46 |
text = re.sub(rf"{SENTINEL_OPEN}(\d+){SENTINEL_CLOSE}", unwrap, text)
|
47 |
return text.replace(SENTINEL_OPEN, "").replace(SENTINEL_CLOSE, "")
|
48 |
|
49 |
+
# ---------------- Prompting (Llama 3.x chat template) ----------------
|
50 |
SYSTEM = (
|
51 |
+
"You are an expert editor. Humanize the user's text: improve flow, vary sentence length, "
|
52 |
+
"split run-ons, replace stiff phrasing with natural alternatives, and preserve meaning. "
|
53 |
+
"Do NOT alter anything wrapped by §§KEEP_OPEN§§<id>§§KEEP_CLOSE§§ (citations, URLs, numbers, code). "
|
54 |
+
"Keep the requested tone and region. No em dashes—use simple punctuation."
|
55 |
)
|
56 |
|
57 |
+
def build_prompt(text: str, tone: str, region: str, level: str, intensity: int) -> str:
|
58 |
user = (
|
59 |
f"Tone: {tone}. Region: {region} English. Reading level: {level}. "
|
60 |
f"Humanization intensity: {intensity} (10 strongest).\n\n"
|
61 |
+
f"Rewrite this text. Keep markers intact:\n\n{text}"
|
62 |
)
|
63 |
+
# Llama 3.x chat format
|
64 |
+
return (
|
65 |
+
"<|begin_of_text|><|start_header_id|>system<|end_header_id|>\n"
|
66 |
+
f"{SYSTEM}\n"
|
67 |
+
"<|eot_id|><|start_header_id|>user<|end_header_id|>\n"
|
68 |
+
f"{user}\n"
|
69 |
+
"<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n"
|
70 |
)
|
71 |
|
72 |
+
def diff_ratio(a: str, b: str) -> float:
|
73 |
+
return difflib.SequenceMatcher(None, a, b).ratio()
|
74 |
+
|
75 |
+
def generate_once(prompt: str, temperature: float, max_new: int = 768) -> str:
|
76 |
+
llm = load_model()
|
77 |
+
out = llm(
|
78 |
+
prompt,
|
79 |
+
temperature=temperature,
|
80 |
+
top_p=0.95,
|
81 |
+
max_new_tokens=max_new,
|
82 |
+
stop=["<|eot_id|>"]
|
83 |
)
|
84 |
+
return out.strip()
|
|
|
|
|
85 |
|
86 |
+
# ---------------- Main humanizer ----------------
|
87 |
+
def humanize_core(text: str, tone: str, region: str, level: str, intensity: int):
|
88 |
protected_text, bag = protect(text)
|
89 |
+
prompt = build_prompt(protected_text, tone, region, level, intensity)
|
|
|
90 |
|
91 |
+
# pass 1 (conservative), pass 2 (stronger) if too similar
|
92 |
draft = generate_once(prompt, temperature=0.35)
|
93 |
if diff_ratio(protected_text, draft) > 0.97:
|
94 |
draft = generate_once(prompt, temperature=0.9)
|
95 |
|
96 |
+
draft = draft.replace("—", "-")
|
97 |
final = restore(draft, bag)
|
98 |
|
99 |
+
# ensure all protected spans survived
|
100 |
for i, span in enumerate(bag):
|
101 |
marker = f"{SENTINEL_OPEN}{i}{SENTINEL_CLOSE}"
|
102 |
if marker in protected_text and span not in final:
|
103 |
final = final.replace(marker, span)
|
104 |
return final
|
105 |
|
106 |
+
# ---------------- Gradio UI (and REST at /api/predict/) ----------------
|
107 |
def ui_humanize(text, tone, region, level, intensity):
|
108 |
return humanize_core(text, tone, region, level, int(intensity))
|
109 |
|
|
|
117 |
gr.Slider(1, 10, value=6, step=1, label="Humanization intensity"),
|
118 |
],
|
119 |
outputs=gr.Textbox(label="Humanized"),
|
120 |
+
title="NoteCraft Humanizer (Llama-3.2-3B-Instruct)",
|
121 |
description="REST: POST /api/predict/ with { data: [text,tone,region,level,intensity] }",
|
122 |
).queue()
|
123 |
|
124 |
if __name__ == "__main__":
|
125 |
+
demo.launch()
|