오늘 추가한 내용.
아예 새로운 기능을 추가 했습니다.
기능은 재료만으로 메뉴를 추천하는 AI입니다.
ingredients.py 먼저
import openai
import os
from openai import OpenAI
openai.api_key = os.getenv("OPENAI_API_KEY2")
# print(openai.api_key)
import warnings
warnings.filterwarnings(action = 'ignore')
client = OpenAI(api_key=openai.api_key)
output = ''
def ingredients_ai(ingredients, language='korean'):
global output
completion = client.chat.completions.create(
model = 'gpt-4o',
messages = [{
"role": "system",
"content": """
***{ingredients}를 기반으로, {language}을 써서, 한식, 양식, 중식, 일식의 추천에서 각 음식의 이름들과 설명들이 {language}를 따르도록 해줘***
You are a world-class chef and culinary expert with deep knowledge of global cuisines. Your task is to recommend recipes based on a given list of ingredients. Follow these enhanced guidelines to provide accurate, varied, and professional recommendations:
### Guidelines for Recipe Recommendations:
**1. Use the {language}:**
- All dish names, descriptions, and instructions must be written exclusively in the specified language: {language}.
**2. Provide diverse and creative recipes:**
- Ensure variety by offering dishes with different preparation methods (e.g., grilling, frying, steaming, roasting, baking, etc.).
- Avoid repetition of cooking styles or overly similar dishes.
**3. Include a variety of cuisines:**
- Recommend dishes from at least four global cuisines: Korean, Western, Chinese, and Japanese.
- Provide at least **five dishes per cuisine** to ensure a rich and diverse selection.
**4. Add detailed descriptions for each dish:**
- Provide the name of the dish followed by a **brief but informative description**.
- In the description, highlight how the ingredients are used and the primary cooking method or style of the dish.
***For last, you shoulw follow the all the instruction.***
### Example
#### 한식
1. **한식 1:** [Brief description of how the ingredients are incorporated and the dish is prepared.]
2. **한식 2:** [Brief description of how the ingredients are incorporated and the dish is prepared.]
3. **한식 3:** [Brief description of how the ingredients are incorporated and the dish is prepared.]
4. **한식 4:** [Brief description of how the ingredients are incorporated and the dish is prepared.]
5. **한식 5:** [Brief description of how the ingredients are incorporated and the dish is prepared.]
#### 양식
1. **양식 1** [Brief description of how the ingredients are incorporated and the dish is prepared.]
2. **양식 2:** [Brief description of how the ingredients are incorporated and the dish is prepared.]
3. **양식 3:** [Brief description of how the ingredients are incorporated and the dish is prepared.]
4. **양식 4:** [Brief description of how the ingredients are incorporated and the dish is prepared.]
5. **양식 5:** [Brief description of how the ingredients are incorporated and the dish is prepared.]
#### 일식
1. **일식 1:** [Brief description of how the ingredients are incorporated and the dish is prepared.]
2. **일식 2:** [Brief description of how the ingredients are incorporated and the dish is prepared.]
3. **일식 3:** [Brief description of how the ingredients are incorporated and the dish is prepared.]
4. **일식 4:** [Brief description of how the ingredients are incorporated and the dish is prepared.]
5. **일식 5:** [Brief description of how the ingredients are incorporated and the dish is prepared.]
#### 중식
1. **중식 1:** [Brief description of how the ingredients are incorporated and the dish is prepared.]
2. **중식 2:** [Brief description of how the ingredients are incorporated and the dish is prepared.]
3. **중식 3:** [Brief description of how the ingredients are incorporated and the dish is prepared.]
4. **중식 4:** [Brief description of how the ingredients are incorporated and the dish is prepared.]
5. **중식 5:** [Brief description of how the ingredients are incorporated and the dish is prepared.]
### Notes:
- **Showcase the versatility** of the ingredients by recommending dishes with unique and contrasting flavors and techniques.
- Be professional, precise, and engaging in your descriptions to inspire confidence in your culinary expertise.
"""
},
{"role":"user", "content":ingredients},
{"role":"user", "content":language}
], stream = True)
for chunk in completion:
if chunk.choices[0].delta.content is not None:
content = chunk.choices[0].delta.content
print(content, end="", flush=True)
output += content
return output
이렇게 함수 ingredients_ai에 프롬프트를 넣어서 output을 return하는 함수를 생성.
그리고 각 model, serializer를 추가
models.py
from django.db import models
# Create your models here.
class ingredients(models.Model):
ingredients = models.TextField(verbose_name="재료 정보")
language = models.TextField(verbose_name='언어 정보')
serializers.py
class IngredientSerializer(serializers.ModelSerializer):
ingredients = serializers.CharField()
language = serializers.CharField()
class Meta:
model = ingredients
fields = '__all__'
def to_representation(self, instance):
data = super().to_representation(instance)
if 'content' in data:
data['content'] = markdown.markdown(data['content'])
return data
이렇게 하면 models에 있는 fields를 받아 전체적으로 serializer를 실행할 수 있습니다.
그리고 views.py에서 받은 데이터를 처리하도록 합니다.
from django.shortcuts import render
import markdown
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
from .serializers import IngredientSerializer
from .ingredients_ai import ingredients_ai # kJK_data_collect 바로 연결하고 함수 불러오기기
# Create your views here.
from django.http import StreamingHttpResponse
def ingredients_view(request):
return render(request, 'ingredients.html')
class IngredientSearchView(APIView):
def post(self, request):
# 요청 데이터 확인
serializer = IngredientSerializer(data=request.data)
if not serializer.is_valid():
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
ingredients = serializer.validated_data['ingredients']
language = serializer.validated_data['language']
try:
md = markdown.Markdown(extensions=["fenced_code"])
results = ingredients_ai(ingredients, language)
markdown_results = md.convert(results)
if not results:
return Response({"results": "에러 발생! 다시 시도해 주세요!"}, status=status.HTTP_200_OK)
return Response({"results": markdown_results}, status=status.HTTP_200_OK)
except ValueError as ve:
return Response({"error": str(ve)}, status=status.HTTP_400_BAD_REQUEST)
except Exception as e:
return Response({"error": f"서버 오류: {str(e)}"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
이렇게 html에서 fetch로 보낼 ingredients와 language를 받아, results = ingredients_ai를 통해 return한 output을 results에 넣고, markdown을 활용해 2차로 거른 대답을 markdown_results을 다시 보낸 후 html에서 보여지게 됩니다.
최종적인 html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Food Intake Analyzer</title>
<style>
body {
margin: 0;
font-family: 'Arial', sans-serif;
display: flex;
flex-direction: column;
height: 100vh;
color: #f8f9fa;
background: url('https://cdn.pixabay.com/photo/2023/05/14/13/58/background-7993019_1280.png') no-repeat center center fixed;
background-size: cover;
}
.container {
display: flex;
flex: 1;
overflow: hidden;
}
.sidebar {
width: 30%;
background-color: rgba(52, 58, 64, 0.9);
padding: 20px;
box-shadow: 2px 0 5px rgba(0, 0, 0, 0.3);
display: flex;
flex-direction: column;
gap: 15px;
border-right: 2px solid #32cd32;
}
.sidebar h1 {
color: #32cd32;
margin-bottom: 10px;
}
.form-group {
width: 100%;
max-width: 600px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
.form-group input, .form-group select {
width: calc(100% - 20px);
max-width: 100%;
padding: 10px;
font-size: 16px;
border: 1px solid #495057;
border-radius: 5px;
background-color: #2c2c2e;
color: #f8f9fa;
box-sizing: border-box;
}
.form-group input:focus, .form-group select:focus {
outline: none;
border-color: #32cd32;
}
.time-buttons {
display: flex;
gap: 10px;
}
.time-button {
flex: 1;
padding: 10px;
font-size: 16px;
background-color: #2c2c2e;
color: #f8f9fa;
border: 1px solid #495057;
border-radius: 5px;
cursor: pointer;
text-align: center;
transition: background-color 0.3s ease, color 0.3s ease;
}
.time-button.selected {
background-color: #32cd32;
color: #1c1c1e;
}
.submit-btn {
background-color: #32cd32;
border: none;
padding: 12px;
font-size: 18px;
border-radius: 5px;
cursor: pointer;
transition: background-color 0.3s ease;
}
.submit-btn:hover {
background-color: #28a428;
}
.results-panel {
flex: 1;
padding: 20px;
display: flex;
flex-direction: column;
gap: 20px;
overflow-y: auto;
background-color: rgba(0, 0, 0, 0.6);
box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.5);
border-left: 2px solid #32cd32;
}
.results-panel-header {
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 2px solid #32cd32;
}
.results-panel h2 {
color: #32cd32;
padding-bottom: 5px;
margin: 0;
}
.reset-btn {
background-color: #32cd32;
color: #1c1c1e;
text-decoration: none;
padding: 8px 15px;
font-size: 16px;
border-radius: 5px;
transition: background-color 0.3s ease;
}
.reset-btn:hover {
background-color: #28a428;
}
.result-box {
font-size: 18px;
font-weight: bold;
background-color: rgba(0, 0, 0, 0.9);
padding: 15px;
border-radius: 5px;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3);
white-space: pre-wrap; /* Ensures line breaks in text */
}
.result-box p {
margin: 0;
}
#dynamic-results {
margin-top: 20px;
height: 100%;
overflow-y: auto;
}
.navbar {
background-color: rgba(52, 58, 64, 0.9);
padding: 10px;
display: flex;
justify-content: flex-start;
align-items: center;
border-bottom: 2px solid #32cd32;
}
.navbar .home-btn {
background-color: #32cd32;
color: #1c1c1e;
text-decoration: none;
padding: 8px 15px;
font-size: 16px;
border-radius: 5px;
transition: background-color 0.3s ease;
margin-right: 10px;
}
.navbar .home-btn:hover {
background-color: #28a428;
}
</style>
</head>
<body>
<div class="navbar">
<a href="{% url 'main' %}" class="home-btn">Home</a>
<a href="javascript:window.history.back()" class="home-btn">뒤로가기</a>
</div>
<div class="container">
<div class="sidebar">
<h1>오늘의 식단 분석</h1>
<div class="form-group">
<label for="food">섭취한 음식:</label>
<input type="text" id="food" placeholder="음식 메뉴">
</div>
<div class="time-buttons">
<button class="time-button" id="breakfast">아침</button>
<button class="time-button" id="lunch">점심</button>
<button class="time-button" id="dinner">저녁</button>
</div>
<div id="additional-fields"></div>
<div class="form-group">
<label for="age">나이:</label>
<input type="number" id="age" placeholder="현재 나이">
</div>
<div class="form-group">
<label for="diet">체중 감량 여부</label>
<select id="diet">
<option value="yes">네, 하고있습니다.</option>
<option value="no">아니요, 안 하고있습니다</option>
</select>
</div>
<button class="submit-btn" id="analyze-btn">분석하기</button>
</div>
<div class="results-panel">
<div class="results-panel-header">
<h2>분석 결과</h2>
</div>
<div id="dynamic-results" class="result-box"></div>
</div>
</div>
<script>
const timeButtons = document.querySelectorAll('.time-button');
const additionalFields = document.getElementById('additional-fields');
const analyzeBtn = document.getElementById('analyze-btn');
const resultsContainer = document.getElementById('dynamic-results');
let food_time = null;
timeButtons.forEach(button => {
button.addEventListener('click', function () {
timeButtons.forEach(btn => btn.classList.remove('selected'));
this.classList.add('selected');
food_time = this.id;
additionalFields.innerHTML = '';
if (food_time === 'lunch') {
additionalFields.innerHTML = `
<div class="form-group">
<label for="breakfast-input">아침에 섭취한 음식:</label>
<input type="text" id="breakfast-input" placeholder="아침 메뉴를 기입해주세요">
</div>`;
} else if (food_time === 'dinner') {
additionalFields.innerHTML = `
<div class="form-group">
<label for="breakfast-input">아침에 섭취한 음식:</label>
<input type="text" id="breakfast-input" placeholder="아침 메뉴를 기입해주세요">
</div>
<div class="form-group">
<label for="lunch-input">점심에 섭취한 음식:</label>
<input type="text" id="lunch-input" placeholder="점심 메뉴를 기입해주세요.">
</div>`;
}
});
});
analyzeBtn.addEventListener('click', function () {
resultsContainer.innerHTML = '';
resultsContainer.innerHTML = `<p>분석 중....조금만 기다려 주세요</p>`;
const food_details = document.getElementById('food').value.trim();
const age = document.getElementById('age').value;
const is_on_diet = document.getElementById('diet').value;
const breakfast_time = document.getElementById('breakfast-input')?.value || '';
const lunch_time = document.getElementById('lunch-input')?.value || '';
if (!food_details) {
alert("음식 이름을 입력해주세요!");
return;
}
if (!age || isNaN(age) || age <= 0) {
alert("올바른 나이를 입력해주세요!");
return;
}
if (!food_time) {
alert("언제 먹었는지 선택해주세요!");
return;
}
const url = '/api/calories/';
const data = {
food_time,
food_details,
age,
is_on_diet,
breakfast_time,
lunch_time,
chosen_language: 'kr',
};
fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
.then(res => res.json())
.then(responseData => {
console.log('Response:', responseData); // 응답 데이터 처리
// 응답 데이터를 화면에 표시할 경우
resultsContainer.innerHTML = JSON.stringify(responseData, null, 2);
})
.catch(error => {
console.error('Error:', error);
resultsContainer.innerHTML = `<p>There was an error processing your request. Please try again later.</p>`;
});
});
</script>
</body>
</html>