| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188 |
- <?php
- namespace App\Filament\Pages;
- use App\Models\PreQuestionCandidate;
- use App\Services\QuestionReviewService;
- use App\Services\QuestionGenerationService;
- use App\Services\AiKnowledgeService;
- use App\Services\AiSolutionService;
- use Filament\Pages\Page;
- use UnitEnum;
- class QuestionReviewWorkbench extends Page
- {
- protected static bool $shouldRegisterNavigation = true;
- protected static ?string $navigationLabel = '题目审核工作台';
- protected static UnitEnum|string|null $navigationGroup = '卷子导入流程';
- protected static ?int $navigationSort = 5;
- protected string $view = 'filament.pages.question-review-workbench';
- public ?int $selectedId = null;
- public array $selectedIds = [];
- public ?string $groupBy = 'paper';
- public function candidates()
- {
- return PreQuestionCandidate::query()
- ->with(['sourcePaper', 'part'])
- ->orderBy('sequence')
- ->limit(200)
- ->get();
- }
- public function groupedCandidates()
- {
- $candidates = $this->candidates();
- if ($this->groupBy === 'part') {
- return $candidates->groupBy(fn ($candidate) => $candidate->part?->title ?? '未分区块');
- }
- if ($this->groupBy === 'type') {
- return $candidates->groupBy(fn ($candidate) => $candidate->meta['question_type'] ?? '未标注题型');
- }
- return $candidates->groupBy(fn ($candidate) => $candidate->sourcePaper?->title ?? '未分卷');
- }
- public function stats(): array
- {
- $all = PreQuestionCandidate::query()->count();
- $pending = PreQuestionCandidate::query()->where('status', PreQuestionCandidate::STATUS_PENDING)->count();
- $reviewed = PreQuestionCandidate::query()->where('status', PreQuestionCandidate::STATUS_REVIEWED)->count();
- $accepted = PreQuestionCandidate::query()->where('status', PreQuestionCandidate::STATUS_ACCEPTED)->count();
- return compact('all', 'pending', 'reviewed', 'accepted');
- }
- public function selectCandidate(int $candidateId): void
- {
- $this->selectedId = $candidateId;
- }
- public function selectAllVisible(): void
- {
- $this->selectedIds = $this->candidates()->pluck('id')->toArray();
- }
- public function clearSelection(): void
- {
- $this->selectedIds = [];
- }
- public function approve(int $candidateId, QuestionReviewService $service): void
- {
- $service->promoteCandidateToQuestion($candidateId);
- }
- public function reject(int $candidateId): void
- {
- PreQuestionCandidate::where('id', $candidateId)->update([
- 'status' => PreQuestionCandidate::STATUS_REJECTED,
- ]);
- }
- public function bulkApprove(QuestionReviewService $service): void
- {
- foreach ($this->selectedIds as $candidateId) {
- $service->promoteCandidateToQuestion((int) $candidateId);
- }
- }
- public function bulkReject(): void
- {
- PreQuestionCandidate::query()
- ->whereIn('id', $this->selectedIds)
- ->update(['status' => PreQuestionCandidate::STATUS_REJECTED]);
- }
- public function aiAssist(): void
- {
- $candidate = $this->currentCandidate();
- if (!$candidate) {
- return;
- }
- $sourceText = $candidate->stem ?: (string) $candidate->raw_markdown;
- $result = app(QuestionGenerationService::class)->generateFromSource($sourceText);
- if (!($result['success'] ?? false)) {
- return;
- }
- $question = $result['question'] ?? [];
- $meta = $candidate->meta ?? [];
- $meta['question_type'] = $question['question_type'] ?? $meta['question_type'] ?? null;
- $meta['difficulty'] = $question['difficulty'] ?? $meta['difficulty'] ?? null;
- $meta['kp_codes'] = $question['knowledge_points'] ?? $meta['kp_codes'] ?? [];
- $meta['solution'] = $question['solution'] ?? $meta['solution'] ?? null;
- $meta['solution_steps'] = $question['solution_steps'] ?? $meta['solution_steps'] ?? [];
- $candidate->update([
- 'stem' => $question['stem'] ?? $candidate->stem,
- 'options' => $question['options'] ?? $candidate->options,
- 'meta' => $meta,
- ]);
- }
- public function aiMatchKp(): void
- {
- $candidate = $this->currentCandidate();
- if (!$candidate) {
- return;
- }
- $text = $candidate->stem ?: (string) $candidate->raw_markdown;
- $matches = app(AiKnowledgeService::class)->matchKnowledgePointsByAi($text);
- $kpCodes = array_values(array_filter(array_map(fn ($item) => $item['kp_code'] ?? null, $matches)));
- $meta = $candidate->meta ?? [];
- $meta['kp_codes'] = $kpCodes;
- $candidate->update(['meta' => $meta]);
- }
- public function aiGenerateSolution(): void
- {
- $candidate = $this->currentCandidate();
- if (!$candidate) {
- return;
- }
- $result = app(AiSolutionService::class)->generateSolution($candidate->stem ?? '');
- $meta = $candidate->meta ?? [];
- $meta['solution'] = $result['solution'] ?? '';
- $meta['solution_steps'] = $result['steps'] ?? [];
- $candidate->update(['meta' => $meta]);
- }
- public function currentCandidate(): ?PreQuestionCandidate
- {
- return $this->selectedId ? PreQuestionCandidate::query()->find($this->selectedId) : null;
- }
- public function jumpToNextIssue(): void
- {
- $candidates = $this->candidates();
- $currentIndex = $candidates->search(fn ($item) => $item->id === $this->selectedId);
- $find = function ($start) use ($candidates) {
- for ($i = $start; $i < $candidates->count(); $i++) {
- $candidate = $candidates[$i];
- $meta = $candidate->meta ?? [];
- if (empty($meta['difficulty']) || empty($meta['kp_codes']) || empty($meta['answer']) || empty($meta['solution'])) {
- return $candidate->id;
- }
- }
- return null;
- };
- $nextId = $find(($currentIndex !== false ? $currentIndex + 1 : 0)) ?? $find(0);
- if ($nextId) {
- $this->selectedId = $nextId;
- }
- }
- }
|