카테고리 없음

스파르타 AI-8기 TIL(1/26)-TeamProject

kimjunki-8 2025. 1. 26. 23:13

오늘 추가한 내용.

아예 새로운 기능을 추가 했습니다.

기능은 재료만으로 메뉴를 추천하는 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>