mirror of
https://gitea.smigz.com/smiggiddy/odin-codeprojects.git
synced 2024-12-27 22:50:42 -05:00
feat: backend database added
This commit is contained in:
parent
721ed4fa4f
commit
2c7bbfb044
13 changed files with 278 additions and 28 deletions
|
@ -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(
|
||||||
|
|
20
memory-game/mg-backend/crud.py
Normal file
20
memory-game/mg-backend/crud.py
Normal 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
|
13
memory-game/mg-backend/database.py
Normal file
13
memory-game/mg-backend/database.py
Normal 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()
|
|
@ -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"}
|
||||||
|
|
|
@ -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)
|
21
memory-game/mg-backend/schemas.py
Normal file
21
memory-game/mg-backend/schemas.py
Normal 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
|
|
@ -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}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
9
memory-game/mg-frontend/src/components/button.jsx
Normal file
9
memory-game/mg-frontend/src/components/button.jsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
7
memory-game/mg-frontend/src/components/footer.jsx
Normal file
7
memory-game/mg-frontend/src/components/footer.jsx
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
export default function Footer() {
|
||||||
|
return (
|
||||||
|
<footer>
|
||||||
|
<a href="https://www.pexels.com">Photos provided by Pexels</a>
|
||||||
|
</footer>
|
||||||
|
);
|
||||||
|
}
|
|
@ -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 (
|
||||||
|
<>
|
||||||
|
{props.gameStarted ? (
|
||||||
<div className="gameboard">
|
<div className="gameboard">
|
||||||
{cards.map((item) => {
|
{cards.map((item) => {
|
||||||
|
console.log(item.topic, item.clicked);
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
title={item.topic}
|
title={item.topic}
|
||||||
imgSrc={item.medium_url}
|
imgSrc={item.medium_url}
|
||||||
imgAl="Placeholder"
|
imgAl="Placeholder"
|
||||||
key={item.topic}
|
key={item.key}
|
||||||
setScore={props.setScore}
|
setScore={props.setScore}
|
||||||
score={props.score}
|
score={props.score}
|
||||||
|
clicked={item.clicked}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</div>
|
</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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,13 @@ import "../styles/scoreboard.css";
|
||||||
|
|
||||||
export default function Scoreboard(props) {
|
export default function Scoreboard(props) {
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
{props.gameStarted ? (
|
||||||
<div className="scoreboard">
|
<div className="scoreboard">
|
||||||
<p>SCORE: {props.score}</p>
|
<p>SCORE: {props.score}</p>
|
||||||
<p>HIGH SCORE: {props.highscore}</p>
|
<p>HIGH SCORE: {props.highscore}</p>
|
||||||
</div>
|
</div>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
59
memory-game/mg-frontend/src/styles/button.css
Normal file
59
memory-game/mg-frontend/src/styles/button.css
Normal 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));
|
||||||
|
}
|
Loading…
Reference in a new issue