Skip to content

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.

  • 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

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

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:

TabFields
ProfileName, service type selector, description, default toggle
Brand VoiceTone, target audience, vocabulary preferences, forbidden words (tags), personality traits (tags), communication standards, formatting rules
ExamplesRepeater of before/after pairs with labels (max 10)
Custom InstructionsFree-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

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 limits
  • min_words / max_words — word count limits
  • forbidden_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

ContentFieldMap defines which fields are rephrased for each service type:

Service TypeFields
hotelhotel_name, address, description
activityname, description, inclusions, warnings, notes
transfername, description

Source: backend/app/Ai/ContentFieldMap.php

The agent implements Agent, HasStructuredOutput, and HasTools from laravel/ai. Configuration: 8000 max tokens, temperature 0.7, 10 max steps, 120s timeout.

The agent’s instructions define a strict tool-calling order:

  1. get_brand_voice — fetch global + service-specific brand voice
  2. get_content_rules — fetch per-field constraints
  3. get_entity_context — load the actual record with relationships (hotel amenities, activity inclusions, etc.)
  4. (Optional) search_similar_content — find similar entities in the same city to avoid duplicate phrasing
  5. (Optional) get_example_content — fetch high-quality examples as style reference
  6. Generate rephrased content as structured JSON output
  7. validate_content — deterministic PHP check against content rules
  8. 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/

Edit pages for hotels, activities, and transfers use the HandlesContentRephrase trait to add a “Rephrase Content” header action.

Flow:

  1. Admin clicks the sparkles button on the Edit page
  2. ContentRephraseService::rephrase() runs the ContentRephraseAgent with the record’s current content
  3. A review modal opens showing before/after for each changed field
  4. Each field has an editable “After” textarea and an accept/reject toggle
  5. Admin clicks “Apply Accepted” — only toggled-on fields update the form (not yet saved to DB)
  6. 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

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().

RephraseRecordJob is rate-limited to 20/minute, retries twice with 30s/60s backoff, and has a 120s timeout. Each job:

  1. Loads the draft and its polymorphic record
  2. Calls ContentRephraseService::rephrase()
  3. Stores rephrased content on the ContentRephraseDraft
  4. Increments batch counters atomically
  5. Checks batch completion in a locked transaction

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

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

ModelTablePurpose
TcaiProfiletcai_profilesBrand voice and style configuration
ContentRulecontent_rulesPer-field validation rules
ContentRephraseBatchcontent_rephrase_batchesTracks a bulk rephrase operation
ContentRephraseDraftcontent_rephrase_draftsIndividual record draft (polymorphic via rephrasable)

Source: backend/app/Models/ContentRule.php, backend/app/Models/ContentRephraseBatch.php, backend/app/Models/ContentRephraseDraft.php

  • 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