build-small-hackathon/Structured-Data-Rescuer
2
1import gradio as gr
2import json
3import os
4import csv
5import tempfile
6from huggingface_hub import InferenceClient
7
8# Replace this with your exact model repo ID
9MODEL_ID = "meta-llama/Llama-3.1-8B-Instruct"
10
11# Securely load the Hugging Face token from Space secrets
12hf_token = os.environ.get("HF_TOKEN")
13
14# Initialize the HF inference client with the token
15client = InferenceClient(model=MODEL_ID, token=hf_token)
16
17# -------------------------
18# Custom CSS Styling
19# -------------------------
20custom_css = """
21.hero-container {
22 background: linear-gradient(135deg, #6366f1 0%, #14b8a6 100%);
23 padding: 2.5rem;
24 border-radius: 20px;
25 color: white;
26 margin-bottom: 2rem;
27 box-shadow: 0 10px 25px -5px rgba(99, 102, 241, 0.2);
28}
29.hero-container h1 {
30 color: white !important;
31 font-size: 2.5rem !important;
32 font-weight: 800 !important;
33 margin-bottom: 0.5rem;
34 text-shadow: 0 2px 4px rgba(0,0,0,0.1);
35}
36.hero-container p {
37 color: rgba(255, 255, 255, 0.9) !important;
38 font-size: 1.1rem !important;
39}
40.primary-btn {
41 background: linear-gradient(90deg, #6366f1 0%, #14b8a6 100%) !important;
42 border: none !important;
43 color: white !important;
44 font-weight: 600 !important;
45 border-radius: 10px !important;
46 transition: all 0.3s ease !important;
47 padding: 12px 24px !important;
48}
49.primary-btn:hover {
50 transform: translateY(-2px);
51 box-shadow: 0 8px 20px -5px rgba(99, 102, 241, 0.4);
52}
53.secondary-btn {
54 border-radius: 10px !important;
55 font-weight: 600 !important;
56}
57.feedback-card {
58 border-left: 4px solid #6366f1;
59 background-color: rgba(99, 102, 241, 0.05);
60}
61"""
62
63# -------------------------
64# Helper & Extraction Logic
65# -------------------------
66def generate_kpi_html(structured_data):
67 """Generates modern, responsive KPI metrics cards dynamically based on JSON data."""
68 if not structured_data or "error" in structured_data:
69 return """
70 <div style='display: flex; justify-content: center; align-items: center; height: 100px; border: 2px dashed var(--border-color-primary, #e5e7eb); border-radius: 12px; color: var(--text-color-subdued, #9ca3af);'>
71 Await extraction to generate KPI metrics...
72 </div>
73 """
74
75 cards_html = ""
76 if isinstance(structured_data, dict):
77 # Pick the top 4 attributes to show as metrics
78 items = list(structured_data.items())[:4]
79 for key, val in items:
80 # Clean up the key label
81 display_key = str(key).replace("_", " ").replace("-", " ").title()
82
83 # Format list value representation
84 if isinstance(val, list):
85 display_val = ", ".join(map(str, val))
86 else:
87 display_val = str(val)
88
89 # Truncate if string is too long for the card layout
90 if len(display_val) > 40:
91 display_val = display_val[:37] + "..."
92
93 # Dynamic highlight accents based on field types
94 accent_color = "#6366f1" # default Indigo
95 if any(x in display_key.lower() for x in ["price", "total", "amount", "cost", "revenue", "budget"]):
96 accent_color = "#10b981" # Emerald for cash/costs
97 elif any(x in display_key.lower() for x in ["date", "deadline", "due", "time"]):
98 accent_color = "#f59e0b" # Amber for dates/reminders
99 elif any(x in display_key.lower() for x in ["status", "priority", "importance"]):
100 accent_color = "#ef4444" # Crimson for status/alerts
101
102 cards_html += f"""
103 <div style='background: var(--body-background-fill, #ffffff); padding: 1rem; border-radius: 12px; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05); border: 1px solid var(--border-color-primary, #e5e7eb); border-left: 5px solid {accent_color}; min-width: 140px; flex: 1;'>
104 <div style='font-size: 0.7rem; color: var(--text-color-subdued, #6b7280); text-transform: uppercase; font-weight: 700; letter-spacing: 0.05em; margin-bottom: 0.25rem;'>{display_key}</div>
105 <div style='font-size: 1.05rem; color: var(--body-text-color, #111827); font-weight: 800; word-break: break-word;'>{display_val}</div>
106 </div>
107 """
108 elif isinstance(structured_data, list):
109 # Summary KPI for array data structures
110 cards_html = f"""
111 <div style='background: var(--body-background-fill, #ffffff); padding: 1rem; border-radius: 12px; box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05); border: 1px solid var(--border-color-primary, #e5e7eb); border-left: 5px solid #6366f1; min-width: 140px; flex: 1;'>
112 <div style='font-size: 0.7rem; color: var(--text-color-subdued, #6b7280); text-transform: uppercase; font-weight: 700; letter-spacing: 0.05em; margin-bottom: 0.25rem;'>Total Records Found</div>
113 <div style='font-size: 1.5rem; color: var(--body-text-color, #111827); font-weight: 800;'>{len(structured_data)}</div>
114 </div>
115 """
116
117 return f"""
118 <div style='display: flex; flex-wrap: wrap; gap: 0.75rem; margin-bottom: 1rem; width: 100%;'>
119 {cards_html}
120 </div>
121 """
122
123def extract_data(raw_text, fields_to_extract):
124 if not hf_token:
125 err_state = {"error": "HF_TOKEN secret is missing. Please add your Hugging Face Access Token to the Space Secrets."}
126 return err_state, [["Error", "HF_TOKEN missing"]], generate_kpi_html(err_state)
127
128 if not raw_text.strip() or not fields_to_extract.strip():
129 err_state = {"error": "Please provide both raw text and fields to extract."}
130 return err_state, [["Error", "Incomplete inputs"]], generate_kpi_html(err_state)
131
132 # Construct the system instruction
133 system_prompt = (
134 "You are an expert data extraction assistant. Your job is to extract specific "
135 "information from messy, unstructured text and output it as clean, valid JSON.\n"
136 "Rules:\n"
137 "1. Only extract the fields requested.\n"
138 "2. If a field is not found in the text, return 'null' for that field.\n"
139 "3. Output ONLY a raw JSON object. Do not include markdown formatting, backticks, or conversational text."
140 )
141
142 user_prompt = f"Fields to extract:\n{fields_to_extract}\n\nUnstructured Text:\n{raw_text}"
143
144 messages = [
145 {"role": "system", "content": system_prompt},
146 {"role": "user", "content": user_prompt}
147 ]
148
149 try:
150 # Call the model via the chat completion API
151 response = client.chat_completion(
152 messages=messages,
153 max_tokens=1024,
154 temperature=0.1,
155 )
156
157 output_text = response.choices[0].message.content.strip()
158
159 # Fallback: Safely strip markdown code blocks without regular expressions
160 cleaned_text = output_text
161 if cleaned_text.startswith("```"):
162 lines = cleaned_text.splitlines()
163 if len(lines) >= 2:
164 if lines[0].startswith("```"):
165 lines = lines[1:]
166 if lines and lines[-1].strip() == "```":
167 lines = lines[:-1]
168 cleaned_text = "\n".join(lines).strip()
169
170 # Parse the text into an actual JSON dictionary
171 structured_data = json.loads(cleaned_text)
172
173 # Convert JSON structure to a displayable 2D list for the Table view
174 table_data = []
175 if isinstance(structured_data, dict):
176 for k, v in structured_data.items():
177 val_str = ", ".join(map(str, v)) if isinstance(v, list) else str(v)
178 table_data.append([k, val_str])
179 elif isinstance(structured_data, list):
180 for idx, item in enumerate(structured_data):
181 table_data.append([f"Item {idx + 1}", str(item)])
182
183 return structured_data, table_data, generate_kpi_html(structured_data)
184
185 except json.JSONDecodeError:
186 error_dict = {
187 "error": "The model failed to return valid JSON. It returned this instead:",
188 "raw_output": output_text
189 }
190 return error_dict, [["Error", "Invalid JSON parsed"]], generate_kpi_html(error_dict)
191 except Exception as e:
192 error_msg = str(e)
193 if "model_not_found" in error_msg or "does not exist" in error_msg:
194 err_dict = {
195 "error": f"The model '{MODEL_ID}' was not found on Hugging Face.",
196 "troubleshooting": [
197 "1. Check your Hugging Face repo for typos (case-sensitive).",
198 "2. Verify HF_TOKEN secret read permissions.",
199 "3. GGUF or LoRA adapter models are not directly supported by the Serverless API."
200 ]
201 }
202 return err_dict, [["Connection Error", "Model Not Found"]], generate_kpi_html(err_dict)
203 err_state = {"error": error_msg}
204 return err_state, [["Error", error_msg]], generate_kpi_html(err_state)
205
206def generate_csv(json_data):
207 """Converts the JSON output into a downloadable CSV file."""
208 if not json_data or "error" in json_data:
209 return None
210
211 if isinstance(json_data, dict):
212 data_list = [json_data]
213 elif isinstance(json_data, list):
214 data_list = json_data
215 else:
216 return None
217
218 # Create a secure temporary file to hold the CSV
219 temp_dir = tempfile.mkdtemp()
220 csv_path = os.path.join(temp_dir, "extracted_data.csv")
221
222 try:
223 with open(csv_path, 'w', newline='', encoding='utf-8') as f:
224 headers = set()
225 for item in data_list:
226 if isinstance(item, dict):
227 headers.update(item.keys())
228 headers = list(headers)
229
230 if not headers:
231 return None
232
233 writer = csv.DictWriter(f, fieldnames=headers)
234 writer.writeheader()
235
236 for item in data_list:
237 if isinstance(item, dict):
238 flat_item = {k: (str(v) if isinstance(v, (list, dict)) else v) for k, v in item.items()}
239 writer.writerow(flat_item)
240
241 return csv_path
242 except Exception as e:
243 return None
244
245# -------------------------
246# Build the Gradio UI
247# -------------------------
248with gr.Blocks(theme=gr.themes.Soft(), css=custom_css) as demo:
249
250 # Styled Header Block
251 with gr.HTML(elem_classes="hero-container"):
252 gr.Markdown(
253 f"""
254 # ๐ The Data Rescuer
255 Turn messy logs, disorganized lists, automated transcripts, and raw OCR scripts into highly structured business-ready assets โ powered by `{MODEL_ID}`.
256 """
257 )
258
259 with gr.Row():
260 # Left Column: Inputs
261 with gr.Column(scale=1):
262 raw_input = gr.Textbox(
263 label="1. Paste Unstructured Text",
264 placeholder="Paste your messy meeting notes, emails, or raw text here...",
265 lines=12
266 )
267
268 schema_input = gr.Textbox(
269 label="2. What fields do you want to extract?",
270 placeholder="e.g., Company Name, Contact Person, Deadline, Action Items (list)",
271 lines=3
272 )
273
274 extract_btn = gr.Button("๐ Extract Structured Data", variant="primary", elem_classes="primary-btn")
275
276 # Right Column: Multi-view Output Panels
277 with gr.Column(scale=1):
278 # Dynamic HTML summary cards (Dashboard metrics style)
279 kpi_output = gr.HTML(
280 value="""
281 <div style='display: flex; justify-content: center; align-items: center; height: 100px; border: 2px dashed var(--border-color-primary, #e5e7eb); border-radius: 12px; color: var(--text-color-subdued, #9ca3af);'>
282 Await extraction to generate KPI metrics...
283 </div>
284 """
285 )
286
287 with gr.Tabs():
288 with gr.TabItem("๐ Structured Table"):
289 table_output = gr.Dataframe(
290 headers=["Field Name", "Extracted Value"],
291 datatype=["str", "str"],
292 interactive=False,
293 wrap=True
294 )
295 with gr.TabItem("๐ Raw JSON Tree"):
296 json_output = gr.JSON(label="JSON Object")
297
298 # Action controls below outputs
299 with gr.Row():
300 export_btn = gr.Button("๐พ Build Export File", variant="secondary", elem_classes="secondary-btn")
301 csv_output = gr.File(label="Ready for Download", interactive=False)
302
303 # -------------------------
304 # Examples Panel
305 # -------------------------
306 gr.Markdown("### Try it out with these examples:")
307 gr.Examples(
308 examples=[
309 [
310 "Hey guys, quick recap of today's sync. Sarah is going to handle the frontend React components by next Tuesday. John, you need to fix the database migration issue before Friday. Also, our client 'Acme Corp' wants the final delivery by October 15th.",
311 "Task Owner, Task Description, Deadline, Client Name"
312 ],
313 [
314 "Invoice #99214. From: BlueTech Software. To: Jane Doe. Items: 1x Server Maintenance ($500), 2x Cloud Storage ($100 each). Total due: $700. Please pay by end of month.",
315 "Invoice Number, Sender, Recipient, Items (list of names and prices), Total Amount"
316 ]
317 ],
318 inputs=[raw_input, schema_input],
319 label="Click an example to populate the inputs"
320 )
321
322 # -------------------------
323 # Event Connections
324 # -------------------------
325 # 1. Connect extraction button to the Table View, JSON Tree, and KPI output
326 extract_btn.click(
327 fn=extract_data,
328 inputs=[raw_input, schema_input],
329 outputs=[json_output, table_output, kpi_output]
330 )
331
332 # 2. Connect CSV generation
333 export_btn.click(
334 fn=generate_csv,
335 inputs=[json_output],
336 outputs=[csv_output]
337 )
338
339# Launch the app
340if __name__ == "__main__":
341 demo.launch()
342 