File size: 4,768 Bytes
af99ebb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a45bdbf
af99ebb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a45bdbf
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
import spaces  # MUST come before torch / transformers
import torch
import gradio as gr

from transformers import AutoModelForCausalLM, AutoTokenizer

MODEL_ID = "superwhisper/s1-mini"

SYSTEM_PROMPT = (
    "You are a text normalizer for speech-to-text transcripts. "
    "The input begins with a control line specifying the styling, structure, "
    "and context settings; clean the transcript to match those settings "
    "and output only the cleaned text."
)

tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
model = AutoModelForCausalLM.from_pretrained(
    MODEL_ID,
    torch_dtype=torch.bfloat16,
    attn_implementation="sdpa",
).to("cuda")
model.eval()


@spaces.GPU(duration=30)
def clean_transcript(
    transcript: str,
    styling: str = "semi-formal",
    structure: str = "prose",
    context: str = "general",
) -> str:
    """Clean and normalize a raw ASR transcript.

    Applies punctuation, truecasing, filler removal, and formatting based on
    the selected styling, structure, and context settings.

    Args:
        transcript: Raw ASR transcript text (disfluent, unpunctuated).
        styling: Register / formality level (casual, semi-casual, semi-formal, formal).
        structure: Output structure (prose or lists).
        context: Context mode (general or email).

    Returns:
        Cleaned, normalized transcript as plain text.
    """
    control = f"[Styling: {styling}] [Structure: {structure}] [Context: {context}]"
    messages = [
        {"role": "system", "content": SYSTEM_PROMPT},
        {"role": "user", "content": f"{control}\n{transcript}"},
    ]
    text = tokenizer.apply_chat_template(
        messages,
        tokenize=False,
        add_generation_prompt=True,
        enable_thinking=False,
    )
    inputs = tokenizer(text, return_tensors="pt").to(model.device)
    input_len = inputs.input_ids.shape[1]
    max_new = min(1024, int(input_len * 1.3) + 32)
    with torch.no_grad():
        out = model.generate(
            **inputs,
            max_new_tokens=max_new,
            do_sample=False,
        )
    generated = out[0][input_len:]
    result = tokenizer.decode(generated, skip_special_tokens=True).strip()
    return result


CSS = """
#col-container { max-width: 900px; margin: 0 auto; }
.dark .gradio-container { color: var(--body-text-color); }
"""

with gr.Blocks() as demo:
    gr.Markdown(
        "# S1-mini · ASR Transcript Cleanup\n"
        "A 0.6B Qwen3-based model that cleans raw speech-to-text transcripts: "
        "removes fillers, resolves self-corrections, adds punctuation and truecasing, "
        "and formats numbers/dates/emails — all controlled by styling and structure settings.\n\n"
        "Model: [superwhisper/s1-mini](https://huggingface.co/superwhisper/s1-mini)"
    )

    with gr.Column(elem_id="col-container"):
        with gr.Row():
            transcript_input = gr.Textbox(
                label="Raw ASR Transcript",
                placeholder="Paste raw, unpunctuated speech-to-text output here…",
                lines=6,
                scale=4,
            )
        with gr.Row():
            styling = gr.Dropdown(
                choices=["casual", "semi-casual", "semi-formal", "formal"],
                value="semi-formal",
                label="Styling",
                scale=1,
            )
            structure = gr.Dropdown(
                choices=["prose", "lists"],
                value="prose",
                label="Structure",
                scale=1,
            )
            context = gr.Dropdown(
                choices=["general", "email"],
                value="general",
                label="Context",
                scale=1,
            )
        run_btn = gr.Button("Clean Transcript", variant="primary")
        output = gr.Textbox(
            label="Cleaned Transcript",
            lines=6,
        )

    run_btn.click(
        fn=clean_transcript,
        inputs=[transcript_input, styling, structure, context],
        outputs=output,
        api_name="clean",
    )

    gr.Examples(
        examples=[
            ["so um i need to like send the the report by uh friday no wait make that thursday"],
            ["hey can you like um check the the numbers for q3 and also um make sure the the spreadsheet is up to date"],
            ["hi john um i was wondering if you could um send me the the quarterly report by end of day friday thanks"],
            ["so the meeting is at um three pm on tuesday and we need to like bring the the slides and also the budget numbers"],
        ],
        inputs=transcript_input,
        outputs=output,
        fn=clean_transcript,
        cache_examples=True,
        cache_mode="lazy",
    )

demo.launch(mcp_server=True, theme=gr.themes.Citrus(), css=CSS)