admin08077 commited on
Commit
453fb59
·
verified ·
1 Parent(s): 6bfc829

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +202 -385
app.py CHANGED
@@ -1,398 +1,215 @@
1
  import gradio as gr
2
- import os
 
 
 
3
  import io
4
  import base64
5
- import uuid
6
- import nltk
7
- import torch
8
- import tiktoken
9
-
10
- import pytesseract
11
- import PyPDF2
12
- import cv2
13
- import tempfile
14
-
15
- from PIL import Image
16
-
17
- # Transformers
18
- from transformers import (
19
- pipeline,
20
- AutoTokenizer,
21
- AutoModelForSequenceClassification,
22
- AutoModelForSeq2SeqLM,
23
- AutoModelForQuestionAnswering,
24
- )
25
-
26
- # We will use a local whisper model for STT
27
- from transformers import WhisperProcessor, WhisperForConditionalGeneration
28
 
29
  nltk.download("punkt", quiet=True)
30
 
31
  ###############################################################################
32
- # 1. Load All Models Locally #
33
- ###############################################################################
34
-
35
- # Zero-Shot Classification Model (Topic Detection)
36
- ZSC_MODEL_NAME = "facebook/bart-large-mnli"
37
- zsc_tokenizer = AutoTokenizer.from_pretrained(ZSC_MODEL_NAME, force_download=True)
38
- zsc_model = AutoModelForSequenceClassification.from_pretrained(ZSC_MODEL_NAME, force_download=True)
39
- zero_shot_classifier = pipeline("zero-shot-classification", model=zsc_model, tokenizer=zsc_tokenizer)
40
-
41
- # Summarization Model (Chunk-based Summaries)
42
- SUM_MODEL_NAME = "facebook/bart-large-cnn"
43
- sum_tokenizer = AutoTokenizer.from_pretrained(SUM_MODEL_NAME, force_download=True)
44
- sum_model = AutoModelForSeq2SeqLM.from_pretrained(SUM_MODEL_NAME, force_download=True)
45
- summarizer = pipeline("summarization", model=sum_model, tokenizer=sum_tokenizer)
46
-
47
- # QA Model (Chunk-based QA)
48
- QA_MODEL_NAME = "deepset/roberta-base-squad2"
49
- qa_tokenizer = AutoTokenizer.from_pretrained(QA_MODEL_NAME, force_download=True)
50
- qa_model = AutoModelForQuestionAnswering.from_pretrained(QA_MODEL_NAME, force_download=True)
51
- qa_pipeline = pipeline("question-answering", model=qa_model, tokenizer=qa_tokenizer)
52
-
53
- # Speech-to-Text (STT) with tiny Whisper
54
- WHISPER_MODEL_NAME = "openai/whisper-tiny"
55
- whisper_processor = WhisperProcessor.from_pretrained(WHISPER_MODEL_NAME, force_download=True)
56
- whisper_model = WhisperForConditionalGeneration.from_pretrained(WHISPER_MODEL_NAME, force_download=True)
57
-
58
- # For real-time token usage, we'll use tiktoken (GPT-3.5 style tokenizer)
59
- encoding = tiktoken.get_encoding("cl100k_base")
60
-
61
- ###############################################################################
62
- # 2. Utility Functions #
63
- ###############################################################################
64
-
65
- def approximate_tokens(text: str) -> int:
66
- return len(encoding.encode(text))
67
-
68
- def chunk_text(text, max_chunk_size=1500):
69
- sentences = nltk.sent_tokenize(text)
70
- chunks = []
71
- current_chunk = ""
72
- current_tokens = 0
73
- for sent in sentences:
74
- sent_tokens = approximate_tokens(sent)
75
- if current_tokens + sent_tokens <= max_chunk_size:
76
- current_chunk += " " + sent
77
- current_tokens += sent_tokens
78
- else:
79
- if current_chunk:
80
- chunks.append(current_chunk.strip())
81
- current_chunk = sent
82
- current_tokens = sent_tokens
83
- if current_chunk:
84
- chunks.append(current_chunk.strip())
85
- return chunks
86
-
87
- def chunk_summarize(text):
88
- chunks = chunk_text(text, max_chunk_size=600)
89
- summaries = []
90
- for ch in chunks:
91
- out = summarizer(ch, max_length=150, min_length=40, do_sample=False)
92
- summaries.append(out[0]["summary_text"])
93
- combined = " ".join(summaries)
94
- if len(chunks) > 1:
95
- final = summarizer(combined, max_length=150, min_length=40, do_sample=False)
96
- return final[0]["summary_text"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
  else:
98
- return combined
99
-
100
- def do_topic_detection(text, candidate_labels=None):
101
- if candidate_labels is None:
102
- candidate_labels = [
103
- "legal", "technical", "creative", "finance", "sports", "health",
104
- "politics", "education", "entertainment", "business"
105
- ]
106
- chunks = chunk_text(text, max_chunk_size=512)
107
- label_counts = {}
108
- for ch in chunks:
109
- result = zero_shot_classifier(ch, candidate_labels)
110
- top_label = result["labels"][0]
111
- label_counts[top_label] = label_counts.get(top_label, 0) + 1
112
- sorted_labels = sorted(label_counts.items(), key=lambda x: x[1], reverse=True)
113
- top_labels = [lbl for (lbl, _) in sorted_labels[:3]]
114
- return top_labels
115
-
116
- def do_ocr_on_image(image_bytes):
117
- image = Image.open(io.BytesIO(image_bytes)).convert("RGB")
118
- return pytesseract.image_to_string(image)
119
-
120
- def is_page_scanned(page_text):
121
- return not page_text or len(page_text.strip()) < 20
122
-
123
- def extract_text_from_pdf(pdf_file) -> str:
124
- reader = PyPDF2.PdfReader(pdf_file)
125
- all_text = []
126
- for page_index, page in enumerate(reader.pages):
127
- extracted = page.extract_text()
128
- if not extracted or is_page_scanned(extracted):
129
- try:
130
- writer = PyPDF2.PdfWriter()
131
- writer.add_page(page)
132
- with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as temp_pdf:
133
- writer.write(temp_pdf)
134
- temp_pdf_path = temp_pdf.name
135
- from pdf2image import convert_from_path
136
- images = convert_from_path(temp_pdf_path)
137
- if images:
138
- ocr_text = pytesseract.image_to_string(images[0])
139
- all_text.append(ocr_text)
140
- else:
141
- all_text.append("")
142
- os.remove(temp_pdf_path)
143
- except Exception as e:
144
- all_text.append(f"[OCR Error on page {page_index + 1}]: {e}")
145
- if extracted:
146
- all_text.append(extracted)
147
- return "\n".join(all_text)
148
-
149
- def parse_pdf(file_obj):
150
- return extract_text_from_pdf(file_obj)
151
-
152
- def parse_json(file_obj):
153
- raw = file_obj.read()
154
- import json
155
- data = json.loads(raw)
156
- return json.dumps(data, indent=2)
157
-
158
- def parse_txt(file_obj):
159
- return file_obj.read().decode("utf-8", errors="ignore")
160
-
161
- def parse_xml(file_obj):
162
- raw = file_obj.read()
163
- return raw.decode("utf-8", errors="ignore")
164
-
165
- def parse_image(file_obj):
166
- image_bytes = file_obj.read()
167
- return do_ocr_on_image(image_bytes)
168
-
169
- def get_file_extension(filename):
170
- return filename.split(".")[-1].lower()
171
 
172
  ###############################################################################
173
- # 3. Data Structures & In-Memory Store #
174
  ###############################################################################
175
 
176
- SESSIONS = {}
177
- def create_session():
178
- return str(uuid.uuid4())
179
-
180
- ###############################################################################
181
- # 4. Multi-File Upload and Analysis #
182
- ###############################################################################
183
-
184
- def load_files(files, session_id):
185
- if session_id not in SESSIONS:
186
- SESSIONS[session_id] = {"files": {}, "chat_history": []}
187
- results = []
188
- for f in files:
189
- ext = get_file_extension(f.name)
190
- try:
191
- if ext == "pdf":
192
- content = parse_pdf(f)
193
- elif ext in ["png", "jpg", "jpeg", "bmp", "tiff"]:
194
- content = parse_image(f)
195
- elif ext == "json":
196
- content = parse_json(f)
197
- elif ext == "xml":
198
- content = parse_xml(f)
199
- elif ext == "txt":
200
- content = parse_txt(f)
201
- else:
202
- content = parse_txt(f)
203
- summary = chunk_summarize(content) if content.strip() else ""
204
- topics = do_topic_detection(content) if content.strip() else []
205
- pages_text = []
206
- if ext == "pdf":
207
- f.seek(0)
208
- reader = PyPDF2.PdfReader(f)
209
- for idx, page in enumerate(reader.pages):
210
- ptext = page.extract_text() or ""
211
- pages_text.append(ptext)
212
- else:
213
- pages_text.append(content)
214
- total_words = len(content.split())
215
- total_tokens = approximate_tokens(content)
216
- SESSIONS[session_id]["files"][f.name] = {
217
- "ext": ext,
218
- "content": content,
219
- "summary": summary,
220
- "topics": topics,
221
- "pages": pages_text,
222
- "stats": {"words": total_words, "tokens": total_tokens}
223
- }
224
- result_str = f"**File:** {f.name}\n - Words: {total_words}, Tokens: {total_tokens}\n - Topics: {topics}\n - Summary: {summary[:200]}..."
225
- results.append(result_str)
226
- except Exception as e:
227
- results.append(f"Error loading {f.name}: {e}")
228
- return "\n\n".join(results)
229
-
230
- def show_file_insights(session_id):
231
- if session_id not in SESSIONS or not SESSIONS[session_id]["files"]:
232
- return "No files uploaded yet."
233
- msg = []
234
- for fname, data in SESSIONS[session_id]["files"].items():
235
- msg.append(f"**{fname}**")
236
- msg.append(f" - Topics: {data['topics']}")
237
- msg.append(f" - Word Count: {data['stats']['words']}, Token Count: {data['stats']['tokens']}")
238
- msg.append(f" - Summary: {data['summary'][:300]}...")
239
- msg.append("-----")
240
- return "\n".join(msg)
241
-
242
- def kill_session(session_id):
243
- if session_id in SESSIONS:
244
- del SESSIONS[session_id]
245
- return "Session data cleared."
246
-
247
- ###############################################################################
248
- # 5. Reference Finder (Page-Based) #
249
- ###############################################################################
250
-
251
- def find_reference(session_id, query):
252
- if session_id not in SESSIONS:
253
- return "No session."
254
- results = []
255
- for fname, data in SESSIONS[session_id]["files"].items():
256
- pages = data["pages"]
257
- for i, ptext in enumerate(pages):
258
- if query.lower() in ptext.lower():
259
- idx = ptext.lower().find(query.lower())
260
- snippet = ptext[max(0, idx-50): idx+len(query)+50]
261
- results.append(f"{fname} (page {i+1}): ...{snippet}...")
262
- if not results:
263
- return "No references found."
264
- return "\n\n".join(results)
265
-
266
- ###############################################################################
267
- # 6. Q&A with Chunk-Based Retrieval #
268
- ###############################################################################
269
-
270
- def retrieve_relevant_chunks(session_id, question):
271
- if session_id not in SESSIONS:
272
- return []
273
- text_blocks = []
274
- for fname, data in SESSIONS[session_id]["files"].items():
275
- chs = chunk_text(data["content"], max_chunk_size=400)
276
- for ch in chs:
277
- text_blocks.append((fname, ch))
278
- question_words = set(question.lower().split())
279
- block_scores = []
280
- for (fname, block) in text_blocks:
281
- block_words = set(block.lower().split())
282
- score = len(question_words.intersection(block_words))
283
- block_scores.append((score, fname, block))
284
- block_scores.sort(key=lambda x: x[0], reverse=True)
285
- top_chunks = [bc for bc in block_scores[:3] if bc[0] > 0]
286
- return top_chunks
287
-
288
- def answer_question(session_id, question):
289
- top_chunks = retrieve_relevant_chunks(session_id, question)
290
- if not top_chunks:
291
- return "No relevant chunks found in the uploaded files."
292
- answers = []
293
- for score, fname, block in top_chunks:
294
- result = qa_pipeline({"question": question, "context": block})
295
- answers.append((result["score"], result["answer"], fname))
296
- answers.sort(key=lambda x: x[0], reverse=True)
297
- best = answers[0]
298
- return f"**Answer:** {best[1]} (confidence={best[0]:.2f}, from file={best[2]})"
299
-
300
- ###############################################################################
301
- # 7. Chat-Like Interface #
302
- ###############################################################################
303
-
304
- def chat(user_input, chat_history, session_id):
305
- if session_id not in SESSIONS:
306
- SESSIONS[session_id] = {"files": {}, "chat_history": []}
307
- # If the user wants to search for a reference:
308
- if user_input.lower().startswith("ref:"):
309
- query = user_input[4:].strip()
310
- result = find_reference(session_id, query)
311
- chat_history.append({"role": "assistant", "content": result})
312
- return "", chat_history
313
- # Process the question using QA:
314
- answer = answer_question(session_id, user_input)
315
- question_tokens = approximate_tokens(user_input)
316
- answer_tokens = approximate_tokens(answer)
317
- usage_str = f"Tokens: Q={question_tokens}, A={answer_tokens}, Total={question_tokens + answer_tokens}"
318
- full_answer = f"{answer}\n\n({usage_str})"
319
- chat_history.append({"role": "assistant", "content": full_answer})
320
- return "", chat_history
321
-
322
- ###############################################################################
323
- # 8. Voice Integration (STT Only) #
324
- ###############################################################################
325
-
326
- def transcribe_audio(audio):
327
- if audio is None:
328
- return ""
329
- filepath = audio
330
- import torchaudio
331
- speech_array, sampling_rate = torchaudio.load(filepath)
332
- inputs = whisper_processor(speech_array, sampling_rate=sampling_rate, return_tensors="pt")
333
- with torch.no_grad():
334
- generated_ids = whisper_model.generate(**inputs)
335
- transcription = whisper_processor.batch_decode(generated_ids, skip_special_tokens=True)[0]
336
- return transcription.strip()
337
-
338
- ###############################################################################
339
- # 9. Gradio Interface #
340
- ###############################################################################
341
-
342
- def reset_session():
343
- sid = create_session()
344
- return sid, "New session created."
345
-
346
  with gr.Blocks() as demo:
347
- gr.Markdown("# **All-in-One Local File QA + OCR + Summaries + Topics + Voice (STT Only)**")
348
- session_id = gr.State(create_session())
349
-
350
- with gr.Column():
351
- gr.Markdown("### 1. File Upload & Analysis")
352
- file_uploader = gr.File(file_count="multiple", label="Upload your files (PDF, images, TXT, JSON, XML)")
353
- upload_btn = gr.Button("Process Files")
354
- upload_output = gr.Markdown()
355
-
356
- def on_upload(files, sid):
357
- return load_files(files, sid)
358
- upload_btn.click(on_upload, inputs=[file_uploader, session_id], outputs=upload_output)
359
-
360
- insights_btn = gr.Button("Show File Insights")
361
- insights_output = gr.Markdown()
362
- insights_btn.click(fn=show_file_insights, inputs=[session_id], outputs=insights_output)
363
-
364
- kill_btn = gr.Button("Kill Session")
365
- kill_msg = gr.Markdown()
366
- kill_btn.click(fn=kill_session, inputs=[session_id], outputs=kill_msg)
367
-
368
- new_session_btn = gr.Button("Reset Session")
369
- new_session_out = gr.Markdown()
370
- new_session_btn.click(fn=reset_session, outputs=[session_id, new_session_out])
371
-
372
- gr.Markdown("### 2. Voice Input (STT Only)")
373
- audio_in = gr.Audio(type="filepath", label="Speak your question")
374
- stt_btn = gr.Button("Transcribe")
375
- stt_output = gr.Textbox(label="Transcribed Text")
376
- stt_btn.click(fn=transcribe_audio, inputs=[audio_in], outputs=[stt_output])
377
-
378
- gr.Markdown("### 3. Chat / Q&A (Enter text below)")
379
- # Set type="messages" for openai-style chat messages
380
- chatbot = gr.Chatbot(label="Chat History", type="messages")
381
- user_input = gr.Textbox(label="Your question (or 'ref: <term>' for reference search)", lines=2)
382
- send_btn = gr.Button("Send")
383
-
384
- def user_message(user_msg, history):
385
- history = history + [{"role": "user", "content": user_msg}]
386
- return "", history
387
- send_btn.click(fn=user_message, inputs=[user_input, chatbot], outputs=[user_input, chatbot], queue=False)
388
-
389
- def bot_message(history, sid):
390
- if not history:
391
- return []
392
- # The most recent message should be from the user.
393
- user_msg = history[-1]["content"]
394
- _, updated_history = chat(user_msg, history, sid)
395
- return updated_history
396
- send_btn.click(fn=bot_message, inputs=[chatbot, session_id], outputs=[chatbot])
397
-
398
- demo.queue().launch(server_name="0.0.0.0", server_port=7860)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import gradio as gr
2
+ from huggingface_hub import InferenceClient
3
+
4
+ import nltk
5
+ import json
6
  import io
7
  import base64
8
+ from fpdf import FPDF
9
+ from textblob import TextBlob
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
 
11
  nltk.download("punkt", quiet=True)
12
 
13
  ###############################################################################
14
+ # Hugging Face Chat Code #
15
+ ###############################################################################
16
+ """
17
+ For more information on `huggingface_hub` Inference API support, please check:
18
+ https://huggingface.co/docs/huggingface_hub/v0.22.2/en/guides/inference
19
+ """
20
+
21
+ # Initialize your Hugging Face model client
22
+ client = InferenceClient("HuggingFaceH4/zephyr-7b-beta")
23
+
24
+ def respond(
25
+ message,
26
+ history: list[tuple[str, str]],
27
+ system_message,
28
+ max_tokens,
29
+ temperature,
30
+ top_p
31
+ ):
32
+ """
33
+ Streams the chat response from the Hugging Face model.
34
+ Yields tokens as they arrive, so Gradio can display partial responses.
35
+ """
36
+ # Build the messages to send to the model
37
+ messages = [{"role": "system", "content": system_message}]
38
+
39
+ for val in history:
40
+ if val[0]:
41
+ messages.append({"role": "user", "content": val[0]})
42
+ if val[1]:
43
+ messages.append({"role": "assistant", "content": val[1]})
44
+
45
+ messages.append({"role": "user", "content": message})
46
+
47
+ # Streaming response
48
+ response = ""
49
+ for partial in client.chat_completion(
50
+ messages,
51
+ max_tokens=max_tokens,
52
+ stream=True,
53
+ temperature=temperature,
54
+ top_p=top_p,
55
+ ):
56
+ token = partial.choices[0].delta.get("content", "")
57
+ response += token
58
+ yield response
59
+
60
+ ###############################################################################
61
+ # Advanced Text Converter Code #
62
+ ###############################################################################
63
+
64
+ def text_to_sentences(text: str):
65
+ """Splits the text into sentences using nltk."""
66
+ return [s.strip() for s in nltk.sent_tokenize(text) if s.strip()]
67
+
68
+ def generate_comments(sentences):
69
+ """
70
+ Generates AI-based comments for each sentence using TextBlob
71
+ sentiment polarity as a simple demonstration.
72
+ """
73
+ comments = []
74
+ for sentence in sentences:
75
+ polarity = TextBlob(sentence).sentiment.polarity
76
+ # A simple "AI Insight" comment
77
+ comment = f"AI Insight: Polarity={polarity:.2f} for sentence: '{sentence}'"
78
+ comments.append(comment)
79
+ return comments
80
+
81
+ def convert_to_json(sentences, comments):
82
+ """Creates a JSON structure where each sentence has a comment."""
83
+ data = [{"sentence": s, "comment": c} for s, c in zip(sentences, comments)]
84
+ return json.dumps({"sentences": data}, indent=2)
85
+
86
+ def convert_to_pdf(sentences, comments):
87
+ """Creates a PDF where each sentence is listed with a comment."""
88
+ pdf = FPDF()
89
+ pdf.add_page()
90
+ pdf.set_auto_page_break(auto=True, margin=15)
91
+ pdf.set_font("Arial", size=12)
92
+
93
+ for s, c in zip(sentences, comments):
94
+ pdf.multi_cell(0, 10, f"Sentence: {s}", 0, 1)
95
+ pdf.multi_cell(0, 10, c, 0, 1)
96
+ pdf.ln(5)
97
+
98
+ pdf_buffer = io.BytesIO()
99
+ pdf.output(pdf_buffer, 'F')
100
+ pdf_buffer.seek(0)
101
+ return pdf_buffer
102
+
103
+ def process_text(user_text, output_format):
104
+ """
105
+ Main function triggered by the Gradio interface.
106
+ Returns either JSON text or a PDF file (as bytes).
107
+ """
108
+ if not user_text.strip():
109
+ return "Error: Please provide non-empty text!", None
110
+
111
+ sentences = text_to_sentences(user_text)
112
+ comments = generate_comments(sentences)
113
+
114
+ if output_format == "JSON":
115
+ # Return JSON text, no file
116
+ json_data = convert_to_json(sentences, comments)
117
+ return json_data, None
118
  else:
119
+ # Return PDF as bytes, no text
120
+ pdf_buffer = convert_to_pdf(sentences, comments)
121
+ # Gradio expects a tuple: (file_name, file_bytes)
122
+ return None, ("output.pdf", pdf_buffer.getvalue())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
 
124
  ###############################################################################
125
+ # Gradio UI Layout #
126
  ###############################################################################
127
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
128
  with gr.Blocks() as demo:
129
+ gr.Markdown("# **Combined Gradio App**")
130
+ gr.Markdown(
131
+ """
132
+ Welcome! This app has **two main tabs**:
133
+ 1. **AI Chat**: A streaming chat interface with a Hugging Face model.
134
+ 2. **Advanced Text Converter**: Convert text to JSON or PDF with AI-based sentiment comments.
135
+ """
136
+ )
137
+
138
+ with gr.Tabs():
139
+ # =========== TAB 1: AI Chat ===========
140
+ with gr.Tab("AI Chat"):
141
+ # We can simply use Gradio's ChatInterface for streaming responses
142
+ gr.Markdown("### Chat with a Hugging Face Model")
143
+ chat = gr.ChatInterface(
144
+ fn=respond,
145
+ additional_inputs=[
146
+ gr.Textbox(
147
+ value="You are a helpful AI assistant.",
148
+ label="System message",
149
+ ),
150
+ gr.Slider(
151
+ minimum=1, maximum=2048, value=512, step=1, label="Max new tokens"
152
+ ),
153
+ gr.Slider(
154
+ minimum=0.1, maximum=4.0, value=0.7, step=0.1, label="Temperature"
155
+ ),
156
+ gr.Slider(
157
+ minimum=0.1,
158
+ maximum=1.0,
159
+ value=0.95,
160
+ step=0.05,
161
+ label="Top-p (nucleus sampling)",
162
+ ),
163
+ ],
164
+ )
165
+
166
+ # =========== TAB 2: Text Converter ===========
167
+ with gr.Tab("Advanced Text Converter"):
168
+ gr.Markdown("### Convert text to JSON or PDF with AI comments")
169
+
170
+ input_text = gr.Textbox(
171
+ label="Enter your text (or paste from a file)",
172
+ placeholder="Type or paste your text here...",
173
+ lines=10,
174
+ )
175
+ format_dropdown = gr.Dropdown(
176
+ choices=["JSON", "PDF"],
177
+ value="JSON",
178
+ label="Choose output format",
179
+ )
180
+
181
+ convert_button = gr.Button("Convert")
182
+
183
+ # Two possible outputs: either JSON text or a PDF file
184
+ output_json = gr.Code(
185
+ label="JSON Output",
186
+ language="json",
187
+ visible=True,
188
+ )
189
+ output_file = gr.File(label="PDF Download")
190
+
191
+ def run_conversion(text, fmt):
192
+ """
193
+ Helper function to connect with Gradio.
194
+ Returns either a JSON string or a PDF file handle.
195
+ """
196
+ json_str, pdf_file = process_text(text, fmt)
197
+ # If we got an error or JSON
198
+ if isinstance(json_str, str) and json_str.startswith("Error:"):
199
+ return json_str, None
200
+ if fmt == "JSON":
201
+ # Show JSON in the code area, no file
202
+ return json_str, None
203
+ else:
204
+ # Return no text, but a file
205
+ return None, pdf_file
206
+
207
+ convert_button.click(
208
+ fn=run_conversion,
209
+ inputs=[input_text, format_dropdown],
210
+ outputs=[output_json, output_file],
211
+ )
212
+
213
+ # Launch the Gradio app
214
+ if __name__ == "__main__":
215
+ demo.launch()