File size: 4,746 Bytes
ca293bb
0034db2
ca293bb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0034db2
ca293bb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0034db2
 
 
 
ca293bb
 
 
 
 
0034db2
ca293bb
 
 
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
# app.py — CSS LexiScan for Hugging Face Spaces
# Uses HF Secret 'SCAN' for Groq API key
# Fully self-contained: précis + vocabulary extraction + Gradio UI

import os
import json
from typing import Dict

import gradio as gr
import pandas as pd
from groq import Groq

# === Prompt Templates ===
SYSTEM_PROMPT = (
    "You are a helpful, concise assistant specialized in creating high-quality précis "
    "and teaching vocabulary for CSS aspirants. Always output valid JSON with keys 'precis' and 'difficult_words'."
)

USER_PROMPT_TEMPLATE = (
    "Here is a passage delimited by triple backticks.\n"
    "Task:\n"
    "1) Produce a concise précis (≤30% of original length).\n"
    "2) Identify up to 7 difficult/uncommon words, each with word, part_of_speech, meaning (1–2 sentences), example sentence.\n"
    "Rules:\n"
    "- Return ONLY valid JSON.\n"
    "- Avoid repetition.\n"
    "Passage:\n```{passage}```"
)

FEW_SHOT = (
    "Example input: 'The rapid expansion of renewable energy sources has altered global energy markets.'\n"
    "Example output (JSON):\n"
    "{\n"
    "  \"precis\": \"Renewable energy growth is reshaping global markets.\",\n"
    "  \"difficult_words\": [\n"
    "    {\"word\": \"renewable\", \"part_of_speech\": \"adjective\", \"meaning\": \"Able to be replenished naturally without being exhausted.\", \"example\": \"Wind and sunlight are renewable sources of power.\"}\n"
    "  ]\n"
    "}"
)

# === Helper Functions ===
def get_groq_api_key(secret_name: str = "SCAN") -> str:
    key = os.environ.get(secret_name)
    if not key:
        raise ValueError(f"No Groq API key found. Set it in HF Secrets as '{secret_name}'.")
    return key

def make_groq_client(api_key: str):
    return Groq(api_key=api_key)

def truncate(text: str, max_len: int) -> str:
    text = text.strip()
    return text if len(text) <= max_len else text[:max_len-1] + "…"

def extract_json(text: str) -> str:
    start = text.find("{")
    end = text.rfind("}")
    if start == -1 or end == -1 or end <= start:
        raise ValueError("No JSON found in model response.")
    return text[start:end+1]

def post_process(parsed: Dict) -> Dict:
    precis = parsed.get("precis", "").strip()
    difficult = parsed.get("difficult_words", [])
    seen = set()
    cleaned = []
    for item in difficult:
        word = item.get("word", "").strip()
        if not word or word.lower() in seen:
            continue
        seen.add(word.lower())
        cleaned.append({
            "word": word,
            "part_of_speech": item.get("part_of_speech", "").strip(),
            "meaning": truncate(item.get("meaning", ""), 200),
            "example": truncate(item.get("example", ""), 200)
        })
    return {"precis": truncate(precis, 2000), "difficult_words": cleaned}

def generate_precis_and_vocab(client: Groq, passage: str, model: str = "llama-3.3-70b-versatile") -> Dict:
    passage = passage.strip()
    chat_completion = client.chat.completions.create(
        messages=[
            {"role": "system", "content": SYSTEM_PROMPT},
            {"role": "user", "content": FEW_SHOT},
            {"role": "user", "content": USER_PROMPT_TEMPLATE.format(passage=passage)}
        ],
        model=model,
        temperature=0.2,
        max_tokens=800
    )
    raw_output = chat_completion.choices[0].message.content
    json_text = extract_json(raw_output)
    parsed = json.loads(json_text)
    return post_process(parsed)

# === Gradio UI ===
def build_app(client: Groq):
    def process_text(passage: str):
        if not passage.strip():
            return "Please enter a passage.", None
        try:
            parsed = generate_precis_and_vocab(client, passage)
        except Exception as e:
            return f"Error: {e}", None
        precis = parsed.get("precis", "")
        df = pd.DataFrame(parsed.get("difficult_words", []))
        return precis, df

    with gr.Blocks() as demo:
        gr.Markdown("# 📝 CSS LexiScan — Précis & Vocabulary Assistant")
        passage_input = gr.Textbox(lines=10, label="Paste your passage here:")
        generate_btn = gr.Button("Generate Précis & Vocabulary")
        precis_output = gr.Textbox(lines=5, label="Generated Précis")
        vocab_output = gr.Dataframe(
            headers=["word", "part_of_speech", "meaning", "example"], 
            label="Difficult Words"
        )
        generate_btn.click(fn=process_text, inputs=passage_input, outputs=[precis_output, vocab_output])
    return demo

# === Main ===
if __name__ == "__main__":
    api_key = get_groq_api_key("SCAN")  # <-- Uses HF secret named 'SCAN'
    client = make_groq_client(api_key)
    app = build_app(client)
    app.launch()  # HF Spaces will automatically host this