Phong1 commited on
Commit
f0ffb0b
·
verified ·
1 Parent(s): 633cc96

Upload interface.py

Browse files
Files changed (1) hide show
  1. interface.py +279 -0
interface.py ADDED
@@ -0,0 +1,279 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import base64
2
+ import os
3
+ import tempfile
4
+ from io import BytesIO
5
+
6
+ import gradio as gr
7
+ import pandas as pd
8
+ import requests
9
+
10
+
11
+ custom_css = """
12
+ /* 1. Deep Neutral Background */
13
+ .gradio-container {
14
+ background-color: #111111 !important;
15
+ font-family: 'Inter', system-ui, sans-serif !important;
16
+ }
17
+
18
+ /* 2. High-Contrast Headers */
19
+ h1 {
20
+ font-weight: 800 !important;
21
+ background: linear-gradient(90deg, #ffffff, #a0aec0);
22
+ -webkit-background-clip: text;
23
+ -webkit-text-fill-color: transparent;
24
+ font-size: 2.5rem !important;
25
+ text-align: center;
26
+ margin-bottom: 2rem !important;
27
+ }
28
+
29
+ /* 3. Defined Cards with Subtle Borders */
30
+ .gradio-container .block {
31
+ background-color: #1a1a1a !important;
32
+ border: 1px solid #2d2d2d !important;
33
+ border-radius: 16px !important;
34
+ box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5) !important;
35
+ }
36
+
37
+ /* 4. Neon-Accent Primary Button */
38
+ button.primary {
39
+ background: #ffffff !important; /* White button on dark background is very 'chic' */
40
+ color: #000000 !important;
41
+ border: none !important;
42
+ font-weight: 700 !important;
43
+ text-transform: uppercase;
44
+ letter-spacing: 1px;
45
+ border-radius: 12px !important;
46
+ padding: 14px !important;
47
+ transition: all 0.3s ease !important;
48
+ }
49
+
50
+ button.primary:hover {
51
+ background: #e2e8f0 !important;
52
+ transform: scale(1.02);
53
+ }
54
+
55
+ /* 5. Readable Inputs & Labels */
56
+ input, textarea, .gr-dropdown {
57
+ background-color: #262626 !important;
58
+ border: 1px solid #3f3f3f !important;
59
+ color: #ffffff !important;
60
+ border-radius: 10px !important;
61
+ }
62
+
63
+ label span {
64
+ color: #a0aec0 !important; /* Muted silver for labels */
65
+ font-weight: 500 !important;
66
+ text-transform: uppercase;
67
+ font-size: 0.8rem !important;
68
+ }
69
+
70
+ /* 6. Fix for readable text in Output components */
71
+ .focusable, .selectable {
72
+ color: #ffffff !important;
73
+ }
74
+ """
75
+
76
+
77
+ API_BASE_URL = os.getenv("MULTIMODAL_API_BASE_URL", "http://127.0.0.1:7861")
78
+ EXTRACTION_API_URL = os.getenv(
79
+ "MULTIMODAL_API_URL",
80
+ f"{API_BASE_URL.rstrip('/')}/information_extraction/",
81
+ )
82
+ MAPPING_API_URL = os.getenv(
83
+ "MULTIMODAL_MAPPING_API_URL",
84
+ f"{API_BASE_URL.rstrip('/')}/mapping/",
85
+ )
86
+
87
+
88
+ def process_zip(zip_file, employee_code, debug=False):
89
+ if zip_file is None:
90
+ raise gr.Error("Please upload a ZIP file.")
91
+ if not employee_code or not employee_code.strip():
92
+ raise gr.Error("Please enter an employee code.")
93
+
94
+ with open(zip_file, "rb") as file_obj:
95
+ response = requests.post(
96
+ EXTRACTION_API_URL,
97
+ files={
98
+ "file": (
99
+ os.path.basename(zip_file),
100
+ file_obj,
101
+ "application/zip",
102
+ )
103
+ },
104
+ data={
105
+ "employee_code": employee_code.strip(),
106
+ "debug": str(debug).lower(),
107
+ },
108
+ timeout=300,
109
+ )
110
+
111
+ try:
112
+ payload = response.json()
113
+ except ValueError as exc:
114
+ raise gr.Error("The API returned an invalid response.") from exc
115
+
116
+ if response.status_code != 200:
117
+ error_message = payload.get("detail") or payload.get("message") or "Request failed."
118
+ raise gr.Error(error_message)
119
+
120
+ excel_base64 = payload.get("excel_data_base64")
121
+ if not excel_base64:
122
+ raise gr.Error("The API response did not include an Excel file.")
123
+
124
+ excel_bytes = base64.b64decode(excel_base64)
125
+ dataframe = pd.read_excel(BytesIO(excel_bytes))
126
+
127
+ output_path = os.path.join(
128
+ tempfile.gettempdir(),
129
+ f"extraction_result_{employee_code.strip()}.xlsx",
130
+ )
131
+ with open(output_path, "wb") as excel_file:
132
+ excel_file.write(excel_bytes)
133
+
134
+ status = (
135
+ f"{payload.get('message', 'Processing completed.')} "
136
+ f"Time: {payload.get('duration', 'N/A')}s."
137
+ )
138
+
139
+ return status, dataframe, output_path
140
+
141
+
142
+ def process_mapping(product_list, dense_weight=0.7, sparse_weight=0.3, normalize=True):
143
+ if not product_list or not product_list.strip():
144
+ raise gr.Error("Please enter at least one product name.")
145
+
146
+ response = requests.post(
147
+ MAPPING_API_URL,
148
+ data={
149
+ "product_list": product_list.strip(),
150
+ "dense_weight": dense_weight,
151
+ "sparse_weight": sparse_weight,
152
+ "normalize": str(normalize).lower(),
153
+ },
154
+ timeout=300,
155
+ )
156
+
157
+ try:
158
+ payload = response.json()
159
+ except ValueError as exc:
160
+ raise gr.Error("The API returned an invalid response.") from exc
161
+
162
+ if response.status_code != 200:
163
+ error_message = payload.get("detail") or payload.get("message") or "Request failed."
164
+ raise gr.Error(error_message)
165
+
166
+ results = payload.get("results")
167
+ if not isinstance(results, list):
168
+ raise gr.Error("The API response did not include mapping results.")
169
+
170
+ dataframe = pd.DataFrame(results)
171
+ if dataframe.empty:
172
+ dataframe = pd.DataFrame(
173
+ columns=["original_product_name", "top_1", "top_2", "top_3", "top_4", "top_5"]
174
+ )
175
+ else:
176
+ preferred_columns = [
177
+ "original_product_name",
178
+ "top_1",
179
+ "top_2",
180
+ "top_3",
181
+ "top_4",
182
+ "top_5",
183
+ ]
184
+ dataframe = dataframe.reindex(columns=preferred_columns)
185
+
186
+ total_products = payload.get("total_products", len(dataframe))
187
+ duration = payload.get("api_duration", payload.get("duration", "N/A"))
188
+ status = f"Mapped {total_products} products. Time: {duration}s."
189
+
190
+ return status, dataframe
191
+
192
+
193
+ with gr.Blocks(title="Multimodal OCR and Product Mapping") as demo:
194
+ gr.Markdown(
195
+ """
196
+ # Multimodal OCR and Product Mapping Interface
197
+ ## Run information extraction or product mapping from a single interface.
198
+ """
199
+ )
200
+
201
+ with gr.Tabs():
202
+ with gr.Tab("Information Extraction"):
203
+ with gr.Row():
204
+ zip_input = gr.File(
205
+ label="ZIP File",
206
+ file_types=[".zip"],
207
+ type="filepath",
208
+ )
209
+ employee_code_input = gr.Textbox(
210
+ label="Employee Code",
211
+ placeholder="e.g. admin",
212
+ )
213
+
214
+ debug_input = gr.Checkbox(label="Debug mode", value=False)
215
+ submit_button = gr.Button("Process", variant="primary")
216
+
217
+ status_output = gr.Textbox(label="Status", interactive=False)
218
+ excel_preview = gr.Dataframe(label="Excel Preview", interactive=False)
219
+ excel_download = gr.File(label="Download Excel", interactive=False)
220
+
221
+ submit_button.click(
222
+ fn=process_zip,
223
+ inputs=[zip_input, employee_code_input, debug_input],
224
+ outputs=[status_output, excel_preview, excel_download],
225
+ )
226
+
227
+ with gr.Tab("Mapping"):
228
+ product_input = gr.Textbox(
229
+ label="Product List",
230
+ placeholder="One product per line",
231
+ lines=10,
232
+ )
233
+
234
+ with gr.Row():
235
+ dense_weight_input = gr.Slider(
236
+ minimum=0,
237
+ maximum=1,
238
+ value=0.7,
239
+ step=0.1,
240
+ label="Dense Weight",
241
+ )
242
+ sparse_weight_input = gr.Slider(
243
+ minimum=0,
244
+ maximum=1,
245
+ value=0.3,
246
+ step=0.1,
247
+ label="Sparse Weight",
248
+ )
249
+ gr.Markdown("---")
250
+ gr.Markdown("#### 💡 Ví dụ")
251
+ gr.Examples(
252
+ examples=[
253
+ ["Đèn LED ốp trần 18W\nBóng LED E27 9W\nĐèn downlight âm trần"],
254
+ ["Bóng Trụ 30W\nPhích nước 2L\nVợt bắt muỗi"],
255
+ ["Đèn năng lượng mặt trời\nĐèn sạc tích điện\nBóng compact 20W"],
256
+ ],
257
+ inputs=[product_input],
258
+ label="Click để thử các ví dụ"
259
+ )
260
+ normalize_input = gr.Checkbox(label="Normalize Query", value=True)
261
+ mapping_button = gr.Button("Run Mapping", variant="primary")
262
+
263
+ mapping_status = gr.Textbox(label="Status", interactive=False)
264
+ mapping_preview = gr.Dataframe(label="Mapping Preview", interactive=False)
265
+
266
+ mapping_button.click(
267
+ fn=process_mapping,
268
+ inputs=[
269
+ product_input,
270
+ dense_weight_input,
271
+ sparse_weight_input,
272
+ normalize_input,
273
+ ],
274
+ outputs=[mapping_status, mapping_preview],
275
+ )
276
+
277
+
278
+ if __name__ == "__main__":
279
+ demo.launch(css=custom_css, theme=gr.themes.Base())