jamison-golson / muscle-man

0 stars 0 forks source link

Main Issue Thread #1

Open jamison-golson opened 1 week ago

jamison-golson commented 1 week ago

I will be writing about problems/ideas/thoughts on the project in this thread

jamison-golson commented 1 week ago

The plan is to build a nice UI that allows users to explore the food they are eating. I am going to go with openfooddb. They are free and so far with my testing, the db seems to be up to date and reliable. Here is a list of keys I will be extracting from the API call: GET /api/v2/product/{barcode} List of keys: "product_name": null, "generic_name": null, "brands": null, "categories": null, "ingredients": null, "nutriments": null, "image_front_url": null, "image_nutrition_url": null, "ecoscore_grade": null, "ecoscore_score": null, "nutriscore_grade": null, "nutriscore_score": null, "states": null

This list is not complete

jamison-golson commented 5 days ago

I ran into a weird problem when trying to render a chart using charts.js.

I originally wanted to generate the chart and the element that contains the chart after the user clicked the desired nutrition label item. I.E if a user selected 'protein' from the nutrition label, this code would fire:

const canvas = document.createElement('canvas');
canvas.id = 'nutrientChart';
detailsContainer.appendChild(canvas);
//...
currentChart = new Chart(canvas, {
                type: 'doughnut',
                data: {
                    labels: [`${nutrient.name} (${percentageOfDV.toFixed(1)}% of DV)`, 'Remaining'],
                    datasets: [{
                        data: [percentageOfDV, Math.max(0, 100 - percentageOfDV)],
                        backgroundColor: ['#FF6384', '#36A2EB']
                    }]
                },
                options: {
                    responsive: true,
                    maintainAspectRatio: false,
                    title: {
                        display: true,
                        text: `${nutrient.name} as Percentage of Daily Value`
                    }
                }
            });

But every time, the div/canvas tags would be generated but the chart would not render. After a few sessions with gpt-4o and claude I got to this working code:

<div id="visualization-section">
  <canvas id="myChart"></canvas>
</div>
const canvas = document.getElementById('myChart')
//....

            const value = parseFloat(nutrient.value) || 0;
            const percentageOfDV = (nutrient.value / dailyValues[nutrient.name]) * 100;

            // Create the chart
            currentChart = new Chart(canvas, {
                type: 'doughnut',
                data: {
                    labels: [`${nutrient.name} (${percentageOfDV.toFixed(1)}% of DV)`, 'Remaining'],
                    datasets: [{
                        data: [percentageOfDV, Math.max(0, 100 - percentageOfDV)],
                        backgroundColor: ['#FF6384', '#36A2EB']
                    }]
                },
                options: {
                    responsive: true,
                    maintainAspectRatio: false,
                    title: {
                        display: true,
                        text: `${nutrient.name} as Percentage of Daily Value`
                    }
                }
            });

The new code, first places the canvas tag into the visualization div, then grabs it once the user clicks on the desired nutrition label item. The code finally renders the graph using the nutrition data. I will go through charts.js docs to see why the canvas element has to defined before creating the chart and update this later.

Commit: https://github.com/jamison-golson/muscle-man/commit/3af133232e929d70df3238c79804bf9484dac288

jamison-golson commented 59 minutes ago

I decided to change my backend to flask so I can use python. I plan to add a lot more functionality to this very soon (i.e nutrition breakdown, workout classification, workout analysis, workout coach, etc...) and using python will make this task easier. This is my first time using flask so this will be pretty interesting.

Flask is very simple, which I like. switching everything over only took a few mins

I removed the server.js script as that was originally my 'backend' and in it's place, I added app.py. This is where the server is initialized and started. This file also houses all the routes created for various functions the backend is intended to handle. So far I have three routes: '/', '/fetch_product_data' and '/fetch_upc_from_image'

Route '/' grabs the html from the templates dir and renders it once the client goes to localhost:5000

@app.route("/")
def index():
    return render_template("index.html")

Route '/fetch_upc_from_image' is called once the user uploads an image using the file upload field. It handles POST request and accepts images. The associated function then uses pyzbar, a python package built on top of zbar that reads one dimensional bar codes , to extract the UPC code from the image provided.

@app.route("/fetch_upc_from_image", methods=["POST"])
def fetch_upc_from_image():
    if "image" not in request.files:
        return jsonify({"error": "No image file in request"}), 400

    file = request.files["image"]

    if file.filename == "":
        return jsonify({"error": "No selected file"}), 400

    if file:
        # filename = secure_filename(file.filename)
        # filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
        # file.save(filepath)
        image = Image.open(file)
        barcodes = pyzbar.decode(image)

        for barcode in barcodes:
            upc = barcode.data.decode("utf-8")

        # Here, you would process the image to extract the UPC

        return jsonify(
            {"upc": upc, "message": "File successfully uploaded and processed"}
        )

Route '/fetch_product_data is called once a UPC code has either been successfully extracted from an image or been sent directly from the user.

// Text search functionality
const textSearch = document.createElement('input');
textSearch.type = 'text';
textSearch.id = 'text-search-input';
textSearch.placeholder = 'Enter UPC code';
document.getElementById('text-search').appendChild(textSearch);

const searchButton = document.createElement('button');
searchButton.textContent = 'Search';
searchButton.id = 'text-search-button';
document.getElementById('text-search').appendChild(searchButton);

async function fetchProductData(upc) {
    try {
        const response = await fetch('/fetch_product_data', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({ upc: upc }),
        });
        if (!response.ok) {
            throw new Error(`HTTP error! status: ${response.status}`);
        }

        return await response.json();
    } catch (error) {
        console.error('Error fetching product data:', error);
        throw error;
    }
}

It handles POST request and a string. The associated function then makes an API call to openfoodfacts and returns the requested data.

@app.route("/fetch_product_data", methods=["POST"])
def fetch_product_data():
    upc = request.json["upc"]
    try:
        response = requests.get(f"https://world.openfoodfacts.org/api/v2/product/{upc}")
        response.raise_for_status()
        data = response.json()

        processed_data = {
            "code": data.get("code", upc),
            "product_name": data.get("product", {}).get("product_name", "N/A"),
            "generic_name": data.get("product", {}).get("generic_name", "N/A"),
            "brands": data.get("product", {}).get("brands", "N/A"),
            "categories": data.get("product", {}).get("categories", "N/A"),
            "ingredients": data.get("product", {}).get("ingredients_text", "N/A"),
            "nutriments": data.get("product", {}).get("nutriments", "N/A"),
            "image_front_url": data.get("product", {}).get("image_front_url", "N/A"),
            "image_nutrition_url": data.get("product", {}).get(
                "image_nutrition_url", "N/A"
            ),
            "ecoscore_grade": data.get("product", {}).get("ecoscore_grade", "N/A"),
            "ecoscore_score": data.get("product", {}).get("ecoscore_score", "N/A"),
            "nutriscore_grade": data.get("product", {}).get("nutriscore_grade", "N/A"),
            "nutriscore_score": data.get("product", {}).get("nutriscore_score", "N/A"),
            "states": data.get("product", {}).get("states", "N/A"),
        }

        return jsonify(processed_data)
    except requests.RequestException as e:
        return jsonify({"error": str(e)}), 400

Once I start adding LLMs/VM/TTS models, having the ability to simply define a route, give it a function and now that function is an api call on my server is great.

commit: 111681b805953a850bae3688889cda059e8298ae