PromptManagement.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. <?php
  2. namespace App\Filament\Pages;
  3. use App\Services\PromptService;
  4. use BackedEnum;
  5. use Filament\Actions;
  6. use Filament\Actions\Action;
  7. use Filament\Notifications\Notification;
  8. use Filament\Pages\Page;
  9. use UnitEnum;
  10. use Livewire\Attributes\Computed;
  11. use Livewire\Attributes\On;
  12. class PromptManagement extends Page
  13. {
  14. protected static string|BackedEnum|null $navigationIcon = 'heroicon-o-chat-bubble-left-right';
  15. protected static string|UnitEnum|null $navigationGroup = '题库管理';
  16. protected static ?string $navigationLabel = '提示词管理';
  17. protected static ?int $navigationSort = 6;
  18. protected ?string $heading = '提示词管理';
  19. protected string $view = 'filament.pages.prompt-management';
  20. public ?string $selectedType = null;
  21. public ?string $search = null;
  22. public int $currentPage = 1;
  23. public int $perPage = 10;
  24. public array $selectedPrompts = [];
  25. // 表单状态
  26. public bool $showPromptModal = false;
  27. public bool $isEditing = false;
  28. public ?string $editingName = null;
  29. public array $form = [
  30. 'template_name' => '',
  31. 'template_type' => 'question_generation',
  32. 'template_content' => '',
  33. 'variables' => '[]',
  34. 'description' => '',
  35. 'tags' => '',
  36. 'is_active' => 'yes',
  37. ];
  38. /**
  39. * 获取提示词列表
  40. */
  41. public function getPrompts(): array
  42. {
  43. $service = app(PromptService::class);
  44. try {
  45. // 获取所有提示词
  46. $prompts = $service->listPrompts();
  47. // 筛选
  48. if ($this->selectedType) {
  49. $prompts = array_filter($prompts, fn($prompt) =>
  50. $prompt['template_type'] === $this->selectedType
  51. );
  52. }
  53. if ($this->search) {
  54. $searchTerm = strtolower($this->search);
  55. $prompts = array_filter($prompts, fn($prompt) =>
  56. str_contains(strtolower($prompt['template_name']), $searchTerm) ||
  57. str_contains(strtolower($prompt['description']), $searchTerm)
  58. );
  59. }
  60. // 分页
  61. $total = count($prompts);
  62. $offset = ($this->currentPage - 1) * $this->perPage;
  63. $paginated = array_slice($prompts, $offset, $this->perPage);
  64. return [
  65. 'data' => $paginated,
  66. 'meta' => [
  67. 'page' => $this->currentPage,
  68. 'per_page' => $this->perPage,
  69. 'total' => $total,
  70. 'total_pages' => (int) ceil($total / $this->perPage),
  71. ]
  72. ];
  73. } catch (\Exception $e) {
  74. \Log::error('Failed to fetch prompts: ' . $e->getMessage());
  75. return ['data' => [], 'meta' => ['total' => 0]];
  76. }
  77. }
  78. /**
  79. * 获取提示词类型统计
  80. */
  81. public function getTypeStats(): array
  82. {
  83. $service = app(PromptService::class);
  84. try {
  85. $prompts = $service->listPrompts();
  86. $stats = [];
  87. foreach ($prompts as $prompt) {
  88. $type = $prompt['template_type'];
  89. if (!isset($stats[$type])) {
  90. $stats[$type] = 0;
  91. }
  92. $stats[$type]++;
  93. }
  94. return $stats;
  95. } catch (\Exception $e) {
  96. \Log::error('Failed to fetch prompt stats: ' . $e->getMessage());
  97. return [];
  98. }
  99. }
  100. /**
  101. * 获取所有类型选项
  102. */
  103. public function getTypeOptions(): array
  104. {
  105. return [
  106. 'question_generation' => '题目生成(系统)',
  107. 'question_enrich' => '题干补全(系统)',
  108. 'question_solution_regen' => '解题重写(系统)',
  109. '题目生成' => '题目生成',
  110. '掌握度评估' => '掌握度评估',
  111. '技能熟练度' => '技能熟练度',
  112. '质量审核' => '质量审核',
  113. ];
  114. }
  115. /**
  116. * 筛选更新
  117. */
  118. public function updatedSelectedType(): void
  119. {
  120. $this->currentPage = 1;
  121. }
  122. public function updatedSearch(): void
  123. {
  124. $this->currentPage = 1;
  125. }
  126. public function updatedPerPage(): void
  127. {
  128. $this->currentPage = 1;
  129. }
  130. /**
  131. * 跳转到指定页
  132. */
  133. public function gotoPage(int $page): void
  134. {
  135. $this->currentPage = $page;
  136. }
  137. /**
  138. * 上一页
  139. */
  140. public function previousPage(): void
  141. {
  142. if ($this->currentPage > 1) {
  143. $this->currentPage--;
  144. }
  145. }
  146. /**
  147. * 下一页
  148. */
  149. public function nextPage(): void
  150. {
  151. $totalPages = $this->prompts['meta']['total_pages'] ?? 1;
  152. if ($this->currentPage < $totalPages) {
  153. $this->currentPage++;
  154. }
  155. }
  156. /**
  157. * 创建新提示词
  158. */
  159. #[On('create-prompt')]
  160. public function createPrompt(): void
  161. {
  162. $this->resetPromptForm();
  163. $this->isEditing = false;
  164. $this->editingName = null;
  165. $this->showPromptModal = true;
  166. }
  167. /**
  168. * 编辑提示词
  169. */
  170. #[On('edit-prompt')]
  171. public function editPrompt(array $prompt): void
  172. {
  173. $this->form = [
  174. 'template_name' => $prompt['template_name'] ?? '',
  175. 'template_type' => $prompt['template_type'] ?? 'question_generation',
  176. 'template_content' => $this->fetchPromptContent($prompt['template_name'] ?? '') ?? '',
  177. 'variables' => $prompt['variables'] ?? '[]',
  178. 'description' => $prompt['description'] ?? '',
  179. 'tags' => $prompt['tags'] ?? '',
  180. 'is_active' => ($prompt['is_active'] === 'yes' || $prompt['is_active'] === true) ? 'yes' : 'no',
  181. ];
  182. $this->isEditing = true;
  183. $this->editingName = $prompt['template_name'] ?? null;
  184. $this->showPromptModal = true;
  185. }
  186. /**
  187. * 删除提示词
  188. */
  189. #[On('delete-prompt')]
  190. public function deletePrompt(string $promptName): void
  191. {
  192. try {
  193. app(PromptService::class)->deletePrompt($promptName);
  194. Notification::make()
  195. ->title('删除成功')
  196. ->body("提示词 {$promptName} 已删除")
  197. ->success()
  198. ->send();
  199. } catch (\Exception $e) {
  200. Notification::make()
  201. ->title('删除失败')
  202. ->body($e->getMessage())
  203. ->danger()
  204. ->send();
  205. }
  206. }
  207. /**
  208. * 启用/禁用提示词
  209. */
  210. #[On('toggle-prompt')]
  211. public function togglePrompt(?string $promptName = null, ?bool $isActive = null): void
  212. {
  213. if (!$promptName || $isActive === null) {
  214. return;
  215. }
  216. try {
  217. $current = app(PromptService::class)->getPrompt($promptName);
  218. if ($current) {
  219. $current['is_active'] = $isActive ? 'no' : 'yes';
  220. app(PromptService::class)->savePrompt($current);
  221. }
  222. Notification::make()
  223. ->title(($isActive ? '已禁用 ' : '已启用 ') . $promptName)
  224. ->success()
  225. ->send();
  226. } catch (\Exception $e) {
  227. Notification::make()
  228. ->title('更新状态失败')
  229. ->body($e->getMessage())
  230. ->danger()
  231. ->send();
  232. }
  233. }
  234. /**
  235. * 复制提示词
  236. */
  237. #[On('duplicate-prompt')]
  238. public function duplicatePrompt(array $prompt): void
  239. {
  240. $newName = ($prompt['template_name'] ?? 'template') . '_copy';
  241. try {
  242. app(PromptService::class)->savePrompt([
  243. 'template_name' => $newName,
  244. 'template_type' => $prompt['template_type'] ?? 'question_generation',
  245. 'template_content' => $this->fetchPromptContent($prompt['template_name'] ?? '') ?? '',
  246. 'variables' => $prompt['variables'] ?? '[]',
  247. 'description' => $prompt['description'] ?? '',
  248. 'tags' => $prompt['tags'] ?? '',
  249. ]);
  250. Notification::make()
  251. ->title('复制成功')
  252. ->body("已创建副本:{$newName}")
  253. ->success()
  254. ->send();
  255. } catch (\Exception $e) {
  256. Notification::make()
  257. ->title('复制失败')
  258. ->body($e->getMessage())
  259. ->danger()
  260. ->send();
  261. }
  262. }
  263. /**
  264. * 刷新数据
  265. */
  266. #[On('refresh-prompts')]
  267. public function refreshPrompts(): void
  268. {
  269. Notification::make()
  270. ->title('数据已刷新')
  271. ->success()
  272. ->send();
  273. }
  274. /**
  275. * 头部操作
  276. */
  277. protected function getHeaderActions(): array
  278. {
  279. return [
  280. Actions\Action::make('create')
  281. ->label('新建提示词')
  282. ->icon('heroicon-m-plus')
  283. ->color('success')
  284. ->action('createPrompt'),
  285. Actions\Action::make('refresh')
  286. ->label('刷新')
  287. ->icon('heroicon-m-arrow-path')
  288. ->color('warning')
  289. ->action('refreshPrompts'),
  290. ];
  291. }
  292. public function savePrompt(): void
  293. {
  294. $data = $this->validate([
  295. 'form.template_name' => $this->isEditing ? 'nullable|string' : 'required|string',
  296. 'form.template_type' => 'required|string',
  297. 'form.template_content' => 'required|string',
  298. 'form.variables' => 'nullable|string',
  299. 'form.description' => 'nullable|string',
  300. 'form.tags' => 'nullable|string',
  301. 'form.is_active' => 'nullable|string',
  302. ])['form'];
  303. try {
  304. $payload = [
  305. 'template_name' => $this->isEditing && $this->editingName
  306. ? $this->editingName
  307. : $data['template_name'],
  308. 'template_content' => $data['template_content'],
  309. 'template_type' => $data['template_type'],
  310. 'variables' => $data['variables'] ?? '[]',
  311. 'description' => $data['description'] ?? '',
  312. 'tags' => $data['tags'] ?? '',
  313. 'is_active' => $data['is_active'] ?? 'yes',
  314. ];
  315. if ($this->isEditing && $this->editingName) {
  316. app(PromptService::class)->savePrompt($payload);
  317. } else {
  318. app(PromptService::class)->savePrompt($payload);
  319. }
  320. $this->showPromptModal = false;
  321. $this->refreshPrompts();
  322. Notification::make()
  323. ->title('保存成功')
  324. ->success()
  325. ->send();
  326. } catch (\Exception $e) {
  327. Notification::make()
  328. ->title('保存失败')
  329. ->body($e->getMessage())
  330. ->danger()
  331. ->send();
  332. }
  333. }
  334. protected function fetchPromptContent(string $templateName): ?string
  335. {
  336. if (!$templateName) {
  337. return null;
  338. }
  339. try {
  340. return app(PromptService::class)->getPromptContent($templateName);
  341. } catch (\Exception $e) {
  342. \Log::warning('获取提示词内容失败: ' . $e->getMessage());
  343. }
  344. return null;
  345. }
  346. protected function resetPromptForm(): void
  347. {
  348. $this->form = [
  349. 'template_name' => '',
  350. 'template_type' => 'question_generation',
  351. 'template_content' => '',
  352. 'variables' => '[]',
  353. 'description' => '',
  354. 'tags' => '',
  355. 'is_active' => 'yes',
  356. ];
  357. }
  358. }