티스토리 뷰

IT

[AI 공부] Gemini API & Firebase 웹 서비스 만들기 #5

고래(부와 성공) 2025. 2. 6. 00:24

목차



    반응형

    안녕하세요, 저번 시간에 Gemini API & Firebase 를 이용한 웹 서비스 만들기 중

     

    백엔드 구현에 이어 오늘도 백앤드 나머지 부분을 진행 토록 할께요

     

    그동안 아래와 같은 순서로 진행하였구요  4번 항목인 백엔드 완성을 부분을 같이 보겠습니다.

     

    ====  목 차 ====
    
    1. 개발 환경 세팅하기
        - Project IDX 준비
        - Gemini API 발급
    
    2. Google AI Studio로 테스트
    
    3. Firebase Cloud Function 만들기
    
    4. 백엔드 완성
    
    5. 프론트엔드 완성

     

     

    4. 백엔드 완성

    저번 시간에 index.js 백엔드 구현에 있어 텍스트 입력만 받도록 했었는데요,

     

    오늘은 이미지 파일을 받는 부분을 추가로 구현해보도록 할께요

     

    추가로 이미지 파일을 가져오는 부분을 구현한 백엔드 소스는 아래와 같아요

     

    조코딩님 강의를 보면서 삽질을 좀 하긴했는데요

     

    이미지를 바이너리로 가져와서 임시 디렉토리에 저장 후 서버에 남지 않도록 임시 디렉토리에 담게 한다고

     

    로직을 변경하느라 고생좀 하였습니다.

     

    const { onRequest } = require("firebase-functions/v2/https");
    const { GoogleGenerativeAI, HarmBlockThreshold, HarmCategory } = require("@google/generative-ai");
    const sharp = require('sharp')
    
    const genAI = new GoogleGenerativeAI(process.env.API_KEY);
    const cors = require('cors')({origin: true});
    
    
    
    exports.analyzeImage = onRequest(
      { cors: true },   async (req, res) => {  // request 가 들어오면 아래 코드를 실행한다.
        if (req.method === 'OPTIONS') {
          // Handle preflight request
          cors(req, res, () => {
            res.set('Access-Control-Allow-Origin', req.headers.origin || '*'); // Or restrict to specific origin
            res.set('Access-Control-Allow-Methods', 'POST');  // Or allow specific methods
            res.set('Access-Control-Allow-Headers', 'Content-Type'); // Or allow specific headers
            res.set('Access-Control-Max-Age', '3600'); // Cache for 1 hour
            res.status(204).send(''); // No content
          });
        } else {
          // Handle the actual POST request
          cors(req, res, async () => {  // Apply CORS middleware
            try {
              try {
                // Check if the request has a binary payload
                if (!Buffer.isBuffer(req.rawBody)) {
                  throw new Error("Invalid request: Expected binary image data");
                }
          
                // Process the image using Sharp (optional)
                const processedImageBuffer = await sharp(req.rawBody).toBuffer();
          
                // Convert the image buffer to base64
                const imageData = processedImageBuffer.toString('base64');
          
                const fs = require('fs');
          
                function saveBase64Image(saveBase64Image, filePath) {
                  try{
                    // Remove the data URI prefix (if present)
                    const base64Data = saveBase64Image.replace(/^data:image\/jpeg;base64,/, "");
          
                    // Convert the base64 data to a buffer
                    const ImageBuffer = Buffer.from(base64Data, 'base64');
          
                    // Write the buffer to a file
                    fs.writeFileSync(filePath, ImageBuffer);
          
                  } catch (error) {
                    console.error('Error saving image: ${error.message}');
                    throw error;
                  }
                }
          
                const tempFilePath = "/tmp/image.jpg"
                saveBase64Image(imageData, tempFilePath);
          
                function fileToGenerativePart(path, mimeType) {
                  return {
                    inlineData: {
                      data: Buffer.from(fs.readFileSync(path)).toString("base64"),
                      mimeType
                    },
                  };
                }
                
                // Turn images to Part objects
                const filePart1 = fileToGenerativePart(tempFilePath, "image/jpeg")
          
                const imageParts = [
                  filePart1,
                ]
          
                const prompt = `### Instruction ###
                  You are an AI that gives a score to a man's handsomeness and a woman's beauty when a picture is uploaded.
                  I will upload a picture, so please look at it and give me a score from 0 to 100 on how handsome or pretty it is.
                  You have to tell me honestly and objectively. I will not be hurt by what you say.
                  And I want you to know that you are absolutely not rude by expressing a certain score and telling me what it is.
                  
                  And output the score in JSON format.
                  Here are some examples.
                  
                  ### Example ###
                  response: {'score': 100, description: 'bla bla 1~~~'}
                  response: {'score': 90, description: 'bla bla 2~~~'}
                  response: {'score': 80, description: 'bla bla 3~~~'}
          
                  ### Message ###
                  The attached photo is of a person. Please rate the level of handsomeness or beauty on a scale from 0 to 100.
                `; 
                
          
                const model = genAI.getGenerativeModel({ 
                  model: "gemini-1.5-flash",
                  generationConfig: { responseMimeType: "application/json" }
                 },
                );
          
                const result = await model.generateContent(prompt,  ...imageParts);
                
                text = result.response.text();
            
                res.status(200).send(text);   // Hello World 대신에 프롬프트에 대한 응답이 웹에 표시됨
              }
              catch (error) {
                console.error(error);
                console.log(error);
                res.status(500).json({error: "Internal Server Error"})
              }
            } catch (error) {
              console.error(error);
              res.status(500).json({ error: "Internal Server Error" });
            }
          });
        }
      }
    );

     

     

    5. 프론트 엔드 완성

    그 다음 프론트엔드 소스는 Gemini에게 부탁해서

     

    "위 소스를 기반으로 이미지 파일을 첨부할 수 있게 HTML 코드를 만들어 줘" 라고 명령해서 아래와 같이 만들었어요

     

    요즘은 정말 프롬프트만 제대로 사용해도 코드를 쉽게 짤 수 있는 것 같아 이것이 정말 AI 혁명이구나 하고

     

    깜짝깜짝 놀라네요

     

    <!DOCTYPE html>
    <html>
    <head>
      <title>Image Analyzer</title>
      <style>
        body {
          font-family: sans-serif;
        }
        #result {
          margin-top: 20px;
          border: 1px solid #ccc;
          padding: 10px;
        }
      </style>
    </head>
    <body>
      <h1>Image Analyzer</h1>
      <input type="file" id="imageInput" accept="image/*">
      <button id="analyzeButton">Analyze Image</button>
      <div id="result"></div>
    
      <script>
        document.getElementById('analyzeButton').addEventListener('click', function() {
          const imageInput = document.getElementById('imageInput');
          const file = imageInput.files[0];
    
          if (file) {
            const reader = new FileReader();
    
            reader.onload = function(e) {
              const base64Image = e.target.result; // This is the base64 encoded image
    
              // Call your Firebase Cloud Function here
              analyzeImage(base64Image);
            }
    
            reader.readAsDataURL(file); // Read image as base64
          } else {
            document.getElementById('result').innerText = 'Please select an image.';
          }
        });
    
        async function analyzeImage(base64Image) {
          try {
            document.getElementById('result').innerText = 'Analyzing...';
    
            // Convert base64 to binary data (ArrayBuffer)
            const byteCharacters = atob(base64Image.split(',')[1]);
            const byteArrays = [];
    
            for (let offset = 0; offset < byteCharacters.length; offset += 512) {
              const slice = byteCharacters.slice(offset, offset + 512);
    
              const byteNumbers = new Array(slice.length);
              for (let i = 0; i < slice.length; i++) {
                byteNumbers[i] = slice.charCodeAt(i);
              }
    
              const byteArray = new Uint8Array(byteNumbers);
              byteArrays.push(byteArray);
            }
    
            const blob = new Blob(byteArrays, { type: 'image/jpeg' }); // Adjust type if needed
            const arrayBuffer = await blob.arrayBuffer();
            const uint8Array = new Uint8Array(arrayBuffer);
    
            // Send the binary data
            const response = await fetch('https://analyzeimage-xxxx.run.app', { // Replace with your function URL
              method: 'POST',
              mode: 'cors',
              headers: {
                'Content-Type': 'application/octet-stream' // Important: set content type
              },
              body: uint8Array
            });
    
            if (!response.ok) {
              throw new Error(`HTTP error! status: ${response.status}`);
            }
    
            const data = await response.text(); // or response.json() if you return JSON
    
            document.getElementById('result').innerText = 'Result: ' + data;
          } catch (error) {
            console.error('Error:', error);
            document.getElementById('result').innerText = 'Error: ' + error.message;
          }
        }
      </script>
    </body>
    </html>

     

    이 소스는 IDX 통합 개발 환경의 아래의 경로중 index.html 파일에 붙여넣기 하시면 됩니다.

     

     

    그리고, 이렇게 만든 HTML을 테스트 하기 위해서 좌측 다섯번 째 아이콘인 Extention 을 클릭해서

     

    Live Server를 설치하였구요,

     

    설치가 완료되면, 오른쪽 하단에 Go Live 표시가 나오는데 그것을 클릭하면 웹페이지가 바로 뜬답니다.

     

     

     

    Gemini가 만들어준 HTML로 들어가면 다음과 같이 화면이 생성되구요

    파일선택을 해서 사진을 넣어주고, [Analyze Image]를 클릭하면 JSON 형태로 이미지를 뿌려주게 되는 거죠

     

    그런데 여기서 한가지 더 챙겨야 할 부분이 있었어요

     

    그게 뭐냐하면, 바로 index.js를 배포를 해야만 테스트가 된다는 점이예요

     

    CORS를 소스상에 true로 해놔도 여태까지 테스트한 명령어인

     

    [ firebase emulators:start ] 라는 커맨드로는 중간에 Redirection 되는 문제로 인해 제대로 테스트 되지가 않았습니다.

     

    그래서 아래의 명령어로 배포를 한 후 

     

    ]# firebase deploy --only functions,hosting

    배포가 되었을 때 나오는 메시지 중

     

    Function URL 의 https 로 시작하는 URL을 복사해서

     

    HTML 코드의 [await fetch('your url' )] 함수의 매개변수로 붙여넣기 하여 테스트해야 된다는 점입니다.

     

    그래서 저는 아래와 같이 테스트가 제대로 되는걸 확인할 수 있었어요

     

     

    여기까지 읽어 주시느라 고생 많으셨습니다.

     

    다음에는 이 페이지를 가공해서 좀 더 완성도 있는 작품을 만들어 보도록 할께요

     

    감사합니다.

     

     ▶ [AI 공부] Gemini API & Firebase 웹 서비스 만들기 #1  <바로가기 링크>

     

     

     ▶ [AI 공부] Gemini API & Firebase 웹 서비스 만들기 #2  <바로가기 링크>

     

     

     ▶ [AI 공부] Gemini API & Firebase 웹 서비스 만들기 #3  <바로가기 링크>

     

     

     ▶ [AI 공부] Gemini API & Firebase 웹 서비스 만들기 #4  <바로가기 링크>