Got it working! 🍎
This commit is contained in:
@@ -15,7 +15,7 @@ class ConfettiAsset extends AssetBundle
|
||||
public $css = [
|
||||
];
|
||||
public $js = [
|
||||
'//cdn.jsdelivr.net/npm/canvas-confetti@1.5.1/dist/confetti.browser.min.js',
|
||||
'//cdn.jsdelivr.net/npm/canvas-confetti@1.9.3/dist/confetti.browser.min.js',
|
||||
];
|
||||
public $depends = [
|
||||
];
|
||||
|
||||
@@ -3,10 +3,14 @@
|
||||
namespace frontend\controllers;
|
||||
|
||||
use common\models\Meal;
|
||||
use frontend\models\MealForm;
|
||||
use Yii;
|
||||
use yii\data\ActiveDataProvider;
|
||||
use yii\filters\AccessControl;
|
||||
use yii\web\Controller;
|
||||
use yii\web\NotFoundHttpException;
|
||||
use yii\filters\VerbFilter;
|
||||
use yii\web\UploadedFile;
|
||||
|
||||
/**
|
||||
* MealController implements the CRUD actions for Meal model.
|
||||
@@ -21,8 +25,17 @@ class MealController extends Controller
|
||||
return array_merge(
|
||||
parent::behaviors(),
|
||||
[
|
||||
'access' => [
|
||||
'class' => AccessControl::class,
|
||||
'rules' => [
|
||||
[
|
||||
'allow' => true,
|
||||
'roles' => ['@'],
|
||||
],
|
||||
],
|
||||
],
|
||||
'verbs' => [
|
||||
'class' => VerbFilter::className(),
|
||||
'class' => VerbFilter::class,
|
||||
'actions' => [
|
||||
'delete' => ['POST'],
|
||||
],
|
||||
@@ -57,6 +70,28 @@ class MealController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
public function actionUpload()
|
||||
{
|
||||
$model = new MealForm();
|
||||
|
||||
if (Yii::$app->request->isPost) {
|
||||
$model->picture = UploadedFile::getInstance($model, 'picture');
|
||||
if ($model->upload()) {
|
||||
$meal = \Yii::$app->gemini->mealInquiry($model->filepath);
|
||||
return $this->render('success', ['model' => $meal]);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->render('upload', [
|
||||
'model' => $model,
|
||||
]);
|
||||
}
|
||||
|
||||
public function actionSuccess()
|
||||
{
|
||||
return $this->render('success');
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a single Meal model.
|
||||
* @param int $id ID
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
namespace frontend\controllers;
|
||||
|
||||
use common\components\SonarApiComponent;
|
||||
use common\components\GeminiApiComponent;
|
||||
use common\jobs\EmailJob;
|
||||
use frontend\models\ResendVerificationEmailForm;
|
||||
use frontend\models\VerifyEmailForm;
|
||||
@@ -162,7 +162,7 @@ class SiteController extends Controller
|
||||
return Yii::$app->response->send();
|
||||
}
|
||||
|
||||
/** @var SonarApiComponent $api */
|
||||
/** @var GeminiApiComponent $api */
|
||||
$api = Yii::$app->sonar;
|
||||
$object = json_decode(Yii::$app->request->getRawBody());
|
||||
|
||||
|
||||
41
frontend/models/MealForm.php
Normal file
41
frontend/models/MealForm.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace frontend\models;
|
||||
|
||||
use Ramsey\Uuid\Uuid;
|
||||
use Yii;
|
||||
use yii\base\Model;
|
||||
use yii\web\UploadedFile;
|
||||
|
||||
class MealForm extends Model
|
||||
{
|
||||
|
||||
/**
|
||||
* @var UploadedFile
|
||||
*/
|
||||
public $picture;
|
||||
public string $filepath;
|
||||
|
||||
public function rules() {
|
||||
return [
|
||||
[['picture'], 'file', 'skipOnEmpty' => false],
|
||||
[['picture'], 'required'],
|
||||
];
|
||||
}
|
||||
|
||||
public function newFileName()
|
||||
{
|
||||
$this->filepath = (string)'uploads/' . Yii::$app->user->id . '-' . Uuid::uuid4() . '.' . $this->picture->extension;
|
||||
}
|
||||
|
||||
public function upload()
|
||||
{
|
||||
if ($this->validate()) {
|
||||
$this->newFileName();
|
||||
$this->picture->saveAs($this->filepath);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,11 +69,19 @@ class SignupForm extends Model
|
||||
protected function sendEmail($user)
|
||||
{
|
||||
Yii::$app->queue->push(new EmailJob([
|
||||
'templateAlias' => EmailJob::VERIFY_EMAIL,
|
||||
'templateAlias' => EmailJob::ADMIN_NOTIFY,
|
||||
'email' => Yii::$app->params['supportEmail'],
|
||||
'templateModel' => [
|
||||
"name" => $user->first_name,
|
||||
"user_name" => $user->email,
|
||||
]
|
||||
]));
|
||||
Yii::$app->queue->push(new EmailJob([
|
||||
'templateAlias' => EmailJob::WELCOME_EMAIL,
|
||||
'email' => $user->email,
|
||||
'templateModel' => [
|
||||
"name" => $user->first_name,
|
||||
"action_url" => Yii::$app->urlManager->createAbsoluteUrl(['site/verify-email', 'token' => $user->verification_token]),
|
||||
"action_url" => Yii::$app->urlManager->createAbsoluteUrl(['site/login']),
|
||||
]
|
||||
]));
|
||||
|
||||
|
||||
@@ -45,7 +45,11 @@ $this->beginPage() ?>
|
||||
|
||||
if (!Yii::$app->user->isGuest) {
|
||||
$menuItems[] = [
|
||||
'label' => 'Meals',
|
||||
'label' => 'Capture Meal',
|
||||
'url' => [Url::to(['meal/upload'])],
|
||||
];
|
||||
$menuItems[] = [
|
||||
'label' => 'List Meals',
|
||||
'url' => [Url::to(['meal/index'])],
|
||||
];
|
||||
$menuItems[] = [
|
||||
|
||||
83
frontend/views/meal/success.php
Normal file
83
frontend/views/meal/success.php
Normal file
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
use common\models\Meal;
|
||||
use frontend\assets\ConfettiAsset;
|
||||
|
||||
/* @var $this yii\web\View */
|
||||
|
||||
$this->title = Yii::$app->name;
|
||||
|
||||
ConfettiAsset::register($this);
|
||||
|
||||
|
||||
$this->registerJs("
|
||||
var end = Date.now() + (2 * 1000);
|
||||
var scalar = 2;
|
||||
var foodEmojis = ['🍕', '🍔', '🍎', '🥑', '🥗', '🍣', '🍩', '🌮', '🍉', '🍞', '🍜', '🥩', '🍪', '🥕', '🧀', '🍓', '🍍', '🥒', '🍇', '🥞', '🦞', '🍗', '🍛'];
|
||||
// Create shapes from all emojis
|
||||
var emojiShapes = foodEmojis.map(emoji => confetti.shapeFromText({ text: emoji, scalar }));
|
||||
var colors = ['#4a8fc4', '#FFA500', '#585858'];
|
||||
// Function to pick 3 random unique emojis
|
||||
function getRandomEmojis() {
|
||||
let shuffled = emojiShapes.sort(() => 0.5 - Math.random());
|
||||
return shuffled.slice(0, 3); // Get first 3 elements
|
||||
}
|
||||
|
||||
// Detect if user is on a phone (simple check)
|
||||
var isMobile = window.innerWidth < 768; // Adjust as needed for tablets
|
||||
|
||||
(function frame() {
|
||||
confetti({
|
||||
particleCount: isMobile ? 1 : 2, // Fewer particles on mobile
|
||||
angle: 60,
|
||||
spread: isMobile ? 35 : 55, // Less spread on mobile,
|
||||
origin: { x: 0 },
|
||||
shapes: getRandomEmojis(),
|
||||
scalar: isMobile ? 1.5 : 2 // Slightly smaller on mobile
|
||||
});
|
||||
confetti({
|
||||
particleCount: isMobile ? 1 : 2, // Fewer particles on mobile
|
||||
angle: 120,
|
||||
spread: isMobile ? 35 : 55, // Less spread on mobile,
|
||||
origin: { x: 1 },
|
||||
shapes: getRandomEmojis(),
|
||||
scalar: isMobile ? 1.5 : 2 // Slightly smaller on mobile
|
||||
});
|
||||
|
||||
if (Date.now() < end) {
|
||||
requestAnimationFrame(frame);
|
||||
}
|
||||
}());");
|
||||
?>
|
||||
<div class="analysis-result text-center p-4">
|
||||
<h2 class="mb-3">Yum! 🎉</h2>
|
||||
|
||||
<p class="lead">
|
||||
Our AI minions have <i>finished judging</i> you...
|
||||
and your food. But also you. 🤖🍕
|
||||
</p>
|
||||
|
||||
<div class="card mx-auto p-3" style="max-width: 400px; border-radius: 12px; box-shadow: 0 4px 10px rgba(0,0,0,0.1); z-index: 200;">
|
||||
<h4 class="mb-3 text-center">🍽️ <strong><?= $model->food_name ?></strong></h4>
|
||||
<div class="row">
|
||||
<div class="col-6 text-right">
|
||||
<p class="mb-0"><strong>🔥 Calories</strong></p>
|
||||
<p class="mb-0"><strong>🍗 Protein</strong></p>
|
||||
<p class="mb-0"><strong>🥑 Fat</strong></p>
|
||||
<p class="mb-0"><strong>🍞 Carbs</strong></p>
|
||||
</div>
|
||||
|
||||
<div class="col-6">
|
||||
<p class="mb-0"><?= $model->calories ?> kcal</p>
|
||||
<p class="mb-0"><?= $model->protein ?> g</p>
|
||||
<p class="mb-0"><?= $model->fat ?> g</p>
|
||||
<p class="mb-0"><?= $model->carbohydrates ?> g</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-5 mb-4 bg-transparent rounded-3">
|
||||
<div class="container-fluid py-5 text-center">
|
||||
<p><a class="btn btn-lg btn-primary" href="/meal/upload" style="z-index: 200;">Feed me more data! 🍔</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
62
frontend/views/meal/upload.php
Normal file
62
frontend/views/meal/upload.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
use yii\helpers\Html;
|
||||
use yii\widgets\ActiveForm;
|
||||
|
||||
/** @var yii\web\View $this */
|
||||
/** @var common\models\Meal $model */
|
||||
/** @var yii\widgets\ActiveForm $form */
|
||||
|
||||
$emoji = ['🍕', '🍔', '🍎', '🥑', '🥗', '🍣', '🍩', '🌮', '🍉', '🍞', '🍜', '🥩', '🍪', '🥕', '🧀', '🍓', '🍍', '🥒', '🍇', '🥞', '🦞', '🍗', '🍛'];
|
||||
$randEmojiIndex = array_rand($emoji, 1);
|
||||
$this->registerJS("
|
||||
let foodEmojis = ".json_encode($emoji).";
|
||||
let index = 0;
|
||||
let lastIndex = ".$randEmojiIndex.";
|
||||
|
||||
function cycleEmojis() {
|
||||
let newIndex;
|
||||
do {
|
||||
newIndex = Math.floor(Math.random() * foodEmojis.length);
|
||||
} while (newIndex === lastIndex); // Ensure it's different from the last one
|
||||
|
||||
lastIndex = newIndex; // Update lastIndex to track the last used emoji
|
||||
|
||||
$('#upload-title').fadeOut(200, function() {
|
||||
$(this).html('Upload Your ' + foodEmojis[newIndex]).fadeIn(200);
|
||||
});
|
||||
}
|
||||
|
||||
setInterval(cycleEmojis, 1500); // Change every 1.5 seconds
|
||||
|
||||
$('#mealform-picture').on('change', function(ev) {
|
||||
$('#submitButton').text('Processing...');
|
||||
$('#submitButton').attr('disabled', true);
|
||||
$(this).parents('form').submit();
|
||||
ev.preventDefault();
|
||||
});
|
||||
");
|
||||
?>
|
||||
|
||||
<div class="meal-form container mt-5">
|
||||
<div class="card shadow-sm p-4">
|
||||
<h5 id="upload-title" class="mb-3">Upload Your <?= $emoji[$randEmojiIndex] ?></h5>
|
||||
|
||||
<?php
|
||||
$form = ActiveForm::begin(['options' => ['enctype' => 'multipart/form-data']]); ?>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="mealform-picture" class="form-label">Your picture will automatically submit after selected. Just
|
||||
be patient.</label>
|
||||
<?= $form->field($model, 'picture')
|
||||
->fileInput([
|
||||
'class' => 'form-control',
|
||||
//'capture' => 'environment',
|
||||
]); ?>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<?= Html::submitButton('Submit', ['id' => 'submitButton', 'class' => 'btn btn-success']) ?>
|
||||
</div>
|
||||
<?php ActiveForm::end(); ?>
|
||||
</div>
|
||||
</div>
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
/** @var yii\web\View $this */
|
||||
|
||||
$this->title = 'Sales Agent';
|
||||
$this->title = 'Calorie Thingy';
|
||||
|
||||
?>
|
||||
|
||||
<div class="alert alert-info" role="info">
|
||||
@@ -14,7 +15,7 @@ $this->title = 'Sales Agent';
|
||||
<h1 class="display-4">Calorie Ease</h1>
|
||||
<p class="fs-5 fw-light">Track your food with a picture!</p>
|
||||
<p>
|
||||
<a class="btn btn-lg btn-success" href="<?= Yii::$app->getUrlManager()->createUrl(['meal/create']) ?>">Log a meal</a>
|
||||
<a class="btn btn-lg btn-success" href="<?= Yii::$app->getUrlManager()->createUrl(['meal/upload']) ?>">Capture a meal</a>
|
||||
<a class="btn btn-lg btn-primary" href="<?= Yii::$app->getUrlManager()->createUrl(['summary']) ?>">View Summary</a></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user