Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 19 additions & 14 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@

All notable changes to this project will be documented in this file.

## [1.7.6] - 2026-03-08

### Added
- Deck AI Analytics results are now saved and reused for 24 hours
- Re-opening the analytics panel shows the previous result instantly, with a note indicating when it was last generated

## [1.7.5] - 2026-03-08

### Added
- Per-feature enable/disable toggles for all four AI features (Card Suggestion, Ability Re-roll, Deck Analytics, Card Swap)
- Per-feature token caps configurable per AI feature to control cost and response length
- Per-user rate limiting with configurable hourly and daily request quotas
- System-wide daily and monthly cost budget enforcement with live spend stats in AI Settings
- Per-feature enable/disable controls for all AI features
- Usage limits and budget controls for AI functionality

## [1.7.0] - 2026-03-07

Expand All @@ -19,19 +23,18 @@ All notable changes to this project will be documented in this file.
- Dashboard widget showing the latest version changelog entries

### Changed
- Admin table filters for Cards, Card Types, Hexas, Figures, and Decks now show only the current user's games and types
- Admin cards table now defaults to newest first
- Table filters for Cards, Card Types, and Decks now show only the current user's own content
- Cards table now defaults to newest first

## [1.6.0] - 2026-03-07

### Changed
- Replaced franchise card suit icons with the official Cards Forge brand icon across the site and admin panel
- Removed webtech-solutions and unreality1 domain templates, routes, and assets no longer used in this application

## [1.5.0] - 2026-02-22

### Added
- Configurable background frame for printed card PDFs: supervisors set a system-wide default, users can upload a personal override in their profile, with automatic fallback to the built-in card back image
- Configurable background frame for printed card PDFs, with personal override support and automatic fallback to the built-in card back image

## [1.4.0] - 2026-02-21

Expand All @@ -41,20 +44,22 @@ All notable changes to this project will be documented in this file.
## [1.3.0] - 2026-02-18

### Added
- AI Card Suggestion module for supervisors: generate card data from game context, re-roll card abilities with tone selection, deck synergy analytics report, and one-click card swap suggestions
- AI features demo section on homepage, visible only to selected supervisor users
- AI Card Suggestion: generate card data from game context
- AI Ability Re-roll: regenerate card abilities with tone selection
- AI Deck Analytics: synergy report for decks
- AI Card Swap: one-click card swap suggestions
- AI features demo section on the homepage

## [1.2.0] - 2026-02-18

### Added
- AI provider configuration page for supervisors to manage API credentials, model selection, and generation parameters
- AI usage log for supervisors showing request history, token consumption, and estimated costs
- Secure credential storage and connection testing for AI service integration
- AI provider configuration for administrators
- AI usage log showing request history and consumption statistics

## [1.1.0] - 2025-12-24

### Added
- Custom email template system with variable injection and live preview for supervisors
- Custom email template system with variable injection and live preview
- Scheduled email dispatcher with flexible recipient targeting and automated delivery
- Public changelog page accessible from the site navigation

Expand Down
17 changes: 15 additions & 2 deletions app/Filament/Resources/DeckResource/Pages/ViewDeck.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,22 @@ protected function getHeaderActions(): array
->modalSubmitAction(false)
->modalCancelActionLabel('Close')
->modalContent(function () {
$service = app(DeckAnalyticsService::class);

// Serve cached result if within 24 hours
$cached = $service->getCached($this->record->id);
if ($cached !== null) {
$cachedAt = $cached['_cached_at'] ?? null;
unset($cached['_cached_at']);
return view('filament.modals.deck-analytics', [
'report' => $cached,
'cached_at' => $cachedAt,
]);
}

try {
$report = app(DeckAnalyticsService::class)->analyze($this->record->id);
return view('filament.modals.deck-analytics', ['report' => $report]);
$report = $service->analyze($this->record->id);
return view('filament.modals.deck-analytics', ['report' => $report, 'cached_at' => null]);
} catch (AiLimitExceededException | AiSuggestionException $e) {
Notification::make()
->danger()
Expand Down
4 changes: 4 additions & 0 deletions app/Models/Deck.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ class Deck extends Model
public $timestamps = true;
protected $casts = [
'deck_data' => 'array',
'ai_analytics' => 'array',
'ai_analytics_at' => 'datetime',
];

protected $fillable = [
Expand All @@ -24,6 +26,8 @@ class Deck extends Model
'deck_data',
'pdf_background',
'pdf_overlay',
'ai_analytics',
'ai_analytics_at',
];

public function game()
Expand Down
25 changes: 25 additions & 0 deletions app/Services/Ai/DeckAnalyticsService.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,25 @@ public function __construct(private AiProxyService $ai) {}
* @throws AiSuggestionException
* @throws AiLimitExceededException
*/
/**
* Return cached analytics if run within the last 24 hours, without calling AI.
*/
public function getCached(int $deckId): ?array
{
$deck = Deck::find($deckId);

if (
$deck &&
$deck->ai_analytics !== null &&
$deck->ai_analytics_at !== null &&
$deck->ai_analytics_at->gt(now()->subHours(24))
) {
return array_merge($deck->ai_analytics, ['_cached_at' => $deck->ai_analytics_at->toIso8601String()]);
}

return null;
}

public function analyze(int $deckId): array
{
AiFeatureGuard::assertEnabled('deck_analytics');
Expand Down Expand Up @@ -93,6 +112,12 @@ public function analyze(int $deckId): array
throw new AiSuggestionException('AI returned an invalid analytics structure.');
}

// Persist result and timestamp so we can serve it from cache for 24 hours
$deck->update([
'ai_analytics' => $data,
'ai_analytics_at' => now(),
]);

return $data;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php
/**
* Webtech-solutions 2025, All rights reserved.
*/

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
public function up(): void
{
Schema::table('decks', function (Blueprint $table) {
$table->json('ai_analytics')->nullable()->after('deck_data');
$table->timestamp('ai_analytics_at')->nullable()->after('ai_analytics');
});
}

public function down(): void
{
Schema::table('decks', function (Blueprint $table) {
$table->dropColumn(['ai_analytics', 'ai_analytics_at']);
});
}
};
1 change: 0 additions & 1 deletion public/build/assets/app-3XmD-qMV.css

This file was deleted.

1 change: 1 addition & 0 deletions public/build/assets/app-CkosICLI.css

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion public/build/manifest.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"resources/css/app.css": {
"file": "assets/app-3XmD-qMV.css",
"file": "assets/app-CkosICLI.css",
"src": "resources/css/app.css",
"isEntry": true
},
Expand Down
14 changes: 14 additions & 0 deletions resources/views/filament/modals/deck-analytics.blade.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
<div class="space-y-6 p-2">

{{-- Cached result notice --}}
@if(!empty($cached_at))
<div class="flex items-center gap-2 rounded-lg bg-info-50 dark:bg-info-900/20 border border-info-200 dark:border-info-700 px-4 py-2 text-sm text-info-700 dark:text-info-300">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M12 2a10 10 0 100 20A10 10 0 0012 2z"/>
</svg>
<span>
Showing saved analysis from
<strong>{{ \Carbon\Carbon::parse($cached_at)->diffForHumans() }}</strong>.
A new AI analysis will run after 24 hours.
</span>
</div>
@endif

{{-- Score + Archetype --}}
<div class="flex items-center gap-4">
<div class="text-center">
Expand Down
Loading