Spaces:
Running
Running
Merge branch 'main' of https://github.com/gamingflexer/Catalog-Digitization
Browse files- .github/workflows/file_size_check.yml +16 -0
- .github/workflows/main.yml +23 -0
- .gitignore +1 -0
- Dockerfile +35 -0
- README.md +10 -1
- requirements.txt +2 -0
- src/app/api/serializers.py +6 -1
- src/app/api/templates/catalouge.html +3 -2
- src/app/api/templates/edit_product.html +44 -0
- src/app/api/urls.py +7 -1
- src/app/api/views.py +48 -6
- src/app2.py +58 -0
.github/workflows/file_size_check.yml
ADDED
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
name: Check file size
|
2 |
+
on:
|
3 |
+
pull_request:
|
4 |
+
branches: [main]
|
5 |
+
|
6 |
+
# to run this workflow manually from the Actions tab
|
7 |
+
workflow_dispatch:
|
8 |
+
|
9 |
+
jobs:
|
10 |
+
sync-to-hub:
|
11 |
+
runs-on: ubuntu-latest
|
12 |
+
steps:
|
13 |
+
- name: Check large files
|
14 |
+
uses: ActionsDesk/[email protected]
|
15 |
+
with:
|
16 |
+
filesizelimit: 10485760 # this is 10MB so we can sync to HF Spaces
|
.github/workflows/main.yml
ADDED
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
name: Sync to Hugging Face hub
|
2 |
+
on:
|
3 |
+
push:
|
4 |
+
branches: [main]
|
5 |
+
|
6 |
+
# to run this workflow manually from the Actions tab
|
7 |
+
workflow_dispatch:
|
8 |
+
|
9 |
+
jobs:
|
10 |
+
sync-to-hub:
|
11 |
+
runs-on: ubuntu-latest
|
12 |
+
steps:
|
13 |
+
- uses: actions/checkout@v2
|
14 |
+
with:
|
15 |
+
fetch-depth: 0
|
16 |
+
- name: Add remote
|
17 |
+
env:
|
18 |
+
HF_TOKEN: ${{ secrets.HF_TOKEN }}
|
19 |
+
run: git remote add space https://asach:[email protected]/spaces/asach/Catalog-Digitization
|
20 |
+
- name: Push to hub
|
21 |
+
env:
|
22 |
+
HF_TOKEN: ${{ secrets.HF_TOKEN }}
|
23 |
+
run: git push --force https://asach:[email protected]/spaces/asach/Catalog-Digitization main
|
.gitignore
CHANGED
@@ -173,3 +173,4 @@ src/app/api/migrations/0005_alter_database_barcode_alter_database_brand_and_more
|
|
173 |
src/app/api/migrations/0006_alter_product_barcode_alter_product_brand_and_more.py
|
174 |
src/app/api/migrations/0007_alter_product_price.py
|
175 |
src/app/media/images/*
|
|
|
|
173 |
src/app/api/migrations/0006_alter_product_barcode_alter_product_brand_and_more.py
|
174 |
src/app/api/migrations/0007_alter_product_price.py
|
175 |
src/app/media/images/*
|
176 |
+
src/app/api/module/image.ipynb
|
Dockerfile
ADDED
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
ARG OPENAI_API_KEY
|
2 |
+
ARG SEVER_IP
|
3 |
+
|
4 |
+
FROM python:3.9
|
5 |
+
|
6 |
+
WORKDIR /code
|
7 |
+
|
8 |
+
COPY ./requirements.txt /code/requirements.txt
|
9 |
+
|
10 |
+
RUN pip install --no-cache-dir --upgrade -r /code/requirements.txt
|
11 |
+
|
12 |
+
# Set up a new user named "user" with user ID 1000
|
13 |
+
RUN useradd -m -u 1000 user
|
14 |
+
|
15 |
+
# Switch to the "user" user
|
16 |
+
USER user
|
17 |
+
|
18 |
+
# Set home to the user's home directory
|
19 |
+
ENV HOME=/home/user \
|
20 |
+
PATH=/home/user/.local/bin:$PATH \
|
21 |
+
OPENAI_API_KEY=$OPENAI_API_KEY \
|
22 |
+
SEVER_IP=$SEVER_IP
|
23 |
+
|
24 |
+
|
25 |
+
# Set the working directory to the user's home directory
|
26 |
+
WORKDIR $HOME/app
|
27 |
+
|
28 |
+
# Copy the current directory contents into the container at $HOME/app setting the owner to the user
|
29 |
+
COPY --chown=user . $HOME/app
|
30 |
+
|
31 |
+
ENV GRADIO_SERVER_NAME=0.0.0.0
|
32 |
+
|
33 |
+
EXPOSE 7860
|
34 |
+
|
35 |
+
CMD ["python", "src/app2.py"]
|
README.md
CHANGED
@@ -1,3 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
# Catalog Digitization - BUILD FOR BHARAT Hackathon 2024
|
2 |
|
3 |
## Introduction
|
@@ -34,4 +43,4 @@ python manage.py runserver
|
|
34 |
- **Django**: Backend framework for managing data operations and interfaces.
|
35 |
- **Tesseract OCR & Easy OCR & AZURE OCR**: Extracts text from images for digitization.
|
36 |
- **LLAMA 7B & GPT 3.5**: Large language model for processing text and voice inputs.
|
37 |
-
- **Speech Recognition**: Converts voice inputs to text for digitization.
|
|
|
1 |
+
---
|
2 |
+
title: Catalog Digitization
|
3 |
+
emoji: 🚀
|
4 |
+
colorFrom: pink
|
5 |
+
colorTo: pink
|
6 |
+
sdk: docker
|
7 |
+
app_port: 7860
|
8 |
+
pinned: true
|
9 |
+
---
|
10 |
# Catalog Digitization - BUILD FOR BHARAT Hackathon 2024
|
11 |
|
12 |
## Introduction
|
|
|
43 |
- **Django**: Backend framework for managing data operations and interfaces.
|
44 |
- **Tesseract OCR & Easy OCR & AZURE OCR**: Extracts text from images for digitization.
|
45 |
- **LLAMA 7B & GPT 3.5**: Large language model for processing text and voice inputs.
|
46 |
+
- **Speech Recognition**: Converts voice inputs to text for digitization.
|
requirements.txt
ADDED
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
1 |
+
gradio
|
2 |
+
pandas
|
src/app/api/serializers.py
CHANGED
@@ -1,7 +1,12 @@
|
|
1 |
from rest_framework import serializers
|
2 |
-
from .models import Product
|
3 |
|
4 |
class ProductSerializer(serializers.ModelSerializer):
|
5 |
class Meta:
|
6 |
model = Product
|
7 |
fields = '__all__'
|
|
|
|
|
|
|
|
|
|
|
|
1 |
from rest_framework import serializers
|
2 |
+
from .models import Product,Database
|
3 |
|
4 |
class ProductSerializer(serializers.ModelSerializer):
|
5 |
class Meta:
|
6 |
model = Product
|
7 |
fields = '__all__'
|
8 |
+
|
9 |
+
class DatabaseSerializer(serializers.ModelSerializer):
|
10 |
+
class Meta:
|
11 |
+
model = Database
|
12 |
+
fields = '__all__'
|
src/app/api/templates/catalouge.html
CHANGED
@@ -30,7 +30,7 @@
|
|
30 |
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
31 |
<div class="container">
|
32 |
<a class="navbar-brand" href="{% url 'index' %}" style="font-weight: bold;">HomePage</a>
|
33 |
-
<a
|
34 |
<a href="{% url 'add_product_by_image' %}" class="btn btn-primary ml-1" style="padding: 0.5rem 1rem; font-size: 1rem;">Add a Product by Image</a>
|
35 |
</div>
|
36 |
</nav>
|
@@ -58,8 +58,9 @@
|
|
58 |
<p class="card-text">Description: {{ product.parent_category }}</p>
|
59 |
<p class="card-text">Brand: {{ product.brand }}</p>
|
60 |
<div class="d-flex justify-content-between align-items-center">
|
61 |
-
<a href="{% url 'product_detail' product.id %}" class="btn btn-primary btn-sm mb-2">View
|
62 |
<a href="{% url 'voice_product_detail' product.id %}" class="btn btn-secondary btn-sm mb-2">Voice Edit</a>
|
|
|
63 |
</div>
|
64 |
</div>
|
65 |
</div>
|
|
|
30 |
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
31 |
<div class="container">
|
32 |
<a class="navbar-brand" href="{% url 'index' %}" style="font-weight: bold;">HomePage</a>
|
33 |
+
<a href="http://34.122.223.224:9003/" class="btn btn-secondary ml-1" target="_blank" style="font-weight: bold;">Database</a>
|
34 |
<a href="{% url 'add_product_by_image' %}" class="btn btn-primary ml-1" style="padding: 0.5rem 1rem; font-size: 1rem;">Add a Product by Image</a>
|
35 |
</div>
|
36 |
</nav>
|
|
|
58 |
<p class="card-text">Description: {{ product.parent_category }}</p>
|
59 |
<p class="card-text">Brand: {{ product.brand }}</p>
|
60 |
<div class="d-flex justify-content-between align-items-center">
|
61 |
+
<a href="{% url 'product_detail' product.id %}" class="btn btn-primary btn-sm mb-2">View</a>
|
62 |
<a href="{% url 'voice_product_detail' product.id %}" class="btn btn-secondary btn-sm mb-2">Voice Edit</a>
|
63 |
+
<a href="{% url 'delete_product_api' product.id %}" class="btn btn-danger btn-sm mb-2">Delete</a>
|
64 |
</div>
|
65 |
</div>
|
66 |
</div>
|
src/app/api/templates/edit_product.html
CHANGED
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!DOCTYPE html>
|
2 |
+
<html lang="en">
|
3 |
+
<head>
|
4 |
+
<meta charset="UTF-8">
|
5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6 |
+
<title>Edit Product</title>
|
7 |
+
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
|
8 |
+
</head>
|
9 |
+
<body>
|
10 |
+
<header>
|
11 |
+
<nav class="navbar navbar-expand-lg navbar-light bg-light">
|
12 |
+
<div class="container">
|
13 |
+
<a class="navbar-brand" href="{% url 'index' %}">Catalogue</a>
|
14 |
+
</div>
|
15 |
+
</nav>
|
16 |
+
</header>
|
17 |
+
<div class="container mt-5">
|
18 |
+
<h1 class="text-center">Edit Product</h1>
|
19 |
+
<form method="post">
|
20 |
+
{% csrf_token %}
|
21 |
+
<div class="form-group">
|
22 |
+
<label for="barcode">Barcode:</label>
|
23 |
+
<input type="text" class="form-control" id="barcode" name="barcode" value="{{ product.barcode }}">
|
24 |
+
</div>
|
25 |
+
<div class="form-group">
|
26 |
+
<label for="brand">Brand:</label>
|
27 |
+
<input type="text" class="form-control" id="brand" name="brand" value="{{ product.brand }}">
|
28 |
+
</div>
|
29 |
+
<div class="form-group">
|
30 |
+
<label for="sub_brand">Sub Brand:</label>
|
31 |
+
<input type="text" class="form-control" id="sub_brand" name="sub_brand" value="{{ product.sub_brand }}">
|
32 |
+
</div>
|
33 |
+
<div class="form-group">
|
34 |
+
<label for="manufacturer">Manufacturer:</label>
|
35 |
+
<input type="text" class="form-control" id="manufacturer" name="manufacturer" value="{{ product.manufacturer }}">
|
36 |
+
</div>
|
37 |
+
<!-- Add similar form fields for other attributes -->
|
38 |
+
<div class="text-center">
|
39 |
+
<button type="submit" class="btn btn-primary">Save</button>
|
40 |
+
</div>
|
41 |
+
</form>
|
42 |
+
</div>
|
43 |
+
</body>
|
44 |
+
</html>
|
src/app/api/urls.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1 |
from django.urls import path
|
2 |
from . import views
|
3 |
-
from .views import ProductAPIView
|
4 |
|
5 |
urlpatterns = [
|
6 |
path('', views.catalouge_page, name='index'),
|
@@ -9,4 +9,10 @@ urlpatterns = [
|
|
9 |
path('product/<int:product_id>/edit/', views.edit_product, name='edit_product'),
|
10 |
path('upload/', views.upload_image_and_audio, name='add_product_by_image'),
|
11 |
path('api/products/', ProductAPIView.as_view(), name='product_api'),
|
|
|
|
|
|
|
|
|
|
|
|
|
12 |
]
|
|
|
1 |
from django.urls import path
|
2 |
from . import views
|
3 |
+
from .views import ProductAPIView,SearchProducts,TotalNumberOfProducts,ProductDetailsById,UpdateProduct
|
4 |
|
5 |
urlpatterns = [
|
6 |
path('', views.catalouge_page, name='index'),
|
|
|
9 |
path('product/<int:product_id>/edit/', views.edit_product, name='edit_product'),
|
10 |
path('upload/', views.upload_image_and_audio, name='add_product_by_image'),
|
11 |
path('api/products/', ProductAPIView.as_view(), name='product_api'),
|
12 |
+
path('api/delete_product/<int:product_id>/', views.delete_product_api, name='delete_product_api'),
|
13 |
+
|
14 |
+
path('api/search_products/', SearchProducts.as_view(), name='search_products'),
|
15 |
+
path('api/total_number_of_products/', TotalNumberOfProducts.as_view(), name='total_number_of_products'),
|
16 |
+
path('api/product_details/<int:pk>/', ProductDetailsById.as_view(), name='product_details_by_id'),
|
17 |
+
path('api/update_product/<int:pk>/', UpdateProduct.as_view(), name='update_product'),
|
18 |
]
|
src/app/api/views.py
CHANGED
@@ -6,12 +6,14 @@ from django.shortcuts import render, get_object_or_404
|
|
6 |
from rest_framework.views import APIView
|
7 |
from rest_framework.response import Response
|
8 |
from rest_framework import status
|
9 |
-
from .models import Product
|
10 |
-
from .serializers import ProductSerializer
|
11 |
from django.http import JsonResponse
|
12 |
import os
|
13 |
from config import BASE_PATH
|
14 |
from api.module.product_description import get_details
|
|
|
|
|
15 |
|
16 |
def catalouge_page(request):
|
17 |
# Fetch all products from the database
|
@@ -41,13 +43,11 @@ def edit_product(request, product_id):
|
|
41 |
|
42 |
if request.method == 'POST':
|
43 |
# Update the product fields with the submitted data
|
|
|
44 |
product.barcode = request.POST.get('barcode')
|
45 |
product.brand = request.POST.get('brand')
|
46 |
product.sub_brand = request.POST.get('sub_brand')
|
47 |
product.manufacturer = request.POST.get('manufacturer')
|
48 |
-
# Update other fields similarly
|
49 |
-
|
50 |
-
# Save the updated product
|
51 |
product.save()
|
52 |
|
53 |
# Redirect to the product page after saving
|
@@ -130,4 +130,46 @@ def edit_voice_product(request,product_id):
|
|
130 |
else:
|
131 |
return JsonResponse({'error': 'No voice file submitted.'}, status=400)
|
132 |
else:
|
133 |
-
return render(request, 'edit_voice_product.html')
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
6 |
from rest_framework.views import APIView
|
7 |
from rest_framework.response import Response
|
8 |
from rest_framework import status
|
9 |
+
from .models import Product,Database
|
10 |
+
from .serializers import ProductSerializer,DatabaseSerializer
|
11 |
from django.http import JsonResponse
|
12 |
import os
|
13 |
from config import BASE_PATH
|
14 |
from api.module.product_description import get_details
|
15 |
+
from django.views.decorators.http import require_http_methods
|
16 |
+
from django.db.models import Q
|
17 |
|
18 |
def catalouge_page(request):
|
19 |
# Fetch all products from the database
|
|
|
43 |
|
44 |
if request.method == 'POST':
|
45 |
# Update the product fields with the submitted data
|
46 |
+
product = Product.objects.get(pk=product_id)
|
47 |
product.barcode = request.POST.get('barcode')
|
48 |
product.brand = request.POST.get('brand')
|
49 |
product.sub_brand = request.POST.get('sub_brand')
|
50 |
product.manufacturer = request.POST.get('manufacturer')
|
|
|
|
|
|
|
51 |
product.save()
|
52 |
|
53 |
# Redirect to the product page after saving
|
|
|
130 |
else:
|
131 |
return JsonResponse({'error': 'No voice file submitted.'}, status=400)
|
132 |
else:
|
133 |
+
return render(request, 'edit_voice_product.html')
|
134 |
+
|
135 |
+
def delete_product_api(request, product_id):
|
136 |
+
try:
|
137 |
+
product = Product.objects.get(pk=product_id)
|
138 |
+
product.delete()
|
139 |
+
return HttpResponseRedirect(reverse('index'))
|
140 |
+
except Product.DoesNotExist:
|
141 |
+
return JsonResponse({'error': 'Product not found.'}, status=404)
|
142 |
+
|
143 |
+
class SearchProducts(APIView):
|
144 |
+
def get(self, request):
|
145 |
+
name = request.query_params.get('name', '')
|
146 |
+
products = Database.objects.filter(Q(product_name__icontains=name) & Q(description__icontains=name))
|
147 |
+
db_serializer = DatabaseSerializer(products, many=True)
|
148 |
+
return Response(db_serializer.data)
|
149 |
+
|
150 |
+
class TotalNumberOfProducts(APIView):
|
151 |
+
def get(self, request):
|
152 |
+
count = Database.objects.count()
|
153 |
+
return Response({"total_number_of_products": count})
|
154 |
+
|
155 |
+
class ProductDetailsById(APIView):
|
156 |
+
def get(self, request, pk):
|
157 |
+
try:
|
158 |
+
product = Product.objects.get(pk=pk)
|
159 |
+
product_serializer = ProductSerializer(product)
|
160 |
+
return Response(product_serializer.data)
|
161 |
+
except Product.DoesNotExist:
|
162 |
+
return Response({"error": "Product not found"}, status=404)
|
163 |
+
|
164 |
+
class UpdateProduct(APIView):
|
165 |
+
def put(self, request, pk):
|
166 |
+
try:
|
167 |
+
product = Product.objects.get(pk=pk)
|
168 |
+
serializer = ProductSerializer(product, data=request.data)
|
169 |
+
print(serializer.is_valid(), serializer.errors)
|
170 |
+
if serializer.is_valid():
|
171 |
+
serializer.save()
|
172 |
+
return Response(serializer.data)
|
173 |
+
return Response(serializer.errors, status=400)
|
174 |
+
except Product.DoesNotExist:
|
175 |
+
return Response({"error": "Product not found"}, status=404)
|
src/app2.py
ADDED
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
import pandas as pd
|
3 |
+
import os
|
4 |
+
import requests
|
5 |
+
|
6 |
+
OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY")
|
7 |
+
SEVER_IP = os.environ.get("SEVER_IP")
|
8 |
+
|
9 |
+
def get_total_number_of_products():
|
10 |
+
response = requests.get(f'{SEVER_IP}/api/total_number_of_products/')
|
11 |
+
if response.status_code == 200:
|
12 |
+
return response.json()['total_number_of_products']
|
13 |
+
else:
|
14 |
+
return "Error fetching total number of products"
|
15 |
+
|
16 |
+
def search_products(product_name):
|
17 |
+
response = requests.get(f'{SEVER_IP}/api/search_products/', params={'name': product_name})
|
18 |
+
if response.status_code == 200:
|
19 |
+
return pd.DataFrame(response.json())
|
20 |
+
else:
|
21 |
+
return pd.DataFrame([]) # Return an empty DataFrame in case of an error
|
22 |
+
|
23 |
+
def get_product_details_by_id(product_id):
|
24 |
+
response = requests.get(f'{SEVER_IP}/api/product_details/{product_id}/')
|
25 |
+
if response.status_code == 200:
|
26 |
+
return response.json() # Returns the product details as a dictionary
|
27 |
+
else:
|
28 |
+
return {"error": f"Product with ID {product_id} not found or error occurred."}
|
29 |
+
|
30 |
+
def sample_fun(voice_input, prodcut_id):
|
31 |
+
print(prodcut_id)
|
32 |
+
return
|
33 |
+
|
34 |
+
with gr.Blocks(theme=gr.themes.Default(primary_hue=gr.themes.colors.red, secondary_hue=gr.themes.colors.pink)) as demo:
|
35 |
+
|
36 |
+
with gr.Tab("Add Your Image"):
|
37 |
+
voice_input = gr.Audio(label="Upload Audio")
|
38 |
+
prodcut_id = gr.Textbox(label="Enter Product ID")
|
39 |
+
with gr.Row():
|
40 |
+
submit_button_tab_1 = gr.Button("Start")
|
41 |
+
|
42 |
+
with gr.Tab("Search Catalog"):
|
43 |
+
with gr.Row():
|
44 |
+
total_no_of_products = gr.Textbox(value=str(get_total_number_of_products()),label="Total Products")
|
45 |
+
with gr.Row():
|
46 |
+
embbed_text_search = gr.Textbox(label="Enter Product Name")
|
47 |
+
submit_button_tab_4 = gr.Button("Start")
|
48 |
+
dataframe_output_tab_4 = gr.Dataframe(headers=['id', 'barcode', 'brand', 'sub_brand', 'manufactured_by', 'product_name',
|
49 |
+
'weight', 'variant', 'net_content', 'price', 'parent_category',
|
50 |
+
'child_category', 'sub_child_category', 'images_paths', 'description',
|
51 |
+
'quantity', 'promotion_on_the_pack', 'type_of_packaging', 'mrp'])
|
52 |
+
|
53 |
+
|
54 |
+
|
55 |
+
submit_button_tab_1.click(fn=sample_fun,inputs=[voice_input,prodcut_id])
|
56 |
+
submit_button_tab_4.click(fn=search_products,inputs=[embbed_text_search] ,outputs= dataframe_output_tab_4)
|
57 |
+
|
58 |
+
demo.launch()
|