feat: backend database added

This commit is contained in:
Mike 2024-07-20 17:29:26 -04:00
parent 721ed4fa4f
commit 2c7bbfb044
13 changed files with 278 additions and 28 deletions

View file

@ -16,6 +16,7 @@ class Topics(BaseModel):
class Results(BaseModel): class Results(BaseModel):
topic: str topic: str
alt: str
medium_url: str medium_url: str
photo_id: int photo_id: int
photo_url: str photo_url: str
@ -35,7 +36,7 @@ class AI:
) )
prompt = """ prompt = """
Return 12 items for a toddler aged 2 to 3 to choose from in a memory game. Return 12 items for a toddler aged 2 to 3 to choose from in a memory game. e.g. "ball", "car" . Don't use my examples words
""" """
response = model.generate_content(prompt).text response = model.generate_content(prompt).text
@ -43,9 +44,11 @@ class AI:
def generate_card_json(self, photo_payload, topic): def generate_card_json(self, photo_payload, topic):
prompt = f""" prompt = f"""
This JSON payload will need to be analayzed. Your job is to pick the alt field with the best match for the toddler matching game. return the properties of the object This JSON payload will be analyzed for a toddler matching game. Your job is to identify which object within the payload that best matches the topic "{topic}".
Define a "best match" as containing the keywords from the topic within the "alt" field of the image object that would be appropriate for a toddler's matching game. a "best match" should also be the primary subject in the photo, if there are other items in the alt text then that's determined to be a bad match.
if its determine the alt text is not a great match for the toddler game set the `bad_match` to True and for best matches set to False
{photo_payload} {photo_payload}
and the original topic is {topic}. If the alt text and topic are a bad match set the bad_match bool.
""" """
model = genai.GenerativeModel( model = genai.GenerativeModel(

View file

@ -0,0 +1,20 @@
from sqlalchemy.orm import Session
from . import models, schemas
def get_card(db: Session, card_id: int):
return db.query(models.Card).filter(models.Card.id == card_id).first()
def get_cards(db: Session, skip: int = 0, limit: int = 100):
return db.query(models.Card).offset(skip).limit(limit).all()
def create_card(db: Session, card: schemas.CardCreate):
db_card = models.Card(**card.model_dump())
db.add(db_card)
db.commit()
db.refresh(db_card)
return db_card

View file

@ -0,0 +1,13 @@
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
SQLALCHEMY_DATABASE_URL = "sqlite:///./memory_game.db"
engine = create_engine(
SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()

View file

@ -1,12 +1,18 @@
from .ai import AI from .ai import AI
from .test_data import data from .test_data import data
from dotenv import load_dotenv from dotenv import load_dotenv
from fastapi import FastAPI import json
from fastapi import Depends, FastAPI
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
import logging import logging
import requests
import os import os
from .photos import Pictures from .photos import Pictures
from . import crud, models, schemas
from .database import SessionLocal, engine
from sqlalchemy.orm import Session
logger = logging.getLogger("uvicorn.error") logger = logging.getLogger("uvicorn.error")
logger.setLevel(logging.DEBUG) logger.setLevel(logging.DEBUG)
load_dotenv() load_dotenv()
@ -24,6 +30,17 @@ app.add_middleware(
allow_headers=["*"], allow_headers=["*"],
) )
models.Base.metadata.create_all(bind=engine)
## DB Deps
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
PEXELS_API_KEY = os.getenv("PEXELS_API_KEY") PEXELS_API_KEY = os.getenv("PEXELS_API_KEY")
photos = Pictures(PEXELS_API_KEY) photos = Pictures(PEXELS_API_KEY)
@ -51,3 +68,43 @@ async def read_main():
# except Exception as e: # except Exception as e:
# logger.error(e) # logger.error(e)
# return {"error": "uname to handle request"} # return {"error": "uname to handle request"}
@app.post("/ai-cards", response_model=schemas.Card)
def ai_cards(card: schemas.CardCreate, db: Session = Depends(get_db)):
return crud.create_card(db=db, card=card)
@app.get("/cards")
def read_cards(skip: int = 0, limit: int = 100, db: Session = Depends(get_db)):
cards = crud.get_cards(db, skip=skip, limit=limit)
return cards
@app.get("/load-data")
def load_data():
topics = ai.generate_topics()
try:
for item in topics:
logger.info(item)
picture_data = photos.search(item["topic"])
logger.info(picture_data)
break
card_json = ai.generate_card_json(picture_data, item)
logger.info(card_json)
r = requests.post(
"http://127.0.0.1:8000/ai-cards",
json=card_json,
headers={"Content-Type": "application/json"},
)
logger.info(r.json())
return {"success": "entered into the db"}
except Exception as e:
logger.error(e)
return {"error": "uname to handle request"}

View file

@ -0,0 +1,15 @@
from sqlalchemy import Boolean, Column, Integer, String
from .database import Base
class Card(Base):
__tablename__ = "card"
id = Column(Integer, primary_key=True)
photo_id = Column(Integer)
medium_url = Column(String)
photo_url = Column(String)
topic = Column(String, index=True)
bad_match = Column(Boolean)
alt = Column(String)

View file

@ -0,0 +1,21 @@
from pydantic import BaseModel
class CardBase(BaseModel):
photo_id: int
medium_url: str
photo_url: str
topic: str
alt: str
bad_match: bool
class CardCreate(CardBase):
pass
class Card(CardBase):
id: int
class Config:
orm_mode = True

View file

@ -1,16 +1,34 @@
import { useState } from "react"; import { useState } from "react";
import "./App.css"; import "./App.css";
import Button from "./components/button";
import GameBoard from "./components/gameboard"; import GameBoard from "./components/gameboard";
import Scoreboard from "./components/scoreboard"; import Scoreboard from "./components/scoreboard";
function App() { function App() {
const [gameStarted, setGameStarted] = useState(false);
const [score, setScore] = useState(0); const [score, setScore] = useState(0);
const [highScore, setHighScore] = useState(0); const [highScore, setHighScore] = useState(0);
return ( return (
<> <>
<Scoreboard score={score} highscore={highScore} /> {!gameStarted ? (
<GameBoard score={score} setScore={setScore} /> <Button
onClick={() => setGameStarted(!gameStarted)}
text="Start Game"
/>
) : null}
<Scoreboard
score={score}
highscore={highScore}
gameStarted={gameStarted}
/>
<GameBoard
score={score}
setScore={setScore}
gameStarted={gameStarted}
highScore={highScore}
setHighScore={setHighScore}
/>
</> </>
); );
} }

View file

@ -0,0 +1,9 @@
import "../styles/button.css";
export default function Button(props) {
return (
<button className={`${props.className} + btn`} onClick={props.onClick}>
{props.text}
</button>
);
}

View file

@ -4,7 +4,9 @@ export default function Card(props) {
return ( return (
<div <div
className="card" className="card"
onClick={(event) => onClick(event, props.setScore, props.score)} onClick={(event) =>
onClick(event, props.clicked, props.setScore, props.score)
}
data-cardname={props.title} data-cardname={props.title}
> >
<div className="card-img"> <div className="card-img">
@ -19,8 +21,9 @@ function Image(props) {
return <img src={props.src} alt={props.alt} />; return <img src={props.src} alt={props.alt} />;
} }
function onClick(event, setScore, score) { function onClick(event, clicked, setScore, score) {
// implement something to handle the things ID // implement something to handle the things ID
setScore(score + 1); setScore(score + 1);
clicked = true;
console.log(event.currentTarget.dataset.cardname); console.log(event.currentTarget.dataset.cardname);
} }

View file

@ -0,0 +1,7 @@
export default function Footer() {
return (
<footer>
<a href="https://www.pexels.com">Photos provided by Pexels</a>
</footer>
);
}

View file

@ -4,29 +4,50 @@ import "../styles/gameboard.css";
export default function GameBoard(props) { export default function GameBoard(props) {
const [cards, setCards] = useState([]); const [cards, setCards] = useState([]);
useEffect(() => { useEffect(() => {
fetchCards({ setCards }); fetchCards({ setCards });
}, []); }, []);
return ( return (
<div className="gameboard"> <>
{cards.map((item) => { {props.gameStarted ? (
return ( <div className="gameboard">
<Card {cards.map((item) => {
title={item.topic} console.log(item.topic, item.clicked);
imgSrc={item.medium_url} return (
imgAl="Placeholder" <Card
key={item.topic} title={item.topic}
setScore={props.setScore} imgSrc={item.medium_url}
score={props.score} imgAl="Placeholder"
/> key={item.key}
); setScore={props.setScore}
})} score={props.score}
</div> clicked={item.clicked}
/>
);
})}
</div>
) : null}
</>
); );
} }
async function fetchCards({ setCards }) { async function fetchCards({ setCards }) {
const cards = await fetch("http://localhost:8000/"); const cards = await fetch("http://localhost:8000/cards");
setCards(await cards.json()); const jsonCards = await cards.json();
let gameCards = await jsonCards.map((c) => {
return { ...c, clicked: false, key: crypto.randomUUID() };
});
setCards(gameCards);
}
function shuffleCards(arr) {
for (let i = arr.length - 1; i > 0; i--) {
let j = Math.floor(Math.random() * (i + 1));
[arr[i], arr[j]] = [arr[j], arr[i]];
return arr;
}
} }

View file

@ -2,9 +2,13 @@ import "../styles/scoreboard.css";
export default function Scoreboard(props) { export default function Scoreboard(props) {
return ( return (
<div className="scoreboard"> <>
<p>SCORE: {props.score}</p> {props.gameStarted ? (
<p>HIGH SCORE: {props.highscore}</p> <div className="scoreboard">
</div> <p>SCORE: {props.score}</p>
<p>HIGH SCORE: {props.highscore}</p>
</div>
) : null}
</>
); );
} }

View file

@ -0,0 +1,59 @@
:root {
/* Grays */
--lightest-gray: 208, 21%, 93%;
--lighter-gray: 210, 16%, 76%;
--light-gray: 208, 12%, 58%;
--dark-gray: 207, 12%, 43%;
--darker-gray: 209, 15%, 28%;
--dark-blue: 246, 87%, 30%;
--blue: 219, 100%, 57%;
--light-blue: 219, 100%, 69%;
--green: 158, 95%, 34%;
--green-hover: 158, 95%, 28%;
}
.btn {
border-radius: 0.4rem;
padding: 1rem;
cursor: pointer;
text-decoration: none;
display: inline-block;
border: none;
font-size: 1.2rem;
flex: 1;
}
.btn:hover {
/* background-color: hsl(var(--dark-gray)); */
}
.normal-btn {
background-color: hsl(var(--light-gray));
transition:
background-color 500ms ease-in-out,
color 500ms ease-in-out;
}
.normal-btn:hover {
background-color: hsl(var(--dark-gray));
color: hsl(var(--lighter-gray));
}
.submit-btn {
background-color: hsl(var(--green));
transition: background-color 600ms;
}
.submit-btn:hover {
background-color: hsl(var(--green-hover));
}
.clear-btn {
background-color: hsl(var(--lighter-gray));
transition: background-color 600ms;
}
.clear-btn:hover {
background-color: hsl(var(--light-gray));
}