kumar-aditya commited on
Commit
7b3f919
·
verified ·
1 Parent(s): a040250

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +319 -0
app.py ADDED
@@ -0,0 +1,319 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from html import escape
5
+
6
+ import gradio as gr
7
+
8
+ from graph import run_pipeline
9
+
10
+
11
+ def _format_value(value) -> str:
12
+ if value is None:
13
+ return "null"
14
+ if isinstance(value, str):
15
+ return value
16
+ return json.dumps(value, ensure_ascii=True)
17
+
18
+
19
+ def _render_report(report_dict: dict) -> str:
20
+ suites = report_dict.get("suites", [])
21
+ if not suites:
22
+ return '<div class="cases-empty">No test cases generated.</div>'
23
+
24
+ sections = []
25
+ for suite in suites:
26
+ student_id = suite.get("student_id", "?")
27
+ cases = suite.get("cases", [])
28
+ copy_lines = [f"Student {student_id}"]
29
+ for idx, case in enumerate(cases, start=1):
30
+ category = case.get("category", "Other")
31
+ desc = case.get("explanation", "").strip()
32
+ input_value = _format_value(case.get("input"))
33
+ copy_lines.append(f"Test Case {idx}")
34
+ copy_lines.append(f"Category: {category}")
35
+ if desc:
36
+ copy_lines.append(f"Description: {desc}")
37
+ copy_lines.append(f"Input: {input_value}")
38
+ copy_lines.append("")
39
+ copy_text = "\n".join(copy_lines).strip()
40
+ sections.append('<div class="student-block">')
41
+ sections.append(
42
+ '<div class="student-head">'
43
+ f'<div class="student-title">Student {escape(str(student_id))}</div>'
44
+ '<button class="copy-btn" type="button">Copy student cases</button>'
45
+ "</div>"
46
+ )
47
+ sections.append(f'<textarea class="copy-source">{escape(copy_text)}</textarea>')
48
+ grouped = {}
49
+ for case in cases:
50
+ category = case.get("category", "Other")
51
+ grouped.setdefault(category, []).append(case)
52
+ for category, items in grouped.items():
53
+ sections.append(f'<div class="category-title">{escape(category)}</div>')
54
+ sections.append('<div class="case-grid">')
55
+ for idx, case in enumerate(items, start=1):
56
+ desc = escape(case.get("explanation", ""))
57
+ input_value = escape(_format_value(case.get("input")))
58
+ expected_value = escape(_format_value(case.get("expected")))
59
+ sections.append(
60
+ '<div class="case-card">'
61
+ f'<div class="case-title">Test Case {idx}</div>'
62
+ f'<div class="case-label">Description</div>'
63
+ f'<div class="case-text">{desc}</div>'
64
+ f'<div class="case-label">Input</div>'
65
+ f'<pre class="case-block">{input_value}</pre>'
66
+ f'<div class="case-label">Expected Output</div>'
67
+ f'<pre class="case-block">{expected_value}</pre>'
68
+ "</div>"
69
+ )
70
+ sections.append("</div>")
71
+ sections.append("</div>")
72
+ return "".join(sections)
73
+
74
+
75
+ def generate_tests(
76
+ problem: str,
77
+ description: str,
78
+ constraints: str,
79
+ code: str,
80
+ language: str,
81
+ student_count: int,
82
+ per_category: int,
83
+ ):
84
+ if not problem.strip():
85
+ return '{\n "error": "Problem statement is required."\n}'
86
+ report = run_pipeline(
87
+ problem=problem,
88
+ description=description,
89
+ constraints=constraints,
90
+ code=code,
91
+ language=language,
92
+ student_count=student_count,
93
+ per_category=per_category,
94
+ )
95
+ report_dict = report.model_dump()
96
+ html = _render_report(report_dict)
97
+ return html, json.dumps(report_dict, indent=2)
98
+
99
+
100
+ with gr.Blocks(title="SpecTest-LLM") as demo:
101
+ gr.Markdown(
102
+ """
103
+ <style>
104
+ :root {
105
+ --bg-dark: #0b0f19;
106
+ --card-dark: #151c2b;
107
+ --text-primary: #eef2ff;
108
+ --text-muted: #9aa4b2;
109
+ --accent: #3b82f6;
110
+ --stroke: #243049;
111
+ }
112
+ .cases-wrap {
113
+ border: 1px solid var(--stroke);
114
+ border-radius: 12px;
115
+ padding: 16px;
116
+ background: var(--bg-dark);
117
+ max-height: 520px;
118
+ overflow-y: auto;
119
+ }
120
+ .student-title {
121
+ font-size: 18px;
122
+ font-weight: 700;
123
+ color: var(--text-primary);
124
+ margin: 12px 0 8px;
125
+ }
126
+ .student-block {
127
+ border: 1px solid var(--stroke);
128
+ border-radius: 12px;
129
+ padding: 14px;
130
+ background: #0f1523;
131
+ margin-bottom: 16px;
132
+ }
133
+ .student-head {
134
+ display: flex;
135
+ align-items: center;
136
+ justify-content: space-between;
137
+ gap: 12px;
138
+ }
139
+ .copy-btn {
140
+ background: var(--accent);
141
+ color: white;
142
+ border: none;
143
+ border-radius: 8px;
144
+ padding: 6px 12px;
145
+ font-size: 12px;
146
+ cursor: pointer;
147
+ }
148
+ .copy-btn.copied {
149
+ background: #16a34a;
150
+ }
151
+ .copy-source {
152
+ position: absolute;
153
+ left: -9999px;
154
+ height: 1px;
155
+ width: 1px;
156
+ opacity: 0;
157
+ }
158
+ .category-title {
159
+ font-size: 16px;
160
+ font-weight: 600;
161
+ color: var(--text-primary);
162
+ margin: 16px 0 10px;
163
+ }
164
+ .case-grid {
165
+ display: grid;
166
+ grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
167
+ gap: 16px;
168
+ margin-bottom: 12px;
169
+ }
170
+ .case-card {
171
+ border: 1px solid var(--stroke);
172
+ border-radius: 12px;
173
+ padding: 14px;
174
+ background: var(--card-dark);
175
+ }
176
+ .case-title {
177
+ font-size: 14px;
178
+ font-weight: 700;
179
+ color: var(--text-primary);
180
+ margin-bottom: 8px;
181
+ }
182
+ .case-label {
183
+ font-size: 12px;
184
+ font-weight: 600;
185
+ color: var(--text-muted);
186
+ margin: 10px 0 6px;
187
+ text-transform: uppercase;
188
+ letter-spacing: 0.04em;
189
+ }
190
+ .case-text {
191
+ font-size: 13px;
192
+ color: var(--text-primary);
193
+ }
194
+ .case-block {
195
+ background: #1f2937;
196
+ border-radius: 8px;
197
+ padding: 10px;
198
+ color: #e5e7eb;
199
+ font-size: 12px;
200
+ line-height: 1.4;
201
+ white-space: pre-wrap;
202
+ word-break: break-word;
203
+ }
204
+ .cases-empty {
205
+ color: var(--text-muted);
206
+ }
207
+ </style>
208
+ <script>
209
+ function bindCopyButtons(root) {
210
+ const container = root || document;
211
+ container.querySelectorAll(".copy-btn").forEach((btn) => {
212
+ if (btn.dataset.bound) return;
213
+ btn.dataset.bound = "true";
214
+ btn.addEventListener("click", async () => {
215
+ const block = btn.closest(".student-block");
216
+ const source = block ? block.querySelector(".copy-source") : null;
217
+ if (!source) return;
218
+ const text = source.value || source.textContent || "";
219
+ try {
220
+ await navigator.clipboard.writeText(text);
221
+ btn.classList.add("copied");
222
+ const original = btn.textContent;
223
+ btn.textContent = "Copied";
224
+ setTimeout(() => {
225
+ btn.classList.remove("copied");
226
+ btn.textContent = original;
227
+ }, 1200);
228
+ } catch (err) {
229
+ const range = document.createRange();
230
+ range.selectNodeContents(source);
231
+ const selection = window.getSelection();
232
+ selection.removeAllRanges();
233
+ selection.addRange(range);
234
+ document.execCommand("copy");
235
+ selection.removeAllRanges();
236
+ }
237
+ });
238
+ });
239
+ }
240
+ const observer = new MutationObserver(() => bindCopyButtons(document));
241
+ observer.observe(document.body, { childList: true, subtree: true });
242
+ window.addEventListener("load", () => bindCopyButtons(document));
243
+ </script>
244
+ """
245
+ )
246
+ gr.Markdown(
247
+ "# SpecTest-LLM\n"
248
+ "Generate explainable, multi-category test cases using a multi-agent "
249
+ "LangGraph pipeline."
250
+ )
251
+
252
+ with gr.Row():
253
+ problem = gr.Textbox(
254
+ label="Problem Statement",
255
+ placeholder="Paste the full problem statement...",
256
+ lines=8,
257
+ )
258
+ code = gr.Textbox(
259
+ label="Source Code (optional)",
260
+ placeholder="Paste code for analysis (optional)",
261
+ lines=8,
262
+ )
263
+
264
+ description = gr.Textbox(
265
+ label="User Description",
266
+ placeholder="Add any extra context or summary...",
267
+ lines=4,
268
+ )
269
+ constraints = gr.Textbox(
270
+ label="User Constraints",
271
+ placeholder="Example: 1 <= n <= 1e5, values can be negative",
272
+ lines=3,
273
+ )
274
+
275
+ language = gr.Dropdown(
276
+ label="Language",
277
+ choices=["python", "cpp", "java", "javascript", "go", "other"],
278
+ value="python",
279
+ )
280
+
281
+ with gr.Row():
282
+ student_count = gr.Slider(
283
+ minimum=1,
284
+ maximum=50,
285
+ value=5,
286
+ step=1,
287
+ label="Number of Students",
288
+ )
289
+ per_category = gr.Slider(
290
+ minimum=2,
291
+ maximum=3,
292
+ value=2,
293
+ step=1,
294
+ label="Cases per Category",
295
+ )
296
+
297
+ run_btn = gr.Button("Generate Test Cases")
298
+
299
+ gr.Markdown("## Generated Test Cases")
300
+ cases_html = gr.HTML('<div class="cases-wrap"></div>')
301
+ output = gr.Code(label="Generated Report (JSON)", language="json")
302
+
303
+ run_btn.click(
304
+ generate_tests,
305
+ inputs=[
306
+ problem,
307
+ description,
308
+ constraints,
309
+ code,
310
+ language,
311
+ student_count,
312
+ per_category,
313
+ ],
314
+ outputs=[cases_html, output],
315
+ )
316
+
317
+
318
+ if __name__ == "__main__":
319
+ demo.launch()