Improve Lighthouse Agentic Browsing readiness #8

Merged
westfarn merged 1 commits from feature/5-agentic-browsing-readiness into master 2026-07-02 04:36:54 -07:00
14 changed files with 436 additions and 44 deletions
+45
View File
@@ -0,0 +1,45 @@
# Agentic Browsing Readiness
This site targets Google's experimental **Agentic Browsing** Lighthouse category (v13.3+), which checks whether AI agents can read, navigate, and act on public pages.
PageSpeed Insights does not yet expose this category. Run audits locally:
```bash
npx lighthouse@latest https://aimloperations.com \
--only-categories=agentic-browsing \
--chrome-flags="--enable-experimental-web-platform-features" \
--output=html --output-path=agentic-browsing-report.html
```
Test at minimum:
- `/` (homepage)
- `/contact` (primary conversion page)
## What we ship
| Check | Implementation |
|-------|----------------|
| **llms.txt** | `GET /llms.txt` — machine-readable site summary |
| **robots.txt** | `GET /robots.txt` — crawl rules + sitemap reference |
| **sitemap.xml** | `GET /sitemap.xml` — public marketing URLs |
| **Accessibility tree** | Form labels, semantic nav controls, ARIA on dialogs |
| **Layout stability** | Fixed cookie banner, reserved hero/marquee space, font fallbacks |
WebMCP is intentionally deferred until browser and agent support stabilizes.
## Regression checklist
Before merging public-facing template or CSS changes:
1. Contact form fields have associated `<label>` elements (not placeholder-only).
2. Nav dropdowns use `<button>` triggers with `aria-expanded` / `aria-haspopup`.
3. Cookie consent banner stays `position: fixed` (no document flow shift).
4. `GET /robots.txt`, `/sitemap.xml`, `/llms.txt` return 200.
5. Re-run Lighthouse agentic-browsing on homepage and contact page.
## References
- [Lighthouse Agentic Browsing audit overview](https://locomotive.agency/blog/lighthouse-agentic-browsing-audit/)
- [llms.txt proposal](https://llmstxt.org/)
- Gitea issue #5
+66
View File
@@ -0,0 +1,66 @@
"""Machine-readable site discovery endpoints for crawlers and AI agents."""
from django.http import HttpResponse
from django.template.loader import render_to_string
from django.urls import reverse
# Public marketing pages included in sitemap and llms.txt.
PUBLIC_PAGE_ENTRIES = (
("public_index", "Home", "weekly", "1.0"),
("forward_deployed", "Forward-Deployed AI", "monthly", "0.9"),
("bot", "AI Agents", "monthly", "0.9"),
("ml_model", "ML Models", "monthly", "0.8"),
("chat", "Secure AI Chat", "monthly", "0.8"),
("ai_sensor", "AI Sensor Algorithms", "monthly", "0.7"),
("ai_education", "AI Education", "monthly", "0.7"),
("computers", "Computer Builds", "monthly", "0.7"),
("file_hosting", "File Hosting", "monthly", "0.7"),
("web_design", "Web Design and Hosting", "monthly", "0.8"),
("contact", "Contact", "monthly", "0.9"),
("terms_of_service", "Terms of Service and Privacy", "yearly", "0.3"),
)
def _absolute_url(request, url_name):
return request.build_absolute_uri(reverse(url_name))
def robots_txt(request):
sitemap_url = _absolute_url(request, "sitemap_xml")
content = render_to_string(
"public/robots.txt",
{"sitemap_url": sitemap_url},
)
return HttpResponse(content, content_type="text/plain; charset=utf-8")
def sitemap_xml(request):
pages = [
{
"loc": _absolute_url(request, url_name),
"changefreq": changefreq,
"priority": priority,
}
for url_name, _title, changefreq, priority in PUBLIC_PAGE_ENTRIES
]
content = render_to_string("public/sitemap.xml", {"pages": pages})
return HttpResponse(content, content_type="application/xml; charset=utf-8")
def llms_txt(request):
pages = [
{
"title": title,
"url": _absolute_url(request, url_name),
}
for url_name, title, _changefreq, _priority in PUBLIC_PAGE_ENTRIES
]
content = render_to_string(
"public/llms.txt",
{
"site_url": request.build_absolute_uri("/"),
"contact_url": _absolute_url(request, "contact"),
"pages": pages,
},
)
return HttpResponse(content, content_type="text/plain; charset=utf-8")
+145 -2
View File
@@ -23,6 +23,7 @@ body {
font-family: var(--font-main);
line-height: 1.6;
overflow-x: hidden;
text-rendering: optimizeLegibility;
}
a {
@@ -81,6 +82,7 @@ nav {
.hero-section {
position: relative;
height: 100vh;
min-height: 520px;
width: 100%;
display: flex;
align-items: center;
@@ -88,6 +90,11 @@ nav {
overflow: hidden;
}
.hero-section--compact {
height: 40vh;
min-height: 300px;
}
#hero-canvas {
position: absolute;
top: 0;
@@ -95,6 +102,7 @@ nav {
width: 100%;
height: 100%;
z-index: 0;
pointer-events: none;
}
.hero-content {
@@ -102,11 +110,14 @@ nav {
z-index: 1;
text-align: center;
padding: 0 1rem;
min-height: 12rem;
}
.hero-title {
font-size: 4rem;
font-size: clamp(2.5rem, 8vw, 4rem);
font-weight: 800;
line-height: 1.1;
min-height: 1.1em;
background: linear-gradient(45deg, var(--primary-color), var(--secondary-color));
-webkit-background-clip: text;
background-clip: text;
@@ -282,6 +293,41 @@ nav {
margin-bottom: 1.5rem;
}
.form-label {
display: block;
margin-bottom: 0.5rem;
color: var(--text-color);
font-weight: 500;
font-size: 0.95rem;
}
.contact-info-list {
list-style: none;
}
.contact-info-item {
margin-bottom: 1rem;
display: flex;
align-items: center;
gap: 1rem;
}
.contact-info-icon {
color: var(--primary-color);
}
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
.form-control {
width: 100%;
padding: 1rem;
@@ -376,7 +422,29 @@ nav {
display: block;
}
.dropdown>a::after {
.dropdown.dropdown-open .dropdown-content {
display: block;
}
.nav-dropdown-trigger {
background: none;
border: none;
cursor: pointer;
font: inherit;
color: var(--text-color);
font-weight: 500;
font-size: 0.9rem;
text-transform: uppercase;
letter-spacing: 0.5px;
padding: 0;
}
.nav-dropdown-trigger:hover,
.nav-dropdown-trigger.active {
color: var(--primary-color);
}
.dropdown .nav-dropdown-trigger::after {
content: ' ▼';
font-size: 0.7em;
margin-left: 5px;
@@ -387,12 +455,72 @@ nav {
display: flex;
align-items: center;
padding: 0.25rem;
background: none;
border: none;
cursor: pointer;
font: inherit;
}
.profile-icon-link::after {
display: none !important;
}
/* Trusted-by marquee */
.trusted-by-section {
padding: 2.5rem 0;
background: var(--bg-color);
}
.trusted-by-label {
text-align: center;
color: var(--text-muted);
text-transform: uppercase;
letter-spacing: 0.12em;
font-size: 0.8rem;
margin-bottom: 1.25rem;
}
.trusted-by-marquee {
overflow: hidden;
min-height: 48px;
}
.trusted-by-marquee-row {
display: flex;
width: max-content;
}
.trusted-by-track {
display: flex;
align-items: center;
gap: 3rem;
padding-right: 3rem;
min-height: 48px;
}
.trusted-by-logo img {
display: block;
height: 48px;
width: auto;
max-width: 320px;
}
@media (prefers-reduced-motion: no-preference) {
.trusted-by-marquee-row {
animation: trusted-by-scroll 30s linear infinite;
}
}
@keyframes trusted-by-scroll {
from {
transform: translateX(0);
}
to {
transform: translateX(-50%);
}
}
.profile-icon {
width: 28px;
height: 28px;
@@ -599,6 +727,21 @@ input:focus, select:focus, textarea:focus {
color: var(--primary-color);
}
.footer-link-btn {
background: none;
border: none;
padding: 0;
color: var(--text-muted);
font: inherit;
font-size: 0.9rem;
cursor: pointer;
text-decoration: none;
}
.footer-link-btn:hover {
color: var(--primary-color);
}
.footer-separator {
color: var(--text-muted);
margin: 0 0.5rem;
@@ -1,4 +1,7 @@
const canvas = document.getElementById('hero-canvas');
if (!canvas) {
// Hero animation only runs on the homepage.
} else {
const ctx = canvas.getContext('2d');
let width, height;
@@ -109,3 +112,4 @@ function animate() {
}
animate();
}
+49 -15
View File
@@ -49,18 +49,18 @@
<body>
<!-- Navigation Bar -->
<nav>
<nav aria-label="Main navigation">
<a href="{% url 'public_index' %}" class="brand-logo">
<img src="{% static 'public/img/logo.png' %}" alt="AI ML Operations" class="brand-logo-img" width="243" height="28">
</a>
<button class="mobile-menu-btn" aria-label="Menu"></button>
<ul class="nav-links">
<button type="button" class="mobile-menu-btn" aria-label="Open menu" aria-expanded="false" aria-controls="main-nav-links"></button>
<ul class="nav-links" id="main-nav-links">
<li><a href="{% url 'public_index' %}"
class="{% if request.resolver_match.url_name == 'public_index' %}active{% endif %}">Home</a></li>
<li class="dropdown">
<a href="#"
class="{% if request.resolver_match.url_name in 'forward_deployed,ai_education,ai_sensor,bot,chat,computers,file_hosting,ml_model,web_design' %}active{% endif %}">Services</a>
<ul class="dropdown-content">
<button type="button" class="nav-dropdown-trigger{% if request.resolver_match.url_name in 'forward_deployed,ai_education,ai_sensor,bot,chat,computers,file_hosting,ml_model,web_design' %} active{% endif %}"
id="services-menu-button" aria-expanded="false" aria-haspopup="true" aria-controls="services-menu">Services</button>
<ul class="dropdown-content" id="services-menu" role="menu" aria-labelledby="services-menu-button">
<li><a href="{% url 'forward_deployed' %}">Forward-Deployed AI</a></li>
<li><a href="{% url 'bot' %}">AI Agents</a></li>
<li><a href="{% url 'ml_model' %}">ML Models</a></li>
@@ -83,14 +83,15 @@
class="{% if 'financial' in request.path %}active{% endif %}"
data-tianji-event="nav_financials">Financials</a></li>
<li class="dropdown" id="user-profile-dropdown">
<a href="#" class="profile-icon-link" title="{{ user.get_full_name|default:user.username }}">
<button type="button" class="profile-icon-link" aria-label="Account menu for {{ user.get_full_name|default:user.username }}"
aria-expanded="false" aria-haspopup="true" aria-controls="profile-menu">
<svg class="profile-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<circle cx="12" cy="8" r="4" />
<path d="M4 21v-1a6 6 0 0 1 12 0v1" />
</svg>
</a>
<ul class="dropdown-content profile-dropdown-content">
</button>
<ul class="dropdown-content profile-dropdown-content" id="profile-menu" role="menu">
<li class="profile-name-item">{{ user.get_full_name|default:user.username }}</li>
<li><a href="{% url 'change_password' %}">Change Password</a></li>
<li>
@@ -123,7 +124,7 @@
<a href="{% url 'terms_of_service' %}" data-tianji-event="footer_terms">Terms &amp; Privacy</a>
{% if tianji_enabled %}
<span class="footer-separator">|</span>
<a href="#" data-open-cookie-preferences data-tianji-event="footer_cookie_preferences">Cookie Preferences</a>
<button type="button" class="footer-link-btn" data-open-cookie-preferences data-tianji-event="footer_cookie_preferences">Cookie Preferences</button>
{% endif %}
</p>
<p class="footer-text footer-text--muted">&copy; 2023 -
@@ -133,10 +134,11 @@
</footer>
{% if tianji_enabled %}
<div id="cookie-consent-banner" class="cookie-consent-banner" hidden role="dialog" aria-live="polite"
aria-label="Cookie consent">
<div id="cookie-consent-banner" class="cookie-consent-banner" hidden role="dialog" aria-modal="true"
aria-labelledby="cookie-consent-title" aria-describedby="cookie-consent-description">
<div class="cookie-consent-content">
<p class="cookie-consent-text">
<h2 id="cookie-consent-title" class="visually-hidden">Cookie consent</h2>
<p class="cookie-consent-text" id="cookie-consent-description">
<span class="cookie-consent-text-full">We use analytics tracking to understand how visitors use our site. Tracking runs only if you accept.
See our <a href="{% url 'terms_of_service' %}">Terms of Service &amp; Privacy Policy</a> for details.</span>
<span class="cookie-consent-text-short">We use analytics if you accept. <a href="{% url 'terms_of_service' %}">Privacy Policy</a></span>
@@ -157,9 +159,41 @@
if (mobileBtn && navLinks) {
mobileBtn.addEventListener('click', function () {
navLinks.classList.toggle('active');
const isOpen = navLinks.classList.toggle('active');
mobileBtn.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
mobileBtn.setAttribute('aria-label', isOpen ? 'Close menu' : 'Open menu');
});
}
function closeDropdown(dropdown) {
dropdown.classList.remove('dropdown-open');
const trigger = dropdown.querySelector('.nav-dropdown-trigger, .profile-icon-link');
if (trigger) {
trigger.setAttribute('aria-expanded', 'false');
}
}
document.querySelectorAll('.dropdown').forEach(function (dropdown) {
const trigger = dropdown.querySelector('.nav-dropdown-trigger, .profile-icon-link');
if (!trigger) {
return;
}
trigger.addEventListener('click', function (event) {
if (window.matchMedia('(min-width: 769px)').matches && dropdown.querySelector('.nav-dropdown-trigger')) {
return;
}
event.preventDefault();
const isOpen = dropdown.classList.toggle('dropdown-open');
trigger.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
});
});
document.addEventListener('click', function (event) {
if (!event.target.closest('.dropdown')) {
document.querySelectorAll('.dropdown.dropdown-open').forEach(closeDropdown);
}
});
});
</script>
{% block tracking_events %}{% endblock %}
@@ -6,7 +6,7 @@
{% block content %}
<!-- Hero Section -->
<div class="hero-section" style="height: 40vh; min-height: 300px;">
<div class="hero-section hero-section--compact">
<div class="hero-content">
<h1 class="hero-title">Get in Touch</h1>
<p class="hero-subtitle hero-subtitle--wide">Describe the workflow or bottleneck you want to automate—we will help you scope a forward-deployed solution.</p>
@@ -32,24 +32,28 @@
{% endif %}
<div class="card">
<h4 class="card-title" style="margin-bottom: 2rem;">Send Us a Message</h4>
<form action="{% url 'contact' %}" method="POST">
<h2 class="card-title contact-form-heading" id="contact-form-heading" style="margin-bottom: 2rem;">Send Us a Message</h2>
<form action="{% url 'contact' %}" method="POST" aria-labelledby="contact-form-heading">
{% csrf_token %}
<div class="form-row">
<div class="form-group">
<input type="text" class="form-control" name="name" placeholder="Your Name">
<label class="form-label" for="contact-name">Your Name</label>
<input type="text" class="form-control" name="name" id="contact-name" autocomplete="name" required>
</div>
<div class="form-group">
<input type="email" class="form-control" name="email" placeholder="Your Email">
<label class="form-label" for="contact-email">Your Email</label>
<input type="email" class="form-control" name="email" id="contact-email" autocomplete="email" required>
</div>
</div>
<div class="form-group">
<input type="text" class="form-control" name="subject" id="contact-subject" placeholder="Subject" value="{{ request.GET.subject|default:'' }}">
<label class="form-label" for="contact-subject">Subject</label>
<input type="text" class="form-control" name="subject" id="contact-subject" value="{{ request.GET.subject|default:'' }}" required>
</div>
<div class="form-group">
<textarea name="message" class="form-control" rows="5" placeholder="What workflow is costing you the most time? What systems does it touch?"></textarea>
<label class="form-label" for="contact-message">Message</label>
<textarea name="message" class="form-control" id="contact-message" rows="5" placeholder="What workflow is costing you the most time? What systems does it touch?"></textarea>
</div>
<div class="form-group">
@@ -69,15 +73,15 @@
<!-- Info Column -->
<div>
<div class="card">
<h5 class="card-title">Contact Information</h5>
<ul style="list-style: none;">
<li style="margin-bottom: 1rem; display: flex; align-items: center; gap: 1rem;">
<span style="color: var(--primary-color);">📞</span>
<p style="color: var(--text-muted);">+1 (330) 402-2675</p>
<h3 class="card-title">Contact Information</h3>
<ul class="contact-info-list">
<li class="contact-info-item">
<span class="contact-info-icon" aria-hidden="true">📞</span>
<a href="tel:+13304022675">+1 (330) 402-2675</a>
</li>
<li style="margin-bottom: 1rem; display: flex; align-items: center; gap: 1rem;">
<span style="color: var(--primary-color);">✉️</span>
<p style="color: var(--text-muted);">ryan@aimloperations.com</p>
<li class="contact-info-item">
<span class="contact-info-icon" aria-hidden="true">✉️</span>
<a href="mailto:ryan@aimloperations.com">ryan@aimloperations.com</a>
</li>
</ul>
</div>
@@ -96,4 +100,4 @@
}
</script>
{% endif %}
{% endblock %}
{% endblock %}
@@ -106,13 +106,16 @@
-->
<div class="form-group col-6" style="padding-left:1rem">
<input type="email" class="form-control" name="email" placeholder="Your Email">
<label for="legacy-contact-email">Your Email</label>
<input type="email" class="form-control" name="email" id="legacy-contact-email" placeholder="Your Email">
</div>
<div class="form-group col-6" style="padding-right:1rem">
<input type="text" class="form-control" name="name" placeholder="Your Name">
<label for="legacy-contact-name">Your Name</label>
<input type="text" class="form-control" name="name" id="legacy-contact-name" placeholder="Your Name">
</div>
<div class="form-group col-12" style="padding:1rem">
<textarea name="message" type="text" class="form-control" rows="5" placeholder="Your message"></textarea>
<label for="legacy-contact-message">Your Message</label>
<textarea name="message" id="legacy-contact-message" class="form-control" rows="5" placeholder="Your message"></textarea>
</div>
<div class="form-group col-12">
{% if capchaForm %}
@@ -0,0 +1,19 @@
# AI ML Operations, LLC
> Forward-deployed AI engineering. We embed with your team to architect, build, and deploy custom agentic workflows, integrations, and production AI systems.
AI ML Operations helps organizations move from AI pilots to production systems. Core services include forward-deployed AI engineering, custom AI agents, ML model development, secure hosted chat, sensor algorithms, education, hardware builds, and web hosting.
## Key pages
{% for page in pages %}- [{{ page.title }}]({{ page.url }})
{% endfor %}
## Contact
- [Contact form]({{ contact_url }})
- Phone: +1 (330) 402-2675
- Email: ryan@aimloperations.com
## Site
{{ site_url }}
@@ -11,7 +11,7 @@
{% block content %}
<!-- Hero Section -->
<div class="hero-section">
<canvas id="hero-canvas"></canvas>
<canvas id="hero-canvas" aria-hidden="true"></canvas>
<div class="hero-content">
<h1 class="hero-title">AI ML Operations</h1>
<p class="hero-subtitle hero-subtitle--wide">
@@ -0,0 +1,20 @@
User-agent: *
Allow: /
Disallow: /admin/
Disallow: /accounts/
Disallow: /financial/
Disallow: /planning/
User-agent: GPTBot
Allow: /
User-agent: ClaudeBot
Allow: /
User-agent: Google-Extended
Allow: /
User-agent: PerplexityBot
Allow: /
Sitemap: {{ sitemap_url }}
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
{% for page in pages %}
<url>
<loc>{{ page.loc }}</loc>
<changefreq>{{ page.changefreq }}</changefreq>
<priority>{{ page.priority }}</priority>
</url>
{% endfor %}
</urlset>
@@ -97,7 +97,7 @@ visually stunning, functional websites tailored to your business.{% endblock %}
<div class="pricing-toggle" role="group" aria-label="Billing period">
<span class="pricing-toggle-label active" id="monthlyLabel">Monthly</span>
<label class="pricing-switch" for="pricingToggle">
<input type="checkbox" id="pricingToggle" role="switch" aria-labelledby="monthlyLabel yearlyLabel">
<input type="checkbox" id="pricingToggle" aria-label="Toggle between monthly and yearly billing">
<span class="pricing-switch-slider"></span>
</label>
<span class="pricing-toggle-label" id="yearlyLabel">Yearly</span>
@@ -119,7 +119,7 @@ visually stunning, functional websites tailored to your business.{% endblock %}
<li style="margin-bottom: 0.5rem;">✓ Email Notifications</li>
<li style="margin-bottom: 0.5rem;">✓ Backend Admin Access</li>
</ul>
<a href="#" class="btn">Get Started</a>
<a href="{% url 'contact' %}?subject=Web%20Hosting%20Standard%20Plan" class="btn">Get Started</a>
</div>
<!-- Yearly Card -->
@@ -135,7 +135,7 @@ visually stunning, functional websites tailored to your business.{% endblock %}
<li style="margin-bottom: 0.5rem;">✓ 2 Months Free (Yearly)</li>
<li style="margin-bottom: 0.5rem;">✓ Priority Support</li>
</ul>
<a href="#" class="btn" style="background: var(--secondary-color); color: white;">Save 20%</a>
<a href="{% url 'contact' %}?subject=Web%20Hosting%20Premium%20Plan" class="btn" style="background: var(--secondary-color); color: white;">Save 20%</a>
</div>
</div>
</div>
+44 -3
View File
@@ -26,9 +26,14 @@ class ContactViewTests(TestCase):
self.assertEqual(response.status_code, 200)
self.assertContains(response, "Send Us a Message")
self.assertContains(response, 'name="name"')
self.assertContains(response, 'name="email"')
self.assertContains(response, 'name="subject"')
self.assertContains(response, 'id="contact-name"')
self.assertContains(response, 'for="contact-name"')
self.assertContains(response, 'id="contact-email"')
self.assertContains(response, 'for="contact-email"')
self.assertContains(response, 'id="contact-subject"')
self.assertContains(response, 'for="contact-subject"')
self.assertContains(response, 'id="contact-message"')
self.assertContains(response, 'for="contact-message"')
@patch("public.views.send_contact_email")
def test_contact_post_success_saves_contact_and_sends_email(self, mock_send_email):
@@ -125,3 +130,39 @@ class TianjiTrackingTests(TestCase):
self.assertEqual(response.status_code, 200)
self.assertNotContains(response, "cookie-consent-banner")
self.assertNotContains(response, "tianji-config")
class AgenticBrowsingSeoTests(TestCase):
def test_robots_txt_is_available(self):
response = self.client.get(reverse("robots_txt"))
self.assertEqual(response.status_code, 200)
self.assertEqual(response["Content-Type"], "text/plain; charset=utf-8")
self.assertContains(response, "User-agent: *")
self.assertContains(response, "Sitemap:")
def test_sitemap_xml_lists_public_pages(self):
response = self.client.get(reverse("sitemap_xml"))
self.assertEqual(response.status_code, 200)
self.assertEqual(response["Content-Type"], "application/xml; charset=utf-8")
self.assertContains(response, "<urlset")
self.assertContains(response, reverse("contact"))
self.assertContains(response, reverse("forward_deployed"))
def test_llms_txt_is_available(self):
response = self.client.get(reverse("llms_txt"))
self.assertEqual(response.status_code, 200)
self.assertEqual(response["Content-Type"], "text/plain; charset=utf-8")
self.assertContains(response, "# AI ML Operations, LLC")
self.assertContains(response, "## Key pages")
self.assertContains(response, reverse("contact"))
def test_homepage_uses_semantic_nav_controls(self):
response = self.client.get(reverse("public_index"))
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'id="services-menu-button"')
self.assertContains(response, 'aria-haspopup="true"')
self.assertContains(response, 'aria-label="Main navigation"')
+4 -1
View File
@@ -1,8 +1,11 @@
from django.urls import path
from . import views
from . import seo, views
urlpatterns = [
path("robots.txt", seo.robots_txt, name="robots_txt"),
path("sitemap.xml", seo.sitemap_xml, name="sitemap_xml"),
path("llms.txt", seo.llms_txt, name="llms_txt"),
path("", views.index, name="public_index"),
path("chat", views.chat, name="chat"),
path("ai_education", views.ai_education, name="ai_education"),