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(): void { $candidateId = $this->selectedId; if (!$candidateId) { return; } $service = app(QuestionReviewService::class); $service->promoteCandidateToQuestion($candidateId); } public function reject(): void { $candidateId = $this->selectedId; if (!$candidateId) { return; } PreQuestionCandidate::where('id', $candidateId)->update([ 'status' => PreQuestionCandidate::STATUS_REJECTED, ]); } public function bulkApprove(): void { $service = app(QuestionReviewService::class); 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; } } }