Got it working! 🍎

This commit is contained in:
Chris Smith
2025-02-19 21:43:44 +01:00
parent 658718ca06
commit 61e85ae9a2
20 changed files with 651 additions and 1200 deletions

View File

@@ -0,0 +1,128 @@
<?php
namespace common\components;
use common\models\Meal;
use Exception;
use Yii;
use yii\helpers\FileHelper;
use yii\httpclient\Client;
class GeminiApiComponent extends \yii\base\Component
{
public Client $client;
public string $apiKey;
public string $baseUrl;
public string $model;
public function init()
{
parent::init();
$this->client = new Client([
'baseUrl' => $this->baseUrl,
'requestConfig' => [
'format' => Client::FORMAT_JSON
],
'responseConfig' => [
'format' => Client::FORMAT_JSON
],
]);
}
public function mealInquiry($filePath)
{
$data = [
"contents" => [
[
"role" => "user",
"parts" => [
[
"text" => "INSERT_INPUT_HERE"
]
]
],
[
"role" => "user",
"parts" => [
[
"inline_data" => [
"data" => base64_encode(file_get_contents($filePath)),
"mimeType" => FileHelper::getMimeType($filePath)
]
]
]
]
],
"systemInstruction" => [
"role" => "user",
"parts" => [
[
"text" => "Provide a caloric and macro estimate for pictures I provide to you. Try to be as accurate as possible and always calculate the everything you see in the picture. Proivde a 3 or 4 word `food_name`"
]
]
],
"generationConfig" => [
"temperature" => 1,
"topK" => 40,
"topP" => 0.95,
"maxOutputTokens" => 1000,
"responseMimeType" => "application/json",
"responseSchema" => [
"type" => "object",
"properties" => [
"food_name" => [
"type" => "string"
],
"calories" => [
"type" => "integer"
],
"protein" => [
"type" => "integer"
],
"fat" => [
"type" => "integer"
],
"carbohydrates" => [
"type" => "integer"
],
"fiber" => [
"type" => "integer"
]
],
"required" => [
"food_name",
"calories",
"protein",
"fat",
"carbohydrates",
"fiber"
]
]
]
];
$response = $this->client
->post([$this->model, 'key' => $this->apiKey])
->setData($data)
->send();
if ($response->statusCode != 200) {
throw new Exception('There was an issue with the AI side of things - sorry! It is a MVP after all :/');
}
$meal = new Meal();
$gemini = json_decode($response->getContent(), true);
Yii::debug($gemini);
$geminiMeal = json_decode($gemini['candidates'][0]['content']['parts'][0]['text'], true);
$meal->protein = $geminiMeal['protein'];
$meal->calories = $geminiMeal['calories'];
$meal->carbohydrates = $geminiMeal['carbohydrates'];
$meal->fat = $geminiMeal['fat'];
$meal->fiber = $geminiMeal['fiber'];
$meal->food_name = $geminiMeal['food_name'];
return $meal;
}
}

View File

@@ -1,469 +0,0 @@
<?php
namespace common\components;
use common\jobs\EmailJob;
use common\models\Account;
use common\models\InvoiceItem;
use common\models\SalesAgent;
use common\models\Service;
use Yii;
use yii\helpers\Url;
use yii\httpclient\Client;
use yii\httpclient\Request;
use yii\httpclient\RequestEvent;
class SonarApiComponent extends \yii\base\Component
{
public Client $client;
public string $baseUrl;
public string $bearerToken;
public function init()
{
parent::init();
$this->client = new Client([
'baseUrl' => $this->baseUrl,
'requestConfig' => [
'format' => Client::FORMAT_JSON
],
'responseConfig' => [
'format' => Client::FORMAT_JSON
],
]);
// Setup event for auth before each send
$this->client->on(Request::EVENT_BEFORE_SEND, function (RequestEvent $event) {
$event->request->addHeaders(['Authorization' => 'Bearer ' . $this->bearerToken]);
});
}
public function getAccounts(int $page = 1, int $limit = 100)
{
$data = [
'form_params' => [
'query' => 'query accountsWithSalesAgents(
$paginator: Paginator,
$search: [Search],
$sorter: [Sorter]
) {
accounts(
paginator: $paginator
search: $search
sorter: $sorter
reverse_relation_filters: {
relation: "custom_field_data",
search: {
integer_fields: {
attribute: "custom_field_id",
search_value: 12,
operator: EQ
}
}
}
general_search_mode: ROOT_PLUS_RELATIONS
account_status_id: 1
) {
entities {
id
name
account_status {
id
name
}
account_services {
entities {
id
quantity
name_override
price_override
price_override_reason
service {
id
name
amount
enabled
application
type
}
}
page_info {
page
records_per_page
total_count
total_pages
}
}
custom_field_data(custom_field_id:12) {
entities {
id
custom_field_id
value
}
}
}
page_info {
page
records_per_page
total_count
total_pages
}
}
}',
'variables' => [
'paginator' => [
'page' => $page,
'records_per_page' => $limit
],
'search' => [],
'sorter' => [
[
'attribute' => 'updated_at',
'direction' => 'ASC',
]
],
]
]
];
$response = $this->client->createRequest()
->setMethod('POST')
->setData($data)
->send();
$account = json_decode($response->getContent(), true);
return $account['form_params']['data'];
}
public function getInvoices(string $startDate, string $endDate)
{
$page = 1;
$limit = 100;
$invoices = [];
do {
$data = [
'form_params' => [
'query' => 'query accountInvoice($paginator: Paginator, $search: [Search], $sorter: [Sorter]) {
invoices(
paginator: $paginator
search: $search
sorter: $sorter
general_search_mode: ROOT_PLUS_RELATIONS
) {
entities {
id
account_id
total_debits
void
remaining_due
date
due_date
end_date
delinquent
debits {
entities {
id
quantity
service_id
service_name
amount
}
}
credits {
entities {
amount
}
}
}
page_info {
page
records_per_page
total_count
total_pages
}
}
}',
'variables' => [
'paginator' => [
'page' => $page,
'records_per_page' => $limit
],
'search' => [
[
'date_fields' => [
['attribute' => 'date', 'search_value' => $startDate, 'operator' => 'GTE'],
['attribute' => 'date', 'search_value' => $endDate, 'operator' => 'LTE'],
]
]
]
],
'sorter' => [
[
'attribute' => 'updated_at',
'direction' => 'ASC',
]
],
]
];
$response = $this->client->createRequest()
->setMethod('POST')
->setData($data)
->send();
$responseData = json_decode($response->getContent(), true);
$invoices = array_merge($invoices, $responseData['form_params']['data']['invoices']['entities']);
$page++;
} while ($page < ($responseData['form_params']['data']['invoices']['page_info']['total_pages'] + 1));
return $invoices;
}
public function getInvoice(int $invoiceId = 1)
{
$page = 1;
$limit = 100;
$data = [
'form_params' => [
'query' => 'query accountInvoice($paginator: Paginator, $search: [Search], $sorter: [Sorter]) {
invoices(
id: ' . $invoiceId . '
paginator: $paginator
search: $search
sorter: $sorter
general_search_mode: ROOT_PLUS_RELATIONS
) {
entities {
id
account_id
total_debits
void
remaining_due
date
due_date
end_date
delinquent
debits {
entities {
id
quantity
service_id
service_name
amount
}
}
credits {
entities {
amount
}
}
}
page_info {
page
records_per_page
total_count
total_pages
}
}
}',
'variables' => [
'paginator' => [
'page' => $page,
'records_per_page' => $limit
],
'search' => [],
'sorter' => [
[
'attribute' => 'updated_at',
'direction' => 'ASC',
]
],
]
]
];
$response = $this->client->createRequest()
->setMethod('POST')
->setData($data)
->send();
$invoice = json_decode($response->getContent(), true);
return $invoice['form_params']['data']['invoices']['entities'][0];
}
public function storeInvoices($invoices)
{
foreach ($invoices as $invoice) {
$this->storeInvoice($invoice);
}
}
public function storeInvoice($invoice)
{
\Yii::debug($invoice);
// $remainingDue is the Entire Invoice remaining to be paid amount, 0 = everything paid
$remainingDue = $invoice['remaining_due'];
// debits = charges on the account
// credits = payments on the account
foreach ($invoice['debits']['entities'] as $i => $rawItem) {
$invoiceItem = InvoiceItem::find()->where(['sonar_id' => (int)$rawItem['id']])->one();
if (null === $invoiceItem) { // create new invoice item
$account = Account::findOne(['sonar_id' => (int)$invoice['account_id']]);
$service = Service::findOne(['sonar_id' => (int)$rawItem['service_id']]);
\Yii::debug($rawItem);
if ($service && $account) {
\Yii::debug($invoice);
$payment = (isset($invoice['credits']['entities'][$i]['amount'])) ? $invoice['credits']['entities'][$i]['amount'] : 0;
// @todo check payment - i think it is wrong to assume we have the same credits and debits ^ CGS
$invoiceItem = new InvoiceItem([
'sonar_id' => (int)$rawItem['id'],
'account_id' => $account->id,
'service_id' => $service->id,
'name' => $rawItem['service_name'],
'status' => InvoiceItem::STATUS_OPEN,
'charge' => $rawItem['amount'],
'payment' => $payment,
'is_commissionable' => $service->hasCommission(),
]);
$invoiceItem->save();
}
}
// is the invoice item paid?
if ($invoiceItem && $remainingDue == 0) {
$invoiceItem->status = InvoiceItem::STATUS_PAYMENT_RECEIVED;
$invoiceItem->save();
}
}
}
private function mapAccounts($accounts)
{
$mapped = [];
$i = 0;
$db = \Yii::$app->db;
foreach ($accounts as $account) {
$mapped[$i]['sonar_id'] = (int)$account['id'];
$mapped[$i]['name'] = $account['name'];
/**
* [
* 'id' => '132'
* 'quantity' => 1
* 'name_override' => 'Bradley'
* 'price_override' => 0
* 'price_override_reason' => 'testing'
* 'service' => [
* 'id' => '10'
* 'name' => 'Business Giga Speed Internet'
* 'amount' => 18000
* 'enabled' => true
* 'application' => 'DEBIT'
* 'type' => 'DATA'
* ]
* ]
*/
$mapped[$i]['services'] = []; // init empty array
foreach ($account['account_services']['entities'] as $key => $account_service) {
$mapped[$i]['services'][$key]['sonar_id'] = (int)$account_service['service']['id'];
$mapped[$i]['services'][$key]['name'] = (!empty($account_service['name_override'])) ? $account_service['name_override'] : $account_service['service']['name'];
$mapped[$i]['services'][$key]['price'] = (!empty($account_service['price_override'])) ? $account_service['price_override'] : $account_service['service']['amount'];
if ($account_service['service']['application'] === 'CREDIT') {
$mapped[$i]['services'][$key]['price'] = -1 * $mapped[$i]['services'][$key]['price'];// store as a negative
}
// set to 0 if null after credit
if (null === $mapped[$i]['services'][$key]['price']) {
$mapped[$i]['services'][$key]['price'] = 0;
}
}
$name = $account['custom_field_data']['entities'][0]['value'];
$salesAgent = SalesAgent::findOne(['name' => $name]);
if (null === $salesAgent) {
$salesAgent = new SalesAgent(['name' => $name]);
$salesAgent->save();
}
$mapped[$i]['sales_agent_id'] = $salesAgent->id;
$i++;
}
return $mapped;
}
public function storeAccounts()
{
$page = 1;
do {
$batch = $this->getAccounts($page, 100);
$accounts = $this->mapAccounts($batch['accounts']['entities']);
foreach ($accounts as $account) {
$accountModel = Account::findOne(['sonar_id' => $account['sonar_id']]);
if (null === $accountModel) {
$accountModel = new Account([
'sonar_id' => $account['sonar_id'],
'name' => $account['name'],
'sales_agent_id' => $account['sales_agent_id'],
]);
$accountModel->save();
} else {
//$accountModel->sonar_id = $account['sonar_id'];
$accountModel->name = $account['name'];
$accountModel->sales_agent_id = $account['sales_agent_id'];
$accountModel->save();
}
foreach ($account['services'] as $rawServiceData) {
$serviceModel = Service::findOne(['sonar_id' => (int)$rawServiceData['sonar_id']]);
if (null === $serviceModel) {
$serviceModel = new Service([
'sonar_id' => $rawServiceData['sonar_id'],
'name' => $rawServiceData['name'],
'price' => $rawServiceData['price'],
'account_id' => $accountModel->id,
'active' => 1, // @todo pull active state in from sonar api
]);
$serviceModel->save();
} else {
$serviceModel->commission = $serviceModel->getFormattedDollar($serviceModel->commission, false);
$serviceModel->sonar_id = $rawServiceData['sonar_id'];
$serviceModel->name = $rawServiceData['name'];
$serviceModel->price = $rawServiceData['price'];
$serviceModel->account_id = $accountModel->id;
if (!empty($serviceModel->dirtyAttributes)) {
if (isset($serviceModel->dirtyAttributes['price'])) {
//Yii::$app->queue->push(new EmailJob([
// 'templateAlias' => EmailJob::PRICE_CHANGE,
// "email" => Yii::$app->params['adminEmail'],
// 'templateModel' => [
// "action_edit_url" => Yii::$app->urlManager->createAbsoluteUrl(
// ['service/update', 'id' => $serviceModel->id]
// ),
// ]
//]));
}
}
$serviceModel->save();
}
}
}
$page++;
} while ($page < ($batch['accounts']['page_info']['total_pages'] + 1));
}
public function processInvoices(int $invoiceId)
{
dump($this->getInvoice($invoiceId));
}
}

View File

@@ -19,12 +19,12 @@ class Yii {
}
/**
* @property yii\rbac\DbManager $authManager
* @property \Da\User\Model\User $user
* @property \common\components\SonarApiComponent $sonar
* @property \common\components\HubspotApiComponent $hubspot
* @property \common\components\PostmarkComponent $postmark
* @property \yii\queue\db\Queue $queue
* @property yii\rbac\DbManager $authManager
* @property \Da\User\Model\User $user
* @property \common\components\GeminiApiComponent $sonar
* @property \common\components\HubspotApiComponent $hubspot
* @property \common\components\PostmarkComponent $postmark
* @property \yii\queue\db\Queue $queue
*
*/
class __Application {

View File

@@ -1,7 +1,7 @@
<?php
use common\components\PostmarkComponent;
use common\components\SonarApiComponent;
use common\components\GeminiApiComponent;
use common\components\HubspotApiComponent;
use yii\caching\FileCache;
use yii\queue\db\Queue;
@@ -22,10 +22,11 @@ return [
'cache' => [
'class' => FileCache::class,
],
'sonar' => [
'class' => SonarApiComponent::class,
'baseUrl' => $params['sonar.url'] . '/api/graphql',
'bearerToken' => $params['sonar.bearerToken'],
'gemini' => [
'class' => GeminiApiComponent::class,
'baseUrl' => $params['gemini.url'],
'apiKey' => $params['gemini.key'],
'model' => $params['gemini.model'],
],
'postmark' => [
'class' => PostmarkComponent::class,

View File

@@ -6,8 +6,9 @@ return [
'senderName' => 'Example.com mailer',
'user.passwordResetTokenExpire' => 3600,
'user.passwordMinLength' => 8,
'sonar.url' => 'https://yourname.sonar.software',
'sonar.bearerToken' => '',
'gemini.url' => 'https://generativelanguage.googleapis.com/v1beta/models',
'gemini.model' => 'gemini-1.5-flash-8b:generateContent',
'gemini.key' => '',
'postmark.serverToken' => 'postmark-server-key',
'postmark.messageStream' => 'outbound',
'sentry.dsn' => 'https://asdf@o4507934844780544.ingest.us.sentry.io/4508006893158400',

View File

@@ -32,10 +32,10 @@ class EmailJob extends BaseObject implements RetryableJobInterface
$this->templateModel = array_merge($this->templateModel, [
"product" => Yii::$app->params['product_name'],
"product_name" => Yii::$app->params['product_name'],
"support_url" => Yii::$app->params['support_url'],
//"support_url" => Yii::$app->params['support_url'],
"product_url" => Yii::$app->urlManager->createAbsoluteUrl(['site/index']),
"company_name" => Yii::$app->params['company_name'],
"company_address" => Yii::$app->params['company_address'],
//"company_address" => Yii::$app->params['company_address'],
]);
Yii::$app->postmark->client->sendEmailWithTemplate(

View File

@@ -7,18 +7,22 @@ namespace common\models;
*
* @property int $id
* @property string $file_name
* @property string $food_name
* @property int $calories
* @property int $protein
* @property int $fat
* @property int $carbohydrates
* @property int $fiber
* @property int $meal
* @property int $user_id
* @property int $created_at
* @property int $updated_at
*/
class Meal extends \yii\db\ActiveRecord
{
public $base64File;
/**
* {@inheritdoc}
*/
@@ -33,8 +37,8 @@ class Meal extends \yii\db\ActiveRecord
public function rules()
{
return [
[['file_name', 'calories', 'protein', 'fat', 'carbohydrates', 'fiber', 'meal', 'created_at', 'updated_at'], 'required'],
[['calories', 'protein', 'fat', 'carbohydrates', 'fiber', 'meal', 'created_at', 'updated_at'], 'integer'],
[['food_name', 'file_name', 'calories', 'protein', 'fat', 'carbohydrates', 'fiber', 'meal', 'created_at', 'updated_at'], 'required'],
[['user_id', 'calories', 'protein', 'fat', 'carbohydrates', 'fiber', 'meal', 'created_at', 'updated_at'], 'integer'],
[['file_name'], 'string', 'max' => 255],
];
}

View File

@@ -54,26 +54,24 @@ class User extends ActiveRecord implements IdentityInterface
];
// register event
$this->on(self::EVENT_AFTER_INSERT, [$this, 'emailTrigger']);
$this->on(self::EVENT_AFTER_UPDATE, [$this, 'emailTrigger']);
//$this->on(self::EVENT_AFTER_INSERT, [$this, 'emailTrigger']);
//$this->on(self::EVENT_AFTER_UPDATE, [$this, 'emailTrigger']);
}
public function emailTrigger(AfterSaveEvent $event)
{
if ($event->sender->status == self::STATUS_ACTIVE && !$event->sender->welcome_email_sent) {
Yii::$app->queue->push(new EmailJob([
'templateAlias' => EmailJob::WELCOME_EMAIL,
'email' => $event->sender->email,
'templateModel' => [
"name" => $event->sender->first_name,
"action_url" => Yii::$app->urlManager->createAbsoluteUrl(['site/login']),
]
]));
Yii::$app->queue->push(new EmailJob([
'templateAlias' => EmailJob::WELCOME_EMAIL,
'email' => $event->sender->email,
'templateModel' => [
"name" => $event->sender->first_name,
"action_url" => Yii::$app->urlManager->createAbsoluteUrl(['site/login']),
]
]));
$event->sender->welcome_email_sent = true;
$event->sender->save(false);
}
$event->sender->welcome_email_sent = true;
$event->sender->save(false);
}
@@ -91,7 +89,7 @@ class User extends ActiveRecord implements IdentityInterface
public function rules()
{
return [
['status', 'default', 'value' => self::STATUS_UNVERIFIED],
['status', 'default', 'value' => self::STATUS_VERIFIED],
[['email'], 'email'],
[['email'], 'unique'],
[['sales_agent_id', 'created_at', 'updated_at'], 'integer'],

View File

@@ -3,20 +3,17 @@
"require": {
"php": ">=7.4.0",
"ext-json": "*",
"yiisoft/yii2": "~2.0.45",
"yiisoft/yii2-bootstrap5": "@dev",
"yiisoft/yii2-authclient": "^2.2",
"yiisoft/yii2-httpclient": "~2.0",
"cgsmith/yii2-flatpickr-widget": "~1.1",
"yiisoft/yii2-queue": "^2.3",
"wildbit/postmark-php": "^6.0",
"donatj/phpuseragentparser": "^1.9",
"twbs/bootstrap-icons": "^1.11",
"ramsey/uuid": "^4.7",
"sentry/sentry": "^4.9",
"kartik-v/yii2-widget-select2": "^2.2",
"kartik-v/yii2-grid": "dev-master",
"kartik-v/yii2-editable": "^1.8",
"kartik-v/yii2-icons": "^1.4"
"twbs/bootstrap-icons": "^1.11",
"wildbit/postmark-php": "^6.0",
"yiisoft/yii2": "~2.0.45",
"yiisoft/yii2-authclient": "^2.2",
"yiisoft/yii2-bootstrap5": "@dev",
"yiisoft/yii2-httpclient": "~2.0",
"yiisoft/yii2-queue": "^2.3"
},
"require-dev": {
"codeception/codeception": "^5.1",
@@ -36,6 +33,7 @@
"config": {
"sort-packages": true,
"allow-plugins": {
"php-http/discovery": true,
"yiisoft/yii2-composer": true
},
"process-timeout": 1800,
@@ -52,6 +50,10 @@
{
"type": "composer",
"url": "https://asset-packagist.org"
},
{
"type": "vcs",
"url": "git@github.com:eficiencia-fiscal/gemini-client.git"
}
]
}

908
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "c38ad4634c62615dbe2a183401f93003",
"content-hash": "2aa71ca3104de10ae9406cd03e93cd1d",
"packages": [
{
"name": "bower-asset/bootstrap",
@@ -60,27 +60,6 @@
"MIT"
]
},
{
"name": "bower-asset/jquery-ui",
"version": "1.12.1",
"source": {
"type": "git",
"url": "git@github.com:components/jqueryui.git",
"reference": "44ecf3794cc56b65954cc19737234a3119d036cc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/components/jqueryui/zipball/44ecf3794cc56b65954cc19737234a3119d036cc",
"reference": "44ecf3794cc56b65954cc19737234a3119d036cc"
},
"require": {
"bower-asset/jquery": ">=1.6"
},
"type": "bower-asset",
"license": [
"MIT"
]
},
{
"name": "bower-asset/punycode",
"version": "v1.4.1",
@@ -117,6 +96,66 @@
"MIT"
]
},
{
"name": "brick/math",
"version": "0.12.1",
"source": {
"type": "git",
"url": "https://github.com/brick/math.git",
"reference": "f510c0a40911935b77b86859eb5223d58d660df1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/brick/math/zipball/f510c0a40911935b77b86859eb5223d58d660df1",
"reference": "f510c0a40911935b77b86859eb5223d58d660df1",
"shasum": ""
},
"require": {
"php": "^8.1"
},
"require-dev": {
"php-coveralls/php-coveralls": "^2.2",
"phpunit/phpunit": "^10.1",
"vimeo/psalm": "5.16.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Brick\\Math\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "Arbitrary-precision arithmetic library",
"keywords": [
"Arbitrary-precision",
"BigInteger",
"BigRational",
"arithmetic",
"bigdecimal",
"bignum",
"bignumber",
"brick",
"decimal",
"integer",
"math",
"mathematics",
"rational"
],
"support": {
"issues": "https://github.com/brick/math/issues",
"source": "https://github.com/brick/math/tree/0.12.1"
},
"funding": [
{
"url": "https://github.com/BenMorel",
"type": "github"
}
],
"time": "2023-11-29T23:19:16+00:00"
},
{
"name": "cebe/markdown",
"version": "1.2.1",
@@ -238,47 +277,6 @@
},
"time": "2024-09-25T08:16:01+00:00"
},
{
"name": "components/flag-icon-css",
"version": "v7.3.2",
"source": {
"type": "git",
"url": "https://github.com/lipis/flag-icons.git",
"reference": "fe15c16e7463d0c66d6c5730e9d0e832438d98e1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/lipis/flag-icons/zipball/fe15c16e7463d0c66d6c5730e9d0e832438d98e1",
"reference": "fe15c16e7463d0c66d6c5730e9d0e832438d98e1",
"shasum": ""
},
"type": "library",
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Panayiotis Lipiridis",
"email": "lipiridis@gmail.com",
"role": "Lead"
}
],
"description": "A curated collection of all country flags in SVG — plus the CSS for easier integration.",
"homepage": "https://flagicons.lipis.dev",
"keywords": [
"country",
"country-flags",
"css",
"icon-css",
"svg"
],
"support": {
"issues": "https://github.com/lipis/flag-icons/issues",
"source": "https://github.com/lipis/flag-icons/tree/v7.3.2"
},
"time": "2025-01-17T14:03:50+00:00"
},
{
"name": "donatj/phpuseragentparser",
"version": "v1.10.0",
@@ -798,539 +796,6 @@
},
"time": "2024-11-18T16:19:46+00:00"
},
{
"name": "kartik-v/bootstrap-popover-x",
"version": "v1.5.4",
"source": {
"type": "git",
"url": "https://github.com/kartik-v/bootstrap-popover-x.git",
"reference": "879def62529797833e10885b6e77864e83e9e240"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/kartik-v/bootstrap-popover-x/zipball/879def62529797833e10885b6e77864e83e9e240",
"reference": "879def62529797833e10885b6e77864e83e9e240",
"shasum": ""
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.5.x-dev"
}
},
"autoload": {
"psr-4": {
"kartik\\plugins\\popover\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Kartik Visweswaran",
"email": "kartikv2@gmail.com",
"homepage": "http://www.krajee.com/"
}
],
"description": "Bootstrap Popover Extended - Popover with modal behavior, styling enhancements and more.",
"homepage": "https://github.com/kartik-v/bootstrap-popover-x",
"keywords": [
"bootstrap",
"extended",
"jquery",
"modal",
"modal-popover",
"popover",
"popover-x"
],
"support": {
"issues": "https://github.com/kartik-v/bootstrap-popover-x/issues",
"source": "https://github.com/kartik-v/bootstrap-popover-x/tree/v1.5.4"
},
"funding": [
{
"url": "https://opencollective.com/bootstrap-popover-x",
"type": "open_collective"
}
],
"time": "2024-03-12T13:23:15+00:00"
},
{
"name": "kartik-v/yii2-dialog",
"version": "v1.0.6",
"source": {
"type": "git",
"url": "https://github.com/kartik-v/yii2-dialog.git",
"reference": "510c3a35ffe79987cde9a9366cedbff545fd92d4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/kartik-v/yii2-dialog/zipball/510c3a35ffe79987cde9a9366cedbff545fd92d4",
"reference": "510c3a35ffe79987cde9a9366cedbff545fd92d4",
"shasum": ""
},
"require": {
"kartik-v/yii2-krajee-base": ">=3.0.0"
},
"type": "yii2-extension",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"kartik\\dialog\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Kartik Visweswaran",
"email": "kartikv2@gmail.com",
"homepage": "http://www.krajee.com/"
}
],
"description": "An asset bundle for bootstrap3-dialog for Yii 2.0 framework.",
"homepage": "https://github.com/kartik-v/yii2-dialog",
"keywords": [
"alert",
"bootstrap",
"dialog",
"extension",
"modal",
"yii2"
],
"support": {
"issues": "https://github.com/kartik-v/yii2-dialog/issues",
"source": "https://github.com/kartik-v/yii2-dialog/tree/v1.0.6"
},
"time": "2021-09-02T08:26:37+00:00"
},
{
"name": "kartik-v/yii2-editable",
"version": "v1.8.0",
"source": {
"type": "git",
"url": "https://github.com/kartik-v/yii2-editable.git",
"reference": "ae4cc2384e667ba9dfe8bfb0098716caeb2500a8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/kartik-v/yii2-editable/zipball/ae4cc2384e667ba9dfe8bfb0098716caeb2500a8",
"reference": "ae4cc2384e667ba9dfe8bfb0098716caeb2500a8",
"shasum": ""
},
"require": {
"kartik-v/yii2-popover-x": "~1.3",
"kartik-v/yii2-widget-activeform": ">=1.6.2"
},
"type": "yii2-extension",
"extra": {
"branch-alias": {
"dev-master": "1.8.x-dev"
}
},
"autoload": {
"psr-4": {
"kartik\\editable\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Kartik Visweswaran",
"email": "kartikv2@gmail.com",
"homepage": "http://www.krajee.com/"
}
],
"description": "An enhanced editable widget for Yii 2.0 that allows easy editing of displayed data with numerous configuration possibilities.",
"homepage": "https://github.com/kartik-v/yii2-editable",
"keywords": [
"bootstrap",
"editable",
"input",
"jquery",
"popover",
"popover-x",
"widget"
],
"support": {
"issues": "https://github.com/kartik-v/yii2-editable/issues",
"source": "https://github.com/kartik-v/yii2-editable/tree/v1.8.0"
},
"time": "2022-04-29T12:51:01+00:00"
},
{
"name": "kartik-v/yii2-grid",
"version": "dev-master",
"source": {
"type": "git",
"url": "https://github.com/kartik-v/yii2-grid.git",
"reference": "bcaacfc442b8c7ac97c00c52dfdb5f7ebdca4e0c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/kartik-v/yii2-grid/zipball/bcaacfc442b8c7ac97c00c52dfdb5f7ebdca4e0c",
"reference": "bcaacfc442b8c7ac97c00c52dfdb5f7ebdca4e0c",
"shasum": ""
},
"require": {
"kartik-v/yii2-dialog": "~1.0",
"kartik-v/yii2-krajee-base": ">=3.0.3"
},
"suggest": {
"kartik-v/yii2-bootstrap4-dropdown": "For enabling dropdown support when using with Bootstrap v4.x",
"kartik-v/yii2-bootstrap5-dropdown": "For enabling dropdown support when using with Bootstrap v5.x",
"kartik-v/yii2-mpdf": "For exporting grids to PDF"
},
"default-branch": true,
"type": "yii2-extension",
"extra": {
"branch-alias": {
"dev-master": "3.5.x-dev"
}
},
"autoload": {
"psr-4": {
"kartik\\grid\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Kartik Visweswaran",
"email": "kartikv2@gmail.com",
"homepage": "http://www.krajee.com/"
}
],
"description": "Yii 2 GridView on steroids. Various enhancements and utilities for the Yii 2.0 GridView widget.",
"homepage": "https://github.com/kartik-v/yii2-grid",
"keywords": [
"extension",
"grid",
"widget",
"yii2"
],
"support": {
"issues": "https://github.com/kartik-v/yii2-grid/issues",
"source": "https://github.com/kartik-v/yii2-grid/tree/master"
},
"funding": [
{
"url": "https://opencollective.com/yii2-grid",
"type": "open_collective"
}
],
"time": "2023-10-15T08:11:32+00:00"
},
{
"name": "kartik-v/yii2-icons",
"version": "v1.4.8",
"source": {
"type": "git",
"url": "https://github.com/kartik-v/yii2-icons.git",
"reference": "81334b3d888d4baaeb6ac458475258130474237e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/kartik-v/yii2-icons/zipball/81334b3d888d4baaeb6ac458475258130474237e",
"reference": "81334b3d888d4baaeb6ac458475258130474237e",
"shasum": ""
},
"require": {
"components/flag-icon-css": "*",
"kartik-v/yii2-krajee-base": ">=3.0.4",
"yiisoft/yii2-jui": "*"
},
"type": "yii2-extension",
"extra": {
"branch-alias": {
"dev-master": "1.4.x-dev"
}
},
"autoload": {
"psr-4": {
"kartik\\icons\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Kartik Visweswaran",
"email": "kartikv2@gmail.com",
"homepage": "http://demos.krajee.com/"
}
],
"description": "Set of icon frameworks for use in Yii Framework 2.0",
"homepage": "https://github.com/kartik-v/yii2-icons",
"keywords": [
"extension",
"font",
"icon",
"yii",
"yii2"
],
"support": {
"issues": "https://github.com/kartik-v/yii2-icons/issues",
"source": "https://github.com/kartik-v/yii2-icons/tree/v1.4.8"
},
"time": "2022-03-04T10:34:44+00:00"
},
{
"name": "kartik-v/yii2-krajee-base",
"version": "v3.0.5",
"source": {
"type": "git",
"url": "https://github.com/kartik-v/yii2-krajee-base.git",
"reference": "5c095126d1be47e0bb1f92779b7dc099f6feae31"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/kartik-v/yii2-krajee-base/zipball/5c095126d1be47e0bb1f92779b7dc099f6feae31",
"reference": "5c095126d1be47e0bb1f92779b7dc099f6feae31",
"shasum": ""
},
"suggest": {
"yiisoft/yii2-bootstrap": "for Krajee extensions to work with Bootstrap 3.x version",
"yiisoft/yii2-bootstrap4": "for Krajee extensions to work with Bootstrap 4.x version",
"yiisoft/yii2-bootstrap5": "for Krajee extensions to work with Bootstrap 5.x version"
},
"type": "yii2-extension",
"extra": {
"branch-alias": {
"dev-master": "3.0.x-dev"
}
},
"autoload": {
"psr-4": {
"kartik\\base\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Kartik Visweswaran",
"email": "kartikv2@gmail.com",
"homepage": "http://www.krajee.com/"
}
],
"description": "Base library and foundation components for all Yii2 Krajee extensions.",
"homepage": "https://github.com/kartik-v/yii2-krajee-base",
"keywords": [
"base",
"extension",
"foundation",
"krajee",
"widget",
"yii2"
],
"support": {
"issues": "https://github.com/kartik-v/yii2-krajee-base/issues",
"source": "https://github.com/kartik-v/yii2-krajee-base/tree/v3.0.5"
},
"time": "2022-06-01T14:05:39+00:00"
},
{
"name": "kartik-v/yii2-popover-x",
"version": "v1.3.5",
"source": {
"type": "git",
"url": "https://github.com/kartik-v/yii2-popover-x.git",
"reference": "b0320d1315bbfce31ec8907882c6f4abed223a28"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/kartik-v/yii2-popover-x/zipball/b0320d1315bbfce31ec8907882c6f4abed223a28",
"reference": "b0320d1315bbfce31ec8907882c6f4abed223a28",
"shasum": ""
},
"require": {
"kartik-v/bootstrap-popover-x": ">=1.4",
"kartik-v/yii2-krajee-base": ">=2.0"
},
"type": "yii2-extension",
"extra": {
"branch-alias": {
"dev-master": "1.3.x-dev"
}
},
"autoload": {
"psr-4": {
"kartik\\popover\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Kartik Visweswaran",
"email": "kartikv2@gmail.com",
"homepage": "http://www.krajee.com/"
}
],
"description": "An extended bootstrap 3.0 popover widget which combines both the bootstrap popover and modal features and includes various new styling enhancements.",
"homepage": "https://github.com/kartik-v/yii2-popover-x",
"keywords": [
"bootstrap",
"extended",
"jquery",
"modal",
"modal-popover",
"popover",
"popover-x"
],
"support": {
"issues": "https://github.com/kartik-v/yii2-popover-x/issues",
"source": "https://github.com/kartik-v/yii2-popover-x/tree/master"
},
"time": "2020-04-02T17:20:29+00:00"
},
{
"name": "kartik-v/yii2-widget-activeform",
"version": "v1.6.4",
"source": {
"type": "git",
"url": "https://github.com/kartik-v/yii2-widget-activeform.git",
"reference": "697407c8fa9c81593a7bb9bef4b7ad53f7d38b79"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/kartik-v/yii2-widget-activeform/zipball/697407c8fa9c81593a7bb9bef4b7ad53f7d38b79",
"reference": "697407c8fa9c81593a7bb9bef4b7ad53f7d38b79",
"shasum": ""
},
"require": {
"kartik-v/yii2-krajee-base": ">=3.0.3"
},
"type": "yii2-extension",
"extra": {
"branch-alias": {
"dev-master": "1.6.x-dev"
}
},
"autoload": {
"psr-4": {
"kartik\\form\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Kartik Visweswaran",
"email": "kartikv2@gmail.com",
"homepage": "http://www.krajee.com/"
}
],
"description": "Enhanced Yii2 active-form and active-field with full bootstrap styling support (sub repo split from yii2-widgets).",
"homepage": "https://github.com/kartik-v/yii2-widget-activeform",
"keywords": [
"activefield",
"activeform",
"extension",
"field",
"form",
"widget",
"yii2"
],
"support": {
"issues": "https://github.com/kartik-v/yii2-widget-activeform/issues",
"source": "https://github.com/kartik-v/yii2-widget-activeform/tree/v1.6.4"
},
"funding": [
{
"url": "https://opencollective.com/yii2-widgets",
"type": "open_collective"
}
],
"time": "2023-07-31T11:33:59+00:00"
},
{
"name": "kartik-v/yii2-widget-select2",
"version": "v2.2.5",
"source": {
"type": "git",
"url": "https://github.com/kartik-v/yii2-widget-select2.git",
"reference": "4b8ef7dd9780531fc997fa23a53a38a1f7674bec"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/kartik-v/yii2-widget-select2/zipball/4b8ef7dd9780531fc997fa23a53a38a1f7674bec",
"reference": "4b8ef7dd9780531fc997fa23a53a38a1f7674bec",
"shasum": ""
},
"require": {
"kartik-v/yii2-krajee-base": ">=3.0.4",
"select2/select2": ">=4.0.0"
},
"type": "yii2-extension",
"extra": {
"branch-alias": {
"dev-master": "2.2.x-dev"
}
},
"autoload": {
"psr-4": {
"kartik\\select2\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Kartik Visweswaran",
"email": "kartikv2@gmail.com",
"homepage": "http://www.krajee.com/"
}
],
"description": "Enhanced Yii2 wrapper for the Select2 jQuery plugin (sub repo split from yii2-widgets).",
"homepage": "https://github.com/kartik-v/yii2-widget-select2",
"keywords": [
"dropdown",
"extension",
"form",
"jquery",
"plugin",
"select2",
"widget",
"yii2"
],
"support": {
"issues": "https://github.com/kartik-v/yii2-widget-select2/issues",
"source": "https://github.com/kartik-v/yii2-widget-select2/tree/v2.2.5"
},
"funding": [
{
"url": "https://opencollective.com/yii2-widget-select2",
"type": "open_collective"
}
],
"time": "2023-06-22T07:43:31+00:00"
},
{
"name": "npm-asset/flatpickr",
"version": "4.6.13",
@@ -1648,46 +1113,185 @@
"time": "2019-03-08T08:55:37+00:00"
},
{
"name": "select2/select2",
"version": "4.0.13",
"name": "ramsey/collection",
"version": "2.0.0",
"source": {
"type": "git",
"url": "https://github.com/select2/select2.git",
"reference": "45f2b83ceed5231afa7b3d5b12b58ad335edd82e"
"url": "https://github.com/ramsey/collection.git",
"reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/select2/select2/zipball/45f2b83ceed5231afa7b3d5b12b58ad335edd82e",
"reference": "45f2b83ceed5231afa7b3d5b12b58ad335edd82e",
"url": "https://api.github.com/repos/ramsey/collection/zipball/a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5",
"reference": "a4b48764bfbb8f3a6a4d1aeb1a35bb5e9ecac4a5",
"shasum": ""
},
"type": "component",
"require": {
"php": "^8.1"
},
"require-dev": {
"captainhook/plugin-composer": "^5.3",
"ergebnis/composer-normalize": "^2.28.3",
"fakerphp/faker": "^1.21",
"hamcrest/hamcrest-php": "^2.0",
"jangregor/phpstan-prophecy": "^1.0",
"mockery/mockery": "^1.5",
"php-parallel-lint/php-console-highlighter": "^1.0",
"php-parallel-lint/php-parallel-lint": "^1.3",
"phpcsstandards/phpcsutils": "^1.0.0-rc1",
"phpspec/prophecy-phpunit": "^2.0",
"phpstan/extension-installer": "^1.2",
"phpstan/phpstan": "^1.9",
"phpstan/phpstan-mockery": "^1.1",
"phpstan/phpstan-phpunit": "^1.3",
"phpunit/phpunit": "^9.5",
"psalm/plugin-mockery": "^1.1",
"psalm/plugin-phpunit": "^0.18.4",
"ramsey/coding-standard": "^2.0.3",
"ramsey/conventional-commits": "^1.3",
"vimeo/psalm": "^5.4"
},
"type": "library",
"extra": {
"component": {
"files": [
"dist/js/select2.js",
"dist/js/i18n/*.js",
"dist/css/select2.css"
],
"styles": [
"dist/css/select2.css"
],
"scripts": [
"dist/js/select2.js"
]
"captainhook": {
"force-install": true
},
"ramsey/conventional-commits": {
"configFile": "conventional-commits.json"
}
},
"autoload": {
"psr-4": {
"Ramsey\\Collection\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "Select2 is a jQuery based replacement for select boxes.",
"homepage": "https://select2.org/",
"authors": [
{
"name": "Ben Ramsey",
"email": "ben@benramsey.com",
"homepage": "https://benramsey.com"
}
],
"description": "A PHP library for representing and manipulating collections.",
"keywords": [
"array",
"collection",
"hash",
"map",
"queue",
"set"
],
"support": {
"issues": "https://github.com/select2/select2/issues",
"source": "https://github.com/select2/select2/tree/4.0.13"
"issues": "https://github.com/ramsey/collection/issues",
"source": "https://github.com/ramsey/collection/tree/2.0.0"
},
"time": "2020-01-28T05:01:22+00:00"
"funding": [
{
"url": "https://github.com/ramsey",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/ramsey/collection",
"type": "tidelift"
}
],
"time": "2022-12-31T21:50:55+00:00"
},
{
"name": "ramsey/uuid",
"version": "4.7.6",
"source": {
"type": "git",
"url": "https://github.com/ramsey/uuid.git",
"reference": "91039bc1faa45ba123c4328958e620d382ec7088"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ramsey/uuid/zipball/91039bc1faa45ba123c4328958e620d382ec7088",
"reference": "91039bc1faa45ba123c4328958e620d382ec7088",
"shasum": ""
},
"require": {
"brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11 || ^0.12",
"ext-json": "*",
"php": "^8.0",
"ramsey/collection": "^1.2 || ^2.0"
},
"replace": {
"rhumsaa/uuid": "self.version"
},
"require-dev": {
"captainhook/captainhook": "^5.10",
"captainhook/plugin-composer": "^5.3",
"dealerdirect/phpcodesniffer-composer-installer": "^0.7.0",
"doctrine/annotations": "^1.8",
"ergebnis/composer-normalize": "^2.15",
"mockery/mockery": "^1.3",
"paragonie/random-lib": "^2",
"php-mock/php-mock": "^2.2",
"php-mock/php-mock-mockery": "^1.3",
"php-parallel-lint/php-parallel-lint": "^1.1",
"phpbench/phpbench": "^1.0",
"phpstan/extension-installer": "^1.1",
"phpstan/phpstan": "^1.8",
"phpstan/phpstan-mockery": "^1.1",
"phpstan/phpstan-phpunit": "^1.1",
"phpunit/phpunit": "^8.5 || ^9",
"ramsey/composer-repl": "^1.4",
"slevomat/coding-standard": "^8.4",
"squizlabs/php_codesniffer": "^3.5",
"vimeo/psalm": "^4.9"
},
"suggest": {
"ext-bcmath": "Enables faster math with arbitrary-precision integers using BCMath.",
"ext-gmp": "Enables faster math with arbitrary-precision integers using GMP.",
"ext-uuid": "Enables the use of PeclUuidTimeGenerator and PeclUuidRandomGenerator.",
"paragonie/random-lib": "Provides RandomLib for use with the RandomLibAdapter",
"ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type."
},
"type": "library",
"extra": {
"captainhook": {
"force-install": true
}
},
"autoload": {
"files": [
"src/functions.php"
],
"psr-4": {
"Ramsey\\Uuid\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "A PHP library for generating and working with universally unique identifiers (UUIDs).",
"keywords": [
"guid",
"identifier",
"uuid"
],
"support": {
"issues": "https://github.com/ramsey/uuid/issues",
"source": "https://github.com/ramsey/uuid/tree/4.7.6"
},
"funding": [
{
"url": "https://github.com/ramsey",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/ramsey/uuid",
"type": "tidelift"
}
],
"time": "2024-04-27T21:32:50+00:00"
},
{
"name": "sentry/sentry",
@@ -2507,59 +2111,6 @@
],
"time": "2025-02-13T21:09:57+00:00"
},
{
"name": "yiisoft/yii2-jui",
"version": "2.0.7",
"source": {
"type": "git",
"url": "https://github.com/yiisoft/yii2-jui.git",
"reference": "ce45c16d4fbbe7d1c516d8d0e8311e07f6138eed"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/yiisoft/yii2-jui/zipball/ce45c16d4fbbe7d1c516d8d0e8311e07f6138eed",
"reference": "ce45c16d4fbbe7d1c516d8d0e8311e07f6138eed",
"shasum": ""
},
"require": {
"bower-asset/jquery-ui": "~1.12.1",
"yiisoft/yii2": "~2.0.4"
},
"type": "yii2-extension",
"extra": {
"branch-alias": {
"dev-master": "2.0.x-dev"
}
},
"autoload": {
"psr-4": {
"yii\\jui\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Qiang Xue",
"email": "qiang.xue@gmail.com"
}
],
"description": "The Jquery UI extension for the Yii framework",
"keywords": [
"jQuery UI",
"yii2"
],
"support": {
"forum": "http://www.yiiframework.com/forum/",
"irc": "irc://irc.freenode.net/yii",
"issues": "https://github.com/yiisoft/yii2-jui/issues",
"source": "https://github.com/yiisoft/yii2-jui",
"wiki": "http://www.yiiframework.com/wiki/"
},
"time": "2017-11-25T15:32:29+00:00"
},
{
"name": "yiisoft/yii2-queue",
"version": "2.3.7",
@@ -3337,16 +2888,16 @@
},
{
"name": "deployer/deployer",
"version": "v7.5.11",
"version": "v7.5.12",
"source": {
"type": "git",
"url": "https://github.com/deployphp/deployer.git",
"reference": "448761221383877d61bed6700805001f872c0ad3"
"reference": "efc71dac9ccc86b3f9946e75d50cb106b775aae2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/deployphp/deployer/zipball/448761221383877d61bed6700805001f872c0ad3",
"reference": "448761221383877d61bed6700805001f872c0ad3",
"url": "https://api.github.com/repos/deployphp/deployer/zipball/efc71dac9ccc86b3f9946e75d50cb106b775aae2",
"reference": "efc71dac9ccc86b3f9946e75d50cb106b775aae2",
"shasum": ""
},
"require": {
@@ -3387,7 +2938,7 @@
"type": "github"
}
],
"time": "2025-02-10T14:27:00+00:00"
"time": "2025-02-19T16:45:27+00:00"
},
{
"name": "doctrine/instantiator",
@@ -6903,7 +6454,6 @@
"aliases": [],
"minimum-stability": "stable",
"stability-flags": {
"kartik-v/yii2-grid": 20,
"yiisoft/yii2-bootstrap5": 20
},
"prefer-stable": false,

View File

@@ -15,12 +15,14 @@ class m250219_133939_create_meal_table extends Migration
$this->createTable('{{%meal}}', [
'id' => $this->primaryKey(),
'file_name' => $this->string()->notNull(),
'food_name' => $this->string()->notNull(),
'calories' => $this->integer()->notNull(),
'protein' => $this->integer()->notNull(),
'fat' => $this->integer()->notNull(),
'carbohydrates' => $this->integer()->notNull(),
'fiber' => $this->integer()->notNull(),
'meal' => $this->integer()->notNull(),
'user_id' => $this->integer()->notNull(),
'created_at' => $this->integer()->notNull(),
'updated_at' => $this->integer()->notNull(),
]);

View File

@@ -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 = [
];

View File

@@ -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

View File

@@ -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());

View 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;
}
}
}

View File

@@ -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']),
]
]));

View File

@@ -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[] = [

View 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>

View 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>

View File

@@ -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>