Skip to content

Commit 346298f

Browse files
authored
Merge branch 'master' into dependabot/pip/scipy-1.14.1
2 parents 20aba91 + 3321b7d commit 346298f

File tree

13 files changed

+287
-157
lines changed

13 files changed

+287
-157
lines changed

Procfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
web: gunicorn app.app.wsgi --log-file - --timeout 120

app/app/settings/base_settings.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77

88
logger = logging.getLogger("django")
99

10-
load_dotenv()
10+
if "DYNO" not in os.environ:
11+
load_dotenv()
1112

1213

1314
BASE_DIR = Path(__file__).resolve().parent.parent.parent # Three levels up
@@ -222,7 +223,7 @@
222223
},
223224
"django.template": {
224225
"handlers": ["error_handler"],
225-
"level": "INFO", # Change to DEBUG to see missing template vars errors
226+
"level": "INFO", # Change to DEBUG to see missing template vars errors
226227
"propagate": True,
227228
},
228229
"django.server": {

app/app/views.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ def privacy_view(request):
1919
request,
2020
context={
2121
"title": "Privacy Policy | Blogthedata.com",
22-
"description": "This site collects IP addresses and geocodes them for use on a map. No personal information is collected. See the privacy policy for more information.",
22+
"description": "I don't collect any information about you. I'm just a guy who likes to blog. You can read my privacy policy here.",
2323
},
2424
template_name="blog/privacy.html",
2525
)

app/app/wsgi.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,4 @@
11
import os
2-
from dotenv import load_dotenv
3-
4-
load_dotenv("/home/jsolly/awesome-django-blog/.env")
5-
62
from django.core.wsgi import get_wsgi_application
73

84
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "app.settings.prod")

app/blog/templates/blog/parts/chatbox.html

Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,27 +7,54 @@
77
<img src="{% static 'svg/man-avatar.svg' %}" alt="Ezra's avatar">
88
</div>
99
<div class="chatbox__content--header">
10-
<p class="chatbox__heading--header">Ask a Question</p>
11-
<p class="chatbox__description--header">Hi, I'm Ezra! I can answer questions about any blog post on
12-
this site!</p>
10+
<p class="chatbox__heading--header">Ezra</p>
11+
<p class="chatbox__time--header"> {% now "jS F Y" %} </p>
1312
</div>
13+
<div class="chatbox__close--header">
14+
<button aria-label="Close chatbox">
15+
<svg width="40" height="40" viewBox="0 0 1024 1024" version="1.1"
16+
xmlns="http://www.w3.org/2000/svg">
17+
<path
18+
d="M777.856 280.192l-33.92-33.952-231.872 231.872-231.84-231.872-33.984 33.888 231.872 231.904-231.84 231.84 33.888 33.984 231.904-231.904 231.84 231.872 33.952-33.888-231.872-231.904z" />
19+
</svg>
20+
</button>
21+
</div>
22+
</div>
23+
<div class="chatbox__messages">
24+
<div class="messages__item messages__item--bot"> I can answer questions about any blog post on this
25+
site! </div>
26+
<div class="messages__item messages__item--bot"> Hi, I'm Ezra! </div>
1427
</div>
15-
<div class="chatbox__messages"></div>
1628
<div class="chatbox__footer">
1729
<form id="chatbox-submit">
1830
{% csrf_token %}
19-
<textarea name="question-text-area" id="question-text-area" placeholder="Write a message..."
20-
hx-trigger="keyup[key === 'Enter' && !shiftKey]" hx-post="/answer-with-gpt/"
21-
hx-include="#question-text-area" hx-target=".chatbox__messages" hx-swap="afterbegin"></textarea>
22-
<button class="chatbox__send--footer send__button" hx-post="/answer-with-gpt/"
23-
hx-include="#question-text-area" hx-target=".chatbox__messages" hx-swap="afterbegin"
24-
aria-label="Send message">Send</button>
31+
<div class="chatbox__input-container">
32+
<textarea name="question-text-area" id="question-text-area" placeholder="Send a message..."
33+
hx-trigger="keyup[key === 'Enter' && !shiftKey]" hx-post="/answer-with-gpt/"
34+
hx-include="#question-text-area" hx-target=".chatbox__messages"
35+
hx-swap="afterbegin"></textarea>
36+
<button class="chatbox__send--footer send__button" hx-post="/answer-with-gpt/"
37+
hx-include="#question-text-area" hx-target=".chatbox__messages" hx-swap="afterbegin"
38+
aria-label="Send message">
39+
<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 256 256">
40+
<g transform="translate(1.41 1.41) scale(2.81 2.81)">
41+
<path
42+
d="M 89.999 3.075 C 90 3.02 90 2.967 89.999 2.912 c -0.004 -0.134 -0.017 -0.266 -0.038 -0.398 c -0.007 -0.041 -0.009 -0.081 -0.018 -0.122 c -0.034 -0.165 -0.082 -0.327 -0.144 -0.484 c -0.018 -0.046 -0.041 -0.089 -0.061 -0.134 c -0.053 -0.119 -0.113 -0.234 -0.182 -0.346 C 89.528 1.382 89.5 1.336 89.469 1.29 c -0.102 -0.147 -0.212 -0.288 -0.341 -0.417 c -0.13 -0.13 -0.273 -0.241 -0.421 -0.344 c -0.042 -0.029 -0.085 -0.056 -0.129 -0.082 c -0.118 -0.073 -0.239 -0.136 -0.364 -0.191 c -0.039 -0.017 -0.076 -0.037 -0.116 -0.053 c -0.161 -0.063 -0.327 -0.113 -0.497 -0.147 c -0.031 -0.006 -0.063 -0.008 -0.094 -0.014 c -0.142 -0.024 -0.285 -0.038 -0.429 -0.041 C 87.03 0 86.983 0 86.936 0.001 c -0.141 0.003 -0.282 0.017 -0.423 0.041 c -0.035 0.006 -0.069 0.008 -0.104 0.015 c -0.154 0.031 -0.306 0.073 -0.456 0.129 L 1.946 31.709 c -1.124 0.422 -1.888 1.473 -1.943 2.673 c -0.054 1.199 0.612 2.316 1.693 2.838 l 34.455 16.628 l 16.627 34.455 C 53.281 89.344 54.334 90 55.481 90 c 0.046 0 0.091 -0.001 0.137 -0.003 c 1.199 -0.055 2.251 -0.819 2.673 -1.943 L 89.815 4.048 c 0.056 -0.149 0.097 -0.3 0.128 -0.453 c 0.008 -0.041 0.011 -0.081 0.017 -0.122 C 89.982 3.341 89.995 3.208 89.999 3.075 z M 75.086 10.672 L 37.785 47.973 L 10.619 34.864 L 75.086 10.672 z M 55.136 79.381 L 42.027 52.216 l 37.302 -37.302 L 55.136 79.381 z"
43+
fill="rgb(0,0,0)" stroke-linecap="round" />
44+
</g>
45+
</svg>
46+
</button>
47+
</div>
2548
</form>
2649
</div>
50+
<div class="chatbox__footer--small">
51+
<p>Powered by <a href="https://blogthedata.com">Blogthedata</a></p>
52+
</div>
2753
</div>
2854
<div class="chatbox__button">
29-
<button aria-label="Toggle chatbox"><img id="chatbox-icon" src="{% static 'svg/chatbox-icon.svg' %}"
30-
alt="Chatbox icon" /></button>
55+
<button aria-label="Toggle chatbox">
56+
<img id="chatbox-icon" src="{% static 'svg/chatbox-icon.svg' %}" alt="Chatbox icon" />
57+
</button>
3158
</div>
3259
</div>
3360
</div>

app/blog/utils.py

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from openai import Embedding, Completion
2-
from openai.embeddings_utils import distances_from_embeddings
2+
from typing import List
3+
from scipy import spatial
34
import pickle
45
from pathlib import Path
56
from sklearn.feature_extraction.text import TfidfVectorizer
@@ -25,6 +26,24 @@ def load_pickle_file():
2526
global_df = load_pickle_file()
2627

2728

29+
def distances_from_embeddings(
30+
query_embedding: List[float],
31+
embeddings: List[List[float]],
32+
distance_metric="cosine",
33+
) -> List[List]:
34+
distance_metrics = {
35+
"cosine": spatial.distance.cosine,
36+
"L1": spatial.distance.cityblock,
37+
"L2": spatial.distance.euclidean,
38+
"Linf": spatial.distance.chebyshev,
39+
}
40+
distances = [
41+
distance_metrics[distance_metric](query_embedding, embedding)
42+
for embedding in embeddings
43+
]
44+
return distances
45+
46+
2847
def create_context(question, df, max_len=1800, size="ada"):
2948
"""
3049
Create a context for a question by finding the most similar context from the dataframe
@@ -108,13 +127,13 @@ def preprocess_text(text: str) -> str:
108127
# Remove punctuation marks
109128
text = text.translate(str.maketrans("", "", string.punctuation))
110129
# Remove numbers
111-
text = re.sub(r'\d+', '', text)
130+
text = re.sub(r"\d+", "", text)
112131
# Remove extra whitespaces
113-
text = re.sub(r'\s+', ' ', text)
132+
text = re.sub(r"\s+", " ", text)
114133
# Remove stopwords
115-
nltk.download('stopwords')
116-
stop_words = set(stopwords.words('english'))
117-
text = ' '.join([word for word in text.split() if word not in stop_words])
134+
nltk.download("stopwords")
135+
stop_words = set(stopwords.words("english"))
136+
text = " ".join([word for word in text.split() if word not in stop_words])
118137
return text
119138

120139

@@ -123,9 +142,10 @@ def getPostChunks(post: Post, chunk_size: int = 1800) -> list:
123142
Split the post content into chunks of specified size
124143
"""
125144
content = preprocess_text(post.content)
126-
chunks = [content[i:i + chunk_size] for i in range(0, len(content), chunk_size)]
145+
chunks = [content[i : i + chunk_size] for i in range(0, len(content), chunk_size)]
127146
return chunks
128147

148+
129149
def compute_similarity(post_id: int) -> None:
130150
post = Post.objects.get(id=post_id)
131151
other_posts = Post.objects.exclude(id=post_id).exclude(content="")
@@ -135,7 +155,8 @@ def compute_similarity(post_id: int) -> None:
135155

136156
# Create a list of (chunk, post_pk) tuples for all other posts
137157
combined_texts_and_pks = [
138-
(chunk, other_post.pk) for other_post in other_posts
158+
(chunk, other_post.pk)
159+
for other_post in other_posts
139160
for chunk in getPostChunks(other_post)
140161
]
141162

@@ -152,18 +173,24 @@ def compute_similarity(post_id: int) -> None:
152173
return
153174

154175
cosine_sim = cosine_similarity(tfidf_matrix[0:1], tfidf_matrix[1:])
155-
176+
156177
# Calculate the number of similar posts to consider
157178
num_similar_posts = min(len(set(post_pks)) - 1, 3) # Exclude the target post itself
158179

159180
# Get the top indices, but make sure to map them back to unique post PKs
160181
top_indices = np.argsort(-cosine_sim[0])[:num_similar_posts]
161-
unique_top_pks = {post_pks[i + 1] for i in top_indices} # +1 to skip the first post itself
182+
unique_top_pks = {
183+
post_pks[i + 1] for i in top_indices
184+
} # +1 to skip the first post itself
162185

163186
for pk in unique_top_pks:
164-
idx = combined_texts_and_pks.index(next(filter(lambda x: x[1] == pk, combined_texts_and_pks)))
187+
idx = combined_texts_and_pks.index(
188+
next(filter(lambda x: x[1] == pk, combined_texts_and_pks))
189+
)
165190
Similarity.objects.update_or_create(
166191
post1=post,
167192
post2=Post.objects.get(pk=pk),
168-
defaults={"score": cosine_sim[0][idx - 1]}, # Adjust index for cosine_sim offset
169-
)
193+
defaults={
194+
"score": cosine_sim[0][idx - 1]
195+
}, # Adjust index for cosine_sim offset
196+
)

app/manage.py

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
#!/usr/bin/env python
22
import sys
3+
import os
34
from pathlib import Path
45
from dotenv import load_dotenv
56
from django.core.management.utils import get_random_secret_key
67

7-
load_dotenv()
8+
if "DYNO" not in os.environ:
9+
load_dotenv()
10+
811

912
def setup_env():
10-
env_example_path = Path('.') / '.env.example'
11-
env_path = Path('.') / '.env'
13+
env_example_path = Path(".") / ".env.example"
14+
env_path = Path(".") / ".env"
1215

1316
if env_path.exists():
1417
print(".env file already exists. Exiting.")
@@ -20,24 +23,27 @@ def setup_env():
2023

2124
secret_key = get_random_secret_key()
2225

23-
with open(env_example_path, 'r') as example_file:
26+
with open(env_example_path, "r") as example_file:
2427
env_content = example_file.readlines()
2528

26-
with open(env_path, 'w') as env_file:
29+
with open(env_path, "w") as env_file:
2730
for line in env_content:
28-
key_value = line.strip().split('=', 1)
29-
if key_value[0] == 'SECRET_KEY':
30-
env_file.write(f'SECRET_KEY={secret_key}\n')
31+
key_value = line.strip().split("=", 1)
32+
if key_value[0] == "SECRET_KEY":
33+
env_file.write(f"SECRET_KEY={secret_key}\n")
3134
else:
32-
env_file.write(key_value[0] + '=' + key_value[1].split('#')[0].strip() + '\n')
35+
env_file.write(
36+
key_value[0] + "=" + key_value[1].split("#")[0].strip() + "\n"
37+
)
3338

3439
print(".env file created with a new SECRET_KEY and other values from .env.example.")
3540

41+
3642
# Make sure DJANGO_SETTINGS_MODULE is added to .env file
3743
def main():
38-
if 'setup_env' in sys.argv:
44+
if "setup_env" in sys.argv:
3945
setup_env()
40-
sys.argv.remove('setup_env')
46+
sys.argv.remove("setup_env")
4147

4248
try:
4349
from django.core.management import execute_from_command_line

0 commit comments

Comments
 (0)