339 lines
8.9 KiB
PHP
339 lines
8.9 KiB
PHP
<?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);
|
|
}
|
|
|
|
}
|