Skip to content

feat: [Admin Assistant] Voice Briefings (TTS Integration)#264

Open
jsirish wants to merge 1 commit intomainfrom
autopipe/issue-193
Open

feat: [Admin Assistant] Voice Briefings (TTS Integration)#264
jsirish wants to merge 1 commit intomainfrom
autopipe/issue-193

Conversation

@jsirish
Copy link
Copy Markdown
Member

@jsirish jsirish commented Apr 4, 2026

Implements #193

Changes

  • VoiceBriefing DataObject (app/src/model/VoiceBriefing.php) - Tracks generated briefings with type, transcript, delivery method, audio path
  • BriefingScripts (app/src/services/BriefingScripts.php) - Script templates for 4 briefing types (morning, meeting, evening, urgent) with placeholder replacement
  • VoiceBriefingService (app/src/services/VoiceBriefingService.php) - TTS integration with ElevenLabs API, audio caching by script hash, quiet hours support
  • VoiceBriefingTask (app/src/tasks/VoiceBriefingTask.php) - BuildTask for cron-scheduled generation via /dev/tasks/VoiceBriefingTask
  • Configuration (app/_config/voicebriefings.yml) - Provider, voice, speed, quiet hours config
  • Tests (app/src/test/VoiceBriefings/VoiceBriefingTest.php) - PHPUnit test suite for BriefingScripts

Briefing Types

  1. Morning Briefing - Weather, meetings, tasks, emails, deals
  2. Meeting Reminder - Title, time, location, join link
  3. End-of-Day Summary - Completed tasks, meetings, deals, tomorrow preview
  4. Urgent Alert - Alert type, message, action needed

Features

  • ElevenLabs TTS API integration (with voice mapping)
  • Script hash caching (avoids regenerating identical briefings)
  • Quiet hours support (no generation during configured hours)
  • Duration estimation (~150 wpm speaking rate)
  • Multiple delivery methods (telegram, email, podcast)

Fixes #193

- Add VoiceBriefing DataObject (model/VoiceBriefing.php)
- Add BriefingScripts with 4 template types: morning, meeting, evening, urgent
- Add VoiceBriefingService with ElevenLabs TTS integration
- Add VoiceBriefingTask BuildTask for cron-scheduled generation
- Add voicebriefings.yml configuration
- Add PHPUnit test suite (BriefingScripts tests)
Copilot AI review requested due to automatic review settings April 4, 2026 22:11
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an “Admin Assistant” voice briefing feature to the SilverStripe app, including script templating, persistence for generated briefings, a cron-invokable BuildTask, and YAML configuration for provider/quiet-hours settings.

Changes:

  • Introduces VoiceBriefing DataObject to store transcripts, audio paths, duration, and metadata.
  • Adds BriefingScripts templates/helpers and a VoiceBriefingService with ElevenLabs TTS generation + caching.
  • Adds VoiceBriefingTask BuildTask and YAML config files for scheduling/provider settings, plus unit tests for script generation helpers.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 19 comments.

Show a summary per file
File Description
app/src/Model/VoiceBriefing.php New DataObject for storing generated briefing records and cleanup logic.
app/src/Scripts/BriefingScripts.php Adds briefing script templates and helper utilities (pluralize, duration estimate, formatting).
app/src/services/VoiceBriefingService.php Implements briefing generation, ElevenLabs API call, audio storage, and cache lookup.
app/src/Tasks/VoiceBriefingTask.php Adds BuildTask entrypoint for scheduled generation + cleanup.
app/src/Test/VoiceBriefings/VoiceBriefingTest.php Adds unit tests for BriefingScripts helpers/templates.
app/_config/voicebriefings.yml Adds YAML configuration for service/task/model (lowercase namespace keys).
app/_config/voice.yml Adds a second YAML configuration for the same feature (different casing/Name).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +159 to +163
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Accept: audio/mpeg',
'Content-Type: application/json',
'xi-api-key: ' . $apiKey,
]);
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The request always sets Accept: audio/mpeg, but the saved file extension comes from the configurable $format (mp3/wav/ogg). If $format is set to wav/ogg you’ll still receive MP3 bytes and save them with the wrong extension. Map $format to the correct ElevenLabs output format/headers (or restrict $format to mp3 when using ElevenLabs).

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,223 @@
<?php

namespace App\services;
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file lives under app/src/Scripts but declares namespace App\services; which makes the class hard to find and inconsistent with the rest of the codebase layout. Consider moving it to app/src/services (or changing the namespace to match the directory), and standardising the namespace casing to App\Services.

Suggested change
namespace App\services;
namespace App\Scripts;

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,184 @@
<?php

namespace App\tasks;
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Namespace casing here (App\tasks) is inconsistent with existing task namespaces (e.g. App\ContentHub\Tasks) and with the config file app/_config/voice.yml which uses App\Tasks\VoiceBriefingTask. Standardise to App\Tasks (or update config/class references) to avoid config manifest / class resolution issues.

Suggested change
namespace App\tasks;
namespace App\Tasks;

Copilot uses AI. Check for mistakes.
Comment on lines +111 to +114
protected function getRecipients(): array
{
return [];
}
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getRecipients() currently always returns an empty array, so the task will always print “No recipients configured” and never generate/deliver any briefings. If this is intended as a placeholder, it should be wired up to the actual recipient source (e.g. SiteConfig/Member list) before merging, or guarded behind a feature flag so cron runs don’t silently no-op.

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,147 @@
<?php

namespace App\model;
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Namespace casing here (App\model) doesn’t match other DataObjects (e.g. namespace App\Model; in app/src/Model/Product.php) and conflicts with app/_config/voice.yml which configures App\Model\VoiceBriefing. Please standardise the namespace to App\Model (and update all references/config keys).

Suggested change
namespace App\model;
namespace App\Model;

Copilot uses AI. Check for mistakes.
Comment on lines +3 to +6
namespace App\test\VoiceBriefings;

use PHPUnit\Framework\TestCase;
use App\services\BriefingScripts;
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Namespace casing here (App\\test) is inconsistent with other tests under app/src/Test (e.g. namespace App\\Test\\ClientPortal;). This can break test discovery/autoloading depending on tooling. Recommend using namespace App\\Test\\VoiceBriefings; and updating the BriefingScripts import to the standard service namespace casing as well.

Suggested change
namespace App\test\VoiceBriefings;
use PHPUnit\Framework\TestCase;
use App\services\BriefingScripts;
namespace App\Test\VoiceBriefings;
use PHPUnit\Framework\TestCase;
use App\Services\BriefingScripts;

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,25 @@
---
Name: voice-briefings
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are two new config files (voice.yml and voicebriefings.yml) configuring the same classes with different Name headers and different class-name casing. Keeping both is likely to cause confusing/duplicate config merges and make it unclear which values are effective. Please consolidate into a single config file and ensure class keys match the actual namespaces used in code.

Suggested change
Name: voice-briefings
Name: voice

Copilot uses AI. Check for mistakes.
Comment on lines +5 to +9
App\model\VoiceBriefing:
cache_ttl: 86400

App\services\VoiceBriefingService:
provider: 'elevenlabs'
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Config keys here use App\\model / App\\services (lowercase segments) while other code/config uses App\\Model / App\\Services. SilverStripe config is keyed by the exact class name string, so mixed casing can lead to config not applying. Align these keys with the canonical namespaces used in code (and remove the duplicate config file).

Copilot uses AI. Check for mistakes.
Comment on lines +60 to +63
echo "Generating {$type} briefings...\n";

$service = new \App\services\VoiceBriefingService();
$schedule = self::$schedule;
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The task instantiates the service with new \App\services\VoiceBriefingService(). If VoiceBriefingService is made Configurable/Injectable (recommended), prefer getting it via Injector (or ::singleton()) so configured dependencies/settings are applied consistently.

Copilot uses AI. Check for mistakes.
Comment on lines +65 to +69
public function generateBriefing(string $type, array $data, string $deliveryMethod = 'telegram', ?string $recipientID = null): ?VoiceBriefing
{
// Check quiet hours (skip for urgent alerts)
if ($type !== 'urgent' && $this->isQuietHours()) {
return null;
Copy link

Copilot AI Apr 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New core behaviours in generateBriefing() (quiet-hours skipping, script-hash caching, and persistence of VoiceBriefing records) aren’t covered by tests. Given the potential for subtle regressions (e.g. cache key/recipient handling), it would be good to add unit tests around generateBriefing() using a stubbed TTS client/audio writer.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Admin Assistant] Task 11: Voice Briefings (TTS Integration)

2 participants