Premier commit
This commit is contained in:
43
modules/site/Module.php
Normal file
43
modules/site/Module.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
namespace app\modules\site;
|
||||
|
||||
use Piko;
|
||||
use Piko\Module\Event\CreateControllerEvent;
|
||||
use Piko\Controller\Event\BeforeActionEvent;
|
||||
|
||||
class Module extends \Piko\Module
|
||||
{
|
||||
public function bootstrap()
|
||||
{
|
||||
Piko::setAlias('@vite_web', '/dev');
|
||||
|
||||
// Instanciate once i18n to setup the language config
|
||||
$this->application->getComponent('Piko\I18n');
|
||||
|
||||
$user = $this->application->getComponent('Piko\User');
|
||||
assert($user instanceof \Piko\User);
|
||||
|
||||
// Pass some parameters to the View component
|
||||
$view = $this->application->getComponent('Piko\View');
|
||||
$view->params['user'] = $user;
|
||||
$view->params['language'] = $this->application->language;
|
||||
$view->attachBehavior('vite', 'app\lib\Vite::vite');
|
||||
|
||||
$userModule = $this->application->getModule('user');
|
||||
assert ($userModule instanceof \app\modules\user\Module);
|
||||
|
||||
$userModule->on(CreateControllerEvent::class, function(CreateControllerEvent $event) {
|
||||
$event->controller->on(BeforeActionEvent::class, function (BeforeActionEvent $event) {
|
||||
|
||||
$action = $event->actionId;
|
||||
|
||||
switch($action) {
|
||||
case 'login':
|
||||
$event->controller->layout = 'minimal';
|
||||
break;
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
338
modules/site/controllers/AssistantController.php
Normal file
338
modules/site/controllers/AssistantController.php
Normal file
@@ -0,0 +1,338 @@
|
||||
<?php
|
||||
namespace app\modules\site\controllers;
|
||||
|
||||
use Piko\User;
|
||||
use Piko\HttpException;
|
||||
use Monolog\Logger;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Psr7\Utils;
|
||||
use GuzzleHttp\Exception\RequestException;
|
||||
|
||||
use app\modules\site\models\Assistant;
|
||||
use PDO;
|
||||
|
||||
class AssistantController extends \Piko\Controller
|
||||
{
|
||||
protected PDO $db;
|
||||
|
||||
protected User $user;
|
||||
|
||||
/**
|
||||
* @var Logger
|
||||
*/
|
||||
private Logger $log;
|
||||
|
||||
protected function init(): void
|
||||
{
|
||||
$app = $this->module->getApplication();
|
||||
|
||||
$this->log = $app->getComponent(Logger::class);
|
||||
assert($this->log instanceof Logger);
|
||||
|
||||
$this->user = $app->getComponent('Piko\User');
|
||||
assert($this->user instanceof User);
|
||||
|
||||
$this->db = $app->getComponent('PDO');
|
||||
assert($this->db instanceof PDO);
|
||||
}
|
||||
|
||||
protected function getUserApiKey()
|
||||
{
|
||||
$identity = $this->user->getIdentity();
|
||||
$apiKey = $identity->profil['api_key'] ?? null;
|
||||
|
||||
if ($apiKey === null) {
|
||||
$this->log->error('API key not defined for user ID :' . $this->user->getId());
|
||||
throw new HttpException(500, 'Internal error');
|
||||
}
|
||||
|
||||
return $apiKey;
|
||||
}
|
||||
|
||||
private function proxyRequest(string $method, string $endPoint, array $params = []): array
|
||||
{
|
||||
$client = new Client([
|
||||
'base_uri' => getenv('PROXY_BASE_URL'),
|
||||
]);
|
||||
|
||||
/*
|
||||
$identity = $this->user->getIdentity();
|
||||
$apiKey = $identity->profil['api_key'] ?? null;
|
||||
|
||||
if ($apiKey === null) {
|
||||
$this->log->error('API key not defined for user ID :' . $this->user->getId());
|
||||
throw new HttpException(500, 'Internal error');
|
||||
}
|
||||
*/
|
||||
|
||||
$apiKey = getenv('PROXY_MASTER_KEY');
|
||||
|
||||
$headers = [
|
||||
'Authorization' => 'Bearer ' . $apiKey,
|
||||
'Content-Type' => 'application/json',
|
||||
];
|
||||
|
||||
try {
|
||||
$response = $client->request($method, $endPoint, [
|
||||
'headers' => $headers,
|
||||
'json' => $params
|
||||
]);
|
||||
} catch (RequestException $e) {
|
||||
$response = $e->getResponse();
|
||||
$responseBody = (string) $response->getBody();
|
||||
$contentType = $response->getHeader('Content-Type');
|
||||
$errorCode = 500;
|
||||
|
||||
if ($contentType[0] == 'application/json') {
|
||||
$data = json_decode($responseBody);
|
||||
|
||||
if (isset($data->error)) {
|
||||
$responseBody = $data->error->message;
|
||||
$errorCode = $data->error->code;
|
||||
}
|
||||
}
|
||||
|
||||
$this->log->error('Chat request error', ['request_body' => (string) $e->getRequest()->getBody()]);
|
||||
$this->log->error('Chat response error', ['response_body' => $responseBody]);
|
||||
throw new HttpException($errorCode, $responseBody);
|
||||
}
|
||||
|
||||
$body = $response->getBody();
|
||||
|
||||
return [
|
||||
'headers' => $response->getHeaders(),
|
||||
'body' => (string) $body
|
||||
];
|
||||
}
|
||||
|
||||
private function getModels()
|
||||
{
|
||||
if (!isset($_SESSION['models'])) {
|
||||
|
||||
$models = [];
|
||||
$response = $this->proxyRequest('GET', '/models');
|
||||
|
||||
if (isset($response['body'])) {
|
||||
$body = json_decode($response['body'], true);
|
||||
$data = $body['data'] ?? [];
|
||||
|
||||
foreach ($data as $obj) {
|
||||
if (isset($obj['id'])) {
|
||||
$models[] = $obj['id'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$_SESSION['models'] = $models;
|
||||
}
|
||||
|
||||
return $_SESSION['models'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream de la réponse
|
||||
*
|
||||
* Pour désactiver l'output buffering:
|
||||
* php -d output_buffering=0 -S localhost:8080 -t web
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function responseAction()
|
||||
{
|
||||
$identity = $this->user->getIdentity();
|
||||
|
||||
$client = new Client([
|
||||
'base_uri' => getenv('PROXY_BASE_URL'),
|
||||
'stream' => true,
|
||||
]);
|
||||
|
||||
$headers = [
|
||||
'Authorization' => 'Bearer ' . getenv('PROXY_KEY'),
|
||||
'Content-Type' => 'application/json',
|
||||
];
|
||||
|
||||
$data = json_decode((string) $this->request->getBody());
|
||||
|
||||
// bdump($data);
|
||||
|
||||
try {
|
||||
$response = $client->request('POST', '/chat/completions', [
|
||||
'headers' => $headers,
|
||||
'stream' => true,
|
||||
'json' => [
|
||||
'model' => $data->model,
|
||||
'messages' => $data->messages,
|
||||
'stream' => true,
|
||||
'temperature' => $data->temperature ?? 0,
|
||||
'top_p' => $config->top_p ?? 0,
|
||||
'user' => $identity->email
|
||||
]
|
||||
]);
|
||||
} catch (RequestException $e) {
|
||||
|
||||
header("Content-Type: text/event-stream;charset=UTF-8");
|
||||
$responseBody = (string) $e->getResponse()->getBody();
|
||||
echo 'data: ' . str_replace("\n", '', $responseBody) . "\n\n";
|
||||
$this->log->error('Chat request error', ['request_body' => (string) $e->getRequest()->getBody()]);
|
||||
$this->log->error('Chat response error', ['response_body' => $responseBody]);
|
||||
exit;
|
||||
}
|
||||
|
||||
$body = $response->getBody();
|
||||
$contentType = $response->getHeader('Content-Type');
|
||||
|
||||
if (count($contentType)) {
|
||||
header("Content-Type:{$contentType[0]}");
|
||||
}
|
||||
|
||||
$content = '';
|
||||
|
||||
while (!$body->eof()) {
|
||||
$line = Utils::readLine($body);
|
||||
|
||||
if ($line != '[DONE]') {
|
||||
$data = preg_replace('/^data:/', '', $line);
|
||||
$data = json_decode($data, true);
|
||||
|
||||
if (isset($data['choices'][0]['delta']['content'])) {
|
||||
$content .= $data['choices'][0]['delta']['content'];
|
||||
}
|
||||
}
|
||||
|
||||
echo $line;
|
||||
flush();
|
||||
}
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
public function indexAction()
|
||||
{
|
||||
// $assistants = Assistant::find($this->db, $this->user->getId(), '`title` ASC');
|
||||
// $assistant = array_pop($assistants);
|
||||
// $apiKey = $this->getUserApiKey();
|
||||
// $assistant = Assistant::getDefaultUserAssistant($this->db, $this->user->getId());
|
||||
|
||||
return $this->render('index', [
|
||||
// 'assistant' => $assistant,
|
||||
'models' => $this->getModels(),
|
||||
// 'apiKey' => $apiKey,
|
||||
]);
|
||||
}
|
||||
|
||||
public function listAction()
|
||||
{
|
||||
$assistants = Assistant::find($this->db, $this->user->getId(), '`title` ASC');
|
||||
|
||||
return $this->jsonResponse($assistants);
|
||||
}
|
||||
|
||||
public function assistantsAction()
|
||||
{
|
||||
return $this->render('assistants', [
|
||||
'assistants' => Assistant::find($this->db, $this->user->getId(), '`title` ASC')
|
||||
]);
|
||||
}
|
||||
|
||||
public function editAction($id = 0)
|
||||
{
|
||||
$model = new Assistant($this->db);
|
||||
|
||||
if ($id) {
|
||||
$model->load($id);
|
||||
}
|
||||
|
||||
$response = [];
|
||||
|
||||
$post = $this->request->getParsedBody();
|
||||
|
||||
if (!empty($post)) {
|
||||
|
||||
$model->bind($post);
|
||||
$model->user_id = $this->user->getId();
|
||||
|
||||
try {
|
||||
|
||||
|
||||
|
||||
if ($model->isValid() && $model->save()) {
|
||||
$response['status'] = 'success';
|
||||
} else {
|
||||
$response['status'] = 'error';
|
||||
$response['error'] = array_pop($model->getErrors());
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
exit ($e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
$response['model'] = $model->toArray();
|
||||
|
||||
return $this->jsonResponse($response);
|
||||
}
|
||||
|
||||
public function saveAction()
|
||||
{
|
||||
$model = new Assistant($this->db);
|
||||
|
||||
$data = json_decode((string) $this->request->getBody(), true);
|
||||
|
||||
if (isset($data['id'])) {
|
||||
$model->load($data['id']);
|
||||
unset($data['id']);
|
||||
}
|
||||
|
||||
$response = [];
|
||||
|
||||
$model->bind($data);
|
||||
$model->user_id = $this->user->getId();
|
||||
|
||||
try {
|
||||
if ($model->isValid() && $model->save()) {
|
||||
$response['status'] = 'success';
|
||||
} else {
|
||||
$response['status'] = 'error';
|
||||
$response['errors'] = $model->getErrors();
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
exit ($e->getMessage());
|
||||
}
|
||||
|
||||
$response['assistant'] = $model->toArray();
|
||||
|
||||
return $this->jsonResponse($response);
|
||||
}
|
||||
|
||||
public function setAsDefaultAction($id = 0)
|
||||
{
|
||||
$model = new Assistant($this->db);
|
||||
|
||||
if ($id) {
|
||||
$model->load($id);
|
||||
$model->default = 1;
|
||||
$model->save();
|
||||
|
||||
return $this->jsonResponse(true);
|
||||
}
|
||||
|
||||
return $this->jsonResponse(false);
|
||||
}
|
||||
|
||||
public function deleteAction($id = 0)
|
||||
{
|
||||
$model = new Assistant($this->db);
|
||||
|
||||
if ($id) {
|
||||
$model->load($id);
|
||||
|
||||
if ($model->delete()) {
|
||||
return $this->jsonResponse(true);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->jsonResponse(false);
|
||||
}
|
||||
|
||||
}
|
||||
14
modules/site/controllers/DefaultController.php
Normal file
14
modules/site/controllers/DefaultController.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
namespace app\modules\site\controllers;
|
||||
|
||||
use Throwable;
|
||||
|
||||
class DefaultController extends \Piko\Controller
|
||||
{
|
||||
public function errorAction(Throwable $exception)
|
||||
{
|
||||
return $this->render('error', [
|
||||
'exception' => $exception
|
||||
]);
|
||||
}
|
||||
}
|
||||
78
modules/site/layouts/main.php
Normal file
78
modules/site/layouts/main.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
/* @var $this \Piko\View */
|
||||
/* @var $content string */
|
||||
|
||||
$user = $this->params['user'];
|
||||
assert($user instanceof Piko\User);
|
||||
|
||||
if (!$this->title) $this->title = 'Openai';
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="<?= $this->params['language'] ?>">
|
||||
<head>
|
||||
<meta charset="<?= $this->charset ?>">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title><?= $this->escape($this->title) ?></title>
|
||||
<?= $this->head() ?>
|
||||
<?= $this->vite('main.css') ?>
|
||||
</head>
|
||||
<body>
|
||||
<nav class="offcanvas offcanvas-start" tabindex="-1" id="offcanvasNavbar" aria-labelledby="offcanvasNavbarLabel">
|
||||
<div class="offcanvas-header">
|
||||
<h5 class="offcanvas-title" id="offcanvasNavbarLabel">Openai</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="offcanvas" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="offcanvas-body">
|
||||
|
||||
<ul class="navbar-nav justify-content-end flex-grow-1 pe-3">
|
||||
<li class="nav-item"><a class="nav-link active" aria-current="page" href="/">Assistant</a></li>
|
||||
<li class="nav-item"><a class="nav-link" href="<?= $this->getUrl('site/ia/assistants') ?>">Gérer les assistants</a></li>
|
||||
|
||||
<?php /*if ($user->can('access.completion')): ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="<?= $this->getUrl('openai/v1/completions') ?>">Completions</a>
|
||||
</li>
|
||||
<?php endif */ ?>
|
||||
|
||||
<?php /* if ($user->can('access.edit')): ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="<?= $this->getUrl('openai/v1/edits') ?>">Edits</a>
|
||||
</li>
|
||||
<?php endif */ ?>
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="<?= $this->getUrl('user/default/edit') ?>">Compte</a>
|
||||
</li>
|
||||
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="<?= $this->getUrl('user/default/logout') ?>">Déconnexion</a>
|
||||
</li>
|
||||
|
||||
<?php if ($user->can('admin')): ?>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="<?= $this->getUrl('user/admin/users') ?>">Gestion utilisateurs</a>
|
||||
</li>
|
||||
<?php endif ?>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<button type="button" id="navBtn" class="hamburger is-closed" data-bs-toggle="offcanvas" data-bs-target="#offcanvasNavbar" aria-controls="offcanvasNavbar" >
|
||||
<span class="hamb-top"></span>
|
||||
<span class="hamb-middle"></span>
|
||||
<span class="hamb-bottom"></span>
|
||||
</button>
|
||||
|
||||
|
||||
<?php if (isset($this->params['message']) && is_array($this->params['message'])): ?>
|
||||
<div class="container alert alert-<?= $this->params['message']['type'] ?> alert-dismissible fade show" role="alert">
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
<?= $this->params['message']['content'] ?>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
<?= $content ?>
|
||||
|
||||
<?= $this->endBody() ?>
|
||||
<?= $this->vite('main.js') ?>
|
||||
</body>
|
||||
</html>
|
||||
23
modules/site/layouts/minimal.php
Normal file
23
modules/site/layouts/minimal.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
/* @var $this \Piko\View */
|
||||
/* @var $content string */
|
||||
|
||||
if (!$this->title) $this->title = 'Openai';
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="<?= $this->params['language'] ?>">
|
||||
<head>
|
||||
<meta charset="<?= $this->charset ?>">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title><?= $this->escape($this->title) ?></title>
|
||||
<?= $this->head() ?>
|
||||
<?= $this->vite('main.css') ?>
|
||||
</head>
|
||||
<body class="d-flex align-items-center py-4">
|
||||
|
||||
<?= $content ?>
|
||||
|
||||
<?= $this->endBody() ?>
|
||||
<?= $this->vite('main.js') ?>
|
||||
</body>
|
||||
</html>
|
||||
145
modules/site/models/Assistant.php
Normal file
145
modules/site/models/Assistant.php
Normal file
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
namespace app\modules\site\models;
|
||||
|
||||
/**
|
||||
* This is the model class for table "chat_assistant".
|
||||
*
|
||||
* @property integer $id
|
||||
* @property integer $user_id
|
||||
* @property string $title;
|
||||
* @property string $model;
|
||||
* @property string $system_prompt;
|
||||
* @property float $temperature;
|
||||
* @property float $top_p;
|
||||
* @property integer $default
|
||||
*
|
||||
* @author Sylvain PHILIP <contact@sphilip.com>
|
||||
*/
|
||||
class Assistant extends \Piko\DbRecord
|
||||
{
|
||||
/**
|
||||
* The table name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $tableName = 'chat_assistant';
|
||||
|
||||
/**
|
||||
* The role permissions
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $permissions = [];
|
||||
|
||||
/**
|
||||
* The table schema
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $schema = [
|
||||
'id' => self::TYPE_INT,
|
||||
'user_id' => self::TYPE_INT,
|
||||
'title' => self::TYPE_STRING,
|
||||
'model' => self::TYPE_STRING,
|
||||
'system_prompt' => self::TYPE_STRING,
|
||||
'temperature' => self::TYPE_STRING,
|
||||
'top_p' => self::TYPE_STRING,
|
||||
'default' => self::TYPE_INT,
|
||||
];
|
||||
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @see \Piko\ModelTrait::validate()
|
||||
*/
|
||||
protected function validate(): void
|
||||
{
|
||||
if (empty($this->user_id)) {
|
||||
$this->errors['user_id'] = 'L\'id de l\'utilisateur est obligatoire.';
|
||||
}
|
||||
|
||||
if (empty($this->title)) {
|
||||
$this->errors['title'] = 'Le titre doit être renseigné.';
|
||||
}
|
||||
|
||||
if (empty($this->model)) {
|
||||
$this->errors['model'] = 'Le modèle doit être renseigné.';
|
||||
}
|
||||
|
||||
if (empty($this->system_prompt)) {
|
||||
$this->errors['system_prompt'] = 'Le prompt système doit être renseigné.';
|
||||
}
|
||||
|
||||
if (!empty($this->temperature) && $this->temperature < 0 || $this->temperature > 2) {
|
||||
$this->errors['temperature'] = 'Le température doit être comprise entre 0 et 2.';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected function beforeSave(bool $insert): bool
|
||||
{
|
||||
if ($this->default == 1) {
|
||||
|
||||
$this->db->beginTransaction();
|
||||
|
||||
try {
|
||||
// Reset default for all other assistants of the same user
|
||||
$sth = $this->db->prepare('UPDATE chat_assistant SET `default` = 0 WHERE user_id = :user_id');
|
||||
$sth->execute(['user_id' => $this->user_id]);
|
||||
$this->db->commit();
|
||||
} catch (\Exception $e) {
|
||||
$this->db->rollBack();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
return parent::beforeSave($insert);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get assistants
|
||||
*
|
||||
* @param \PDO $db A pdo connexion
|
||||
* @param string $order The order condition
|
||||
* @param number $start The offset start
|
||||
* @param number $limit The offset limit
|
||||
*
|
||||
* @return array An array of role rows
|
||||
*/
|
||||
public static function find(\PDO $db, $userId = 0, $order = '', $start = 0, $limit = 0)
|
||||
{
|
||||
$query = 'SELECT * FROM chat_assistant';
|
||||
|
||||
if ($userId) {
|
||||
$query .= ' WHERE user_id = :user_id';
|
||||
}
|
||||
|
||||
$query .= ' ORDER BY ' . (empty($order) ? '`id` DESC' : $order);
|
||||
|
||||
if (!empty($start)) {
|
||||
$query .= ' OFFSET ' . (int) $start;
|
||||
}
|
||||
|
||||
if (!empty($limit)) {
|
||||
$query .= ' LIMIT ' . (int) $limit;
|
||||
}
|
||||
|
||||
$sth = $db->prepare($query);
|
||||
|
||||
$sth->execute(['user_id' => $userId]);
|
||||
|
||||
return $sth->fetchAll(\PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
public static function getDefaultUserAssistant(\PDO $db, int $userId): ?self
|
||||
{
|
||||
$query = 'SELECT * FROM chat_assistant WHERE user_id = :user_id AND `default` = 1 LIMIT 1';
|
||||
$sth = $db->prepare($query);
|
||||
$sth->execute(['user_id' => $userId]);
|
||||
|
||||
return $sth->fetchObject(self::class) ?: null;
|
||||
}
|
||||
}
|
||||
28
modules/site/views/assistant/index.php
Normal file
28
modules/site/views/assistant/index.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
assert($this instanceof \Piko\View);
|
||||
|
||||
$this->title = 'Assistant chat';
|
||||
|
||||
$modelsJs = json_encode($models);
|
||||
$responseUrl = $this->getUrl('site/assistant/response');
|
||||
|
||||
$script = <<<JS
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
new ChatApp({
|
||||
target: document.getElementById('chat-app'),
|
||||
props : {
|
||||
proxyBaseUrl: '$responseUrl',
|
||||
model_list: $modelsJs
|
||||
}
|
||||
});
|
||||
})
|
||||
JS;
|
||||
|
||||
$this->registerJs($script);
|
||||
|
||||
?>
|
||||
|
||||
<div id="chat-app"></div>
|
||||
|
||||
|
||||
34
modules/site/views/default/error.php
Normal file
34
modules/site/views/default/error.php
Normal file
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
/* @var $this \piko\View */
|
||||
/* @var $exception \Exception */
|
||||
|
||||
$message = getenv('APP_ENV') === 'dev' ? $exception->getMessage() . ' (#' . $exception->getCode() . ')' : 'Not found';
|
||||
|
||||
$this->title = $message;
|
||||
|
||||
?>
|
||||
<div class="site-error">
|
||||
|
||||
<h1><?= $this->escape($this->title) ?></h1>
|
||||
|
||||
<div class="alert alert-danger">
|
||||
<?= nl2br($this->escape($message)) ?>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
The above error occurred while the Web server was processing your request.
|
||||
</p>
|
||||
<p>
|
||||
Please contact us if you think this is a server error. Thank you.
|
||||
</p>
|
||||
|
||||
<?php if (getenv('APP_DEBUG')): ?>
|
||||
<div class="card bg-light mb-3">
|
||||
<div class="card-header">Trace:</div>
|
||||
<div class="card-body">
|
||||
<?= nl2br($exception->getTraceAsString()) ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif ?>
|
||||
</div>
|
||||
Reference in New Issue
Block a user