KnowledgeGraphManagement.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. <?php
  2. namespace App\Filament\Pages;
  3. use App\Services\KnowledgeGraphService;
  4. use Filament\Actions\Action;
  5. use Filament\Forms\Components\FileUpload;
  6. use Filament\Forms\Components\Select;
  7. use Filament\Forms\Components\Textarea;
  8. use Filament\Forms\Components\TextInput;
  9. use Filament\Notifications\Notification;
  10. use Filament\Pages\Page;
  11. use Filament\Support\Enums\ActionSize;
  12. use Illuminate\Support\Facades\Storage;
  13. class KnowledgeGraphManagement extends Page
  14. {
  15. protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-rectangle-stack';
  16. protected static string|\UnitEnum|null $navigationGroup = '知识图谱管理';
  17. protected static ?string $navigationLabel = '知识图谱管理';
  18. protected static ?int $navigationSort = 5;
  19. protected static ?string $title = '知识图谱管理';
  20. protected string $view = 'filament.pages.knowledge-graph-management';
  21. protected function getHeaderActions(): array
  22. {
  23. return [
  24. Action::make('create')
  25. ->label('新增知识点')
  26. ->icon('heroicon-o-plus')
  27. ->color('primary')
  28. ->form([
  29. TextInput::make('cn_name')->label('中文名称')->required(),
  30. TextInput::make('en_name')->label('英文名称'),
  31. TextInput::make('kp_code')->label('知识点编码')->placeholder('自动生成或手动输入'),
  32. Select::make('phase')->label('学段')->options([
  33. '小学' => '小学', '初中' => '初中', '高中' => '高中'
  34. ])->required(),
  35. Select::make('grade')->label('年级')->options([
  36. 7 => '七年级', 8 => '八年级', 9 => '九年级'
  37. ]),
  38. TextInput::make('category')->label('分类')->default('数学'),
  39. TextInput::make('importance')->label('重要性')->numeric()->default(5),
  40. Textarea::make('description')->label('描述'),
  41. ])
  42. ->action(function (array $data, KnowledgeGraphService $service) {
  43. if ($service->createKnowledgePoint($data)) {
  44. Notification::make()->title('创建成功')->success()->send();
  45. $this->mount($service);
  46. } else {
  47. Notification::make()->title('创建失败')->danger()->send();
  48. }
  49. }),
  50. Action::make('import')
  51. ->label('导入图谱数据')
  52. ->icon('heroicon-o-arrow-up-tray')
  53. ->color('success')
  54. ->form([
  55. FileUpload::make('tree_file')
  56. ->label('Tree JSON (知识点结构)')
  57. ->helperText('上传包含知识点层次结构的 JSON 文件')
  58. ->required()
  59. ->storeFiles(false),
  60. FileUpload::make('edges_file')
  61. ->label('Edges JSON (依赖关系)')
  62. ->helperText('上传包含知识点间关系的 JSON 文件')
  63. ->required()
  64. ->storeFiles(false),
  65. ])
  66. ->action(function (array $data, KnowledgeGraphService $service) {
  67. try {
  68. $treeFile = $data['tree_file'];
  69. $edgesFile = $data['edges_file'];
  70. if (is_array($treeFile)) $treeFile = reset($treeFile);
  71. if (is_array($edgesFile)) $edgesFile = reset($edgesFile);
  72. // 详细日志
  73. \Log::info('开始导入知识图谱', [
  74. 'tree_file' => is_object($treeFile) ? get_class($treeFile) : $treeFile,
  75. 'edges_file' => is_object($edgesFile) ? get_class($edgesFile) : $edgesFile
  76. ]);
  77. // 处理 TemporaryUploadedFile 对象
  78. $treeContent = null;
  79. $edgesContent = null;
  80. // 读取 Tree 文件
  81. if ($treeFile instanceof \Illuminate\Http\UploadedFile) {
  82. // 从临时上传文件读取
  83. $treeContent = json_decode(file_get_contents($treeFile->getRealPath()), true);
  84. } elseif (is_string($treeFile)) {
  85. // 从字符串路径读取
  86. if (Storage::disk('public')->exists($treeFile)) {
  87. $treeContent = json_decode(Storage::disk('public')->get($treeFile), true);
  88. } elseif (file_exists($treeFile)) {
  89. $treeContent = json_decode(file_get_contents($treeFile), true);
  90. } else {
  91. throw new \Exception("Tree文件不存在: {$treeFile}");
  92. }
  93. } else {
  94. throw new \Exception("无效的Tree文件类型");
  95. }
  96. // 读取 Edges 文件
  97. if ($edgesFile instanceof \Illuminate\Http\UploadedFile) {
  98. $edgesContent = json_decode(file_get_contents($edgesFile->getRealPath()), true);
  99. } elseif (is_string($edgesFile)) {
  100. if (Storage::disk('public')->exists($edgesFile)) {
  101. $edgesContent = json_decode(Storage::disk('public')->get($edgesFile), true);
  102. } elseif (file_exists($edgesFile)) {
  103. $edgesContent = json_decode(file_get_contents($edgesFile), true);
  104. } else {
  105. throw new \Exception("Edges文件不存在: {$edgesFile}");
  106. }
  107. } else {
  108. throw new \Exception("无效的Edges文件类型");
  109. }
  110. if (json_last_error() !== JSON_ERROR_NONE) {
  111. throw new \Exception('JSON解析错误: ' . json_last_error_msg());
  112. }
  113. if (!$treeContent) {
  114. throw new \Exception('Tree JSON解析失败或为空');
  115. }
  116. if (!$edgesContent) {
  117. throw new \Exception('Edges JSON解析失败或为空');
  118. }
  119. \Log::info('文件解析成功', [
  120. 'tree_keys' => array_keys($treeContent),
  121. 'edges_count' => is_array($edgesContent) ? count($edgesContent) : 0
  122. ]);
  123. if ($service->importGraph($treeContent, $edgesContent)) {
  124. Notification::make()->title('导入成功')->success()->send();
  125. $this->mount($service);
  126. } else {
  127. Notification::make()->title('导入失败')->body('请查看日志获取详细信息')->danger()->send();
  128. }
  129. } catch (\Exception $e) {
  130. \Log::error('导入知识图谱失败', [
  131. 'error' => $e->getMessage(),
  132. 'trace' => $e->getTraceAsString()
  133. ]);
  134. Notification::make()
  135. ->title('导入失败')
  136. ->body($e->getMessage())
  137. ->danger()
  138. ->persistent()
  139. ->send();
  140. }
  141. }),
  142. Action::make('edit')
  143. ->label('编辑')
  144. ->icon('heroicon-o-pencil-square')
  145. ->color('warning')
  146. ->modalHeading('编辑知识点')
  147. ->modalDescription('修改知识点的基本信息和属性')
  148. ->form([
  149. TextInput::make('cn_name')->label('中文名称')->required(),
  150. TextInput::make('en_name')->label('英文名称'),
  151. Select::make('phase')->label('学段')->options([
  152. '小学' => '小学', '初中' => '初中', '高中' => '高中'
  153. ])->required(),
  154. Select::make('grade')->label('年级')->options([
  155. 7 => '七年级', 8 => '八年级', 9 => '九年级'
  156. ]),
  157. TextInput::make('category')->label('分类'),
  158. TextInput::make('importance')->label('重要性')->numeric(),
  159. Textarea::make('description')->label('描述'),
  160. ])
  161. ->fillForm(function (array $arguments, KnowledgeGraphService $service) {
  162. $code = $arguments['code'];
  163. $data = $service->getKnowledgePoint($code);
  164. return $data ?? [];
  165. })
  166. ->action(function (array $data, array $arguments, KnowledgeGraphService $service) {
  167. if ($service->updateKnowledgePoint($arguments['code'], $data)) {
  168. Notification::make()->title('更新成功')->success()->send();
  169. $this->mount($service);
  170. } else {
  171. Notification::make()->title('更新失败')->danger()->send();
  172. }
  173. }),
  174. Action::make('delete')
  175. ->label('删除')
  176. ->icon('heroicon-o-trash')
  177. ->color('error')
  178. ->requiresConfirmation()
  179. ->modalHeading('删除知识点')
  180. ->modalDescription('此操作将永久删除该知识点及其所有关联数据,此操作不可恢复!')
  181. ->modalSubmitActionLabel('确认删除')
  182. ->action(function (array $arguments, KnowledgeGraphService $service) {
  183. if ($service->deleteKnowledgePoint($arguments['code'])) {
  184. Notification::make()->title('删除成功')->success()->send();
  185. $this->mount($service);
  186. } else {
  187. Notification::make()->title('删除失败')->danger()->send();
  188. }
  189. }),
  190. ];
  191. }
  192. public array $knowledgePoints = [];
  193. public function mount(): void
  194. {
  195. $service = app(KnowledgeGraphService::class);
  196. $result = $service->listKnowledgePoints(1, 1000);
  197. $this->knowledgePoints = $result['data'] ?? [];
  198. }
  199. public function edit(string $code): void
  200. {
  201. $this->mountAction('edit', ['code' => $code]);
  202. }
  203. public function delete(string $code): void
  204. {
  205. $this->mountAction('delete', ['code' => $code]);
  206. }
  207. }