chat suggestions, SEo integration, and more

This commit is contained in:
colinislit
2025-12-02 13:37:45 +01:00
parent 9dc2b4f216
commit aacada2197
17 changed files with 2276 additions and 739 deletions

View File

@@ -41,6 +41,72 @@ export async function generateMetadata({ params }: ReleasePageProps) {
}
}
function ArticleJsonLd({
title,
description,
releaseDate,
slug,
}: {
title: string
description: string
releaseDate: string
slug: string
}) {
const siteUrl = process.env.NEXT_PUBLIC_APP_URL || 'https://aispeedrun.vercel.app'
const jsonLd = {
'@context': 'https://schema.org',
'@graph': [
{
'@type': 'Article',
'@id': `${siteUrl}/documentatie/${slug}#article`,
headline: title,
description: description,
datePublished: releaseDate,
dateModified: releaseDate,
author: {
'@type': 'Person',
name: 'Colin van der Heijden',
url: 'https://ikbenlit.nl',
},
publisher: { '@id': `${siteUrl}/#organization` },
mainEntityOfPage: `${siteUrl}/documentatie/${slug}`,
inLanguage: 'nl-NL',
},
{
'@type': 'BreadcrumbList',
'@id': `${siteUrl}/documentatie/${slug}#breadcrumb`,
itemListElement: [
{
'@type': 'ListItem',
position: 1,
name: 'Home',
item: siteUrl,
},
{
'@type': 'ListItem',
position: 2,
name: 'Documentatie',
item: `${siteUrl}/documentatie`,
},
{
'@type': 'ListItem',
position: 3,
name: title,
},
],
},
],
}
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
)
}
export default async function ReleasePage({ params }: ReleasePageProps) {
const { category } = await params
const release = await getRelease(category)
@@ -53,6 +119,12 @@ export default async function ReleasePage({ params }: ReleasePageProps) {
return (
<div className="min-h-screen bg-white pb-16">
<ArticleJsonLd
title={frontmatter.title}
description={frontmatter.description}
releaseDate={frontmatter.releaseDate}
slug={category}
/>
<article className="max-w-4xl mx-auto px-4 md:px-8 pt-20 md:pt-20">
{/* Header */}
<header className="mb-8 pb-8 border-b border-slate-200">

View File

@@ -5,6 +5,9 @@ import { getSession } from '@/lib/auth/server'
import { detectCategories } from '@/lib/docs/category-detector'
import { loadKnowledgeSections } from '@/lib/docs/knowledge-loader'
import { buildSystemPrompt } from '@/lib/docs/prompt-builder'
import { detectQuestionType } from '@/lib/docs/question-type-detector'
import { loadClientContext } from '@/lib/docs/client-context-loader'
import { buildClientPrompt, buildClientErrorPrompt } from '@/lib/docs/client-prompt-builder'
const DOCS_ASSISTANT_MODEL = process.env.DOCS_ASSISTANT_MODEL ?? 'claude-sonnet-4-20250514'
const MAX_HISTORY_MESSAGES = 10
@@ -52,6 +55,7 @@ const ChatMessageSchema = z.object({
const RequestSchema = z.object({
messages: z.array(ChatMessageSchema).optional(),
userMessage: z.string().min(1).max(MAX_USER_MESSAGE_LENGTH),
clientId: z.string().uuid().optional(), // UUID van actieve patiënt
})
type ChatMessage = z.infer<typeof ChatMessageSchema>
@@ -114,9 +118,28 @@ export async function POST(request: NextRequest) {
const conversation: ChatMessage[] = [...history, { role: 'user', content: rawUserMessage }]
const categories = detectCategories(rawUserMessage)
const knowledgeSections = await loadKnowledgeSections(categories)
const systemPrompt = buildSystemPrompt(knowledgeSections)
// Detect question type and build appropriate prompt
const clientId = parsed.data.clientId
const questionType = detectQuestionType(rawUserMessage, !!clientId)
let systemPrompt: string
if (questionType === 'client' && clientId) {
// Client-specific question: load client context
const clientContext = await loadClientContext(clientId)
if (clientContext) {
systemPrompt = buildClientPrompt(clientContext)
} else {
// Client not found or error loading
systemPrompt = buildClientErrorPrompt()
}
} else {
// Documentation question: use existing knowledge base flow
const categories = detectCategories(rawUserMessage)
const knowledgeSections = await loadKnowledgeSections(categories)
systemPrompt = buildSystemPrompt(knowledgeSections)
}
const apiKey = process.env.ANTHROPIC_API_KEY
if (!apiKey) {

View File

@@ -119,6 +119,34 @@ export const metadata: Metadata = {
},
};
const siteUrl = process.env.NEXT_PUBLIC_APP_URL || 'https://aispeedrun.vercel.app'
const jsonLd = {
'@context': 'https://schema.org',
'@graph': [
{
'@type': 'Organization',
'@id': `${siteUrl}/#organization`,
name: 'AI Speedrun',
url: siteUrl,
description: 'AI-powered EPD development experiment - bouw een EPD in 4 weken voor €200',
founder: {
'@type': 'Person',
name: 'Colin van der Heijden',
url: 'https://ikbenlit.nl',
},
},
{
'@type': 'WebSite',
'@id': `${siteUrl}/#website`,
url: siteUrl,
name: 'AI Speedrun',
publisher: { '@id': `${siteUrl}/#organization` },
inLanguage: 'nl-NL',
},
],
}
export default function RootLayout({
children,
}: Readonly<{
@@ -126,6 +154,12 @@ export default function RootLayout({
}>) {
return (
<html lang="nl">
<head>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
</head>
<body
className={`${crimsonText.variable} ${inter.variable} ${jetBrainsMono.variable} antialiased`}
>

View File

@@ -1,18 +1,32 @@
/**
* Sitemap Generator
*
*
* Generates sitemap.xml for SEO.
* Next.js will automatically serve this at /sitemap.xml
*/
import type { MetadataRoute } from 'next'
import { getAllReleases } from '@/lib/mdx/documentatie'
export default function sitemap(): MetadataRoute.Sitemap {
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const baseUrl = process.env.NEXT_PUBLIC_APP_URL || 'https://aispeedrun.vercel.app'
// Get current date for lastModified
const currentDate = new Date()
// Fetch all documentation releases dynamically
const releases = await getAllReleases()
const releaseUrls: MetadataRoute.Sitemap = releases.map((release) => {
const releaseDate = new Date(release.frontmatter.releaseDate)
const isValidDate = !isNaN(releaseDate.getTime())
return {
url: `${baseUrl}/documentatie/${release.slug}`,
lastModified: isValidDate ? releaseDate : currentDate,
changeFrequency: 'monthly',
priority: 0.8,
}
})
return [
{
url: baseUrl,
@@ -20,19 +34,19 @@ export default function sitemap(): MetadataRoute.Sitemap {
changeFrequency: 'weekly',
priority: 1.0,
},
// Future routes can be added here:
// {
// url: `${baseUrl}/build-log`,
// lastModified: currentDate,
// changeFrequency: 'weekly',
// priority: 0.8,
// },
// {
// url: `${baseUrl}/demo`,
// lastModified: currentDate,
// changeFrequency: 'monthly',
// priority: 0.7,
// },
{
url: `${baseUrl}/documentatie`,
lastModified: currentDate,
changeFrequency: 'weekly',
priority: 0.9,
},
...releaseUrls,
{
url: `${baseUrl}/contact`,
lastModified: currentDate,
changeFrequency: 'monthly',
priority: 0.7,
},
]
}