chat suggestions, SEo integration, and more
This commit is contained in:
@@ -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">
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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`}
|
||||
>
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user