import "server-only"
import type Anthropic from "@anthropic-ai/sdk"
import { buildKnowledge, businessName } from "./knowledge"
import { CHANNELS, type Channel, type Tone } from "./schemas"
import type { AiContext } from "./server"
function facts(ctx: AiContext) {
return {
brand: businessName(ctx.def, ctx.content),
industry: ctx.def.industry,
knowledge: `\n${buildKnowledge(ctx.def, ctx.content)}\n`,
}
}
// Datum in einem eigenen Block nach dem gecachten Wissen, damit der Cache den Tag überlebt
export function nowNote() {
const now = new Intl.DateTimeFormat("de-DE", { dateStyle: "full", timeStyle: "short", timeZone: "Europe/Berlin" }).format(new Date())
return `Aktuelles Datum und Uhrzeit (Europe/Berlin): ${now}.`
}
export const BOOKING_TOOL: Anthropic.Beta.BetaTool = {
name: "anfrage_erfassen",
description:
"Erfasst eine Termin-, Reservierungs-, Rückruf- oder Angebotsanfrage und leitet sie an das Team weiter. Erst aufrufen, wenn Name, Kontakt und das Anliegen mit den nötigen Details (z. B. Wunschtermin, Personenzahl) feststehen.",
eager_input_streaming: true,
input_schema: {
type: "object",
properties: {
art: { type: "string", description: "Art der Anfrage, z. B. Reservierung, Termin, Rückruf, Angebot" },
name: { type: "string", description: "Name der anfragenden Person" },
kontakt: { type: "string", description: "Telefonnummer oder E-Mail-Adresse" },
datum: { type: "string", description: "Wunschdatum, wie genannt" },
uhrzeit: { type: "string", description: "Wunschuhrzeit, wie genannt" },
personen: { type: "string", description: "Anzahl Personen, falls relevant" },
anliegen: { type: "string", description: "Kurze Zusammenfassung des Anliegens" },
},
required: ["art", "name", "kontakt"],
},
}
export function chatSystem(ctx: AiContext, mode: "chat" | "phone") {
const { brand, industry, knowledge } = facts(ctx)
const name = ctx.content.ai.name.trim() || "der digitale Assistent"
const intro =
mode === "phone"
? `Du bist ${name} und nimmst Anrufe für ${brand} (${industry}) entgegen. Deine Antworten werden vorgelesen.
- Sprich natürlich und knapp: höchstens zwei kurze Sätze pro Antwort.
- Keine Listen, Abkürzungen, Sonderzeichen, Emojis oder Links. Uhrzeiten, Daten und Preise so, wie man sie ausspricht.`
: `Du bist ${name}, der KI-Assistent auf der Webseite von ${brand} (${industry}).
- Antworte freundlich und auf den Punkt, meist in zwei bis vier Sätzen, als reiner Text ohne Markdown.`
return `${intro}
- Antworte in der Sprache der Person, standardmäßig Deutsch in der Sie-Form.
- Stütze dich auf das Wissen unten. Fehlt eine Information, sag das offen und biete an, eine Anfrage aufzunehmen, oder nenne die Kontaktdaten.
- Möchte jemand einen Termin, eine Reservierung, einen Rückruf oder ein Angebot, sammle die nötigen Angaben im Gespräch – immer nur eine Frage auf einmal – und rufe dann anfrage_erfassen auf. Bestätige danach, dass die Anfrage weitergeleitet wurde und sich das Team meldet. Sage nie zu, dass etwas fest gebucht ist, und erfinde keine freien Termine.
- Bei Themen ohne Bezug zum Betrieb hilfst du kurz und lenkst freundlich zurück.
${knowledge}`
}
export function emailSystem(ctx: AiContext) {
const { brand, industry, knowledge } = facts(ctx)
return `Du beantwortest für ${brand} (${industry}) eine Kundenanfrage per E-Mail.
- Herzlich, kompetent und konkret: Geh auf jede Frage der Anfrage ein, in der Sie-Form (außer die Anfrage duzt).
- Nutze Leistungen, Preise, Zeiten und Kontaktdaten nur aus dem Wissen unten. Wo Angaben fehlen, stelle eine passende Rückfrage statt zu raten, und schlage einen nächsten Schritt vor (Termin, Rückruf, Angebot).
- Format: erste Zeile „Betreff: …“, dann eine Leerzeile, dann die E-Mail mit Anrede, Text und Grußformel im Namen des Teams von ${brand}. Reiner Text, kein Markdown.
${knowledge}`
}
export function quoteSystem(ctx: AiContext) {
const { brand, industry, knowledge } = facts(ctx)
return `Du erstellst für ${brand} (${industry}) aus einer Kundenanfrage einen Angebotsentwurf, den das Team vor dem Versand prüft.
- Leite sinnvolle Positionen aus der Anfrage ab, jeweils mit Menge, Einheit und Einzelpreis netto in Euro.
- Nutze Preise aus dem Wissen, wenn vorhanden. Sonst schätze realistische Preise für Deutschland und weise in den Hinweisen darauf hin, dass es Schätzwerte sind.
- Erfinde keine Details über die anfragende Person. Was für ein verbindliches Angebot noch fehlt, gehört in „rueckfragen“ (das sieht nur das Team).
- Anrede, Einleitung und Abschluss im Ton von ${brand}, in der Sie-Form.
${knowledge}`
}
const CHANNEL_RULES: Record = {
instagram: "starker erster Satz als hook, Text 60–140 Wörter mit Absätzen, sparsam Emojis, 5–8 passende Hashtags ohne #-Zeichen",
facebook: "persönlicher Ton, Text 40–110 Wörter, 0–3 Hashtags ohne #-Zeichen",
linkedin: "professionell mit echtem Mehrwert, Text 80–170 Wörter, 3 Hashtags ohne #-Zeichen",
google: "hook ist der Anzeigentitel mit höchstens 30 Zeichen, text die Beschreibung mit höchstens 90 Zeichen, keine Hashtags",
}
export function contentSystem(ctx: AiContext, channel: Channel, tone: Tone) {
const { brand, industry, knowledge } = facts(ctx)
return `Du schreibst Social-Media- und Werbetexte für ${brand} (${industry}).
Erstelle drei klar unterschiedliche Varianten für ${CHANNELS[channel]} (Regeln: ${CHANNEL_RULES[channel]}). Tonalität: ${tone}. Jede Variante hat einen Call-to-Action und eine kurze Bildidee. Nutze nur Fakten aus dem Wissen unten.
${knowledge}`
}