feat(broadcast): enhanced ListView + dashboard widget + AI tools

Closes the M7/M9/M10 plan items in one pass since they share patterns.

ListView (M7)
- 4 stats cards at the top: versendet YTD, Ø Öffnungsrate, Ø Klickrate,
  Entwürfe. Same layout pattern as invoices for consistency.
- Status filter chips with live counts per status.
- Search across name + subject.
- Row now shows open-rate per-campaign when available.
- Settings gear in the header matches the invoices polish.

Dashboard widget (M10)
- BroadcastsWidget.svelte: 2x stats (sent YTD + avg open rate), next
  scheduled link, last sent link with open-rate badge. Empty state
  nudges toward creating a first campaign.
- Registered as 'broadcasts' in WIDGET_REGISTRY and the component map.
- Medium default size, no requiredBackend (reads from Dexie only;
  stats are mirrored from the last DetailView poll so no server
  round-trip for the widget).

AI tools (M9)
- 3 tools added to @mana/shared-ai's AI_TOOL_CATALOG:
  - create_campaign_draft (propose) — generates HTML body from a
    topic, lands as a draft; user picks audience + sends via UI
  - list_campaigns (auto) — id/name/subject/status/recipients
  - get_campaign_stats (auto) — rates as 0..1 floats
- broadcast/tools.ts: execute handlers with an HTML→CampaignContent
  shim (stores both html and a minimal Tiptap JSON placeholder so
  ListView renders without the editor having to remount). stripHtml
  helper derives plaintext.
- Registered in data/tools/init.ts after library.

Suggest-style tools (suggest_subject_lines) deliberately omitted —
they're pure generative and don't need an executor. The LLM can
produce subject ideas without a tool call.

Verified:
- pnpm check: 0 broadcast errors (4 pre-existing errors in articles
  module from parallel work, not mine)
- shared-ai test suite: 44/44 green (function-schema roundtrips the
  expanded catalog cleanly)
- mana-ai drift guard: 41/41 green

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-21 15:27:59 +02:00
parent c5a76d726c
commit 75832faef7
8 changed files with 623 additions and 11 deletions

View file

@ -1323,6 +1323,79 @@ export const AI_TOOL_CATALOG: readonly ToolSchema[] = [
},
],
},
// ── Broadcast (Newsletter) ───────────────────────────────
{
name: 'create_campaign_draft',
module: 'broadcast',
description:
'Erstellt einen Newsletter-/Kampagnen-Entwurf mit Name, Betreff, optionalem Preheader und fertigem HTML-Body. Empfaengerliste bleibt leer — der Nutzer waehlt sie in der UI. Gibt die ID zurueck.',
defaultPolicy: 'propose',
parameters: [
{
name: 'name',
type: 'string',
description: 'Interner Arbeitstitel der Kampagne',
required: true,
},
{
name: 'subject',
type: 'string',
description: 'E-Mail-Betreff (was im Posteingang steht)',
required: true,
},
{
name: 'preheader',
type: 'string',
description: 'Vorschau-Text neben dem Betreff in Gmail',
required: false,
},
{
name: 'htmlContent',
type: 'string',
description:
'Body als HTML. Erlaubte Tags: p, h1, h2, h3, ul, ol, li, a, strong, em, br. Links verwenden href="https://…".',
required: true,
},
],
},
{
name: 'list_campaigns',
module: 'broadcast',
description:
'Listet Kampagnen (id, name, subject, status, Empfaengerzahl, sentAt) — optional nach Status gefiltert.',
defaultPolicy: 'auto',
parameters: [
{
name: 'status',
type: 'string',
description: 'Nur diesen Status zeigen',
required: false,
enum: ['draft', 'scheduled', 'sending', 'sent', 'cancelled'],
},
{
name: 'limit',
type: 'number',
description: 'Maximale Anzahl (Standard 20)',
required: false,
},
],
},
{
name: 'get_campaign_stats',
module: 'broadcast',
description:
'Gibt Kennzahlen zu einer Kampagne zurueck: Oeffnungsrate, Klickrate, Bounce-Rate, Abmelderate (jeweils 0..1).',
defaultPolicy: 'auto',
parameters: [
{
name: 'campaignId',
type: 'string',
description: 'ID der Kampagne (aus list_campaigns)',
required: true,
},
],
},
];
// ═══════════════════════════════════════════════════════════════