Skip to content

Commit

Permalink
Many improvements (#9)
Browse files Browse the repository at this point in the history
  • Loading branch information
Currie32 authored Jul 30, 2024
1 parent f102b46 commit d496f79
Show file tree
Hide file tree
Showing 14 changed files with 543 additions and 229 deletions.
3 changes: 3 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# Use the official Python base image
FROM python:3.11-slim

# Install ffmpeg
RUN apt-get update && apt-get install -y ffmpeg

# Set the working directory in the container
WORKDIR /app

Expand Down
5 changes: 2 additions & 3 deletions app.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,9 @@ def serve_sitemap():
gtag('config', 'G-THNE3MSS49');
</script>
<meta charset="UTF-8">
<meta name="description" content="Practice speaking and writing in a foreign language.">
<meta name="keywords" content="practice a language, learn a language, practice having conversations in a language, duolingo alternatives">
<meta name="description" content="Practice a language by having conversations about the topic of your choice.">
<meta property="og:title" content="Practice a Language">
<meta property="og:description" content="Practice speaking and writing in a foreign language.">
<meta property="og:description" content="Practice a language by having conversations about the topic of your choice.">
<meta property="og:image" content="https://practicealanguage.xyz/assets/favicon.ico">
<meta property="og:url" content="https://practicealanguage.xyz">
<meta name="twitter:card" content="https://practicealanguage.xyz/assets/favicon.ico">
Expand Down
44 changes: 41 additions & 3 deletions assets/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ body {
margin: auto;
max-width: 600px;
min-height: 470px;
padding: 0px 20px;
padding: 0px 20px 10px;
}
#conversation {
display: block;
Expand Down Expand Up @@ -68,6 +68,13 @@ body {
margin: 10px 0px -15px;
width: fit-content;
}
#intro {
color: #333;
font-size: 20px;
font-style: italic;
margin: 0px 20px 20px;
text-align: center;
}
.languages {
display: flex;
min-width: 100%;
Expand Down Expand Up @@ -102,13 +109,19 @@ body {
float: left;
max-width: 75%;
padding: 5px 10px;
text-decoration: underline;
text-decoration-color: #87b7ff;
text-decoration-style: dotted;
width: fit-content;
}
.message-user {
background-color: #87b7ff;
border-radius: 4px 0px 4px 4px;
clear: both;
padding: 5px 10px;
text-decoration: underline;
text-decoration-color: #000;
text-decoration-style: dotted;
width: fit-content;
}
.message-user-wrapper {
Expand All @@ -117,15 +130,28 @@ body {
margin: 15px 0px;
width: 100%;
}
#toggle-play-audio-text {
#audio-settings-text {
color: #aaa;
font-size: 14px;
font-style: italic;
margin-right: 10px;
padding-top: 2px;
}
#toggle-play-audio-wrapper {
#audio-settings {
display: flex;
}
#toggle-play-audio-div {
display: flex;
margin: 0px 120px 10px 0px;
}
#slider-audio-speed-div {
display: flex;
margin: 0px 0px 10px;
}
#audio-speed {
margin-left: 10px;
margin-top: 5px;
width: 50px;
}
#translation {
clear: both;
Expand Down Expand Up @@ -154,3 +180,15 @@ body {
#user-response-text::placeholder {
color: #aaa;
}

@media screen and (max-width: 600px) {
#audio-settings {
display: block;
}
#intro {
font-size: 18px;
}
#toggle-play-audio-div {
margin: 0px;
}
}
17 changes: 13 additions & 4 deletions assets/audio.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import base64

from gtts import gTTS
from pydub import AudioSegment


def get_audio_file(text: str, language: str) -> str:
def get_audio_file(text: str, language: str, playback_speed: float) -> str:
"""
Create and return an mp3 file that contains the audio
for a message to be played in the desired language's accent.
Expand All @@ -21,10 +22,18 @@ def get_audio_file(text: str, language: str) -> str:
audio_path = "temp_audio.mp3"
tts.save(audio_path)

# Read and encode the audio file
with open(audio_path, "rb") as audio_file:
# Create a new audio segment with adjusted speed
audio = AudioSegment.from_file(audio_path)
playback_speed = 1 + (playback_speed / 100)
adjusted_audio = audio.speedup(playback_speed=playback_speed)

# Save the adjusted audio to a new file
adjusted_audio_file = f"adjusted_audio.mp3"
adjusted_audio.export(adjusted_audio_file, format="mp3")

with open(adjusted_audio_file, "rb") as audio_file:
audio_data = audio_file.read()
audio_base64 = base64.b64encode(audio_data).decode("utf-8")
audio_src = f"data:audio/mpeg;base64,{audio_base64}"

return audio_src
return audio_src
32 changes: 12 additions & 20 deletions assets/chat_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
import time
from typing import Dict, List

import openai

import requests
from dash import Input, Output, callback, no_update
from openai import OpenAI
from tenacity import retry, stop_after_attempt, wait_random_exponential

openai.api_key = os.environ.get("OPENAI_KEY")
client = OpenAI(api_key=os.environ.get("OPENAI_KEY"))


@callback(
Expand Down Expand Up @@ -36,10 +37,11 @@ def convert_audio_recording_to_text(check_for_audio_file: bool) -> str:

while check_for_audio_file:
if os.path.exists(audio_recording):

audio_file = open(audio_recording, "rb")
os.remove(audio_recording)
transcript = openai.Audio.transcribe("whisper-1", audio_file)
transcript = client.audio.transcriptions.create(
model="whisper-1", file=audio_file
)
message_user = transcript.to_dict()["text"]

return message_user, {"display": "none"}, False
Expand All @@ -62,7 +64,7 @@ def get_assistant_message(messages: List[Dict[str, str]]) -> str:
"""

chat_response = _chat_completion_request(messages)
message_assistant = chat_response.json()["choices"][0]["message"]["content"]
message_assistant = chat_response.choices[0].message.content

# Remove space before "!" or "?"
message_assistant = re.sub(r"\s+([!?])", r"\1", message_assistant)
Expand All @@ -82,22 +84,11 @@ def _chat_completion_request(messages: List[Dict[str, str]]) -> Dict:
A response from OpenAI's model to the user's statement.
"""

headers = {
"Content-Type": "application/json",
"Authorization": "Bearer " + openai.api_key,
}
json_data = {
"model": "gpt-3.5-turbo-0613",
"messages": messages,
"temperature": 1.5, # Higher values provide more varied responses
}
try:
response = requests.post(
"https://api.openai.com/v1/chat/completions",
headers=headers,
json=json_data,
completion = client.chat.completions.create(
model="gpt-4o-mini", temperature=1.5, max_tokens=50, messages=messages
)
return response
return completion
except Exception as e:
return e

Expand All @@ -120,7 +111,8 @@ def system_content(
The content message for the system.
"""

content = f"Start a conversation about {conversation_setting} in {language_learn}. \
content = f"Act as an excellent {language_learn} teacher who is helping me to practice {language_learn}. \
Start a conversation about {conversation_setting} in {language_learn}. \
Provide one statement in {language_learn}, then wait for my response. \
Do not write in {language_known}. \
Always finish your response with a question. \
Expand Down
8 changes: 6 additions & 2 deletions assets/footer.css
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
#buy-me-a-coffee-logo {
max-width: 100px;
margin-top: -3px;
}
#email {
font-size: 14px;
text-align: center;
}
#footer {
border-top: 1px solid #cccccc;
display: flex;
font-size: 13px;
justify-content: center;
margin: 20px auto 0px;
margin: 20px auto 5px;
width: 100%;
}
#footer a {
Expand All @@ -23,6 +24,9 @@
margin-top: 25px;
}
@media (max-width: 800px) {
#buy-me-a-coffee-logo {
margin-top: 0px;
}
#footer {
display: block;
padding: 20px;
Expand Down
46 changes: 46 additions & 0 deletions assets/message_correction.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import os

from openai import OpenAI
from tenacity import retry, stop_after_attempt, wait_random_exponential

client = OpenAI(api_key=os.environ.get("OPENAI_KEY"))


def get_corrected_message(message: str, language_learn: str) -> str:
"""
Get and process the assistant's (OpenAI's model) message to continue the conversation.
Params:
message: The message from the assistant.
language_learn: The language that the user wants to learn.
Returns:
The corrected message from the assistant.
"""

message_corrected = _chat_completion_request(message, language_learn)
if message_corrected != message:
return message_corrected


@retry(wait=wait_random_exponential(multiplier=1, max=40), stop=stop_after_attempt(3))
def _chat_completion_request(message: str, language_learn: str) -> str:
"""
Request a response to the user's statement from one of OpenAI's chat models.
Params:
messages: The conversation history between the user and the chat model.
language_learn: The language that the user wants to learn.
Returns:
The corrected message from OpenAI's model.
"""

try:
content = f"You are an excellent {language_learn} teacher. Correct this sentence for any mistakes:\n{message}"
completion = client.chat.completions.create(
model="gpt-3.5-turbo", messages=[{"role": "system", "content": content}]
)
return completion.choices[0].message.content
except Exception as e:
return e
4 changes: 3 additions & 1 deletion footer.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
footer = html.Div(id='footer', children=[
html.P("Practice a Language. All rights reserved."),
html.Div("|", className="footer-pipe"),
dcc.Link("About", href="/about"),
html.Div("|", className="footer-pipe"),
html.A("We're open source!", target="_blank", href="https://github.com/Currie32/practice-a-language"),
html.Div("|", className="footer-pipe"),
html.A(
Expand All @@ -14,7 +16,7 @@
html.Div("|", className="footer-pipe"),
html.P("[email protected]"),
html.Div("|", className="footer-pipe"),
dcc.Link("Terms & Conditions", href="/terms"),
dcc.Link("Terms", href="/terms"),
html.Div("|", className="footer-pipe"),
dcc.Link("Privacy Policy", href="/privacy_policy"),
])
37 changes: 37 additions & 0 deletions pages/about.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from dash import html, register_page


register_page(__name__, path="/about")

meta_tags = [
{
"name": "description",
"content": "Practice A Language - Learn and practice languages through conversations.",
},
]

layout = html.Div(
id="content",
children=[
html.H1("About Practice a Language"),
html.P(
"Welcome to Practice A Language, a website to help you practice a language by having conversations. This website started from wanting to make it easier to learn a language before going on trips abroad. I became annoyed with the over-repetition of apps like Duolingo and losing track of how many times I translated “Juan come manzanas”."
),
html.H2("Learn what you want faster"),
html.P(
"Unlike other tools that force you to learn according to their lesson plans, you can practice the conversation topics and phrases that you want, whenever you want. This control should help you to be ready for your next trip abroad much faster."
),
html.H2("Practice at your level"),
html.P(
"You chat in either the language you’re learning or your native language. This allows experienced speakers to practice their vocabulary and grammar, while beginners can write in their native language and it will automatically be translated into the language they are learning."
),
html.H2("Practice writing and speaking"),
html.P(
"You have the choice to practice your new language by either writing your response or recording your voice. If you record your voice, it will be transcribed so that you can see what was understood. If you want to make a change, then you can edit the text or rerecord yourself."
),
html.H2("Learn from your mistakes"),
html.P(
"When speaking or writing in your new language, your responses are always analyzed for mistakes and will be automatically corrected. This quick feedback will help you to learn more from each conversation."
),
],
)
Loading

0 comments on commit d496f79

Please sign in to comment.