Create app.py
Browse files
app.py
ADDED
@@ -0,0 +1,198 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import streamlit as st
|
2 |
+
from cryptography.fernet import Fernet
|
3 |
+
import time
|
4 |
+
import pandas as pd
|
5 |
+
import io
|
6 |
+
from transformers import pipeline
|
7 |
+
from streamlit_extras.stylable_container import stylable_container
|
8 |
+
import json
|
9 |
+
|
10 |
+
st.subheader("Table Question Answering (QA)", divider="blue")
|
11 |
+
|
12 |
+
# generate Fernet key
|
13 |
+
if 'fernet_key' not in st.session_state:
|
14 |
+
st.session_state.fernet_key = Fernet.generate_key()
|
15 |
+
|
16 |
+
key = st.session_state.fernet_key
|
17 |
+
|
18 |
+
|
19 |
+
# function for generating and validating fernet key
|
20 |
+
def generate_fernet_token(key, data):
|
21 |
+
fernet = Fernet(key)
|
22 |
+
token = fernet.encrypt(data.encode())
|
23 |
+
return token
|
24 |
+
|
25 |
+
def validate_fernet_token(key, token, ttl_seconds):
|
26 |
+
|
27 |
+
fernet = Fernet(key)
|
28 |
+
try:
|
29 |
+
decrypted_data = fernet.decrypt(token, ttl=ttl_seconds).decode()
|
30 |
+
return decrypted_data, None
|
31 |
+
except Exception as e:
|
32 |
+
return None, f"Expired token: {e}"
|
33 |
+
|
34 |
+
# sidebar
|
35 |
+
with st.sidebar:
|
36 |
+
with stylable_container(
|
37 |
+
key="test_button",
|
38 |
+
css_styles="""
|
39 |
+
button {
|
40 |
+
background-color: yellow;
|
41 |
+
border: 1px solid black;
|
42 |
+
padding: 5px;
|
43 |
+
color: black;
|
44 |
+
}
|
45 |
+
""",
|
46 |
+
):
|
47 |
+
st.button("ONE-DAY SUBSCRIPTION")
|
48 |
+
|
49 |
+
|
50 |
+
expander = st.expander("**Important notes on the Table Question Answering (QA) App**")
|
51 |
+
expander.write('''
|
52 |
+
|
53 |
+
**Supported File Formats**
|
54 |
+
This app accepts files in .csv and .xlsx formats.
|
55 |
+
|
56 |
+
|
57 |
+
**How to Use**
|
58 |
+
Upload your file first. Then, type your question into the text area provided and click the 'Retrieve your answer' button.
|
59 |
+
|
60 |
+
|
61 |
+
**Usage Limits**
|
62 |
+
You can ask up to 30 questions per day. Once you reach this limit, you will need to wait until the daily automatic renewal to continue using the app. The app's daily renewal occurs automatically.
|
63 |
+
|
64 |
+
|
65 |
+
**Subscription Management**
|
66 |
+
This app uses a one-day subscription plan. To cancel your subscription, please visit your Account settings.
|
67 |
+
|
68 |
+
|
69 |
+
**Authorization**
|
70 |
+
For security purposes, your authorization access expires hourly. To restore access, click the "Request Authorization" button. A file must be uploaded before you can request authorization.
|
71 |
+
|
72 |
+
|
73 |
+
**Customization**
|
74 |
+
To change the app's background color to white or black, click the three-dot menu on the right-hand side of your app, go to Settings and then Choose app theme, colors and fonts.
|
75 |
+
|
76 |
+
|
77 |
+
**File Handling and Errors**
|
78 |
+
The app may display an error message if your file is corrupt, contains missing values, or has other errors.
|
79 |
+
To get your file cleaned and pre-processed before using this QA app, please use our Text Preprocessing Service which can be found in the navigation menu of our website: https://nlpblogs.com/
|
80 |
+
|
81 |
+
For any other errors or inquiries, please contact us at info@nlpblogs.com
|
82 |
+
|
83 |
+
''')
|
84 |
+
|
85 |
+
|
86 |
+
# count attempts based on questions
|
87 |
+
if 'question_attempts' not in st.session_state:
|
88 |
+
st.session_state['question_attempts'] = 0
|
89 |
+
|
90 |
+
max_attempts = 3
|
91 |
+
|
92 |
+
# upload file
|
93 |
+
upload_file = st.file_uploader("Upload your file. Accepted file formats include: .csv, .xlsx", type=['csv', 'xlsx'])
|
94 |
+
df = None
|
95 |
+
|
96 |
+
if upload_file is not None:
|
97 |
+
file_extension = upload_file.name.split('.')[-1].lower()
|
98 |
+
if file_extension == 'csv':
|
99 |
+
try:
|
100 |
+
df = pd.read_csv(io.StringIO(upload_file.getvalue().decode("utf-8")))
|
101 |
+
if df.isnull().values.any():
|
102 |
+
st.error("Error: The CSV file contains missing values.")
|
103 |
+
st.stop()
|
104 |
+
else:
|
105 |
+
new_columns = [f'column_{i+1}' for i in range(len(df.columns))]
|
106 |
+
df.columns = new_columns
|
107 |
+
|
108 |
+
st.dataframe(df, key="csv_dataframe")
|
109 |
+
st.write("_number of rows_", df.shape[0])
|
110 |
+
st.write("_number of columns_", df.shape[1])
|
111 |
+
except pd.errors.ParserError:
|
112 |
+
st.error("Error: The CSV file is not readable or is incorrectly formatted.")
|
113 |
+
st.stop()
|
114 |
+
except UnicodeDecodeError:
|
115 |
+
st.error("Error: The CSV file could not be decoded.")
|
116 |
+
st.stop()
|
117 |
+
except Exception as e:
|
118 |
+
st.error(f"An unexpected error occurred while reading CSV: {e}")
|
119 |
+
st.stop()
|
120 |
+
elif file_extension == 'xlsx':
|
121 |
+
try:
|
122 |
+
df = pd.read_excel(io.BytesIO(upload_file.getvalue()))
|
123 |
+
if df.isnull().values.any():
|
124 |
+
st.error("Error: The Excel file contains missing values.")
|
125 |
+
st.stop()
|
126 |
+
else:
|
127 |
+
new_columns = [f'column_{i+1}' for i in range(len(df.columns))]
|
128 |
+
df.columns = new_columns
|
129 |
+
st.dataframe(df, key="excel_dataframe")
|
130 |
+
st.write("_number of rows_", df.shape[0])
|
131 |
+
st.write("_number of columns_", df.shape[1])
|
132 |
+
except ValueError:
|
133 |
+
st.error("Error: The Excel file is not readable or is incorrectly formatted.")
|
134 |
+
st.stop()
|
135 |
+
except Exception as e:
|
136 |
+
st.error(f"An unexpected error occurred while reading Excel: {e}")
|
137 |
+
st.stop()
|
138 |
+
else:
|
139 |
+
st.warning("Unsupported file type.")
|
140 |
+
st.stop()
|
141 |
+
|
142 |
+
# generate and validate Fernet token for the current file
|
143 |
+
if 'fernet_token' not in st.session_state:
|
144 |
+
if df is not None:
|
145 |
+
st.session_state.fernet_token = generate_fernet_token(key, df.to_json())
|
146 |
+
else:
|
147 |
+
st.stop()
|
148 |
+
|
149 |
+
decrypted_data_streamlit, error_streamlit = validate_fernet_token(key, st.session_state.fernet_token, ttl_seconds=10)
|
150 |
+
|
151 |
+
if error_streamlit:
|
152 |
+
st.warning("Please press Request Authorization. Please note that a file should be uploaded before you press Request Authorization.")
|
153 |
+
if st.button("Request Authorization"):
|
154 |
+
st.session_state.fernet_token = generate_fernet_token(key, df.to_json())
|
155 |
+
st.success("Authorization granted")
|
156 |
+
decrypted_data_streamlit, error_streamlit = validate_fernet_token(key, st.session_state.fernet_token, ttl_seconds=10)
|
157 |
+
if error_streamlit:
|
158 |
+
st.error(f"Your authorization has expired: {error_streamlit}")
|
159 |
+
st.stop()
|
160 |
+
if error_streamlit:
|
161 |
+
st.error("Please upload a file.")
|
162 |
+
st.stop()
|
163 |
+
else:
|
164 |
+
try:
|
165 |
+
df = pd.read_json(decrypted_data_streamlit)
|
166 |
+
except Exception as e:
|
167 |
+
st.error(f"Error decoding data: {e}")
|
168 |
+
st.stop()
|
169 |
+
else:
|
170 |
+
st.error(f"Your authorization has expired: {error_streamlit}")
|
171 |
+
st.stop()
|
172 |
+
|
173 |
+
st.divider()
|
174 |
+
|
175 |
+
# ask question
|
176 |
+
def clear_question():
|
177 |
+
st.session_state["question"] = ""
|
178 |
+
|
179 |
+
question = st.text_input("Type your question here and then press **Retrieve your answer**:", key="question")
|
180 |
+
st.button("Clear question", on_click=clear_question)
|
181 |
+
|
182 |
+
#retrive answer
|
183 |
+
if st.button("Retrieve your answer"):
|
184 |
+
if st.session_state['question_attempts'] >= max_attempts:
|
185 |
+
st.error(f"You have asked {max_attempts} questions. You have reached your daily request limit. Your subscription will renew automatically. To cancel, please go to your Account.")
|
186 |
+
st.stop()
|
187 |
+
st.session_state['question_attempts'] += 1
|
188 |
+
if error_streamlit:
|
189 |
+
st.warning("Please enter a question before retrieving the answer.")
|
190 |
+
else:
|
191 |
+
with st.spinner('Wait for it...'):
|
192 |
+
time.sleep(2)
|
193 |
+
if df is not None:
|
194 |
+
tqa = pipeline(task="table-question-answering", model="google/tapas-base-finetuned-wikisql-supervised")
|
195 |
+
st.write(tqa(table=df, query=question)['answer'])
|
196 |
+
|
197 |
+
st.divider()
|
198 |
+
st.write(f"Number of questions asked: {st.session_state['question_attempts']}/{max_attempts}")
|