1
- from flask import Flask , render_template , request , redirect , flash , url_for
2
- from flask_socketio import SocketIO
3
- from threading import Thread
4
- from time import time , sleep
5
- from werkzeug .utils import secure_filename
6
- from pprint import pprint
7
- import os , json , shutil , validators
8
-
9
- from api import retrieve_response
10
- import db
11
-
12
-
13
- UPLOAD_FOLDER = 'uploads/'
14
- ALLOWED_EXTENSIONS = {'pdf' , 'csv' }
15
- CONTEXT_FILE = "context.json"
16
- SOURCES_FILE = "sources.txt"
17
-
18
- RESPONSE_COMMENTS = {
19
- "new" : "This answer is newly generated" ,
20
- "similar" : "This answer came from a similar question" ,
21
- "identical" : "This answer came from an identical question"
22
- }
23
-
24
- class Message ():
25
- def __init__ (self , message : str = "" ) -> None :
26
- self .message = message # the user's question or the answer
27
-
28
- def __str__ (self ) -> str :
29
- return self .message
30
-
31
- class Question (Message ):
32
- def __init__ (self , message : str ) -> None :
33
- super ().__init__ (message )
34
-
35
- class Answer (Message ):
36
- def __init__ (self , message : str = "" , saved_question : str = None , comment : str = None ) -> None :
37
- super ().__init__ (message )
38
- self .saved_question = saved_question # the primary key of the previous question
39
- self .comment = comment # whether the answer is from a `new`, `similar`, or `identical` question
40
-
41
- def lines (self ) -> list [str ]:
42
- return self .message .splitlines ()
43
-
44
-
45
- app = Flask (__name__ )
46
- app .config ['UPLOAD_FOLDER' ] = UPLOAD_FOLDER
47
- app .config ['SECRET_KEY' ] = 'a super secret key'
48
- app .debug = True
49
- socketio = SocketIO (app )
50
-
51
-
52
- if not os .path .exists (UPLOAD_FOLDER ):
53
- os .mkdir (UPLOAD_FOLDER )
54
-
55
- if os .path .exists (CONTEXT_FILE ):
56
- with open (CONTEXT_FILE ) as file :
57
- context = json .load (file )
58
-
59
- if os .path .exists (SOURCES_FILE ):
60
- with open (SOURCES_FILE ) as sources_file :
61
- # print(list(map(lambda e: e.strip(), sources_file.readlines())))
62
- context ["sources" ] = list (map (lambda e : e .strip (), sources_file .readlines ()))
63
- else :
64
- context = {
65
- "chat_items" : [],
66
- "waiting" : False ,
67
- "response_time" : None ,
68
- "collection_exists" : False ,
69
- "sources_to_add" : [],
70
- "sources" : [],
71
- "processing_sources" : False
72
- }
73
-
74
-
75
- def save_context ():
76
- with open (CONTEXT_FILE , 'w' if os .path .exists (CONTEXT_FILE ) else 'x' ) as file :
77
- new_context = context .copy ()
78
- new_context ["chat_items" ] = []
79
- new_context ["response_time" ] = None
80
- new_context ["waiting" ] = False
81
- json .dump (new_context , file , indent = 4 )
82
- with open (SOURCES_FILE , 'w' if os .path .exists (SOURCES_FILE ) else 'x' ) as file :
83
- file .write ("\n " .join (context ["sources" ]))
84
-
85
-
86
- @app .route ("/" , methods = ['GET' , 'POST' ])
87
- def home ():
88
- save_context ()
89
- if request .method == 'POST' :
90
- context ["response_time" ] = None
91
- user_input = request .form ['user_input' ]
92
-
93
- context ["chat_items" ].append (Question (user_input ))
94
-
95
- thread = Thread (target = response , args = (user_input ,))
96
- thread .start ()
97
-
98
- context ["waiting" ] = True
99
-
100
- return render_template ("index.html" , ** context , response_comments = RESPONSE_COMMENTS )
101
-
102
-
103
- def response (user_input : str , force_generate_new : bool = False ):
104
- st = time ()
105
- sleep (0.1 )
106
-
107
- answer = Answer ()
108
-
109
- if force_generate_new :
110
- relevant_qa = {}
111
- else :
112
- relevant_qa = db .query_most_relevant_question (user_input )
113
- # pprint(relevant_qa)
114
-
115
- if "distance" in relevant_qa :
116
- print ("Relevant question distance: " + str (relevant_qa ["distance" ]))
117
- distance = relevant_qa ["distance" ]
118
- if distance < 0.4 :
119
- answer .message = relevant_qa ["answer" ]
120
- answer .saved_question = relevant_qa ["question" ]
121
- if distance < 0.1 :
122
- answer .comment = "identical"
123
- else :
124
- answer .comment = "similar"
125
- else :
126
- force_generate_new = True
127
- else :
128
- force_generate_new = True
129
-
130
- if force_generate_new :
131
- answer .comment = "new"
132
- relevant_docs = db .retrieve_relevant_docs (user_input )
133
- # pprint(relevant_docs)
134
- response = retrieve_response (user_input , relevant_docs )
135
- answer .message = response
136
-
137
- context ["chat_items" ].append (answer )
138
-
139
- context ["waiting" ] = False
140
-
141
- sleep (0.2 )
142
- et = time ()
143
-
144
- response_time = et - st
145
- print (f"Response time: { response_time } " )
146
-
147
- context ["response_time" ] = response_time
148
- socketio .emit ('response_received' )
149
-
150
-
151
- @app .route ("/generate_new_answer/<int:index>" )
152
- def generate_new_answer (index : int ):
153
- question = context ["chat_items" ][index - 1 ].message
154
-
155
- context ["response_time" ] = None
156
- context ["chat_items" ].append (Question (question ))
157
-
158
- thread = Thread (target = response , args = (question ,True ))
159
- thread .start ()
160
-
161
- context ["waiting" ] = True
162
-
163
- return redirect ("/" )
164
-
165
-
166
- @app .route ('/create_collection' )
167
- def create_collection ():
168
- if not db .collection_exists ():
169
- db .create_collections ()
170
- context ["collection_exists" ] = True
171
- flash ("Collection successfully created" , "success" )
172
- return redirect ("/" )
173
-
174
-
175
- def allowed_file (filename ):
176
- return '.' in filename and \
177
- filename .rsplit ('.' , 1 )[1 ].lower () in ALLOWED_EXTENSIONS
178
-
179
-
180
- @app .route ("/include_source" , methods = ['GET' , 'POST' ])
181
- def include_source ():
182
- if request .method == 'POST' :
183
- file = request .files ['file' ]
184
- # If the user does not select a file, the browser submits an
185
- # empty file without a filename.
186
- if file .filename == '' :
187
- context ["sources_to_add" ].append (request .form ["include-url" ])
188
- if file and allowed_file (file .filename ):
189
- filename = secure_filename (file .filename )
190
- path = os .path .join (app .config ['UPLOAD_FOLDER' ], filename )
191
- file .save (path )
192
-
193
- context ["sources_to_add" ].append (filename )
194
-
195
- return redirect ("/" )
196
-
197
-
198
- @app .route ("/clear_sources_to_add" )
199
- def clear_sources_to_add ():
200
- context ["sources_to_add" ] = []
201
- shutil .rmtree (UPLOAD_FOLDER )
202
- os .mkdir (UPLOAD_FOLDER )
203
- return redirect ("/" )
204
-
205
-
206
- @app .route ("/add_sources" , methods = ['GET' , 'POST' ])
207
- def add_sources ():
208
- if request .method == 'POST' :
209
- if context ["sources_to_add" ]:
210
- valid_sources = []
211
-
212
- for source in context ["sources_to_add" ]:
213
- if validators .url (source ) or os .path .exists (os .path .join (UPLOAD_FOLDER , source )):
214
- valid_sources .append (source )
215
- if valid_sources :
216
- db .add_sources (valid_sources )
217
- context ["sources" ].extend (valid_sources )
218
- clear_sources_to_add ()
219
- flash ("Successfully added sources" , "success" )
220
- else :
221
- flash ("No valid sources provided" , "warning" )
222
- else :
223
- flash ("No sources to add" , "warning" )
224
- return redirect ("/" )
225
-
226
-
227
- @app .route ("/remove_source/<int:index>" )
228
- def remove_source (index : int ):
229
- source = context ["sources" ][index ]
230
- db .remove_source (source )
231
- flash (f"Successfully removed { source } " , "primary" )
232
- context ["sources" ].pop (index )
233
- return redirect ("/" )
234
-
235
-
236
- @app .route ("/like_answer/<int:index>" )
237
- def like_answer (index : int ):
238
- question = context ["chat_items" ][index - 1 ]
239
- answer = context ["chat_items" ][index ]
240
- db .add_question_answer (question .message , answer .message )
241
- flash ("Answer saved as a response" , "success" )
242
- return redirect ("/" )
243
-
244
-
245
- @app .route ("/dislike_answer/<int:index>" )
246
- def dislike_answer (index : int ):
247
- answer = context ["chat_items" ][index ] # type: Message
248
- if answer .saved_question :
249
- db .remove_answer (answer .saved_question )
250
- flash ("Answer removed from saved responses" , "primary" )
251
- return redirect ("/" )
252
-
253
-
254
- @app .route ("/delete_collection" )
255
- def delete_collection ():
256
- db .delete_collection ()
257
-
258
- if os .path .exists (CONTEXT_FILE ):
259
- os .remove (CONTEXT_FILE )
260
- if os .path .exists (SOURCES_FILE ):
261
- os .remove (SOURCES_FILE )
262
-
263
- context ["collection_exists" ] = False
264
- context ["sources" ] = []
265
- context ["response_time" ] = None
266
- context ["chat_items" ] = []
267
-
268
- flash ("Collection successfully deleted" , "primary" )
269
- return redirect ("/" )
270
-
1
+ from qa_over_docs import socketio , app
271
2
272
3
if __name__ == '__main__' :
273
4
socketio .run (app )
0 commit comments