This commit is contained in:
TiclemFR
2023-12-29 16:00:02 +01:00
parent 9d79d7c0c6
commit 884eb3011a
8361 changed files with 1160554 additions and 4 deletions

View File

@@ -0,0 +1,48 @@
<?php
namespace Spatie\Ignition\Solutions\OpenAi;
use Psr\SimpleCache\CacheInterface;
class DummyCache implements CacheInterface
{
public function get(string $key, mixed $default = null): mixed
{
return null;
}
public function set(string $key, mixed $value, \DateInterval|int|null $ttl = null): bool
{
return true;
}
public function delete(string $key): bool
{
return true;
}
public function clear(): bool
{
return true;
}
public function getMultiple(iterable $keys, mixed $default = null): iterable
{
return [];
}
public function setMultiple(iterable $values, \DateInterval|int|null $ttl = null): bool
{
return true;
}
public function deleteMultiple(iterable $keys): bool
{
return true;
}
public function has(string $key): bool
{
return false;
}
}

View File

@@ -0,0 +1,46 @@
<?php
namespace Spatie\Ignition\Solutions\OpenAi;
class OpenAiPromptViewModel
{
public function __construct(
protected string $file,
protected string $exceptionMessage,
protected string $exceptionClass,
protected string $snippet,
protected string $line,
protected string|null $applicationType = null,
) {
}
public function file(): string
{
return $this->file;
}
public function line(): string
{
return $this->line;
}
public function snippet(): string
{
return $this->snippet;
}
public function exceptionMessage(): string
{
return $this->exceptionMessage;
}
public function exceptionClass(): string
{
return $this->exceptionClass;
}
public function applicationType(): string|null
{
return $this->applicationType;
}
}

View File

@@ -0,0 +1,115 @@
<?php
namespace Spatie\Ignition\Solutions\OpenAi;
use OpenAI;
use Psr\SimpleCache\CacheInterface;
use Spatie\Backtrace\Backtrace;
use Spatie\Backtrace\Frame;
use Spatie\Ignition\Contracts\Solution;
use Spatie\Ignition\ErrorPage\Renderer;
use Spatie\Ignition\Ignition;
use Throwable;
class OpenAiSolution implements Solution
{
public bool $aiGenerated = true;
protected string $prompt;
protected OpenAiSolutionResponse $openAiSolutionResponse;
public function __construct(
protected Throwable $throwable,
protected string $openAiKey,
protected CacheInterface|null $cache = null,
protected int|null $cacheTtlInSeconds = 60,
protected string|null $applicationType = null,
protected string|null $applicationPath = null,
) {
$this->prompt = $this->generatePrompt();
$this->openAiSolutionResponse = $this->getAiSolution();
}
public function getSolutionTitle(): string
{
return 'AI Generated Solution';
}
public function getSolutionDescription(): string
{
return $this->openAiSolutionResponse->description();
}
public function getDocumentationLinks(): array
{
return $this->openAiSolutionResponse->links();
}
public function getAiSolution(): ?OpenAiSolutionResponse
{
$solution = $this->cache->get($this->getCacheKey());
if ($solution) {
return new OpenAiSolutionResponse($solution);
}
$solutionText = OpenAI::client($this->openAiKey)
->chat()
->create([
'model' => $this->getModel(),
'messages' => [['role' => 'user', 'content' => $this->prompt]],
'max_tokens' => 1000,
'temperature' => 0,
])->choices[0]->message->content;
$this->cache->set($this->getCacheKey(), $solutionText, $this->cacheTtlInSeconds);
return new OpenAiSolutionResponse($solutionText);
}
protected function getCacheKey(): string
{
$hash = sha1($this->prompt);
return "ignition-solution-{$hash}";
}
protected function generatePrompt(): string
{
$viewPath = Ignition::viewPath('aiPrompt');
$viewModel = new OpenAiPromptViewModel(
file: $this->throwable->getFile(),
exceptionMessage: $this->throwable->getMessage(),
exceptionClass: get_class($this->throwable),
snippet: $this->getApplicationFrame($this->throwable)->getSnippetAsString(15),
line: $this->throwable->getLine(),
applicationType: $this->applicationType,
);
return (new Renderer())->renderAsString(
['viewModel' => $viewModel],
$viewPath,
);
}
protected function getModel(): string
{
return 'gpt-3.5-turbo';
}
protected function getApplicationFrame(Throwable $throwable): ?Frame
{
$backtrace = Backtrace::createForThrowable($throwable);
if ($this->applicationPath) {
$backtrace->applicationPath($this->applicationPath);
}
$frames = $backtrace->frames();
return $frames[$backtrace->firstApplicationFrameIndex()] ?? null;
}
}

View File

@@ -0,0 +1,62 @@
<?php
namespace Spatie\Ignition\Solutions\OpenAi;
use Psr\SimpleCache\CacheInterface;
use Spatie\Ignition\Contracts\HasSolutionsForThrowable;
use Throwable;
class OpenAiSolutionProvider implements HasSolutionsForThrowable
{
public function __construct(
protected string $openAiKey,
protected ?CacheInterface $cache = null,
protected int $cacheTtlInSeconds = 60 * 60,
protected string|null $applicationType = null,
protected string|null $applicationPath = null,
) {
$this->cache ??= new DummyCache();
}
public function canSolve(Throwable $throwable): bool
{
return true;
}
public function getSolutions(Throwable $throwable): array
{
return [
new OpenAiSolution(
$throwable,
$this->openAiKey,
$this->cache,
$this->cacheTtlInSeconds,
$this->applicationType,
$this->applicationPath,
),
];
}
public function applicationType(string $applicationType): self
{
$this->applicationType = $applicationType;
return $this;
}
public function applicationPath(string $applicationPath): self
{
$this->applicationPath = $applicationPath;
return $this;
}
public function useCache(CacheInterface $cache, int $cacheTtlInSeconds = 60 * 60): self
{
$this->cache = $cache;
$this->cacheTtlInSeconds = $cacheTtlInSeconds;
return $this;
}
}

View File

@@ -0,0 +1,58 @@
<?php
namespace Spatie\Ignition\Solutions\OpenAi;
class OpenAiSolutionResponse
{
protected string $rawText;
public function __construct(string $rawText)
{
$this->rawText = trim($rawText);
}
public function description(): string
{
return $this->between('FIX', 'ENDFIX', $this->rawText);
}
public function links(): array
{
$textLinks = $this->between('LINKS', 'ENDLINKS', $this->rawText);
$textLinks = explode(PHP_EOL, $textLinks);
$textLinks = array_map(function ($textLink) {
$textLink = str_replace('\\', '\\\\', $textLink);
$textLink = str_replace('\\\\\\', '\\\\', $textLink);
return json_decode($textLink, true);
}, $textLinks);
array_filter($textLinks);
$links = [];
foreach ($textLinks as $textLink) {
$links[$textLink['title']] = $textLink['url'];
}
return $links;
}
protected function between(string $start, string $end, string $text): string
{
$startPosition = strpos($text, $start);
if ($startPosition === false) {
return "";
}
$startPosition += strlen($start);
$endPosition = strpos($text, $end, $startPosition);
if ($endPosition === false) {
return "";
}
return trim(substr($text, $startPosition, $endPosition - $startPosition));
}
}