Skip to content

Commit bd9e097

Browse files
committed
restructured into packages
1 parent fc2fc6e commit bd9e097

File tree

13 files changed

+277
-271
lines changed

13 files changed

+277
-271
lines changed

app.py

+1-270
Original file line numberDiff line numberDiff line change
@@ -1,273 +1,4 @@
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
2712

2723
if __name__ == '__main__':
2734
socketio.run(app)

qa_over_docs/__init__.py

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
from flask import Flask, render_template, request, redirect, flash, url_for
2+
from flask_socketio import SocketIO
3+
import os, json
4+
5+
UPLOAD_FOLDER = 'uploads/'
6+
ALLOWED_EXTENSIONS = {'pdf', 'csv'}
7+
CONTEXT_FILE = "context.json"
8+
SOURCES_FILE = "sources.txt"
9+
10+
app = Flask(__name__)
11+
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
12+
app.config['SECRET_KEY'] = 'a super secret key'
13+
app.debug = True
14+
socketio = SocketIO(app)
15+
16+
if not os.path.exists(UPLOAD_FOLDER):
17+
os.mkdir(UPLOAD_FOLDER)
18+
19+
context: dict
20+
21+
if os.path.exists(CONTEXT_FILE):
22+
with open(CONTEXT_FILE) as file:
23+
context = json.load(file)
24+
25+
if os.path.exists(SOURCES_FILE):
26+
with open(SOURCES_FILE) as sources_file:
27+
# print(list(map(lambda e: e.strip(), sources_file.readlines())))
28+
context["sources"] = list(map(lambda e: e.strip(), sources_file.readlines()))
29+
else:
30+
context = {
31+
"chat_items": [],
32+
"waiting": False,
33+
"response_time": None,
34+
"collection_exists": False,
35+
"sources_to_add": [],
36+
"sources": [],
37+
"processing_sources": False
38+
}
39+
40+
def save_context():
41+
with open(CONTEXT_FILE, 'w' if os.path.exists(CONTEXT_FILE) else 'x') as file:
42+
new_context = context.copy()
43+
new_context["chat_items"] = []
44+
new_context["response_time"] = None
45+
new_context["waiting"] = False
46+
json.dump(new_context, file, indent=4)
47+
with open(SOURCES_FILE, 'w' if os.path.exists(SOURCES_FILE) else 'x') as file:
48+
file.write("\n".join(context["sources"]))
49+
50+
51+
from qa_over_docs.views import chat, sources
File renamed without changes.

db.py renamed to qa_over_docs/db.py

-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
from langchain.embeddings.openai import OpenAIEmbeddings
66
from langchain.embeddings.huggingface import HuggingFaceEmbeddings
77
from pymilvus import connections, Collection, FieldSchema, CollectionSchema, DataType, utility
8-
from typing import Dict, List, Tuple
98
import validators, os
109

1110
print("retrieving Embeddings model")

qa_over_docs/message.py

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
class Message():
2+
def __init__(self, message: str = "") -> None:
3+
self.message = message # the user's question or the answer
4+
5+
def __str__(self) -> str:
6+
return self.message
7+
8+
class Question(Message):
9+
def __init__(self, message: str) -> None:
10+
super().__init__(message)
11+
12+
class Answer(Message):
13+
def __init__(self, message: str = "", saved_question: str = None, comment: str = None) -> None:
14+
super().__init__(message)
15+
self.saved_question = saved_question # the primary key of the previous question
16+
self.comment = comment # whether the answer is from a `new`, `similar`, or `identical` question
17+
18+
def lines(self) -> list[str]:
19+
return self.message.splitlines()
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

qa_over_docs/views/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)