mirror of
https://github.com/Since-3/since3-portal.git
synced 2026-10-12 19:51:17 +00:00
100 lines
5.7 KiB
TypeScript
100 lines
5.7 KiB
TypeScript
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: `<wissen>\n${buildKnowledge(ctx.def, ctx.content)}\n</wissen>`,
|
||
}
|
||
}
|
||
|
||
// 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<Channel, string> = {
|
||
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}`
|
||
}
|