Files
laralog/app/Blog/Controllers/LlmsController.php
T
goukiandCursor ae47447062
Build and Deploy / verify (push) Successful in 40s
Build and Deploy / deploy (push) Successful in 36s
feat: restore sablog browse modes and clean imported HTML
Keep the public site readable after import (nbsp, highlighter, wrapping code, compact pager) and finish the admin extras already in this tree: sticky levels, preview, page cache, and live llms.txt.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-18 21:33:02 +00:00

121 lines
3.3 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Blog\Controllers;
use App\Models\Post;
use Illuminate\Support\Str;
use League\HTMLToMarkdown\HtmlConverter;
class LlmsController extends Controller
{
/**
* /llms.txt — live index for agents. Not a static file.
*/
public function index()
{
$siteName = (string) blog_setting('site_name', config('blog.name'));
$siteDescription = (string) blog_setting('site_description', config('blog.description'));
$posts = Post::query()
->published()
->latest('published_at')
->limit(200)
->get();
$lines = [
'# '.$siteName,
'',
'> '.$siteDescription,
'',
'## Feeds',
'',
'- [RSS]('.route('feed.rss').')',
'- [Sitemap]('.route('feed.sitemap').')',
'- [llms.txt]('.route('feed.llms').')',
'',
'## Articles',
'',
];
foreach ($posts as $post) {
if ($this->isRestricted($post)) {
continue;
}
$summary = Str::limit(trim(preg_replace('/\s+/', ' ', strip_tags($post->excerpt_or_fallback)) ?? ''), 120);
$markdownUrl = $this->markdownUrl($post);
$htmlUrl = $post->url;
$title = $post->title;
$lines[] = $markdownUrl
? '- ['.$title.']('.$markdownUrl.') — '.$summary.' ([html]('.$htmlUrl.'))'
: '- ['.$title.']('.$htmlUrl.') — '.$summary;
}
return response(implode("\n", $lines)."\n", 200, [
'Content-Type' => 'text/plain; charset=utf-8',
'Cache-Control' => 'no-store',
]);
}
/**
* /posts/{slug}.md — full markdown for a published public post.
*/
public function postMarkdown(string $slug)
{
$post = Post::query()
->where('slug', $slug)
->orWhere('id', (int) $slug)
->first();
abort_unless($post && $post->status === 'published' && ! $this->isRestricted($post), 404);
$body = $post->content_format === 'markdown'
? (string) $post->content
: $this->htmlToMarkdown((string) $post->content);
$out = '# '.$post->title."\n\n";
if ($post->excerpt) {
$out .= '> '.$post->excerpt."\n\n";
}
$out .= $body."\n";
return response($out, 200, [
'Content-Type' => 'text/markdown; charset=utf-8',
'Cache-Control' => 'no-store',
]);
}
private function isRestricted(Post $post): bool
{
if (filled($post->read_password)) {
return true;
}
return ! empty(($post->meta ?? [])['members_only']);
}
private function markdownUrl(Post $post): ?string
{
$slug = trim((string) $post->slug);
if ($slug === '') {
return route('geo.post.markdown', ['slug' => (string) $post->id]);
}
return route('geo.post.markdown', ['slug' => $slug]);
}
private function htmlToMarkdown(string $html): string
{
$converter = new HtmlConverter([
'strip_tags' => false,
'hard_break' => true,
]);
return $converter->convert($html);
}
}