@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
|
||||
namespace Modules\AiAgent\Filament\Pages;
|
||||
|
||||
use BackedEnum;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\BulkAction;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Pages\Page;
|
||||
use Filament\Support\Icons\Heroicon;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Concerns\InteractsWithTable;
|
||||
use Filament\Tables\Contracts\HasTable;
|
||||
use Filament\Tables\Filters\SelectFilter;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Support\Collection;
|
||||
use Modules\AiAgent\Filament\Concerns\HandlesBnfexpressErrors;
|
||||
use Modules\AiAgent\Filament\Concerns\PaginatesBnfexpressLists;
|
||||
use Modules\Shared\Bnfexpress\BnfexpressAdminClient;
|
||||
use UnitEnum;
|
||||
|
||||
/**
|
||||
* Browse bnfexpress's "suggestion misses" — queries typed by real users that
|
||||
* no suggestion tier answered — and either dismiss them (noise) or promote
|
||||
* a batch straight into the suggestions bank. Read-mostly: no create/edit,
|
||||
* these rows are only ever produced by bnfexpress's own suggest pipeline.
|
||||
*/
|
||||
class ManageSuggestionMisses extends Page implements HasTable
|
||||
{
|
||||
use HandlesBnfexpressErrors;
|
||||
use InteractsWithTable;
|
||||
use PaginatesBnfexpressLists;
|
||||
|
||||
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedMagnifyingGlassCircle;
|
||||
|
||||
protected static string|UnitEnum|null $navigationGroup = 'AI Agent';
|
||||
|
||||
protected static ?string $navigationLabel = 'Suggestion Misses';
|
||||
|
||||
protected static ?string $title = 'Suggestion Misses';
|
||||
|
||||
protected string $view = 'ai-agent::filament.pages.manage-suggestion-misses';
|
||||
|
||||
public static function canAccess(): bool
|
||||
{
|
||||
return auth()->user()?->can('manage_ai_agent') ?? false;
|
||||
}
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->records(function (array $filters, int $page, int $recordsPerPage): LengthAwarePaginator {
|
||||
$wasUsed = $filters['was_used']['value'] ?? null;
|
||||
|
||||
$result = app(BnfexpressAdminClient::class)->listSuggestionMisses(
|
||||
wasUsed: $wasUsed === null || $wasUsed === '' ? null : (bool) $wasUsed,
|
||||
limit: $recordsPerPage,
|
||||
offset: ($page - 1) * $recordsPerPage,
|
||||
);
|
||||
|
||||
return $this->paginateBareList($result, 'misses', 'id', $page, $recordsPerPage);
|
||||
})
|
||||
->columns([
|
||||
TextColumn::make('id'),
|
||||
TextColumn::make('text_norm')
|
||||
->limit(80)
|
||||
->wrap(),
|
||||
TextColumn::make('lang')
|
||||
->placeholder('—'),
|
||||
TextColumn::make('syllables')
|
||||
->placeholder('—'),
|
||||
IconColumn::make('was_used')
|
||||
->boolean(),
|
||||
TextColumn::make('created_at')
|
||||
->dateTime()
|
||||
->sortable(),
|
||||
])
|
||||
->defaultSort('created_at', 'desc')
|
||||
// SelectFilter (not TernaryFilter) so the value lands in
|
||||
// $filters['was_used']['value'] predictably — TernaryFilter's
|
||||
// internal field key isn't documented for the custom-data path.
|
||||
->filters([
|
||||
SelectFilter::make('was_used')
|
||||
->label('Used?')
|
||||
->options(['1' => 'Used', '0' => 'Not used']),
|
||||
])
|
||||
->recordActions([
|
||||
$this->dismissAction(),
|
||||
])
|
||||
->toolbarActions([
|
||||
$this->promoteBulkAction(),
|
||||
]);
|
||||
}
|
||||
|
||||
protected function dismissAction(): Action
|
||||
{
|
||||
return Action::make('dismiss')
|
||||
->color('danger')
|
||||
->icon(Heroicon::OutlinedTrash)
|
||||
->requiresConfirmation()
|
||||
->action(function (array $record): void {
|
||||
$this->callBnfexpress(
|
||||
fn () => app(BnfexpressAdminClient::class)->dismissSuggestionMiss($record['id']),
|
||||
successTitle: 'Miss dismissed',
|
||||
failureTitle: 'Failed to dismiss miss',
|
||||
);
|
||||
|
||||
$this->resetTable();
|
||||
});
|
||||
}
|
||||
|
||||
protected function promoteBulkAction(): BulkAction
|
||||
{
|
||||
return BulkAction::make('promote')
|
||||
->label('Promote Selected')
|
||||
->icon(Heroicon::OutlinedArrowUp)
|
||||
->fetchSelectedRecords(false)
|
||||
->schema([
|
||||
TextInput::make('lang')
|
||||
->label('Language override')
|
||||
->helperText("Applied to every selected miss; leave blank to keep each one's own language.")
|
||||
->maxLength(10),
|
||||
TextInput::make('intent'),
|
||||
])
|
||||
->deselectRecordsAfterCompletion()
|
||||
->action(function (array $data, Collection $records): void {
|
||||
// Same caveat as ManageSuggestions' deleteSelected — the collection
|
||||
// holds full row arrays, not just keys, for a custom-data table.
|
||||
$this->callBnfexpressForResult(
|
||||
fn () => app(BnfexpressAdminClient::class)->promoteSuggestionMisses(
|
||||
$records->keys()->all(),
|
||||
$data['lang'] ?: null,
|
||||
$data['intent'] ?: null,
|
||||
),
|
||||
function (array $result): void {
|
||||
Notification::make()
|
||||
->title("{$result['created']} promoted, {$result['skipped']} skipped")
|
||||
->success()
|
||||
->send();
|
||||
|
||||
$this->resetTable();
|
||||
},
|
||||
failureTitle: 'Failed to promote suggestion misses',
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user