File size: 5,582 Bytes
d2457b8
b86b8a9
49e9bd8
 
d2457b8
14f649b
92da610
49e9bd8
 
 
 
 
 
 
 
92da610
ae07758
 
 
d8e4400
d2457b8
ae07758
 
d2457b8
3e0ae5e
ae07758
 
d2457b8
 
 
 
49e9bd8
 
 
 
 
 
 
d2457b8
 
 
 
 
3e0ae5e
49e9bd8
ae07758
7234f68
14f649b
 
7234f68
ae07758
3e0ae5e
 
d2457b8
 
 
 
 
3e0ae5e
14f649b
 
49e9bd8
 
92da610
49e9bd8
3e0ae5e
49e9bd8
61eea38
33718d6
61eea38
49e9bd8
33718d6
49e9bd8
 
33718d6
49e9bd8
d2457b8
3e0ae5e
49e9bd8
 
 
 
 
 
 
 
f96ba11
49e9bd8
d2457b8
b547608
49e9bd8
 
 
 
d2457b8
1a92a4c
 
d2457b8
49e9bd8
 
 
 
 
 
 
 
 
1a92a4c
 
49e9bd8
 
 
1a92a4c
49e9bd8
1a92a4c
 
49e9bd8
d2457b8
49e9bd8
1a92a4c
49e9bd8
 
 
 
 
 
 
 
 
f96ba11
49e9bd8
 
 
d2457b8
 
3e0ae5e
49e9bd8
92da610
 
3e0ae5e
49e9bd8
 
 
d8e4400
49e9bd8
f96ba11
61eea38
1a92a4c
 
3bfeef1
49e9bd8
3e0ae5e
49e9bd8
b547608
3bfeef1
49e9bd8
b07a6cc
3e0ae5e
 
49e9bd8
 
61eea38
1a92a4c
b547608
f96ba11
49e9bd8
 
 
 
1a92a4c
49e9bd8
1a92a4c
49e9bd8
b547608
2009574
92da610
3e0ae5e
49e9bd8
 
 
92da610
 
 
 
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
import os
import gradio as gr
from transformers import pipeline
import torch
from openai import OpenAI
from pypdf import PdfReader


device = "cuda" if torch.cuda.is_available() else "cpu"

vision_pipe = pipeline(
    "image-to-text",
    model="nlpconnect/vit-gpt2-image-captioning",
    device=0 if device == "cuda" else -1
)

api_key = os.environ.get("YUNWU_API_KEY")
if not api_key:
    raise RuntimeError("YUNWU_API_KEY not set in Space secrets.")

client = OpenAI(
    api_key=api_key,
    base_url="https://yunwu.ai/v1"
)


def call_llm(prompt, model="deepseek-chat", temperature=0.2, max_tokens=512):
    resp = client.chat.completions.create(
        model=model,
        messages=[
            {
                "role": "system",
                "content": (
                    "You are an academic figure interpretation assistant. "
                    "Write accurate, natural-sounding text for scientific use."
                ),
            },
            {"role": "user", "content": prompt},
        ],
        temperature=temperature,
        max_tokens=max_tokens,
    )
    return resp.choices[0].message.content.strip()


def extract_pdf_snippet(pdf_path, max_chars=2500):
    if not pdf_path:
        return ""
    try:
        reader = PdfReader(pdf_path)
        texts = []
        for page in reader.pages:
            txt = page.extract_text() or ""
            texts.append(txt)
            if sum(len(t) for t in texts) > max_chars * 1.5:
                break
        full = " ".join(texts)
        return full[:max_chars]
    except Exception:
        return ""


def analyze_figure(image, style, pdf_path):
    if image is None:
        return None, "Please upload a figure first.", "", ""

    vision_raw = vision_pipe(image)[0]["generated_text"]
    pdf_context = extract_pdf_snippet(pdf_path)

    step1_prompt = f"""
You are looking at a scientific figure from a paper.

A vision model produced this rough description:
\"\"\"{vision_raw}\"\"\"

Paper context (may be noisy or incomplete):
\"\"\"{pdf_context}\"\"\"

Task:
Write a clear, paper-style explanation (4–6 sentences) of what the figure shows.
- Describe what is compared on the x-axis/panels and what the y-axis measures.
- Summarize the main pattern/trend across conditions.
- Do NOT invent exact numbers, statistics, or p-values.
- Do NOT restate the full experimental design.

Write in formal academic English, but keep it readable.
"""
    step1_text = call_llm(step1_prompt, model="deepseek-chat", max_tokens=420)

    step2_prompt = f"""
You are helping a student annotate this scientific figure for a presentation.

Rough visual description:
\"\"\"{vision_raw}\"\"\"

Paper context:
\"\"\"{pdf_context}\"\"\"

Give practical suggestions for how to annotate the figure directly on the image.
Constraints:
- Output 4–6 bullet points.
- Use plain hyphen bullets only (no numbering, no bold, no asterisks, no markdown headings).
- Sound like a helpful human TA, not an AI.
- Focus on labels, arrows, callouts, grouping, legend clarity, and highlighting key contrasts.
"""
    step2_text = call_llm(step2_prompt, model="deepseek-chat", temperature=0.4, max_tokens=260)
    step2_text = step2_text.replace("**", "").replace("*", "").strip()

    style_map = {
        "formal": "formal but still plain language, suitable for a report",
        "fluency": "smooth, narrative, easy to speak aloud in a presentation",
        "simple": "very simple words for quick student notes"
    }
    style_instruction = style_map.get(style, style_map["fluency"])

    step3_prompt = f"""
You are writing a plain-language explanation of the figure for a student.

Paper-style meaning:
\"\"\"{step1_text}\"\"\"

Paper context:
\"\"\"{pdf_context}\"\"\"

Now paraphrase/explain the figure in {style_instruction}.
- 3–5 sentences.
- Keep it accurate to the paper-style meaning above.
- No numbers or p-values unless they are explicitly visible in the figure.
- Make it easy to reuse in slides or homework.
"""
    step3_text = call_llm(step3_prompt, model="deepseek-chat", temperature=0.5, max_tokens=240)

    return image, step1_text, step2_text, step3_text


with gr.Blocks() as demo:
    gr.Markdown("## ChartSmith – AI Figure Explainer (multi-model workflow)")

    with gr.Row():
        with gr.Column():
            img_in = gr.Image(
                type="pil",
                label="Upload your scientific figure (screenshot is fine)"
            )

            style = gr.Radio(
                ["formal", "fluency", "simple"],
                value="fluency",
                label="Explanation style for Step 3"
            )

            pdf_in = gr.File(
                label="Upload the paper PDF (optional, for context)",
                type="filepath"
            )

            run_btn = gr.Button("Run workflow", variant="primary")

        with gr.Column():
            preview_img = gr.Image(label="Figure preview")

            step1_box = gr.Textbox(
                label="Step 1: Explanation of what the figure shows (paper-style)",
                lines=8
            )

            step2_box = gr.Textbox(
                label="Step 2: Suggestions for annotating the figure",
                lines=7
            )

            step3_box = gr.Textbox(
                label="Step 3: Plain-language explanation (style-adapted)",
                lines=6
            )

    run_btn.click(
        analyze_figure,
        inputs=[img_in, style, pdf_in],
        outputs=[preview_img, step1_box, step2_box, step3_box],
    )

if __name__ == "__main__":
    demo.launch()