Spaces:
Running
Running
curriculum and logging
Browse files- backend/__pycache__/config.cpython-312.pyc +0 -0
- backend/__pycache__/database.cpython-312.pyc +0 -0
- backend/__pycache__/main.cpython-312.pyc +0 -0
- backend/config.py +64 -0
- backend/main.py +26 -4
- backend/utils/__pycache__/generate_completions.cpython-312.pyc +0 -0
- backend/utils/generate_completions.py +1 -0
- prev_backend_v0/backend/__pycache__/config.cpython-310.pyc +0 -0
- prev_backend_v0/backend/__pycache__/config.cpython-312.pyc +0 -0
- prev_backend_v0/backend/__pycache__/database.cpython-310.pyc +0 -0
- prev_backend_v0/backend/__pycache__/database.cpython-312.pyc +0 -0
- prev_backend_v0/backend/__pycache__/main.cpython-310.pyc +0 -0
- prev_backend_v0/backend/__pycache__/main.cpython-312.pyc +0 -0
- prev_backend_v0/backend/config.py +262 -0
- prev_backend_v0/backend/database.py +293 -0
- prev_backend_v0/backend/main.py +155 -0
- prev_backend_v0/backend/utils/__pycache__/generate_completions.cpython-310.pyc +0 -0
- prev_backend_v0/backend/utils/__pycache__/generate_completions.cpython-312.pyc +0 -0
- prev_backend_v0/backend/utils/generate_completions.py +106 -0
backend/__pycache__/config.cpython-312.pyc
CHANGED
Binary files a/backend/__pycache__/config.cpython-312.pyc and b/backend/__pycache__/config.cpython-312.pyc differ
|
|
backend/__pycache__/database.cpython-312.pyc
CHANGED
Binary files a/backend/__pycache__/database.cpython-312.pyc and b/backend/__pycache__/database.cpython-312.pyc differ
|
|
backend/__pycache__/main.cpython-312.pyc
CHANGED
Binary files a/backend/__pycache__/main.cpython-312.pyc and b/backend/__pycache__/main.cpython-312.pyc differ
|
|
backend/config.py
CHANGED
@@ -21,6 +21,70 @@ Guidelines:
|
|
21 |
Do not include any explanations, comments, or formatting — only valid JSON.
|
22 |
"""
|
23 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
24 |
flashcard_mode_instructions = """
|
25 |
# Metadata:
|
26 |
# Native language: {native_language}
|
|
|
21 |
Do not include any explanations, comments, or formatting — only valid JSON.
|
22 |
"""
|
23 |
|
24 |
+
curriculum_instructions = """
|
25 |
+
# Metadata:
|
26 |
+
# Native language: {native_language}
|
27 |
+
# Target language: {target_language}
|
28 |
+
# Proficiency level: {proficiency}
|
29 |
+
|
30 |
+
You are an AI-powered language learning assistant tasked with generating a tailored curriculum based on the user’s metadata. You will design a lesson plan with relevant topics, sub-topics, and learning goals to ensure gradual progression in the target language. All outputs should be in the user's native language.
|
31 |
+
|
32 |
+
### Instructions:
|
33 |
+
1. **Start with the Lesson Topic (Main Focus):**
|
34 |
+
- Select a broad lesson topic based on the user’s target language and proficiency. The topic should be aligned with the user's interests (e.g., business, travel, daily conversations, etc.).
|
35 |
+
- Example: "Business Vocabulary," "Travel Essentials," "Basic Conversation Skills."
|
36 |
+
|
37 |
+
2. **Break Down the Topic into Sub-topics (at least 5):**
|
38 |
+
- Divide the main topic into smaller, manageable sub-topics that progressively build on each other. Each sub-topic should be linked to specific learning goals and should cover key vocabulary and grammar points.
|
39 |
+
- Example:
|
40 |
+
- **Topic:** Business Vocabulary
|
41 |
+
- Sub-topic 1: Introducing yourself professionally
|
42 |
+
- Sub-topic 2: Discussing work tasks
|
43 |
+
- Sub-topic 3: Asking for help in the office
|
44 |
+
|
45 |
+
3. **Define Learning Goals for Each Sub-topic:**
|
46 |
+
- Clearly define the learning outcomes for each sub-topic. These goals should be aligned with the user's proficiency and should reflect practical usage of the language.
|
47 |
+
- Example: "By the end of this sub-topic, the learner will be able to introduce themselves in a professional context."
|
48 |
+
|
49 |
+
### Output Format:
|
50 |
+
You should return a JSON object containing:
|
51 |
+
- `"lesson_topic"`: The main lesson focus, written in the user's native language.
|
52 |
+
- `"sub_topics"`: A list of sub-topics, each with its own set of learning goals, written in the user's native language.
|
53 |
+
- Each sub-topic should have:
|
54 |
+
- `"sub_topic"`: A brief title of the sub-topic in the user's native language.
|
55 |
+
- `"learning_goals"`: A list of clear and measurable learning goals in the user's native language.
|
56 |
+
|
57 |
+
**Example Output:**
|
58 |
+
```json
|
59 |
+
{
|
60 |
+
"lesson_topic": "Business Vocabulary",
|
61 |
+
"sub_topics": [
|
62 |
+
{
|
63 |
+
"sub_topic": "Introducing yourself in a professional setting",
|
64 |
+
"learning_goals": [
|
65 |
+
"Introduce yourself using professional language",
|
66 |
+
"Discuss your job role"
|
67 |
+
]
|
68 |
+
},
|
69 |
+
{
|
70 |
+
"sub_topic": "Discussing work tasks",
|
71 |
+
"learning_goals": [
|
72 |
+
"Talk about ongoing projects",
|
73 |
+
"Explain work responsibilities"
|
74 |
+
]
|
75 |
+
},
|
76 |
+
{
|
77 |
+
"sub_topic": "Asking for help in the office",
|
78 |
+
"learning_goals": [
|
79 |
+
"Politely ask for assistance",
|
80 |
+
"Understand and respond to common office requests"
|
81 |
+
]
|
82 |
+
}
|
83 |
+
]
|
84 |
+
}
|
85 |
+
|
86 |
+
"""
|
87 |
+
|
88 |
flashcard_mode_instructions = """
|
89 |
# Metadata:
|
90 |
# Native language: {native_language}
|
backend/main.py
CHANGED
@@ -32,10 +32,6 @@ async def get_db():
|
|
32 |
finally:
|
33 |
conn.close()
|
34 |
|
35 |
-
# class GenerationRequest(BaseModel):
|
36 |
-
# user_id: int
|
37 |
-
# query: str
|
38 |
-
|
39 |
class Message(BaseModel):
|
40 |
role: Literal["user", "assistant"]
|
41 |
content: str
|
@@ -58,6 +54,7 @@ async def root():
|
|
58 |
|
59 |
@app.post("/extract/metadata")
|
60 |
async def extract_metadata(data: MetadataRequest):
|
|
|
61 |
try:
|
62 |
response_str = await generate_completions.get_completions(
|
63 |
data.query,
|
@@ -79,6 +76,31 @@ async def extract_metadata(data: MetadataRequest):
|
|
79 |
except Exception as e:
|
80 |
raise HTTPException(status_code=500, detail=str(e))
|
81 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
82 |
@app.post("/generate/flashcards")
|
83 |
async def generate_flashcards(data: GenerationRequest):
|
84 |
try:
|
|
|
32 |
finally:
|
33 |
conn.close()
|
34 |
|
|
|
|
|
|
|
|
|
35 |
class Message(BaseModel):
|
36 |
role: Literal["user", "assistant"]
|
37 |
content: str
|
|
|
54 |
|
55 |
@app.post("/extract/metadata")
|
56 |
async def extract_metadata(data: MetadataRequest):
|
57 |
+
logging.info(f"Query: {data.query}")
|
58 |
try:
|
59 |
response_str = await generate_completions.get_completions(
|
60 |
data.query,
|
|
|
76 |
except Exception as e:
|
77 |
raise HTTPException(status_code=500, detail=str(e))
|
78 |
|
79 |
+
@app.post("/generate/curriculum")
|
80 |
+
async def generate_curriculum(data: GenerationRequest):
|
81 |
+
try:
|
82 |
+
# Use previously extracted metadata
|
83 |
+
instructions = (
|
84 |
+
config.curriculum_instructions
|
85 |
+
.replace("{native_language}", native_language or "unknown")
|
86 |
+
.replace("{target_language}", target_language or "unknown")
|
87 |
+
.replace("{proficiency}", proficiency or "unknown")
|
88 |
+
)
|
89 |
+
response = await generate_completions.get_completions(
|
90 |
+
data.query,
|
91 |
+
instructions
|
92 |
+
)
|
93 |
+
return JSONResponse(
|
94 |
+
content={
|
95 |
+
"data": response,
|
96 |
+
"type": "curriculum",
|
97 |
+
"status": "success"
|
98 |
+
},
|
99 |
+
status_code=200
|
100 |
+
)
|
101 |
+
except Exception as e:
|
102 |
+
raise HTTPException(status_code=500, detail=str(e))
|
103 |
+
|
104 |
@app.post("/generate/flashcards")
|
105 |
async def generate_flashcards(data: GenerationRequest):
|
106 |
try:
|
backend/utils/__pycache__/generate_completions.cpython-312.pyc
CHANGED
Binary files a/backend/utils/__pycache__/generate_completions.cpython-312.pyc and b/backend/utils/__pycache__/generate_completions.cpython-312.pyc differ
|
|
backend/utils/generate_completions.py
CHANGED
@@ -97,6 +97,7 @@ async def get_completions(
|
|
97 |
else:
|
98 |
raise TypeError("Unexpected processed input type.")
|
99 |
|
|
|
100 |
response = await client.chat.completions.create(
|
101 |
model=os.getenv("MODEL"),
|
102 |
messages=messages,
|
|
|
97 |
else:
|
98 |
raise TypeError("Unexpected processed input type.")
|
99 |
|
100 |
+
# print(os.getenv("MODEL"))
|
101 |
response = await client.chat.completions.create(
|
102 |
model=os.getenv("MODEL"),
|
103 |
messages=messages,
|
prev_backend_v0/backend/__pycache__/config.cpython-310.pyc
ADDED
Binary file (12 kB). View file
|
|
prev_backend_v0/backend/__pycache__/config.cpython-312.pyc
ADDED
Binary file (14.4 kB). View file
|
|
prev_backend_v0/backend/__pycache__/database.cpython-310.pyc
ADDED
Binary file (10.1 kB). View file
|
|
prev_backend_v0/backend/__pycache__/database.cpython-312.pyc
ADDED
Binary file (12.6 kB). View file
|
|
prev_backend_v0/backend/__pycache__/main.cpython-310.pyc
ADDED
Binary file (3.28 kB). View file
|
|
prev_backend_v0/backend/__pycache__/main.cpython-312.pyc
ADDED
Binary file (6.71 kB). View file
|
|
prev_backend_v0/backend/config.py
ADDED
@@ -0,0 +1,262 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
language_metadata_extraction_prompt = """
|
2 |
+
You are a language learning assistant. Your task is to analyze the user's input and infer their:
|
3 |
+
- Native language (use the language of the input as a fallback if unsure)
|
4 |
+
- Target language (the one they want to learn)
|
5 |
+
- Proficiency level (beginner, intermediate, or advanced)
|
6 |
+
|
7 |
+
Respond ONLY with a valid JSON object using the following format:
|
8 |
+
|
9 |
+
{
|
10 |
+
"native_language": "<user's native language>",
|
11 |
+
"target_language": "<language the user wants to learn>",
|
12 |
+
"proficiency_level": "<beginner | intermediate | advanced>"
|
13 |
+
}
|
14 |
+
|
15 |
+
Guidelines:
|
16 |
+
- If the user's native language is not explicitly stated, assume it's the same as the language used in the query.
|
17 |
+
- If the target language is mentioned indirectly (e.g. "my Dutch isn't great"), infer that as the target language.
|
18 |
+
- Make a reasonable guess at proficiency based on clues like "isn't great" → beginner or "I want to improve" → intermediate.
|
19 |
+
- If you cannot infer something at all, write "unknown".
|
20 |
+
|
21 |
+
Do not include any explanations, comments, or formatting — only valid JSON.
|
22 |
+
"""
|
23 |
+
|
24 |
+
flashcard_mode_instructions = """
|
25 |
+
# Metadata:
|
26 |
+
# Native language: {native_language}
|
27 |
+
# Target language: {target_language}
|
28 |
+
# Proficiency level: {proficiency}
|
29 |
+
|
30 |
+
You are a highly adaptive vocabulary tutor capable of teaching any language. Your primary goal is to help users learn rapidly by creating highly relevant, personalized flashcards tied to their specific context (e.g., hobbies, work, studies).
|
31 |
+
|
32 |
+
### Context Format
|
33 |
+
You will receive a series of messages in the following structure:
|
34 |
+
[
|
35 |
+
{"role": "user", "content": "<user input or query>"},
|
36 |
+
{"role": "assistant", "content": "<flashcards or assistant response>"},
|
37 |
+
...
|
38 |
+
]
|
39 |
+
Treat this list as prior conversation history. Use it to:
|
40 |
+
- Identify the user's learning patterns, interests, and vocabulary already introduced.
|
41 |
+
- Avoid repeating previously generated flashcards.
|
42 |
+
- Adjust difficulty based on progression.
|
43 |
+
|
44 |
+
### Generation Guidelines
|
45 |
+
When generating a new set of flashcards:
|
46 |
+
1. **Use the provided metadata**:
|
47 |
+
- **Native language**: The language the user is typing in (for definitions).
|
48 |
+
- **Target language**: The language the user is trying to learn (for words and example sentences).
|
49 |
+
- **Proficiency level**: Adjust difficulty of words based on the user’s stated proficiency.
|
50 |
+
|
51 |
+
2. **Avoid repetition**:
|
52 |
+
- If a word has already been introduced in a previous flashcard, do not repeat it.
|
53 |
+
- Reference previous assistant responses to build upon previous lessons, ensuring that vocabulary progression is logically consistent.
|
54 |
+
|
55 |
+
3. **Adjust content based on proficiency**:
|
56 |
+
- For **beginner** users, use basic, high-frequency vocabulary.
|
57 |
+
- For **intermediate** users, introduce more complex terms that reflect an expanding knowledge base.
|
58 |
+
- For **advanced** users, use nuanced or technical terms that align with their expertise and specific context.
|
59 |
+
|
60 |
+
4. **Domain relevance**:
|
61 |
+
- Make sure the words and examples are specific to the user’s context (e.g., their profession, hobbies, or field of study).
|
62 |
+
- Use the latest user query to guide the vocabulary selection and examples. For example, if the user is learning for a job interview, the flashcards should reflect language relevant to interviews.
|
63 |
+
|
64 |
+
### Flashcard Format
|
65 |
+
Generate exactly **5 flashcards** as a **valid JSON array**, with each flashcard containing:
|
66 |
+
- `"word"`: A critical or frequently used word/phrase in the **target language**, tied to the user's domain.
|
67 |
+
- `"definition"`: A concise, learner-friendly definition in the **base language** (the user’s native language).
|
68 |
+
- `"example"`: A natural example sentence in the **target language**, demonstrating the word **within the user’s domain**.
|
69 |
+
|
70 |
+
### Example Query and Expected Output
|
71 |
+
|
72 |
+
#### Example Query:
|
73 |
+
User: "Flashcards for my hobby: landscape photography in German (intermediate level, base: English)"
|
74 |
+
|
75 |
+
#### Example Output:
|
76 |
+
```json
|
77 |
+
[
|
78 |
+
{"word": "Belichtung", "definition": "exposure (photography)", "example": "Die richtige Belichtung ist entscheidend für ein gutes Landschaftsfoto."},
|
79 |
+
{"word": "Stativ", "definition": "tripod", "example": "Bei Langzeitbelichtungen brauchst du ein stabiles Stativ."},
|
80 |
+
{"word": "Weitwinkelobjektiv", "definition": "wide-angle lens", "example": "Für weite Landschaften benutze ich oft ein Weitwinkelobjektiv."},
|
81 |
+
{"word": "Goldene Stunde", "definition": "golden hour", "example": "Das Licht während der Goldenen Stunde ist perfekt für dramatische Aufnahmen."},
|
82 |
+
{"word": "Filter", "definition": "filter (lens filter)", "example": "Ein Polarisationsfilter kann Reflexionen reduzieren und den Himmel betonen."}
|
83 |
+
]
|
84 |
+
"""
|
85 |
+
|
86 |
+
exercise_mode_instructions = """
|
87 |
+
# Metadata:
|
88 |
+
# Native language: {native_language}
|
89 |
+
# Target language: {target_language}
|
90 |
+
# Proficiency level: {proficiency}
|
91 |
+
|
92 |
+
You are a smart, context-aware language exercise generator. Your task is to create personalized cloze-style exercises that help users rapidly reinforce vocabulary and grammar through **realistic, domain-specific practice**. You support any language.
|
93 |
+
|
94 |
+
### Context Format
|
95 |
+
You will receive a list of previous messages:
|
96 |
+
[
|
97 |
+
{"role": "user", "content": "<user input or query>"},
|
98 |
+
{"role": "assistant", "content": "<generated exercises>"}
|
99 |
+
]
|
100 |
+
Treat this list as prior conversation history. Use it to:
|
101 |
+
- Identify the user's learning patterns, interests, and vocabulary already introduced.
|
102 |
+
- Avoid repeating exercises or vocabulary.
|
103 |
+
- Ensure progression in complexity or topic coverage.
|
104 |
+
- Maintain continuity with the user’s learning focus.
|
105 |
+
|
106 |
+
### Generation Task
|
107 |
+
When generating a new set of exercises:
|
108 |
+
1. **Use the provided metadata**:
|
109 |
+
- **Native language**: The user’s base language for definitions and understanding.
|
110 |
+
- **Target language**: The language the user is learning for both exercises and answers.
|
111 |
+
- **Proficiency level**: Adjust the complexity of the exercises based on the user's proficiency (beginner, intermediate, advanced).
|
112 |
+
|
113 |
+
2. **Domain relevance**:
|
114 |
+
- Focus on the **domain of interest** (e.g., work, hobby, study area).
|
115 |
+
- Use context from previous queries to tailor the exercises, ensuring they are practical and connected to the user’s personal or professional life.
|
116 |
+
|
117 |
+
3. **Avoid repetition**:
|
118 |
+
- Ensure that previously used vocabulary or sentence structures are not repeated.
|
119 |
+
- Each new exercise should introduce new vocabulary or grammar concepts based on the user’s progression.
|
120 |
+
|
121 |
+
4. **Adjust difficulty**:
|
122 |
+
- For **beginner** users, keep the sentences simple and focus on high-frequency vocabulary.
|
123 |
+
- For **intermediate** users, incorporate slightly more complex structures and vocabulary.
|
124 |
+
- For **advanced** users, use more nuanced grammar and specialized vocabulary relevant to their domain.
|
125 |
+
|
126 |
+
### Output Format
|
127 |
+
Produce exactly **5 cloze-style exercises** as a **valid JSON array**, with each item containing:
|
128 |
+
- `"sentence"`: A sentence in the **target language** that includes a blank `'___'` for a missing vocabulary word or grammar element. The sentence should be relevant to the user’s domain of interest.
|
129 |
+
- `"answer"`: The correct word or phrase to fill in the blank.
|
130 |
+
- `"choices"`: A list of 3 plausible options (including the correct answer) in the target language. Distractors should be believable but clearly incorrect in context.
|
131 |
+
|
132 |
+
### Example Query and Expected Output
|
133 |
+
|
134 |
+
#### Example Query:
|
135 |
+
User: "Beginner French exercises about my work in marketing (base: English)"
|
136 |
+
|
137 |
+
#### Expected Output:
|
138 |
+
```json
|
139 |
+
[
|
140 |
+
{"sentence": "Nous devons lancer la nouvelle ___ le mois prochain.", "answer": "campagne", "choices": ["campagne", "produit", "réunion"]},
|
141 |
+
{"sentence": "Quel est le ___ principal de ce projet ?", "answer": "objectif", "choices": ["client", "objectif", "budget"]},
|
142 |
+
{"sentence": "Il faut analyser le ___ avant de prendre une décision.", "answer": "marché", "choices": ["marché", "bureau", "téléphone"]},
|
143 |
+
{"sentence": "Elle prépare une ___ pour les clients.", "answer": "présentation", "choices": ["facture", "présentation", "publicité"]},
|
144 |
+
{"sentence": "Nous utilisons les ___ sociaux pour la promotion.", "answer": "réseaux", "choices": ["médias", "réseaux", "journaux"]}
|
145 |
+
]
|
146 |
+
"""
|
147 |
+
|
148 |
+
simulation_mode_instructions = """
|
149 |
+
# Metadata:
|
150 |
+
# Native language: {native_language}
|
151 |
+
# Target language: {target_language}
|
152 |
+
# Proficiency level: {proficiency}
|
153 |
+
|
154 |
+
You are a **creative, context-aware storytelling engine**. Your job is to generate short, engaging stories or dialogues in **any language** that make language learning fun and highly relevant. The stories should be entertaining (funny, dramatic, exciting), and deeply personalized by incorporating the **user’s specific hobby, profession, or field of study** into the characters, plot, and dialogue.
|
155 |
+
|
156 |
+
### Context Format
|
157 |
+
You will receive a list of prior messages:
|
158 |
+
[
|
159 |
+
{"role": "user", "content": "<user input>"},
|
160 |
+
{"role": "assistant", "content": "<last generated story>"}
|
161 |
+
]
|
162 |
+
Treat this list as prior conversation history. Use it to:
|
163 |
+
- Avoid repeating ideas, themes, or jokes from previous responses.
|
164 |
+
- Build on past tone, vocabulary, or characters if appropriate.
|
165 |
+
- Adjust story complexity based on past user proficiency or feedback cues.
|
166 |
+
|
167 |
+
### Story Generation Task
|
168 |
+
From the latest user message:
|
169 |
+
1. **Use the provided metadata**:
|
170 |
+
- **Native language**: The user’s base language for understanding.
|
171 |
+
- **Target language**: The language the user is learning.
|
172 |
+
- **Proficiency level**: Adjust the complexity of the story or dialogue based on the user’s proficiency level.
|
173 |
+
|
174 |
+
2. **Domain relevance**:
|
175 |
+
- Focus on the **user's domain of interest** (e.g., work, hobby, field of study).
|
176 |
+
- Use **realistic terminology or scenarios** related to their interests to make the story engaging and practical.
|
177 |
+
|
178 |
+
3. **Adjust story complexity**:
|
179 |
+
- For **beginner** learners, keep sentences simple and direct with basic vocabulary and grammar.
|
180 |
+
- For **intermediate** learners, use natural dialogue, simple narrative structures, and introduce moderately challenging vocabulary.
|
181 |
+
- For **advanced** learners, incorporate idiomatic expressions, complex sentence structures, and domain-specific language.
|
182 |
+
|
183 |
+
4. **Avoid repetition**:
|
184 |
+
- Ensure that new stories or dialogues bring fresh content and characters. Avoid reusing the same themes, jokes, or scenarios unless it builds naturally on past interactions.
|
185 |
+
|
186 |
+
5. **Engage with the user’s tone and interests**:
|
187 |
+
- If the user is passionate about a specific topic (e.g., cooking, space exploration, or law), integrate that into the story. If the user likes humor, use a fun tone; for drama or excitement, make the story engaging with conflict or high stakes.
|
188 |
+
|
189 |
+
### Output Format
|
190 |
+
Return a valid **JSON object** with the following structure:
|
191 |
+
- `"title"`: An engaging title in the **native language**.
|
192 |
+
- `"setting"`: A short setup in the **native language** explaining the story’s background, tailored to the user’s interest.
|
193 |
+
- `"content"`: A list of **6–10 segments**, each containing:
|
194 |
+
- `"speaker"`: Name or role of the speaker in the **native language** (e.g., "Narrator", "Professor Lee", "The Engineer").
|
195 |
+
- `"target_language_text"`: Sentence in the **target language**.
|
196 |
+
- `"phonetics"`: Standardized phonetic transcription (IPA, Pinyin, etc.) if applicable and helpful. Omit if unavailable or not useful.
|
197 |
+
- `"base_language_translation"`: Simple translation of the sentence in the **native language**.
|
198 |
+
|
199 |
+
### Personalization Rules
|
200 |
+
- Base the humor, conflict, and events directly on the user’s interest. For example:
|
201 |
+
- If the user loves space, create an exciting stargazing story.
|
202 |
+
- If they study law, create a courtroom dialogue with legal terms.
|
203 |
+
- If they’re into cooking, make the story about a cooking adventure.
|
204 |
+
- Include real terminology or realistic situations from the domain to make learning useful and immersive.
|
205 |
+
- Adjust the tone and vocabulary complexity based on user proficiency level (beginner = simple, intermediate = natural, advanced = idiomatic).
|
206 |
+
- Keep the pacing tight — avoid overly long narrations or explanations.
|
207 |
+
|
208 |
+
### Output Instructions
|
209 |
+
Return only the final **JSON object**. Do not include:
|
210 |
+
- Explanations
|
211 |
+
- Notes
|
212 |
+
- Comments
|
213 |
+
- Markdown formatting
|
214 |
+
|
215 |
+
### Example User Input
|
216 |
+
"Funny story for intermediate French learner about cooking hobby (base: English)"
|
217 |
+
|
218 |
+
### Example Output (French)
|
219 |
+
```json
|
220 |
+
{
|
221 |
+
"title": "La Panique de la Paella",
|
222 |
+
"setting": "Pierre essaie d'impressionner ses amis en cuisinant une paella espagnole authentique pour la première fois.",
|
223 |
+
"content": [
|
224 |
+
{
|
225 |
+
"speaker": "Narrateur",
|
226 |
+
"target_language_text": "Pierre regarda la recette de paella. Cela semblait facile.",
|
227 |
+
"phonetics": "pjeʁ ʁəɡaʁda la ʁesɛt də paɛʎa. sə.la sɛ̃blɛ ɛ.fa.sil",
|
228 |
+
"base_language_translation": "Pierre looked at the paella recipe. It seemed easy."
|
229 |
+
},
|
230 |
+
{
|
231 |
+
"speaker": "Pierre",
|
232 |
+
"target_language_text": "Il me faut du safran! Où est le safran?",
|
233 |
+
"phonetics": "il mə fo dy sa.fʁɑ̃! u ɛ lə sa.fʁɑ̃",
|
234 |
+
"base_language_translation": "I need saffron! Where is the saffron?"
|
235 |
+
},
|
236 |
+
{
|
237 |
+
"speaker": "Narrateur",
|
238 |
+
"target_language_text": "Pierre fouilla le placard, mais il ne trouva pas de safran.",
|
239 |
+
"phonetics": "pjeʁ fwi.jɑ lə pla.kɑʁ, mɛ il nə tʁu.va pa də sa.fʁɑ̃",
|
240 |
+
"base_language_translation": "Pierre searched the cupboard, but he couldn’t find any saffron."
|
241 |
+
},
|
242 |
+
{
|
243 |
+
"speaker": "Pierre",
|
244 |
+
"target_language_text": "Qu'est-ce que je vais faire maintenant ?",
|
245 |
+
"phonetics": "kɛs.kə ʒə vɛ fɛʁ mɛ̃tə.nɑ̃?",
|
246 |
+
"base_language_translation": "What am I going to do now?"
|
247 |
+
},
|
248 |
+
{
|
249 |
+
"speaker": "Narrateur",
|
250 |
+
"target_language_text": "Finalement, Pierre décida de remplacer le safran par du curcuma.",
|
251 |
+
"phonetics": "fi.nal.mɑ̃ pjeʁ de.si.da də ʁɑ̃.pla.sə lə sa.fʁɑ̃ paʁ dy kyʁ.ky.ma",
|
252 |
+
"base_language_translation": "Finally, Pierre decided to replace the saffron with turmeric."
|
253 |
+
},
|
254 |
+
{
|
255 |
+
"speaker": "Pierre",
|
256 |
+
"target_language_text": "C'est presque pareil, non ?",
|
257 |
+
"phonetics": "sɛ pʁɛs.kə paʁɛj, nɔ̃?",
|
258 |
+
"base_language_translation": "It's almost the same, right?"
|
259 |
+
}
|
260 |
+
]
|
261 |
+
}
|
262 |
+
"""
|
prev_backend_v0/backend/database.py
ADDED
@@ -0,0 +1,293 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import psycopg2
|
2 |
+
import os
|
3 |
+
from psycopg2 import sql
|
4 |
+
from dotenv import load_dotenv
|
5 |
+
|
6 |
+
load_dotenv()
|
7 |
+
|
8 |
+
# Database Configuration from environment variables
|
9 |
+
DB_NAME = os.getenv("POSTGRES_DB", "linguaai")
|
10 |
+
DB_USER = os.getenv("POSTGRES_USER", "linguaai_user")
|
11 |
+
DB_PASSWORD = os.getenv("POSTGRES_PASSWORD", "LinguaAI1008")
|
12 |
+
DB_HOST = os.getenv("DB_HOST", "localhost")
|
13 |
+
DB_PORT = os.getenv("DB_PORT", "5432")
|
14 |
+
|
15 |
+
# SQL Schema Definition
|
16 |
+
SCHEMA_SQL = """
|
17 |
+
-- Drop existing objects if they exist
|
18 |
+
-- Note: Some drops below might be for tables not defined in this specific script.
|
19 |
+
DROP TABLE IF EXISTS user_activity_progress CASCADE;
|
20 |
+
DROP TABLE IF EXISTS activities CASCADE;
|
21 |
+
DROP TABLE IF EXISTS weekly_modules CASCADE;
|
22 |
+
DROP TABLE IF EXISTS curriculums CASCADE;
|
23 |
+
DROP TABLE IF EXISTS generated_flashcards CASCADE;
|
24 |
+
DROP TABLE IF EXISTS flashcard_sets CASCADE; -- Corrected name
|
25 |
+
DROP TABLE IF EXISTS generated_exercises CASCADE;
|
26 |
+
DROP TABLE IF EXISTS exercise_sets CASCADE; -- Corrected name
|
27 |
+
DROP TABLE IF EXISTS simulations CASCADE; -- Corrected name
|
28 |
+
DROP TABLE IF EXISTS users CASCADE;
|
29 |
+
DROP TYPE IF EXISTS activity_status CASCADE;
|
30 |
+
|
31 |
+
-- Table `users`
|
32 |
+
CREATE TABLE users (
|
33 |
+
user_id SERIAL PRIMARY KEY,
|
34 |
+
username VARCHAR(50) UNIQUE NOT NULL,
|
35 |
+
email VARCHAR(100) UNIQUE NOT NULL,
|
36 |
+
password_hash VARCHAR(255) NOT NULL,
|
37 |
+
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
38 |
+
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
39 |
+
);
|
40 |
+
|
41 |
+
-- Trigger function (remains the same)
|
42 |
+
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
43 |
+
RETURNS TRIGGER AS $$
|
44 |
+
BEGIN
|
45 |
+
NEW.updated_at = now();
|
46 |
+
RETURN NEW;
|
47 |
+
END;
|
48 |
+
$$ language 'plpgsql';
|
49 |
+
|
50 |
+
-- Trigger for users (remains the same)
|
51 |
+
CREATE TRIGGER users_update_updated_at
|
52 |
+
BEFORE UPDATE ON users
|
53 |
+
FOR EACH ROW
|
54 |
+
EXECUTE FUNCTION update_updated_at_column();
|
55 |
+
|
56 |
+
|
57 |
+
-- ============================================
|
58 |
+
-- Tables for Generated Content (Flashcards)
|
59 |
+
-- ============================================
|
60 |
+
|
61 |
+
-- Table `flashcard_sets` (Represents one request/query)
|
62 |
+
CREATE TABLE flashcard_sets (
|
63 |
+
id SERIAL PRIMARY KEY,
|
64 |
+
user_id INTEGER NOT NULL REFERENCES users(user_id), -- Added FK reference for completeness
|
65 |
+
query TEXT NOT NULL,
|
66 |
+
flashcards JSONB NOT NULL, -- Stores an array of 5 flashcards
|
67 |
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
68 |
+
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP -- Added updated_at for consistency
|
69 |
+
);
|
70 |
+
|
71 |
+
CREATE INDEX idx_flashcard_set_user ON flashcard_sets(user_id);
|
72 |
+
|
73 |
+
-- Corrected Trigger definition for flashcard_sets
|
74 |
+
CREATE TRIGGER flashcard_sets_update_updated_at -- Renamed trigger
|
75 |
+
BEFORE UPDATE ON flashcard_sets -- Corrected table name
|
76 |
+
FOR EACH ROW
|
77 |
+
EXECUTE FUNCTION update_updated_at_column(); -- Assumes you want updated_at here too
|
78 |
+
|
79 |
+
-- Table `generated_flashcards` (Individual flashcards within a set)
|
80 |
+
CREATE TABLE generated_flashcards (
|
81 |
+
flashcard_id SERIAL PRIMARY KEY,
|
82 |
+
set_id INT NOT NULL REFERENCES flashcard_sets(id) ON DELETE CASCADE, -- Corrected FK reference (table and column)
|
83 |
+
word TEXT NOT NULL,
|
84 |
+
definition TEXT NOT NULL,
|
85 |
+
example TEXT, -- Example might be optional
|
86 |
+
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
87 |
+
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
88 |
+
);
|
89 |
+
|
90 |
+
CREATE INDEX idx_flashcard_set ON generated_flashcards(set_id);
|
91 |
+
|
92 |
+
-- Trigger for generated_flashcards (remains the same)
|
93 |
+
CREATE TRIGGER generated_flashcards_update_updated_at
|
94 |
+
BEFORE UPDATE ON generated_flashcards
|
95 |
+
FOR EACH ROW
|
96 |
+
EXECUTE FUNCTION update_updated_at_column();
|
97 |
+
|
98 |
+
|
99 |
+
-- ============================================
|
100 |
+
-- Tables for Generated Content (Exercises)
|
101 |
+
-- ============================================
|
102 |
+
|
103 |
+
-- Table `exercise_sets` (Represents one request/query) -- Corrected comment
|
104 |
+
CREATE TABLE exercise_sets (
|
105 |
+
id SERIAL PRIMARY KEY,
|
106 |
+
user_id INTEGER NOT NULL REFERENCES users(user_id), -- Added FK reference for completeness
|
107 |
+
query TEXT NOT NULL,
|
108 |
+
exercises JSONB NOT NULL, -- Array of 5 exercises
|
109 |
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
110 |
+
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP -- Added updated_at for consistency
|
111 |
+
);
|
112 |
+
|
113 |
+
CREATE INDEX idx_exercise_set_user ON exercise_sets(user_id); -- Corrected table name (was already correct but double-checked)
|
114 |
+
|
115 |
+
-- Corrected Trigger definition for exercise_sets
|
116 |
+
CREATE TRIGGER exercise_sets_update_updated_at -- Renamed trigger
|
117 |
+
BEFORE UPDATE ON exercise_sets -- Corrected table name
|
118 |
+
FOR EACH ROW
|
119 |
+
EXECUTE FUNCTION update_updated_at_column(); -- Assumes you want updated_at here too
|
120 |
+
|
121 |
+
-- Table `generated_exercises` (Individual exercises within a set)
|
122 |
+
CREATE TABLE generated_exercises (
|
123 |
+
exercise_id SERIAL PRIMARY KEY,
|
124 |
+
set_id INT NOT NULL REFERENCES exercise_sets(id) ON DELETE CASCADE, -- Corrected FK reference (table and column)
|
125 |
+
sentence TEXT NOT NULL,
|
126 |
+
answer TEXT NOT NULL,
|
127 |
+
choices JSONB NOT NULL, -- Storing the array of choices
|
128 |
+
created_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP,
|
129 |
+
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP
|
130 |
+
);
|
131 |
+
|
132 |
+
CREATE INDEX idx_exercise_set ON generated_exercises(set_id);
|
133 |
+
|
134 |
+
-- Trigger for generated_exercises (remains the same)
|
135 |
+
CREATE TRIGGER generated_exercises_update_updated_at
|
136 |
+
BEFORE UPDATE ON generated_exercises
|
137 |
+
FOR EACH ROW
|
138 |
+
EXECUTE FUNCTION update_updated_at_column();
|
139 |
+
|
140 |
+
|
141 |
+
-- ============================================
|
142 |
+
-- Table for Generated Content (Simulations)
|
143 |
+
-- ============================================
|
144 |
+
|
145 |
+
-- Table `simulations` (Represents one simulation request/result) -- Corrected comment
|
146 |
+
CREATE TABLE simulations (
|
147 |
+
id SERIAL PRIMARY KEY,
|
148 |
+
user_id INTEGER NOT NULL REFERENCES users(user_id), -- Added FK reference for completeness
|
149 |
+
query TEXT NOT NULL,
|
150 |
+
scenario TEXT NOT NULL,
|
151 |
+
dialog JSONB NOT NULL, -- Array of turns with 'role', 'chinese', 'pinyin', 'english'
|
152 |
+
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
153 |
+
updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP -- Added updated_at for consistency
|
154 |
+
);
|
155 |
+
|
156 |
+
CREATE INDEX idx_simulation_user ON simulations(user_id); -- Corrected table name
|
157 |
+
|
158 |
+
-- Corrected Trigger definition for simulations
|
159 |
+
CREATE TRIGGER simulations_update_updated_at -- Renamed trigger
|
160 |
+
BEFORE UPDATE ON simulations -- Corrected table name
|
161 |
+
FOR EACH ROW
|
162 |
+
EXECUTE FUNCTION update_updated_at_column(); -- Assumes you want updated_at here too
|
163 |
+
"""
|
164 |
+
|
165 |
+
def get_db_connection():
|
166 |
+
"""Get a synchronous database connection."""
|
167 |
+
try:
|
168 |
+
conn = psycopg2.connect(
|
169 |
+
dbname=DB_NAME,
|
170 |
+
user=DB_USER,
|
171 |
+
password=DB_PASSWORD,
|
172 |
+
host=DB_HOST,
|
173 |
+
port=DB_PORT
|
174 |
+
)
|
175 |
+
return conn
|
176 |
+
except psycopg2.Error as e:
|
177 |
+
print(f"Database connection error: {e}")
|
178 |
+
raise
|
179 |
+
|
180 |
+
def reset_sequences():
|
181 |
+
"""Generate SQL to reset all sequences (auto-incrementing IDs) to 1."""
|
182 |
+
sequences_sql = """
|
183 |
+
SELECT 'ALTER SEQUENCE ' || sequence_name || ' RESTART WITH 1;'
|
184 |
+
FROM information_schema.sequences
|
185 |
+
WHERE sequence_schema = 'public';
|
186 |
+
"""
|
187 |
+
return sequences_sql
|
188 |
+
|
189 |
+
def reset_database(confirm=True):
|
190 |
+
"""Reset the database by dropping all tables and recreating them."""
|
191 |
+
if confirm:
|
192 |
+
user_confirm = input("WARNING: This will DELETE ALL DATA. Type 'yes' to proceed: ")
|
193 |
+
if user_confirm.lower() != 'yes':
|
194 |
+
print("Database reset cancelled.")
|
195 |
+
return
|
196 |
+
|
197 |
+
conn = None
|
198 |
+
try:
|
199 |
+
conn = get_db_connection()
|
200 |
+
conn.autocommit = False
|
201 |
+
print("Database connection established.")
|
202 |
+
|
203 |
+
with conn.cursor() as cur:
|
204 |
+
print("Dropping and recreating schema...")
|
205 |
+
# Execute the main schema SQL (includes drops)
|
206 |
+
cur.execute(SCHEMA_SQL)
|
207 |
+
print("Schema recreated successfully.")
|
208 |
+
|
209 |
+
# Generate and execute sequence reset SQL
|
210 |
+
print("Resetting sequences...")
|
211 |
+
reset_sql_query = reset_sequences()
|
212 |
+
cur.execute(reset_sql_query)
|
213 |
+
reset_commands = cur.fetchall()
|
214 |
+
for command in reset_commands:
|
215 |
+
cur.execute(command[0])
|
216 |
+
print("Sequences reset successfully.")
|
217 |
+
|
218 |
+
conn.commit()
|
219 |
+
print("Database reset complete.")
|
220 |
+
|
221 |
+
except psycopg2.Error as e:
|
222 |
+
print(f"Database error during reset: {e}")
|
223 |
+
if conn:
|
224 |
+
conn.rollback()
|
225 |
+
print("Transaction rolled back.")
|
226 |
+
except Exception as e:
|
227 |
+
print(f"An unexpected error occurred during reset: {e}")
|
228 |
+
if conn:
|
229 |
+
conn.rollback()
|
230 |
+
finally:
|
231 |
+
if conn:
|
232 |
+
conn.close()
|
233 |
+
print("Database connection closed.")
|
234 |
+
|
235 |
+
def setup_database(confirm=True):
|
236 |
+
"""Set up the database schema if tables do not exist."""
|
237 |
+
if confirm:
|
238 |
+
user_confirm = input("Do you want to set up the database? Type 'yes' to proceed: ")
|
239 |
+
if user_confirm.lower() != 'yes':
|
240 |
+
print("Database setup cancelled.")
|
241 |
+
return
|
242 |
+
|
243 |
+
conn = None
|
244 |
+
try:
|
245 |
+
conn = get_db_connection()
|
246 |
+
conn.autocommit = False
|
247 |
+
print("Database connection established.")
|
248 |
+
|
249 |
+
with conn.cursor() as cur:
|
250 |
+
print("Checking if tables exist...")
|
251 |
+
cur.execute("""
|
252 |
+
SELECT EXISTS (
|
253 |
+
SELECT FROM information_schema.tables
|
254 |
+
WHERE table_schema = 'public'
|
255 |
+
AND table_name = 'users'
|
256 |
+
);
|
257 |
+
""")
|
258 |
+
tables_exist = cur.fetchone()[0]
|
259 |
+
|
260 |
+
if tables_exist:
|
261 |
+
print("Tables already exist. Use reset_database() to reset the database or run setup with confirm=False.")
|
262 |
+
conn.rollback() # Rollback as no changes should be made
|
263 |
+
return
|
264 |
+
|
265 |
+
print("Creating schema...")
|
266 |
+
cur.execute(SCHEMA_SQL)
|
267 |
+
print("Schema created successfully.")
|
268 |
+
|
269 |
+
conn.commit()
|
270 |
+
print("Database setup complete.")
|
271 |
+
|
272 |
+
except psycopg2.Error as e:
|
273 |
+
print(f"Database error during setup: {e}")
|
274 |
+
if conn:
|
275 |
+
conn.rollback()
|
276 |
+
print("Transaction rolled back.")
|
277 |
+
except Exception as e:
|
278 |
+
print(f"An unexpected error occurred during setup: {e}")
|
279 |
+
if conn:
|
280 |
+
conn.rollback()
|
281 |
+
finally:
|
282 |
+
if conn:
|
283 |
+
conn.close()
|
284 |
+
print("Database connection closed.")
|
285 |
+
|
286 |
+
if __name__ == "__main__":
|
287 |
+
action = input("Enter 'setup' to setup database or 'reset' to reset database: ").lower()
|
288 |
+
if action == 'reset':
|
289 |
+
reset_database()
|
290 |
+
elif action == 'setup':
|
291 |
+
setup_database()
|
292 |
+
else:
|
293 |
+
print("Invalid action. Use 'setup' or 'reset'.")
|
prev_backend_v0/backend/main.py
ADDED
@@ -0,0 +1,155 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import FastAPI, HTTPException
|
2 |
+
from fastapi.responses import JSONResponse
|
3 |
+
from fastapi.middleware.cors import CORSMiddleware
|
4 |
+
from pydantic import BaseModel
|
5 |
+
from backend.utils import generate_completions
|
6 |
+
from backend import config
|
7 |
+
from backend.database import get_db_connection
|
8 |
+
import psycopg2
|
9 |
+
from psycopg2.extras import RealDictCursor
|
10 |
+
from typing import Union, List, Literal, Optional
|
11 |
+
import logging
|
12 |
+
import json
|
13 |
+
|
14 |
+
logging.basicConfig(level=logging.INFO)
|
15 |
+
|
16 |
+
app = FastAPI()
|
17 |
+
|
18 |
+
# Add CORS middleware
|
19 |
+
app.add_middleware(
|
20 |
+
CORSMiddleware,
|
21 |
+
allow_origins=["*"], # Allows all origins
|
22 |
+
allow_credentials=True,
|
23 |
+
allow_methods=["*"], # Allows all methods
|
24 |
+
allow_headers=["*"], # Allows all headers
|
25 |
+
)
|
26 |
+
|
27 |
+
# Dependency to get database connection
|
28 |
+
async def get_db():
|
29 |
+
conn = await get_db_connection()
|
30 |
+
try:
|
31 |
+
yield conn
|
32 |
+
finally:
|
33 |
+
conn.close()
|
34 |
+
|
35 |
+
# class GenerationRequest(BaseModel):
|
36 |
+
# user_id: int
|
37 |
+
# query: str
|
38 |
+
|
39 |
+
class Message(BaseModel):
|
40 |
+
role: Literal["user", "assistant"]
|
41 |
+
content: str
|
42 |
+
|
43 |
+
class GenerationRequest(BaseModel):
|
44 |
+
user_id: int
|
45 |
+
query: Union[str, List[Message]]
|
46 |
+
|
47 |
+
class MetadataRequest(BaseModel):
|
48 |
+
query: str
|
49 |
+
|
50 |
+
# Global metadata variables
|
51 |
+
native_language: Optional[str] = None
|
52 |
+
target_language: Optional[str] = None
|
53 |
+
proficiency: Optional[str] = None
|
54 |
+
|
55 |
+
@app.get("/")
|
56 |
+
async def root():
|
57 |
+
return {"message": "Welcome to the AI Learning Assistant API!"}
|
58 |
+
|
59 |
+
@app.post("/extract/metadata")
|
60 |
+
async def extract_metadata(data: MetadataRequest):
|
61 |
+
try:
|
62 |
+
response_str = await generate_completions.get_completions(
|
63 |
+
data.query,
|
64 |
+
config.language_metadata_extraction_prompt
|
65 |
+
)
|
66 |
+
metadata_dict = json.loads(response_str)
|
67 |
+
# Update globals for other endpoints
|
68 |
+
globals()['native_language'] = metadata_dict.get('native_language', 'unknown')
|
69 |
+
globals()['target_language'] = metadata_dict.get('target_language', 'unknown')
|
70 |
+
globals()['proficiency'] = metadata_dict.get('proficiency_level', 'unknown')
|
71 |
+
return JSONResponse(
|
72 |
+
content={
|
73 |
+
"data": metadata_dict,
|
74 |
+
"type": "language_metadata",
|
75 |
+
"status": "success"
|
76 |
+
},
|
77 |
+
status_code=200
|
78 |
+
)
|
79 |
+
except Exception as e:
|
80 |
+
raise HTTPException(status_code=500, detail=str(e))
|
81 |
+
|
82 |
+
@app.post("/generate/flashcards")
|
83 |
+
async def generate_flashcards(data: GenerationRequest):
|
84 |
+
try:
|
85 |
+
# Use previously extracted metadata
|
86 |
+
instructions = (
|
87 |
+
config.flashcard_mode_instructions
|
88 |
+
.replace("{native_language}", native_language or "unknown")
|
89 |
+
.replace("{target_language}", target_language or "unknown")
|
90 |
+
.replace("{proficiency}", proficiency or "unknown")
|
91 |
+
)
|
92 |
+
response = await generate_completions.get_completions(
|
93 |
+
data.query,
|
94 |
+
instructions
|
95 |
+
)
|
96 |
+
return JSONResponse(
|
97 |
+
content={
|
98 |
+
"data": response,
|
99 |
+
"type": "flashcards",
|
100 |
+
"status": "success"
|
101 |
+
},
|
102 |
+
status_code=200
|
103 |
+
)
|
104 |
+
except Exception as e:
|
105 |
+
raise HTTPException(status_code=500, detail=str(e))
|
106 |
+
|
107 |
+
@app.post("/generate/exercises")
|
108 |
+
async def generate_exercises(data: GenerationRequest):
|
109 |
+
try:
|
110 |
+
# Use previously extracted metadata
|
111 |
+
instructions = (
|
112 |
+
config.exercise_mode_instructions
|
113 |
+
.replace("{native_language}", native_language or "unknown")
|
114 |
+
.replace("{target_language}", target_language or "unknown")
|
115 |
+
.replace("{proficiency}", proficiency or "unknown")
|
116 |
+
)
|
117 |
+
response = await generate_completions.get_completions(
|
118 |
+
data.query,
|
119 |
+
instructions
|
120 |
+
)
|
121 |
+
return JSONResponse(
|
122 |
+
content={
|
123 |
+
"data": response,
|
124 |
+
"type": "exercises",
|
125 |
+
"status": "success"
|
126 |
+
},
|
127 |
+
status_code=200
|
128 |
+
)
|
129 |
+
except Exception as e:
|
130 |
+
raise HTTPException(status_code=500, detail=str(e))
|
131 |
+
|
132 |
+
@app.post("/generate/simulation")
|
133 |
+
async def generate_simulation(data: GenerationRequest):
|
134 |
+
try:
|
135 |
+
# Use previously extracted metadata
|
136 |
+
instructions = (
|
137 |
+
config.simulation_mode_instructions
|
138 |
+
.replace("{native_language}", native_language or "unknown")
|
139 |
+
.replace("{target_language}", target_language or "unknown")
|
140 |
+
.replace("{proficiency}", proficiency or "unknown")
|
141 |
+
)
|
142 |
+
response = await generate_completions.get_completions(
|
143 |
+
data.query,
|
144 |
+
instructions
|
145 |
+
)
|
146 |
+
return JSONResponse(
|
147 |
+
content={
|
148 |
+
"data": response,
|
149 |
+
"type": "simulation",
|
150 |
+
"status": "success"
|
151 |
+
},
|
152 |
+
status_code=200
|
153 |
+
)
|
154 |
+
except Exception as e:
|
155 |
+
raise HTTPException(status_code=500, detail=str(e))
|
prev_backend_v0/backend/utils/__pycache__/generate_completions.cpython-310.pyc
ADDED
Binary file (2.55 kB). View file
|
|
prev_backend_v0/backend/utils/__pycache__/generate_completions.cpython-312.pyc
ADDED
Binary file (3.69 kB). View file
|
|
prev_backend_v0/backend/utils/generate_completions.py
ADDED
@@ -0,0 +1,106 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from openai import AsyncOpenAI, OpenAI
|
2 |
+
import asyncio
|
3 |
+
import json
|
4 |
+
from typing import AsyncIterator
|
5 |
+
from typing import Union, List, Dict, Literal
|
6 |
+
from dotenv import load_dotenv
|
7 |
+
import os
|
8 |
+
from pydantic import BaseModel
|
9 |
+
load_dotenv()
|
10 |
+
|
11 |
+
# Initialize the async client
|
12 |
+
client = AsyncOpenAI(
|
13 |
+
base_url=os.getenv("BASE_URL"),
|
14 |
+
api_key=os.getenv("API_KEY"),
|
15 |
+
)
|
16 |
+
|
17 |
+
class Message(BaseModel):
|
18 |
+
role: Literal["user", "assistant"]
|
19 |
+
content: str
|
20 |
+
|
21 |
+
# Helper function to flatten chat messages into a single string prompt
|
22 |
+
def flatten_messages(messages: List[Message]) -> str:
|
23 |
+
return "\n".join([f"{m.role}: {m.content}" for m in messages])
|
24 |
+
|
25 |
+
def process_input(data: Union[str, List[Dict[str, str]]]) -> Union[str, List[Dict[str, str]]]:
|
26 |
+
"""
|
27 |
+
Processes input to either uppercase a string or modify the 'content' field
|
28 |
+
of a list of dictionaries.
|
29 |
+
"""
|
30 |
+
if isinstance(data, str):
|
31 |
+
return data.strip() # Ensures prompt is cleaned up (optional)
|
32 |
+
|
33 |
+
elif isinstance(data, list):
|
34 |
+
# Ensure each item in the list is a dictionary with a 'content' key
|
35 |
+
return [
|
36 |
+
{**item, "content": item["content"].strip()} # Trims whitespace in 'content'
|
37 |
+
for item in data if isinstance(item, dict) and "content" in item
|
38 |
+
]
|
39 |
+
|
40 |
+
else:
|
41 |
+
raise TypeError("Input must be a string or a list of dictionaries with a 'content' field")
|
42 |
+
|
43 |
+
|
44 |
+
# async def get_completions(
|
45 |
+
# prompt: Union[str, List[Dict[str, str]]],
|
46 |
+
# instructions: str
|
47 |
+
# ) -> str:
|
48 |
+
# processed_prompt = process_input(prompt) # Ensures the input format is correct
|
49 |
+
|
50 |
+
# if isinstance(processed_prompt, str):
|
51 |
+
# messages = [
|
52 |
+
# {"role": "system", "content": instructions},
|
53 |
+
# {"role": "user", "content": processed_prompt}
|
54 |
+
# ]
|
55 |
+
# elif isinstance(processed_prompt, list):
|
56 |
+
# messages = [{"role": "system", "content": instructions}] + processed_prompt
|
57 |
+
# else:
|
58 |
+
# raise TypeError("Unexpected processed input type.")
|
59 |
+
|
60 |
+
# response = await client.chat.completions.create(
|
61 |
+
# model=os.getenv("MODEL"),
|
62 |
+
# messages=messages,
|
63 |
+
# response_format={"type": "json_object"}
|
64 |
+
# )
|
65 |
+
|
66 |
+
# output: str = response.choices[0].message.content
|
67 |
+
# return output
|
68 |
+
|
69 |
+
async def get_completions(
|
70 |
+
prompt: Union[str, List[Dict[str, str]]],
|
71 |
+
instructions: str
|
72 |
+
) -> str:
|
73 |
+
if isinstance(prompt, list):
|
74 |
+
formatted_query = flatten_messages(prompt)
|
75 |
+
else:
|
76 |
+
formatted_query = prompt
|
77 |
+
|
78 |
+
processed_prompt = process_input(formatted_query)
|
79 |
+
|
80 |
+
messages = [{"role": "system", "content": instructions}]
|
81 |
+
|
82 |
+
if isinstance(processed_prompt, str):
|
83 |
+
messages.append({"role": "user", "content": processed_prompt})
|
84 |
+
|
85 |
+
elif isinstance(processed_prompt, list):
|
86 |
+
# Only keep the history for context and append the latest user query at the end
|
87 |
+
history = processed_prompt[:-1]
|
88 |
+
last_user_msg = processed_prompt[-1]
|
89 |
+
|
90 |
+
# Optional: Validate that the last message is from the user
|
91 |
+
if last_user_msg.get("role") != "user":
|
92 |
+
raise ValueError("Last message must be from the user.")
|
93 |
+
|
94 |
+
messages += history
|
95 |
+
messages.append(last_user_msg)
|
96 |
+
|
97 |
+
else:
|
98 |
+
raise TypeError("Unexpected processed input type.")
|
99 |
+
|
100 |
+
response = await client.chat.completions.create(
|
101 |
+
model=os.getenv("MODEL"),
|
102 |
+
messages=messages,
|
103 |
+
response_format={"type": "json_object"}
|
104 |
+
)
|
105 |
+
|
106 |
+
return response.choices[0].message.content # adjust based on your client
|