QuestionGeneration.php 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. <?php
  2. namespace App\Filament\Pages;
  3. use App\Services\QuestionBankService;
  4. use App\Services\KnowledgeGraphService;
  5. use BackedEnum;
  6. use Filament\Notifications\Notification;
  7. use Filament\Pages\Page;
  8. use UnitEnum;
  9. use Livewire\Attributes\Computed;
  10. use Illuminate\Support\Facades\Session;
  11. class QuestionGeneration extends Page
  12. {
  13. protected static ?string $title = '题目生成';
  14. protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-sparkles';
  15. protected static ?string $navigationLabel = '题目生成';
  16. protected static string|UnitEnum|null $navigationGroup = '资源';
  17. protected static ?int $navigationSort = 21;
  18. protected string $view = 'filament.pages.question-generation';
  19. public ?string $generateKpCode = null;
  20. public array $selectedSkills = [];
  21. public int $questionCount = 100;
  22. public ?string $generateDifficulty = null;
  23. public ?string $generateType = null;
  24. public ?string $promptTemplate = null;
  25. public bool $isGenerating = false;
  26. public ?string $currentTaskId = null;
  27. public int $currentTaskProgress = 0;
  28. public ?string $currentTaskMessage = null;
  29. #[Computed(cache: false)]
  30. public function knowledgePointOptions(): array
  31. {
  32. return app(\App\Services\QuestionServiceApi::class)->getKnowledgePointOptions();
  33. }
  34. #[Computed(cache: false)]
  35. public function skillsOptions(): array
  36. {
  37. if (!$this->generateKpCode) {
  38. return [];
  39. }
  40. $service = app(KnowledgeGraphService::class);
  41. return $service->getSkillsByKnowledgePoint($this->generateKpCode);
  42. }
  43. #[Computed(cache: false)]
  44. public function questionTypeOptions(): array
  45. {
  46. return [
  47. 'CHOICE' => '单选题',
  48. 'MULTIPLE_CHOICE' => '多选题',
  49. 'FILL_IN_THE_BLANK' => '填空题',
  50. 'CALCULATION' => '计算题',
  51. 'WORD_PROBLEM' => '应用题',
  52. 'PROOF' => '证明题',
  53. ];
  54. }
  55. #[Computed(cache: false)]
  56. public function promptOptions(): array
  57. {
  58. $service = app(\App\Services\QuestionServiceApi::class);
  59. $prompts = $service->listPrompts(type: 'question_generation', active: 'yes');
  60. // 只展示激活的题目生成模板
  61. $options = [];
  62. foreach ($prompts as $prompt) {
  63. $label = $prompt['template_name'];
  64. if (!empty($prompt['description'])) {
  65. $label .= ' - ' . (is_string($prompt['description']) ? $prompt['description'] : json_encode($prompt['description']));
  66. }
  67. $options[$prompt['template_name']] = $label;
  68. }
  69. // 自动选择第一个激活模板
  70. if (!$this->promptTemplate && !empty($options)) {
  71. $this->promptTemplate = array_key_first($options);
  72. }
  73. return $options;
  74. }
  75. public function updatedGenerateKpCode(): void
  76. {
  77. // 选择新知识点时清空技能选择
  78. $this->selectedSkills = [];
  79. }
  80. public function toggleAllSkills(): void
  81. {
  82. $skillsOptions = $this->skillsOptions;
  83. $skillsCount = count($skillsOptions);
  84. \Log::info('[ToggleAllSkills] Called', [
  85. 'skillsCount' => $skillsCount,
  86. 'selectedSkillsCount' => count($this->selectedSkills),
  87. 'skillsOptions' => array_column($skillsOptions, 'code'),
  88. 'selectedSkills' => $this->selectedSkills
  89. ]);
  90. if ($skillsCount === 0) {
  91. \Log::info('[ToggleAllSkills] No skills available, returning');
  92. return;
  93. }
  94. // 获取所有技能的编码列表
  95. $allSkillCodes = array_values(array_unique(array_column($skillsOptions, 'code')));
  96. // 检查当前选中的技能是否等于全部技能
  97. if (count($this->selectedSkills) === $skillsCount &&
  98. count(array_intersect($this->selectedSkills, $allSkillCodes)) === $skillsCount) {
  99. // 如果已全选,则清空
  100. $this->selectedSkills = [];
  101. \Log::info('[ToggleAllSkills] Deselecting all skills');
  102. } else {
  103. // 否则全选
  104. $this->selectedSkills = $allSkillCodes;
  105. \Log::info('[ToggleAllSkills] Selecting all skills', ['newSelection' => $this->selectedSkills]);
  106. }
  107. }
  108. public function executeGenerate()
  109. {
  110. if ($this->isGenerating) {
  111. return;
  112. }
  113. if (!$this->generateKpCode) {
  114. Notification::make()->title('请选择知识点')->danger()->send();
  115. return;
  116. }
  117. $skillsOptions = $this->skillsOptions;
  118. $skillsCount = count($skillsOptions);
  119. if ($skillsCount > 0 && empty($this->selectedSkills)) {
  120. Notification::make()->title('请选择至少一个技能')->danger()->send();
  121. return;
  122. }
  123. $this->isGenerating = true;
  124. $this->currentTaskId = null;
  125. try {
  126. // 增加PHP脚本执行时间到120秒,给足够时间启动异步任务
  127. set_time_limit(120);
  128. $service = app(QuestionBankService::class);
  129. $callbackUrl = route('api.questions.callback');
  130. \Log::info("[QuestionGen] 开始生成,callback URL: " . $callbackUrl);
  131. $result = $service->generateIntelligentQuestions([
  132. 'kp_code' => $this->generateKpCode,
  133. 'skills' => $this->selectedSkills,
  134. 'count' => $this->questionCount,
  135. 'difficulty' => $this->generateDifficulty,
  136. 'type' => $this->generateType,
  137. 'prompt_template' => $this->promptTemplate ?? null
  138. ], $callbackUrl);
  139. if ($result['success'] ?? false) {
  140. $this->currentTaskId = $result['task_id'] ?? null;
  141. \Log::info("[QuestionGen] ✅ 任务已创建: {$this->currentTaskId},准备跳转到题库管理");
  142. \Log::info("[QuestionGen] 完整结果: " . json_encode($result));
  143. // 准备跳转URL(不传递task_id参数)
  144. $redirectUrl = "/admin/question-management";
  145. $taskId = $this->currentTaskId ?? 'unknown';
  146. \Log::info("[QuestionGen] ✅ 准备跳转到: {$redirectUrl}");
  147. \Log::info("[QuestionGen] ✅ 准备传递的taskId: {$taskId}");
  148. // 使用简单可靠的Livewire dispatch事件
  149. // 注意:不要设置 isGenerating = false,让状态栏继续显示
  150. \Log::info("[QuestionGen] ✅ 即将分发跳转事件", [
  151. 'url' => $redirectUrl,
  152. 'taskId' => $taskId
  153. ]);
  154. // 使用最简单的事件名称和参数
  155. $this->dispatch('redirect-now', url: $redirectUrl, taskId: $taskId);
  156. \Log::info("[QuestionGen] ✅ 事件已分发");
  157. // 使用Livewire的JavaScript方法执行直接跳转
  158. $this->js("console.log('[QuestionGen] 直接JS跳转启动');
  159. alert('✅ 任务已启动\\n任务 ID: {$taskId}\\n正在跳转到题库管理页面...');
  160. setTimeout(function() {
  161. console.log('[QuestionGen] 直接跳转执行:', '{$redirectUrl}');
  162. window.location.href = '{$redirectUrl}';
  163. }, 1000);");
  164. // 明确返回,避免执行后面的代码
  165. \Log::info("[QuestionGen] ✅ 即将返回");
  166. return;
  167. } else {
  168. $this->isGenerating = false;
  169. Notification::make()
  170. ->title('创建任务失败')
  171. ->body($result['message'] ?? '未知错误')
  172. ->danger()
  173. ->send();
  174. }
  175. } catch (\Exception $e) {
  176. $this->isGenerating = false;
  177. \Log::error("[QuestionGen] 生成异常: " . $e->getMessage());
  178. Notification::make()
  179. ->title('生成异常')
  180. ->body($e->getMessage())
  181. ->danger()
  182. ->send();
  183. }
  184. }
  185. public function forceCloseStatusBar(string $taskId): void
  186. {
  187. \Log::info("[QuestionGen] 强制关闭状态栏: {$taskId}");
  188. $this->isGenerating = false;
  189. $this->currentTaskId = null;
  190. Notification::make()
  191. ->title('⚠️ 任务超时')
  192. ->body('生成任务可能已完成,请刷新页面查看')
  193. ->warning()
  194. ->persistent()
  195. ->send();
  196. }
  197. }