From c0281ec7143c90c09263bd3045f464c287581fe1 Mon Sep 17 00:00:00 2001 From: Jurriaan Roelofs Date: Mon, 11 Aug 2025 09:19:59 +0200 Subject: [PATCH] feat: add time window configuration UI for content recency filtering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add "Favor recent content" checkbox with opt-in design philosophy - Add "Count interactions from" dropdown with hardcoded second values - Implement progressive disclosure using #states API - Add contextual help section with content-type specific recommendations - Update query() method to use getThompsonScoresWithWindow() when enabled - Update adminSummary() to display active time window settings - Support time windows: Last month, 3 months, 6 months, year - Maintain backward compatibility when checkbox is disabled Enables content marketers to focus AI recommendations on recently active content. Perfect for news sites, blogs, seasonal products, and campaign-driven content. Evergreen content sites can leave the feature disabled. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/Plugin/views/sort/AISorting.php | 94 ++++++++++++++++++++++++++++- 1 file changed, 91 insertions(+), 3 deletions(-) diff --git a/src/Plugin/views/sort/AISorting.php b/src/Plugin/views/sort/AISorting.php index 81fd57a..91136f9 100644 --- a/src/Plugin/views/sort/AISorting.php +++ b/src/Plugin/views/sort/AISorting.php @@ -83,6 +83,9 @@ protected function defineOptions() { $options = parent::defineOptions(); $options['order'] = ['default' => '']; $options['cache_max_age'] = ['default' => 60]; + $options['favor_recent'] = ['default' => FALSE]; + // 3 months default + $options['time_window_seconds'] = ['default' => 7776000]; return $options; } @@ -94,7 +97,17 @@ public function query() { $this->ensureMyTable(); $experiment_uuid = sha1($this->view->id() . ':' . $this->view->current_display); - $scores = $this->experimentManager->getThompsonScores($experiment_uuid); + + // Only apply time window if "Favor recent content" is enabled. + $time_window_seconds = NULL; + if ($this->options['favor_recent']) { + $time_window_seconds = $this->options['time_window_seconds']; + } + + $scores = $this->experimentManager->getThompsonScoresWithWindow( + $experiment_uuid, + $time_window_seconds + ); if (empty($scores)) { throw new \RuntimeException(sprintf( @@ -143,8 +156,53 @@ public function buildOptionsForm(&$form, FormStateInterface $form_state) { How it works:
• Turns: When content appears in this view
• Rewards: When users click on that content
- • The algorithm balances showing popular content with exploring new options

- Best for: News feeds, product listings, blog posts, or any content where user engagement matters.'), + • The algorithm balances showing popular content with exploring new options.'), + ]; + + $form['ai_sorting_settings']['favor_recent'] = [ + '#type' => 'checkbox', + '#title' => $this->t('Favor recent content'), + '#default_value' => $this->options['favor_recent'] ?? FALSE, + '#description' => $this->t('Enable this for content that becomes outdated (news, campaigns, seasonal products).'), + ]; + + $form['ai_sorting_settings']['time_window_seconds'] = [ + '#type' => 'select', + '#title' => $this->t('Count interactions from'), + '#default_value' => $this->options['time_window_seconds'] ?? 7776000, + '#options' => [ + // 30 * 24 * 60 * 60 + 2592000 => $this->t('Last month'), + // 90 * 24 * 60 * 60 + 7776000 => $this->t('Last 3 months'), + // 180 * 24 * 60 * 60 + 15552000 => $this->t('Last 6 months'), + // 365 * 24 * 60 * 60 + 31536000 => $this->t('Last year'), + ], + '#states' => [ + 'visible' => [ + ':input[name="options[ai_sorting_settings][favor_recent]"]' => ['checked' => TRUE], + ], + ], + '#description' => $this->t('Only recently active content influences recommendations.'), + ]; + + $form['ai_sorting_settings']['help'] = [ + '#type' => 'details', + '#title' => $this->t('Which timeframe should I choose?'), + '#open' => FALSE, + '#states' => [ + 'visible' => [ + ':input[name="options[ai_sorting_settings][favor_recent]"]' => ['checked' => TRUE], + ], + ], + '#description' => $this->t(' + News & announcements: Last month
+ Blog posts: Last 3 months
+ Products: Last 6 months
+ Leave unchecked for: Documentation, tutorials, evergreen content + '), ]; $url = Url::fromUri('https://en.wikipedia.org/wiki/Thompson_sampling', [ @@ -186,6 +244,14 @@ public function submitOptionsForm(&$form, FormStateInterface $form_state) { $options = &$form_state->getValue('options'); + if (isset($options['ai_sorting_settings']['favor_recent'])) { + $this->options['favor_recent'] = $options['ai_sorting_settings']['favor_recent']; + } + + if (isset($options['ai_sorting_settings']['time_window_seconds'])) { + $this->options['time_window_seconds'] = $options['ai_sorting_settings']['time_window_seconds']; + } + if (isset($options['ai_sorting_settings']['advanced']['cache_max_age'])) { $this->options['cache_max_age'] = $options['ai_sorting_settings']['advanced']['cache_max_age']; } @@ -223,6 +289,28 @@ public function submitOptionsForm(&$form, FormStateInterface $form_state) { public function adminSummary() { $summary = []; + // Time window summary. + if (!empty($this->options['favor_recent'])) { + $time_window_seconds = $this->options['time_window_seconds']; + if ($time_window_seconds == 2592000) { + $summary[] = $this->t('Time window: Last month'); + } + elseif ($time_window_seconds == 7776000) { + $summary[] = $this->t('Time window: Last 3 months'); + } + elseif ($time_window_seconds == 15552000) { + $summary[] = $this->t('Time window: Last 6 months'); + } + elseif ($time_window_seconds == 31536000) { + $summary[] = $this->t('Time window: Last year'); + } + else { + $days = round($time_window_seconds / 86400); + $summary[] = $this->t('Time window: Last @days days', ['@days' => $days]); + } + } + + // Cache summary. $cache_max_age = $this->options['cache_max_age']; if ($cache_max_age == 0) { $summary[] = $this->t('Cache: Never cache');