feat(AI): add ai manager admin and create powerpoint with ai

This commit is contained in:
2026-07-22 08:06:48 +03:30
parent ae974c2afa
commit 4a3c109bf3
23 changed files with 743 additions and 23 deletions

View File

@@ -9,7 +9,11 @@ use PhpOffice\PhpPresentation\IOFactory;
use PhpOffice\PhpPresentation\PhpPresentation;
use PhpOffice\PhpPresentation\Shape\Drawing\File as FileDrawing;
use PhpOffice\PhpPresentation\Shape\Drawing\Gd as GdDrawing;
use PhpOffice\PhpPresentation\Shape\Group as GroupShape;
use PhpOffice\PhpPresentation\Shape\RichText;
use PhpOffice\PhpPresentation\Shape\RichText\BreakElement;
use PhpOffice\PhpPresentation\Shape\RichText\Paragraph;
use PhpOffice\PhpPresentation\Shape\Table as TableShape;
use RuntimeException;
class TemplateFillService
@@ -23,18 +27,26 @@ class TemplateFillService
$values = $generation->values ?? [];
$textValues = [];
foreach ($template->keys as $key) {
if (! array_key_exists($key->key, $values)) {
continue;
}
match ($key->type) {
TemplateKeyType::Text => $this->fillText($presentation, $key->key, (string) $values[$key->key]),
TemplateKeyType::Text => $textValues[$key->key] = (string) $values[$key->key],
TemplateKeyType::Image => $this->fillImage($presentation, $key->shape_name, Storage::disk('local')->path($values[$key->key])),
TemplateKeyType::Chart => throw new RuntimeException('پرکردن کلید از نوع چارت هنوز پشتیبانی نمی‌شود.'),
};
}
if ($textValues !== []) {
foreach ($presentation->getAllSlides() as $slide) {
$this->replaceInShapes($slide->getShapeCollection(), $textValues);
}
}
$outputRelativePath = 'templates/generations/'.$generation->uuid.'.pptx';
$outputAbsolutePath = Storage::disk('local')->path($outputRelativePath);
@@ -46,27 +58,103 @@ class TemplateFillService
return $outputRelativePath;
}
private function fillText(PhpPresentation $presentation, string $key, string $value): void
/**
* @param iterable<mixed> $shapes
* @param array<string, string> $textValues
*/
private function replaceInShapes(iterable $shapes, array $textValues): void
{
$placeholder = '{{'.$key.'}}';
foreach ($presentation->getAllSlides() as $slide) {
foreach ($slide->getShapeCollection() as $shape) {
if (! $shape instanceof RichText) {
continue;
foreach ($shapes as $shape) {
if ($shape instanceof RichText) {
foreach ($shape->getParagraphs() as $paragraph) {
$this->replaceInParagraph($paragraph, $textValues);
}
foreach ($shape->getParagraphs() as $paragraph) {
foreach ($paragraph->getRichTextElements() as $element) {
if (str_contains($element->getText(), $placeholder)) {
$element->setText(str_replace($placeholder, $value, $element->getText()));
continue;
}
if ($shape instanceof TableShape) {
foreach ($shape->getRows() as $row) {
foreach ($row->getCells() as $cell) {
foreach ($cell->getParagraphs() as $paragraph) {
$this->replaceInParagraph($paragraph, $textValues);
}
}
}
continue;
}
if ($shape instanceof GroupShape) {
$this->replaceInShapes($shape->getShapeCollection(), $textValues);
}
}
}
/**
* @param array<string, string> $textValues
*/
private function replaceInParagraph(Paragraph $paragraph, array $textValues): void
{
$elements = $paragraph->getRichTextElements();
if ($elements === []) {
return;
}
$originalText = '';
foreach ($elements as $element) {
$originalText .= $element->getText();
}
if (! str_contains($originalText, '{')) {
return;
}
$lookup = [];
foreach ($textValues as $key => $value) {
$lookup[$this->normalizeKey($key)] = $value;
}
$newText = preg_replace_callback(
'/\{\{?\s*([^{}]+?)\s*\}?\}/u',
function (array $matches) use ($lookup): string {
$normalizedKey = $this->normalizeKey($matches[1]);
return array_key_exists($normalizedKey, $lookup) ? $lookup[$normalizedKey] : $matches[0];
},
$originalText
);
if ($newText === null || $newText === $originalText) {
return;
}
$target = null;
foreach ($elements as $element) {
if ($element instanceof BreakElement) {
continue;
}
if ($target === null) {
$target = $element;
$element->setText($newText);
continue;
}
$element->setText('');
}
}
private function normalizeKey(string $key): string
{
return mb_strtolower(trim($key, "{} \t\n\r\0\x0B"));
}
private function fillImage(PhpPresentation $presentation, ?string $shapeName, string $imagePath): void
{
if ($shapeName === null || $shapeName === '') {
@@ -74,16 +162,30 @@ class TemplateFillService
}
foreach ($presentation->getAllSlides() as $slide) {
foreach ($slide->getShapeCollection() as $shape) {
if ($shape->getName() !== $shapeName) {
continue;
}
$this->replaceImageInShapes($slide->getShapeCollection(), $shapeName, $imagePath);
}
}
if ($shape instanceof GdDrawing) {
$this->replaceGdImage($shape, $imagePath);
} elseif ($shape instanceof FileDrawing) {
$shape->setPath($imagePath);
}
/**
* @param iterable<mixed> $shapes
*/
private function replaceImageInShapes(iterable $shapes, string $shapeName, string $imagePath): void
{
foreach ($shapes as $shape) {
if ($shape instanceof GroupShape) {
$this->replaceImageInShapes($shape->getShapeCollection(), $shapeName, $imagePath);
continue;
}
if ($shape->getName() !== $shapeName) {
continue;
}
if ($shape instanceof GdDrawing) {
$this->replaceGdImage($shape, $imagePath);
} elseif ($shape instanceof FileDrawing) {
$shape->setPath($imagePath);
}
}
}