Skip to content

Commit c5663c4

Browse files
committed
Add prompt finder
1 parent 699db9c commit c5663c4

File tree

9 files changed

+197
-17
lines changed

9 files changed

+197
-17
lines changed

src/Bootstrap.php

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,21 @@
44

55
namespace LLM\Assistant;
66

7-
use LLM\Assistant\Module\Finder\Finder;
8-
use LLM\Assistant\Module\Finder\Internal\FinderImpl;
7+
use LLM\Assistant\Module\Finder\FilesystemFinder;
8+
use LLM\Assistant\Module\Finder\Internal\FilesystemFinderImpl;
9+
use LLM\Assistant\Module\Finder\Internal\PromptFinderImpl;
10+
use LLM\Assistant\Module\Finder\PromptFinder;
911
use LLM\Assistant\Service\Cache;
1012
use LLM\Assistant\Service\Container;
1113
use LLM\Assistant\Service\Internal\Cache\PsrCache;
1214
use LLM\Assistant\Service\Internal\Container\ContainerImpl;
1315
use LLM\Assistant\Service\Internal\Container\Injection\ConfigLoader;
1416
use LLM\Assistant\Service\Internal\Logger\LoggerImpl;
1517
use LLM\Assistant\Service\Logger;
16-
use Psr\SimpleCache\CacheInterface;
1718
use Symfony\Component\Console\Input\InputInterface;
1819
use Symfony\Component\Console\Output\OutputInterface;
1920
use Symfony\Component\Console\Style\StyleInterface;
2021
use Symfony\Component\Console\Style\SymfonyStyle;
21-
use Yiisoft\Cache\File\FileCache;
2222

2323
/**
2424
* Build the container based on the configuration.
@@ -48,7 +48,8 @@ public function finish(): Container
4848
$c = $this->container;
4949
unset($this->container);
5050

51-
$c->bind(Finder::class, FinderImpl::class);
51+
$c->bind(FilesystemFinder::class, FilesystemFinderImpl::class);
52+
$c->bind(PromptFinder::class, PromptFinderImpl::class);
5253
$c->bind(Cache::class, PsrCache::class);
5354

5455
return $c;

src/Command/Run.php

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
namespace LLM\Assistant\Command;
66

7-
use LLM\Assistant\Module\Finder\Finder;
7+
use LLM\Assistant\Module\Finder\PromptFinder;
88
use Symfony\Component\Console\Attribute\AsCommand;
99
use Symfony\Component\Console\Command\Command;
1010
use Symfony\Component\Console\Input\InputInterface;
@@ -32,11 +32,14 @@ protected function execute(InputInterface $input, OutputInterface $output): int
3232

3333
$this->logger->alert('Assistant is running');
3434

35-
$finder = $this->container->get(Finder::class);
35+
$finder = $this->container->get(PromptFinder::class);
3636

37-
foreach ($finder->files() as $name => $file) {
38-
$this->logger->info($name);
39-
}
37+
#AI test
38+
tr($finder->getNext());
39+
40+
// foreach ($finder->files() as $name => $file) {
41+
// $this->logger->info($name);
42+
// }
4043

4144
return Command::SUCCESS;
4245
}

src/Module/Finder/Dto/File.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace LLM\Assistant\Module\Finder\Dto;
6+
7+
final readonly class File
8+
{
9+
/**
10+
* @param list<Prompt> $prompts
11+
*/
12+
public function __construct(
13+
public readonly \SplFileInfo $file,
14+
public readonly string $content,
15+
public readonly array $prompts,
16+
) {}
17+
}

src/Module/Finder/Dto/Prompt.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace LLM\Assistant\Module\Finder\Dto;
6+
7+
final readonly class Prompt
8+
{
9+
/**
10+
* @param int<0, max> $start
11+
* @param positive-int $length
12+
* @param non-empty-string $prompt
13+
*/
14+
public function __construct(
15+
public readonly int $start,
16+
public readonly int $length,
17+
public readonly string $prompt,
18+
) {}
19+
}

src/Module/Finder/Finder.php renamed to src/Module/Finder/FilesystemFinder.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
*
1010
* @extends \IteratorAggregate<string, \SplFileInfo>
1111
*/
12-
interface Finder extends \IteratorAggregate
12+
interface FilesystemFinder extends \IteratorAggregate
1313
{
1414
/**
1515
* @return \Traversable<string, \SplFileInfo>
@@ -18,6 +18,11 @@ public function getIterator(): \Traversable;
1818

1919
public function after(\DateTimeInterface $date): static;
2020

21+
/**
22+
* Sort by modify time, oldest first
23+
*/
24+
public function oldest(): static;
25+
2126
/**
2227
* @return \Traversable<string, \SplFileInfo>
2328
*/

src/Module/Finder/Internal/FinderImpl.php renamed to src/Module/Finder/Internal/FilesystemFinderImpl.php

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,17 @@
55
namespace LLM\Assistant\Module\Finder\Internal;
66

77
use LLM\Assistant\Config\Source;
8-
use LLM\Assistant\Module\Finder\Finder;
8+
use LLM\Assistant\Module\Finder\FilesystemFinder;
99

10-
final class FinderImpl implements Finder
10+
final class FilesystemFinderImpl implements FilesystemFinder
1111
{
12+
/**
13+
* @param bool|null $sort If true, sort by newest first
14+
*/
1215
public function __construct(
1316
private readonly Source $config,
1417
private ?\DateTimeInterface $after = null,
18+
private ?bool $sort = null,
1519
) {}
1620

1721
public function getIterator(): \Traversable
@@ -21,7 +25,12 @@ public function getIterator(): \Traversable
2125

2226
public function after(\DateTimeInterface $date): static
2327
{
24-
return new self($this->config, $date);
28+
return new self($this->config, $date, $this->sort);
29+
}
30+
31+
public function oldest(): static
32+
{
33+
return new self($this->config, $this->after, false);
2534
}
2635

2736
public function files(): \Traversable
@@ -51,9 +60,9 @@ private function finder(): \Symfony\Component\Finder\Finder
5160
$finder->exclude(\array_map($this->directoryToPattern(...), $this->config->excludeDir));
5261
// $finder->exclude($this->config->cacheDir);
5362

54-
if ($this->after !== null) {
55-
$finder->date('> ' . $this->after->format('Y-m-d H:i:s'));
56-
}
63+
$this->after === null or $finder->date('>= ' . $this->after->format('Y-m-d H:i:s'));
64+
// todo direction
65+
$this->sort === null or $finder->sortByModifiedTime();
5766

5867
return $finder;
5968
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace LLM\Assistant\Module\Finder\Internal;
6+
7+
final class PromptCache
8+
{
9+
public ?\DateTimeInterface $lastScan = null;
10+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace LLM\Assistant\Module\Finder\Internal;
6+
7+
use LLM\Assistant\Module\Finder\Dto\File;
8+
use LLM\Assistant\Module\Finder\Dto\Prompt;
9+
use LLM\Assistant\Module\Finder\FilesystemFinder;
10+
use LLM\Assistant\Module\Finder\PromptFinder;
11+
use LLM\Assistant\Service\Cache;
12+
use LLM\Assistant\Service\Logger;
13+
14+
final class PromptFinderImpl implements PromptFinder
15+
{
16+
private const CACHE_KEY = 'prompt_finder';
17+
18+
private ?PromptCache $lastCache = null;
19+
20+
public function __construct(
21+
private readonly FilesystemFinder $files,
22+
private readonly Cache $cache,
23+
private readonly Logger $logger,
24+
) {}
25+
26+
public function getNext(): ?File
27+
{
28+
$cache = $this->getOrUpdateCache();
29+
30+
// Get last modified files
31+
$files = $cache->lastScan === null
32+
? $this->files->oldest()->files()
33+
: $this->files->after($cache->lastScan)->oldest()->files();
34+
35+
foreach ($files as $file) {
36+
$result = $this->scanFile($file);
37+
if ($result !== null) {
38+
// Update modified time in cache DTO to be saved later
39+
$mtime = $file->getMTime();
40+
$mtime === false or $cache->lastScan = new \DateTimeImmutable('@' . $mtime);
41+
42+
$this->lastCache = $cache;
43+
return $result;
44+
}
45+
}
46+
47+
return null;
48+
}
49+
50+
private function getOrUpdateCache(): PromptCache
51+
{
52+
try {
53+
if ($this->lastCache !== null) {
54+
$this->cache->set(self::CACHE_KEY, $this->lastCache);
55+
return $this->lastCache;
56+
}
57+
58+
return $this->cache->get(self::CACHE_KEY) ?? new PromptCache();
59+
} catch (\Throwable) {
60+
return new PromptCache();
61+
}
62+
}
63+
64+
private function scanFile(\SplFileInfo $file): ?File
65+
{
66+
$content = \file_get_contents($file->getPathname());
67+
$this->logger->debug(
68+
\sprintf(
69+
'Scanning MT: %s Path: %s',
70+
(new \DateTimeImmutable('@' . (int) $file->getMTime()))->format('Y-m-d H:i:s'),
71+
$file->getPathname(),
72+
),
73+
);
74+
75+
// Find all inline prompts started with "#AI" from a new line
76+
$matches = [];
77+
$result = \preg_match_all(
78+
'/^[ \\t]*#AI[ \\t]+(.+)$/m',
79+
$content,
80+
$matches,
81+
\PREG_OFFSET_CAPTURE,
82+
);
83+
84+
if ($result === 0) {
85+
return null;
86+
}
87+
88+
$prompts = [];
89+
/**
90+
* @var array{
91+
* list<array{non-empty-string, int<0, max>}>,
92+
* list<array{non-empty-string, positive-int}>
93+
* } $matches
94+
*/
95+
foreach ($matches[0] as $key => $match) {
96+
$prompts[] = new Prompt($match[1], \strlen($match[0]), $matches[1][$key][0]);
97+
}
98+
99+
return new File($file, $content, $prompts);
100+
}
101+
}

src/Module/Finder/PromptFinder.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace LLM\Assistant\Module\Finder;
6+
7+
use LLM\Assistant\Module\Finder\Dto\File;
8+
9+
/**
10+
* Finds new prompts
11+
*/
12+
interface PromptFinder
13+
{
14+
public function getNext(): ?File;
15+
}

0 commit comments

Comments
 (0)