From b04bb4ecf3e0d00397ea598488aab20272ebe390 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Thu, 2 Jul 2026 20:45:41 +0300 Subject: [PATCH] =?UTF-8?q?feat(bot):=20HelpArticleParser=20=E2=80=94=20fr?= =?UTF-8?q?ontmatter=20+=20=D1=87=D0=B0=D0=BD=D0=BA=D0=B8=20=D1=81=D1=82?= =?UTF-8?q?=D0=B0=D1=82=D0=B5=D0=B9=20=D0=B8=D0=BD=D1=81=D1=82=D1=80=D1=83?= =?UTF-8?q?=D0=BA=D1=86=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/app/Support/Help/HelpArticle.php | 17 ++++++ app/app/Support/Help/HelpArticleParser.php | 62 ++++++++++++++++++++ app/tests/Unit/Bot/HelpArticleParserTest.php | 47 +++++++++++++++ 3 files changed, 126 insertions(+) create mode 100644 app/app/Support/Help/HelpArticle.php create mode 100644 app/app/Support/Help/HelpArticleParser.php create mode 100644 app/tests/Unit/Bot/HelpArticleParserTest.php diff --git a/app/app/Support/Help/HelpArticle.php b/app/app/Support/Help/HelpArticle.php new file mode 100644 index 00000000..47154d2f --- /dev/null +++ b/app/app/Support/Help/HelpArticle.php @@ -0,0 +1,17 @@ + $chunks */ +class HelpArticle +{ + public function __construct( + public readonly string $sourcePath, + public readonly string $title, + public readonly ?string $tour, + public readonly string $topics, + public readonly array $chunks, + ) {} +} diff --git a/app/app/Support/Help/HelpArticleParser.php b/app/app/Support/Help/HelpArticleParser.php new file mode 100644 index 00000000..97f557a6 --- /dev/null +++ b/app/app/Support/Help/HelpArticleParser.php @@ -0,0 +1,62 @@ + $p !== '' + )); + + $chunks = []; + $current = ''; + foreach ($paragraphs as $p) { + if ($current !== '' && mb_strlen($current) + mb_strlen($p) > self::CHUNK_TARGET_CHARS) { + $chunks[] = $current; + $current = $p; + } else { + $current = $current === '' ? $p : $current."\n\n".$p; + } + } + if ($current !== '') { + $chunks[] = $current; + } + + return new HelpArticle( + sourcePath: $sourcePath, + title: $meta['title'], + tour: ($meta['tour'] ?? '') !== '' ? $meta['tour'] : null, + topics: $meta['topics'] ?? '', + chunks: $chunks, + ); + } +} diff --git a/app/tests/Unit/Bot/HelpArticleParserTest.php b/app/tests/Unit/Bot/HelpArticleParserTest.php new file mode 100644 index 00000000..1edb06a7 --- /dev/null +++ b/app/tests/Unit/Bot/HelpArticleParserTest.php @@ -0,0 +1,47 @@ +parse('help/x.md', $md); + + expect($article->title)->toBe('Что такое проект') + ->and($article->tour)->toBe('create-project') + ->and($article->topics)->toBe('создать проект, заявка на лиды') + ->and($article->chunks)->toHaveCount(1) + ->and($article->chunks[0])->toContain('Первый абзац'); +}); + +it('без frontmatter кидает понятную ошибку', function () { + expect(fn () => (new HelpArticleParser)->parse('help/bad.md', 'просто текст')) + ->toThrow(InvalidArgumentException::class); +}); + +it('длинное тело режет на несколько чанков ~1200 символов по границам абзацев', function () { + $body = implode("\n\n", array_fill(0, 10, str_repeat('а', 300))); + $md = "---\ntitle: Т\ntopics: т\n---\n\n".$body; + + $article = (new HelpArticleParser)->parse('help/long.md', $md); + + expect(count($article->chunks))->toBeGreaterThan(1); + foreach ($article->chunks as $chunk) { + expect(mb_strlen($chunk))->toBeLessThanOrEqual(1500); + } +});