Content Management
AI-powered content management system for rephrasing hotel, activity, and transfer descriptions to match the Volare brand voice. Supports single-record editing with review and bulk operations via queued jobs.
When to Use
Section titled “When to Use”- Brand consistency — ensure all supplier content follows the same tone, vocabulary, and style guidelines
- Single-record rephrase — click “Rephrase Content” on any hotel, activity, or transfer Edit page to get AI suggestions with a before/after review modal
- Bulk rephrase — select multiple records on a list page, queue them for AI rephrasing, then review drafts in the Content Rephrase Dashboard
- Content rules — define per-field constraints (max length, forbidden words, required topics) that the AI validates against during generation
Configuration
Section titled “Configuration”Required env vars: OPENROUTER_API_KEY, OPENROUTER_BASE_URL, OPENROUTER_MODEL
Config file: config/ai.php — model resolution via UsesConfiguredModel concern
Rate limiter: content-rephrase — 20 requests/minute, defined in AppServiceProvider
Brand Voice Profiles
Section titled “Brand Voice Profiles”TCAI Profiles (tcai_profiles table) store brand voice configuration. Each profile has a service_type discriminator:
null(Global) — brand-wide tone, vocabulary, and personality applied to all content- Service-specific (
hotel,activity,transfer,cms_home,cms_about_us,cms_legal) — guidelines tailored to a content type
The admin form at /admin/tcai-profiles has four tabs:
| Tab | Fields |
|---|---|
| Profile | Name, service type selector, description, default toggle |
| Brand Voice | Tone, target audience, vocabulary preferences, forbidden words (tags), personality traits (tags), communication standards, formatting rules |
| Examples | Repeater of before/after pairs with labels (max 10) |
| Custom Instructions | Free-text instructions appended to AI prompts |
TcaiProfile::getBrandVoicePrompt() compiles all structured fields into a single prompt string used by agents.
Lookup logic: GetBrandVoiceTool fetches the global default profile plus the service-type-specific default, merging both into the agent context.
Source: backend/app/Models/TcaiProfile.php, backend/app/Filament/Resources/TcaiProfiles/Schemas/TcaiProfileForm.php
Content Rules
Section titled “Content Rules”Per-field validation rules stored in the content_rules table and managed via the ContentRuleResource Filament resource (nav group: “AI Configuration”).
Each rule targets a service_type + field_name combination and contains a JSON rules object with constraints such as:
min_length/max_length— character limitsmin_words/max_words— word count limitsforbidden_words— array of words that must not appear
Rules are fetched by ContentRule::getFor($serviceType, $field) and exposed to the agent via GetContentRulesTool. The ValidateContentTool performs deterministic PHP validation against these rules, enabling a self-correction loop where the agent fixes violations and re-validates.
Source: backend/app/Models/ContentRule.php, backend/app/Filament/Resources/ContentRules/ContentRuleResource.php
Content Field Map
Section titled “Content Field Map”ContentFieldMap defines which fields are rephrased for each service type:
| Service Type | Fields |
|---|---|
hotel | hotel_name, address, description |
activity | name, description, inclusions, warnings, notes |
transfer | name, description |
Source: backend/app/Ai/ContentFieldMap.php
ContentRephraseAgent
Section titled “ContentRephraseAgent”The agent implements Agent, HasStructuredOutput, and HasTools from laravel/ai. Configuration: 8000 max tokens, temperature 0.7, 10 max steps, 120s timeout.
Prescribed Workflow
Section titled “Prescribed Workflow”The agent’s instructions define a strict tool-calling order:
get_brand_voice— fetch global + service-specific brand voiceget_content_rules— fetch per-field constraintsget_entity_context— load the actual record with relationships (hotel amenities, activity inclusions, etc.)- (Optional)
search_similar_content— find similar entities in the same city to avoid duplicate phrasing - (Optional)
get_example_content— fetch high-quality examples as style reference - Generate rephrased content as structured JSON output
validate_content— deterministic PHP check against content rules- If validation fails, fix violations and re-validate
The structured output schema is dynamically built from the field names for the given service type.
Source: backend/app/Ai/Agents/ContentRephraseAgent.php, backend/app/Ai/Tools/Content/
Single-Record Rephrasing
Section titled “Single-Record Rephrasing”Edit pages for hotels, activities, and transfers use the HandlesContentRephrase trait to add a “Rephrase Content” header action.
Flow:
- Admin clicks the sparkles button on the Edit page
ContentRephraseService::rephrase()runs theContentRephraseAgentwith the record’s current content- A review modal opens showing before/after for each changed field
- Each field has an editable “After” textarea and an accept/reject toggle
- Admin clicks “Apply Accepted” — only toggled-on fields update the form (not yet saved to DB)
- Admin reviews the form and saves normally
The modal also includes a “Discard All” button to reject everything.
Source: backend/app/Filament/Concerns/HandlesContentRephrase.php, backend/app/Services/ContentRephraseService.php
Bulk Rephrasing
Section titled “Bulk Rephrasing”Dispatching
Section titled “Dispatching”On list pages (hotels, activities, transfers), the RephraseContentBulkAction adds a bulk action. Selecting records and clicking “Rephrase Content” opens a modal with:
- Brand voice profile selector (global profiles)
- Content guideline selector (service-type-specific profiles)
- Additional instructions textarea
Submitting dispatches a ContentRephraseBatch and queues individual RephraseRecordJob instances via Bus::batch().
Job Processing
Section titled “Job Processing”RephraseRecordJob is rate-limited to 20/minute, retries twice with 30s/60s backoff, and has a 120s timeout. Each job:
- Loads the draft and its polymorphic record
- Calls
ContentRephraseService::rephrase() - Stores rephrased content on the
ContentRephraseDraft - Increments batch counters atomically
- Checks batch completion in a locked transaction
Review Dashboard
Section titled “Review Dashboard”The ContentRephraseDashboard Filament page (nav group: “AI Configuration”) is admin-only — supplier-managers and other non-admin roles cannot see it in the navigation nor reach it via direct URL. It shows two views:
Batches table — lists all batches with status, record counts, and actions:
- “Review Drafts” — drill into individual drafts
- “Apply Approved” — bulk-apply all approved drafts in the batch
Drafts table — per-draft actions:
- Approve — mark as ready to apply (from Pending status)
- Reject — discard the draft
- Apply — write the rephrased content to the actual record
Draft Lifecycle
Section titled “Draft Lifecycle”Drafts follow this status flow: Pending -> Approved -> Applied (or Rejected / Failed at any point).
Batch status transitions: Processing -> Completed (all succeeded) or Failed (any failures).
Source: backend/app/Filament/Actions/RephraseContentBulkAction.php, backend/app/Services/BulkRephraseService.php, backend/app/Jobs/RephraseRecordJob.php, backend/app/Filament/Pages/ContentRephraseDashboard.php
Data Model
Section titled “Data Model”| Model | Table | Purpose |
|---|---|---|
TcaiProfile | tcai_profiles | Brand voice and style configuration |
ContentRule | content_rules | Per-field validation rules |
ContentRephraseBatch | content_rephrase_batches | Tracks a bulk rephrase operation |
ContentRephraseDraft | content_rephrase_drafts | Individual record draft (polymorphic via rephrasable) |
Source: backend/app/Models/ContentRule.php, backend/app/Models/ContentRephraseBatch.php, backend/app/Models/ContentRephraseDraft.php
Related
Section titled “Related”- AI System — parent AI architecture, other agents, and TCAI chat
- Product Templates — AI content generation for tour templates
- Source:
backend/app/Ai/Agents/ContentRephraseAgent.php - Source:
backend/app/Ai/Tools/Content/— all 6 content tools - Source:
backend/app/Services/ContentRephraseService.php - Source:
backend/app/Services/BulkRephraseService.php - Source:
backend/app/Filament/Concerns/HandlesContentRephrase.php - Source:
backend/app/Filament/Pages/ContentRephraseDashboard.php