-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Bernhard Schmitt
committed
Jan 23, 2024
1 parent
2b69141
commit 9e206e1
Showing
12 changed files
with
404 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Sitegeist\Chatterbox\Command; | ||
|
||
use Neos\Flow\Annotations as Flow; | ||
use Neos\Flow\Cli\CommandController; | ||
use Neos\Flow\Utility\Environment; | ||
use OpenAI\Contracts\ClientContract as OpenAiClientContract; | ||
use Sitegeist\Chatterbox\Domain\Knowledge\KnowledgePool; | ||
use Sitegeist\Flow\OpenAiClientFactory\OpenAiClientFactory; | ||
|
||
#[Flow\Scope('singleton')] | ||
class KnowledgeCommandController extends CommandController | ||
{ | ||
private readonly OpenAiClientContract $client; | ||
|
||
public function __construct( | ||
private readonly KnowledgePool $knowledgePool, | ||
private readonly Environment $environment, | ||
OpenAiClientFactory $clientFactory | ||
) { | ||
$this->client = $clientFactory->createClient(); | ||
parent::__construct(); | ||
} | ||
|
||
public function updatePoolCommand(): void | ||
{ | ||
$this->outputLine('Updating knowledge pool'); | ||
$sources = $this->knowledgePool->findAllSources(); | ||
$this->output->progressStart(count($sources)); | ||
foreach ($sources as $sourceOfKnowledge) { | ||
$content = $sourceOfKnowledge->getContent(); | ||
|
||
$path = $this->environment->getPathToTemporaryDirectory() . '/' . $sourceOfKnowledge->getName() . '-' . time() . '.jsonl'; | ||
\file_put_contents($path, (string)$content); | ||
|
||
$this->client->files()->upload([ | ||
'file' => fopen($path, 'r'), | ||
'purpose' => 'assistants' | ||
]); | ||
\unlink($path); | ||
$this->output->progressAdvance(); | ||
} | ||
$this->output->progressFinish(); | ||
$this->outputLine(''); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
66 changes: 66 additions & 0 deletions
66
Classes/Domain/Knowledge/ContentRepositorySourceDesignator.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Sitegeist\Chatterbox\Domain\Knowledge; | ||
|
||
use Neos\ContentRepository\Domain\ContentSubgraph\NodePath; | ||
use Neos\ContentRepository\Domain\Model\Node; | ||
use Neos\ContentRepository\Domain\NodeAggregate\NodeAggregateIdentifier; | ||
use Neos\ContentRepository\Domain\Service\ContextFactory; | ||
use Neos\Flow\Annotations as Flow; | ||
use Neos\Neos\Domain\Service\ContentDimensionPresetSourceInterface; | ||
|
||
#[Flow\Proxy(false)] | ||
final class ContentRepositorySourceDesignator | ||
{ | ||
private function __construct( | ||
private readonly NodeAggregateIdentifier|NodePath $root, | ||
private readonly array $dimensionValues, | ||
) { | ||
} | ||
|
||
public static function createFromConfiguration(array $values): self | ||
{ | ||
$rootDesignator = $values['root']; | ||
if (\str_starts_with($rootDesignator, '#')) { | ||
return new self( | ||
NodeAggregateIdentifier::fromString(\mb_substr($rootDesignator, 1)), | ||
$values['dimensions'] | ||
); | ||
} elseif (\str_starts_with($rootDesignator, '/')) { | ||
return new self( | ||
NodePath::fromString($rootDesignator), | ||
$values['dimensions'] | ||
); | ||
} | ||
|
||
throw new \DomainException('ContentRepository source designators can only be instantiated from valid node aggregate ids or absolute node paths, "' . $rootDesignator . '" given.', 1705938983); | ||
} | ||
|
||
public function findRootNode(ContextFactory $contentContextFactory, ContentDimensionPresetSourceInterface $contentDimensionPresetSource): Node | ||
{ | ||
$contextDimensions = []; | ||
foreach ($contentDimensionPresetSource->getAllPresets() as $dimensionId => $presetConfig) { | ||
$contextDimensions[$dimensionId] = $presetConfig['presets'][$this->dimensionValues[$dimensionId]]['values']; | ||
} | ||
$subgraph = $contentContextFactory->create([ | ||
'dimensions' => $contextDimensions, | ||
'targetDimensions' => $this->dimensionValues, | ||
]); | ||
|
||
$rootNode = $this->root instanceof NodeAggregateIdentifier | ||
? $subgraph->getNodeByIdentifier((string)$this->root) | ||
: $subgraph->getNode((string)$this->root); | ||
|
||
if (!$rootNode instanceof Node) { | ||
$designatorType = $this->root instanceof NodeAggregateIdentifier | ||
? 'id' | ||
: 'path'; | ||
|
||
throw new \DomainException('Could not find root node with ' . $designatorType . ' "' . $this->root . '".', 1705939289); | ||
} | ||
|
||
return $rootNode; | ||
} | ||
} |
103 changes: 103 additions & 0 deletions
103
Classes/Domain/Knowledge/ContentRepositorySourceOfKnowledge.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Sitegeist\Chatterbox\Domain\Knowledge; | ||
|
||
use GuzzleHttp\Psr7\Uri; | ||
use Neos\ContentRepository\Domain\Model\NodeInterface; | ||
use Neos\Flow\Annotations as Flow; | ||
use Neos\Neos\Domain\Service\ContentContextFactory; | ||
use Neos\Neos\Domain\Service\ContentDimensionPresetSourceInterface; | ||
|
||
final class ContentRepositorySourceOfKnowledge implements SourceOfKnowledgeContract | ||
{ | ||
#[Flow\Inject] | ||
protected ContentContextFactory $contentContextFactory; | ||
|
||
#[Flow\Inject] | ||
protected ContentDimensionPresetSourceInterface $contentDimensionPresetSource; | ||
|
||
public function __construct( | ||
private readonly string $name, | ||
private readonly string $description, | ||
private readonly ContentRepositorySourceDesignator $designator, | ||
) { | ||
} | ||
|
||
public static function createFromConfiguration(string $name, array $options): static | ||
{ | ||
return new static( | ||
$name, | ||
$options['description'] ?? 'null', | ||
ContentRepositorySourceDesignator::createFromConfiguration($options), | ||
); | ||
} | ||
|
||
public function getName(): string | ||
{ | ||
return $this->name; | ||
} | ||
|
||
public function getDescription(): string | ||
{ | ||
return $this->description; | ||
} | ||
|
||
public function getContent(): JsonlRecordCollection | ||
{ | ||
$rootNode = $this->designator->findRootNode($this->contentContextFactory, $this->contentDimensionPresetSource); | ||
|
||
return new JsonlRecordCollection(...$this->traverseSubtree($rootNode)); | ||
} | ||
|
||
/** | ||
* @return JsonlRecord[] | ||
*/ | ||
private function traverseSubtree(NodeInterface $documentNode): array | ||
{ | ||
$documents = []; | ||
if (!$documentNode->getNodeType()->isOfType('Neos.Neos:Shortcut')) { | ||
$documents[] = $this->transformDocument($documentNode); | ||
} | ||
foreach ($documentNode->getChildNodes('Neos.Neos:Document') as $childDocument) { | ||
$documents = array_merge($documents, $this->traverseSubtree($childDocument)); | ||
} | ||
|
||
return $documents; | ||
} | ||
|
||
private function transformDocument(NodeInterface $documentNode): JsonlRecord | ||
{ | ||
$content = ''; | ||
foreach ($documentNode->getChildNodes('Neos.Neos:Content,Neos.Neos:ContentCollection') as $childNode) { | ||
$content .= ' ' . $this->extractContent($childNode); | ||
} | ||
|
||
return new JsonlRecord( | ||
$documentNode->getIdentifier(), | ||
new Uri('node://' . $documentNode->getIdentifier()), | ||
trim($content) | ||
); | ||
} | ||
|
||
private function extractContent(NodeInterface $contentNode): string | ||
{ | ||
$content = ''; | ||
|
||
if ($contentNode->getNodeType()->isOfType('Neos.Neos:ContentCollection')) { | ||
foreach ($contentNode->getChildNodes('Neos.Neos:Content,Neos.Neos:ContentCollection') as $childNode) { | ||
$content .= $this->extractContent($childNode); | ||
} | ||
} | ||
if ($contentNode->getNodeType()->isOfType('Neos.Neos:Content')) { | ||
foreach ($contentNode->getNodeType()->getProperties() as $propertyName => $propertyConfiguration) { | ||
if (($propertyConfiguration['type'] ?? 'string') === 'string' && ($propertyConfiguration['ui']['inlineEditable'] ?? false) === true) { | ||
$content .= ' ' . $contentNode->getProperty($propertyName); | ||
} | ||
} | ||
} | ||
|
||
return trim($content); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Sitegeist\Chatterbox\Domain\Knowledge; | ||
|
||
use Neos\Flow\Annotations as Flow; | ||
use Psr\Http\Message\UriInterface; | ||
|
||
#[Flow\Proxy(false)] | ||
final class JsonlRecord implements \JsonSerializable | ||
{ | ||
public function __construct( | ||
public readonly string $id, | ||
public readonly UriInterface $url, | ||
public readonly string $content, | ||
) { | ||
} | ||
|
||
/** | ||
* @return array<string,mixed> | ||
*/ | ||
public function jsonSerialize(): array | ||
{ | ||
return [ | ||
'id' => $this->id, | ||
'url' => (string)$this->url, | ||
'content' => $this->content | ||
]; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Sitegeist\Chatterbox\Domain\Knowledge; | ||
|
||
final class JsonlRecordCollection implements \Stringable | ||
{ | ||
/** | ||
* @var array<JsonlRecord> | ||
*/ | ||
private readonly array $records; | ||
|
||
public function __construct( | ||
JsonlRecord ...$records | ||
) { | ||
$this->records = $records; | ||
} | ||
|
||
public function __toString(): string | ||
{ | ||
return implode("\n", array_map( | ||
fn (JsonlRecord $record): string => \str_replace(PHP_EOL, ' ', \json_encode($record, JSON_THROW_ON_ERROR)), | ||
$this->records | ||
)); | ||
} | ||
} |
Oops, something went wrong.