user()?->can('manage_ai_agent') ?? false; } public function table(Table $table): Table { return $table ->records(function (?string $search, array $filters, int $page, int $recordsPerPage): LengthAwarePaginator { $result = app(BnfexpressAdminClient::class)->listFaqs( q: $search, search: $filters['search_mode']['value'] ?? 'normal', limit: $recordsPerPage, offset: ($page - 1) * $recordsPerPage, ); return $this->paginate($result, 'faqs', $page, $recordsPerPage); }) ->columns([ TextColumn::make('id'), TextColumn::make('content') ->limit(80) ->wrap(), TextColumn::make('metadata') ->formatStateUsing(fn (mixed $state): string => json_encode($state ?? [], JSON_THROW_ON_ERROR)) ->toggleable(isToggledHiddenByDefault: true), ]) ->searchable() ->filters([ SelectFilter::make('search_mode') ->label('Search mode') ->options([ 'normal' => 'Normal (substring)', 'semantic' => 'Semantic (meaning-based)', ]) ->default('normal'), ]) ->recordActions([ $this->editAction(), $this->deleteAction(), ]) ->headerActions([ $this->createAction(), ]); } protected function createAction(): Action { return Action::make('create') ->label('New FAQ') ->icon(Heroicon::OutlinedPlus) ->schema($this->formSchema()) ->action(function (array $data): void { $this->callBnfexpress( fn () => app(BnfexpressAdminClient::class)->createFaq($data['content'], $data['metadata'] ?? []), successTitle: 'FAQ created', failureTitle: 'Failed to create FAQ', ); $this->resetTable(); }); } protected function editAction(): Action { return Action::make('edit') ->icon(Heroicon::OutlinedPencilSquare) ->fillForm(fn (array $record): array => $record) ->schema($this->formSchema()) ->action(function (array $data, array $record): void { $this->callBnfexpress( fn () => app(BnfexpressAdminClient::class)->updateFaq($record['id'], $data['content'], $data['metadata'] ?? []), successTitle: 'FAQ updated', failureTitle: 'Failed to update FAQ', ); $this->resetTable(); }); } protected function deleteAction(): Action { return Action::make('delete') ->color('danger') ->icon(Heroicon::OutlinedTrash) ->requiresConfirmation() ->action(function (array $record): void { $this->callBnfexpress( fn () => app(BnfexpressAdminClient::class)->deleteFaq($record['id']), successTitle: 'FAQ deleted', failureTitle: 'Failed to delete FAQ', ); $this->resetTable(); }); } /** * @return array */ protected function formSchema(): array { return [ Textarea::make('content') ->required() ->rows(4), KeyValue::make('metadata'), ]; } /** * bnfexpress's list/faqs and list/agent-instructions endpoints return a * bare JSON array (confirmed live via `php artisan bnfexpress:smoke-test`), * with no total/limit/offset envelope — so there's no real total to * report, and this falls back to a "there might be one more page" * heuristic (also tolerates a {total, <$itemsKey>} envelope, in case * that ever changes). * * @param array $result */ private function paginate(array $result, string $itemsKey, int $page, int $recordsPerPage): LengthAwarePaginator { $items = $result[$itemsKey] ?? (array_is_list($result) ? $result : []); $total = $result['total'] ?? (($page - 1) * $recordsPerPage) + count($items) + (count($items) === $recordsPerPage ? 1 : 0); return new LengthAwarePaginator( items: collect($items)->mapWithKeys(fn (array $item): array => [$item['id'] => $item]), total: $total, perPage: $recordsPerPage, currentPage: $page, ); } }