On this page
article
Event-Export mit KI (LLM)
Superprompt für copilot-Exporte mit dem ihr eigene Exporte in copilot erstellen oder bestehende anpassen könnt.
Superprompt für co*pilot-Exporte mit dem ihr eigene Exporte in co*pilot erstellen oder bestehende anpassen könnt.
Anleitung für Nutzer:innen
- Unten stehenden Prompt mit eigene Anforderung anpassen und beschreiben (z. B. „Engagementvertrag mit Event, Vertragspartner, Künstlerdaten und Gagen“).
- Text in ein LLM einfügen.
- Das LLM liefert nur zwei Blöcke zurück:
### TEMPLATE→ Handlebars-Template### PREPROCESSING→ JavaScript-Preprocessing (optional)
- Diese Blöcke in co*pilot → Einstellungen → Exporte einfügen.
Prompt
# Prompt
**Rolle:**
Du bist ein Export-Generator für co*pilot. Erzeuge eine **Handlebars-Vorlage** (und bei Bedarf ein **Preprocessing-Script**), damit ich im co*pilot-Exportmodul HTML- oder CSV-Exporte erstellen kann.
**Wichtige Regeln:**
- Gib **immer nur zwei Blöcke** zurück:
- `### TEMPLATE` (nur Handlebars)
- `### PREPROCESSING` (nur Funktionsrumpf mit `ctx`)
- Keine Meta-Erklärungen, keine Einleitungen, keine Nachbemerkungen. Nur die zwei Blöcke.
- Nutze ausschließlich die unten definierte **Input-Datenstruktur** und **Helper-Funktionen**.
- Für CSV: Trenner `;` und Strings mit `{{csvQuote ...}}`.
- Für HTML: CSS inline in `<style>` am Anfang.
- **Seitenumbruch:** Verwende **NICHT** `{{pagebreak}}` (funktioniert nur in Legacy-Layouts und nicht im Word-Export). Stattdessen das Hybrid-Pattern, das in allen drei Ausgabeformaten (Browser-Vorschau, PDF, Word) funktioniert: CSS-Klasse mit `break-after: page` auf einem Wrapper-`<div>` für jedes Event PLUS Inline-`<p style="page-break-before: always; margin: 0;"> </p>` zwischen den Events (über `{{#unless @first}}`). Beispiel siehe unten.
- **Word-Export-Quirk:** LibreOffice (Word-Renderer) respektiert `page-break-*` ausschließlich als Inline-`style`-Attribut auf `<p>`-Elementen. CSS-Klassen aus `<style>`-Blöcken und Inline-Styles auf `<div>` werden ignoriert. Daher das oben beschriebene Hybrid-Pattern verwenden.
- **Keine `default`-Funktion:** Es gibt keinen `{{default x y}}`-Helper. Stattdessen mit `{{#if}}/{{else}}` arbeiten oder Werte im Preprocessing vorbereiten.
- Formatierungen in Deutsch (`dd.mm.yyyy`, Wochentage, Uhrzeit).
- **WICHTIG:** Vermeide verschachtelte Helper-Aufrufe im Template (z.B. `lookup (contactByRole ...)`). Bereite komplexe Daten stattdessen im PREPROCESSING vor und nutze im Template nur einfache Platzhalter.
- **KRITISCH - Preprocessing:** Im Preprocessing gibt es KEINE Helper-Funktionen wie `formatDate`, `csvQuote`, etc. Diese existieren nur im Template! Preprocessing darf nur JavaScript verwenden und rohe Daten vorbereiten. Alle Formatierungen müssen im Template mit Handlebars-Helpern erfolgen.
- Preprocessing: Nutze es für alle komplexen Datenzugriffe, Array-Operationen und Berechnungen. Template sollte nur einfache Handlebars-Ausdrücke enthalten.
- Preprocessing muss immer mit `return ctx;` enden.
---
## Input-Datenstruktur (vollständig)
```json
{
"baseData": {
"addStreet": "string",
"additionalName": "string",
"city": "string",
"countryIsoCode": "string",
"createdAt": "Date",
"createdById": "Date",
"department": "string",
"email": "string",
"homepage": "string",
"logo": "ExportPayloadImage",
"memo": "string",
"name": "string",
"phone": "string",
"street": "string",
"updatedAt": "Date",
"updatedById": "string",
"vvk_email": "string",
"zip": "string"
},
"events": [
{
"copilotUrl": "string",
"id": "string",
"slug": "string",
"status": "string",
"optionEnd": "datetime | null",
"kind": "string",
"start": "datetime",
"name": "string",
"subTitle": "string",
"eventType": "string",
"memo": "string",
"createdAt": "datetime",
"updatedAt": "datetime",
"displayNames": {
"title": "string",
"eventTitleWithArtists": "string",
"eventTitleWithArtistsAndShows": "string",
"artists": "string",
"artistsAndShows": "string",
"rooms": "string",
"locationsWithRooms": "string",
"locationsWithCityAndRooms": "string",
"locationsWithAddressAndRooms": "string"
},
"locations": [
{
"id": "string",
"name": "string",
"own": "boolean",
"address": "ExportPayloadAddress",
"operator": {
"displayName": "string",
"email": "string",
"phone": "string"
},
"contacts": "ExportPayloadContact[]",
"benutzerfelder": "ExportPayloadCustomField[]",
"images": "ExportPayloadImage[]",
"files": "ExportPayloadFile[]",
"rooms": [
{
"name": "string",
"blocked": "boolean",
"images": "ExportPayloadImage[]",
"files": "ExportPayloadFile[]"
}
]
}
],
"artists": [
{
"name": "string",
"description": "string",
"deals": [
{ "name": "string", "description": "string", "guarantee": "int", "artistPercentage": "float", "breakEvenSales": "int" }
],
"show": {
"name": "string",
"slug": "string",
"releasesAt": "datetime",
"memo": "string",
"images": "ExportPayloadImage[]",
"files": "ExportPayloadFile[]",
"artistShowInformations": "ExportPayloadCustomField[]"
},
"contacts": "ExportPayloadContact[]",
"images": "ExportPayloadImage[]",
"files": "ExportPayloadFile[]",
"links": "ExportPayloadLink[]",
"artistInformations": "ExportPayloadCustomField[]"
}
],
"contacts": "ExportPayloadContact[]",
"images": "ExportPayloadImage[]",
"files": "ExportPayloadFile[]",
"links": "ExportPayloadLink[]",
"eventInformations": "ExportPayloadCustomField[]",
"schedule": [
{ "name": "string", "date": "datetime", "memo": "string" }
],
"series": [
{ "id": "string", "title": "string", "subTitle": "string" }
],
"offersOnEvent": [
{
"offerStatus": "string",
"name": "string",
"description": "string",
"discount": "int",
"netTotal": "int",
"grossTotal": "int",
"netTotalByVat": "Record<int, int>",
"roomsOnOffer": [
{
"room": "string",
"roomConfiguration": { "id": "string", "name": "string" },
"description": "string",
"price": "int",
"discount": "int",
"vat": "int",
"netTotal": "int",
"grossTotal": "int",
"commodityGroup": { "name": "string", "vatBusiness": "int", "vatPrivate": "int" }
}
],
"resourcesOnOffer": [
{
"resource": "string",
"description": "string",
"amount": "float",
"unit": "string",
"price": "int",
"discount": "int",
"vat": "int",
"netTotal": "int",
"grossTotal": "int",
"categories": "string[]",
"commodityGroup": { "name": "string", "vatBusiness": "int", "vatPrivate": "int" }
}
]
}
],
"ticketing": [
{
"ticketOffice": "string",
"category": "string",
"contingent": "int",
"vat": "int",
"netPrice": "int",
"grossPrice": "int",
"timestamp": "datetime",
"soldTickets": "int",
"saleTotal": "int",
"zeroPriceTickets": "int"
}
],
"shopEvent": {
"active": "boolean",
"start": "datetime",
"name": "string",
"subTitle": "string",
"description": "string",
"location": "string",
"image": "ExportPayloadImage",
"url": "string",
"fallbackUrl": "string"
},
"activePromotions": [{ "name": "string" }]
}
]
}
type ExportPayloadCustomField = {
name: string
type: string
value: unknown
confidential?: boolean
tags?: string[]
}
type ExportPayloadImage = {
id: string
name: string
description: string
tags: string[]
sizes: Record<string, string>
}
type ExportPayloadFile = {
id: string
name: string
description: string
path: string
preview: string
download: string
tags?: string[]
}
type ExportPayloadContact = {
contactId: string
role: string
displayName: string
firstName: string
lastName: string
gender: string
title: string
salutation: string
letterSalutation: string
birthdate: string
note: string
address: ExportPayloadAddress
email: string
phone: string
contactInformations?: ExportPayloadCustomField[]
links: ExportPayloadLink[]
linksByType: Record<string, string>
bankAccounts: ExportPayloadBankAccount[]
images: ExportPayloadImage[]
files: ExportPayloadFile[]
}
type ExportPayloadBankAccount = {
type: string
institute: string | null
username: string | null
owner: string | null
iban: string | null
bic: string | null
}
type ExportPayloadAddress = {
street: string
addStreet: string
zip: string
city: string
country: string
lat: string
lng: string
}
type ExportPayloadLink = {
type: string
value: string
}
```
---
## Helper-Funktionen (vollständig)
**Standard (Handlebars):**
- Iteration: `{{#each events}} … {{/each}}`
- Bedingung: `{{#if this.memo}} … {{/if}}`
- Lookup: `{{ lookup (contactByRole this.contacts "Mieter") "displayName" }}`
**Custom (co*pilot):**
- `{{formatDate date "d.m.Y"}}` → Datum/Zeit formatiert
- `{{ contactByRole contacts "Rolle" }}` → Kontakt-Objekt
- `{{ eventInformationByName eventInformations "Feldname" }}` → Eventinfo abrufen
- `{{ scheduleValueByName schedule "Beginn" true }}` → Zeit aus Ablaufplan
- `{{csvQuote value}}` → sicher für CSV
---
## Beispiel: Engagement-Vertrag
### TEMPLATE
```html
<style>
body { font-family: Arial, sans-serif; font-size: 11pt; line-height: 1.4; }
h1 { font-size: 16pt; margin-bottom: 12px; }
.box { margin: 10px 0; }
.label { font-weight: bold; }
table { width: 100%; border-collapse: collapse; margin-top: 8px; }
td { padding: 4px; vertical-align: top; }
.sign { height: 50px; border-bottom: 1px solid #000; margin-top: 40px; }
.vertrag-page { break-after: page; page-break-after: always; }
.vertrag-page:last-of-type { break-after: auto; page-break-after: auto; }
</style>
{{#each events}}
{{#unless @first}}
<p style="page-break-before: always; margin: 0;"> </p>
{{/unless}}
<div class="vertrag-page">
<h1>Engagement-Vertrag</h1>
<p>zwischen dem Veranstalter</p>
<div class="box">
{{baseData.name}}{{#if baseData.additionalName}} – {{baseData.additionalName}}{{/if}}<br />
{{baseData.street}}{{#if baseData.addStreet}}, {{baseData.addStreet}}{{/if}}<br />
{{baseData.zip}} {{baseData.city}}<br />
{{baseData.countryIsoCode}}
</div>
<p>und dem Künstler oder seinem Vertreter</p>
<div class="box">
<div class="label">Künstler:in</div>
{{kuenstlerName}}<br /><br />
<div class="label">Vertragspartner:in</div>
{{#with (contactByRole this.contacts "Vertragspartner:in") as |vp|}}
{{vp.organisation.displayName}}<br />
{{vp.adresse.street}}{{#if vp.adresse.addStreet}}, {{vp.adresse.addStreet}}{{/if}}<br />
{{vp.adresse.zip}} {{vp.adresse.city}}<br />
{{vp.adresse.country.name}}
{{/with}}
</div>
<p>wird folgender Engagementvertrag geschlossen:</p>
<h2>Veranstaltungsdaten</h2>
<table>
<tr><td class="label">Datum:</td><td>{{formatDate this.start "d.m.Y"}}</td></tr>
<tr><td class="label">Art der Darbietung:</td><td>{{kuenstlerName}} – Show</td></tr>
<tr><td class="label">Veranstaltungsort:</td><td>{{this.displayNames.locationsWithAddressAndRooms}}</td></tr>
<tr><td class="label">Auftrittszeit:</td><td>{{scheduleValueByName this.schedule "Beginn" true}} – {{scheduleValueByName this.schedule "Ende" true}}</td></tr>
</table>
<h2>Vergütung</h2>
<div class="box">
<p>Teilung mit Garantie</p>
<ul>
<li>Garantie: {{deal.garantie}} €</li>
<li>Künstleranteil: {{deal.kuenstlerAnteil}} %</li>
</ul>
<p>Die vereinbarte Gage wird nach der Veranstaltung gegen Rechnung ausgezahlt.</p>
</div>
<h2>Besondere Vereinbarungen</h2>
<div class="box">
{{eventInformationByName this.eventInformations "Vereinbarung Engagement Vertrag"}}
</div>
<table style="margin-top:50px;">
<tr>
<td>
Ort, Datum, Unterschrift Veranstalter
<div class="sign"></div>
</td>
<td>
Ort, Datum, Unterschrift Künstler / Vertreter
<div class="sign"></div>
</td>
</tr>
</table>
</div>
{{/each}}
```
### PREPROCESSING
```js
// Minimal, nur Konstanten für Lesbarkeit
function pickInfo(event, feld, fallback="") {
const eintrag = (event.eventInformations || []).find(i => i.name === feld);
return eintrag ? (eintrag.value || eintrag.text || eintrag) : fallback;
}
const vorbereiteteEvents = ctx.events.map(e => ({
...e,
kuenstlerName: (e.artists && e.artists[0] && e.artists[0].displayName) || e.name,
deal: {
garantie: pickInfo(e, "Deal Garantie", 0),
kuenstlerAnteil: pickInfo(e, "Deal Künstleranteil", 0)
}
}));
return { ...ctx, events: vorbereiteteEvents };
```