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}` }