ExportAiTrainingData.php 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. <?php
  2. namespace App\Console\Commands;
  3. use App\Http\Api\ChannelApi;
  4. use App\Http\Api\MdRender;
  5. use App\Models\PaliSentence;
  6. use App\Models\Sentence;
  7. use App\Services\PaliTextService;
  8. use Illuminate\Console\Command;
  9. use Illuminate\Support\Facades\File;
  10. use Illuminate\Support\Facades\Log;
  11. class ExportAiTrainingData extends Command
  12. {
  13. private $ShortTrans = 0.17;
  14. /**
  15. * The name and signature of the console command.
  16. * php artisan export:ai.training.data
  17. *
  18. * @var string
  19. */
  20. protected $signature = 'export:ai.training.data {--format=gz : zip file format 7z,lzma,gz } {--test}';
  21. /**
  22. * The console command description.
  23. *
  24. * @var string
  25. */
  26. protected $description = 'export ai training data';
  27. /**
  28. * Create a new command instance.
  29. *
  30. * @return void
  31. */
  32. public function __construct()
  33. {
  34. parent::__construct();
  35. }
  36. /**
  37. * Execute the console command.
  38. *
  39. * @return int
  40. */
  41. public function handle()
  42. {
  43. Log::info('task export offline sentence-table start');
  44. // 创建文件夹
  45. $base = 'app/tmp/export/offline';
  46. $exportDir = storage_path($base);
  47. if (! is_dir($exportDir)) {
  48. $res = mkdir($exportDir, 0755, true);
  49. if (! $res) {
  50. $this->error('mkdir fail path='.$exportDir);
  51. return 1;
  52. } else {
  53. $this->info('make dir successful '.$exportDir);
  54. }
  55. }
  56. // 创建临时文件夹\
  57. $dirname = $exportDir.'/'.'wikipali-offline-ai-training-'.date('YmdHis');
  58. $tmp = mkdir($dirname, 0755, true);
  59. if (! $tmp) {
  60. $this->error('mkdir fail path='.$dirname);
  61. return 1;
  62. } else {
  63. $this->info('make dir successful '.$dirname);
  64. }
  65. $fpIndex = fopen($dirname.'/index.md', 'w');
  66. if ($fpIndex === false) {
  67. exit('无法创建索引文件');
  68. }
  69. $channels = [
  70. '7ac4d13b-a43d-4409-91b5-5f2a82b916b3',
  71. 'e5bc5c97-a6fb-4ccb-b7df-be6dcfee9c43',
  72. '74ebf4c5-c243-4948-955d-6c277e29276a',
  73. '3b0cb0aa-ea88-4ce5-b67d-00a3e76220cc',
  74. '5310999c-0b0c-4bb0-9bb9-9cdd176e9ef0',
  75. '331447b6-39bb-4b49-ac10-6206db93a050',
  76. ];
  77. $start = time();
  78. foreach ($channels as $key => $channel) {
  79. if ($this->option('test') && $key > 0) {
  80. // test mode 只跑一个
  81. break;
  82. }
  83. fwrite($fpIndex, "# {$channel}\n");
  84. $channelInfo = ChannelApi::getById($channel);
  85. if ($channelInfo) {
  86. fwrite($fpIndex, "- 版本名称:{$channelInfo['name']}\n");
  87. fwrite($fpIndex, "- 语言:{$channelInfo['lang']}\n");
  88. }
  89. // 创建文件
  90. $this->info('export start'.$channel);
  91. $filename = $channel.'.jsonl';
  92. $exportFile = $dirname.'/'.$filename;
  93. $fp = fopen($exportFile, 'w');
  94. if ($fp === false) {
  95. exit('无法创建文件');
  96. }
  97. $db = Sentence::where('channel_uid', $channel);
  98. $bar = $this->output->createProgressBar($db->count());
  99. $srcDb = $db->select([
  100. 'book_id',
  101. 'paragraph',
  102. 'word_start',
  103. 'word_end',
  104. 'content',
  105. 'content_type',
  106. 'updated_at',
  107. ])
  108. ->whereNotNull('content')
  109. ->orderBy('book_id')
  110. ->orderBy('paragraph')
  111. ->orderBy('word_start')->cursor();
  112. $done = [];
  113. foreach ($srcDb as $sent) {
  114. $id = "{$sent->book_id}-{$sent->paragraph}-{$sent->word_start}-{$sent->word_end}";
  115. if (isset($done[$id])) {
  116. continue;
  117. }
  118. // 获取原文
  119. $origin = PaliSentence::where('book', $sent->book_id)
  120. ->where('paragraph', $sent->paragraph)
  121. ->where('word_begin', $sent->word_start)
  122. ->where('word_end', $sent->word_end)
  123. ->value('text');
  124. // 忽略空的原文
  125. if (self::isEmpty($origin)) {
  126. Log::warning('origin is empty id='.$id);
  127. continue;
  128. }
  129. // 渲染译文
  130. $translation = MdRender::render(
  131. $sent->content,
  132. [$channel],
  133. null,
  134. 'read',
  135. 'translation',
  136. $sent->content_type,
  137. 'html',
  138. );
  139. $translation = trim($translation);
  140. // 忽略空的译文
  141. if (self::isEmpty($translation)) {
  142. Log::warning('translation is empty id='.$id);
  143. continue;
  144. }
  145. // 忽略过短的译文
  146. /*
  147. if (mb_strlen($translation) / mb_strlen($origin) < $this->ShortTrans) {
  148. Log::warning('translation is short id=' . $id);
  149. continue;
  150. }
  151. */
  152. // 原文与翻译完全相同
  153. if ($translation === $origin) {
  154. Log::warning('translation is same id='.$id);
  155. continue;
  156. }
  157. // 获取分类标签
  158. $paliTextService = app(PaliTextService::class);
  159. $tags = $paliTextService->getParaCategoryTags($sent->book_id, $sent->paragraph);
  160. $path = $paliTextService->getParaPathTitle($sent->book_id, $sent->paragraph);
  161. $currData = [
  162. 'id' => $id,
  163. 'original' => $origin,
  164. 'translation' => $translation,
  165. 'category' => $tags,
  166. 'path' => $path,
  167. 'updated_at' => $sent->updated_at,
  168. ];
  169. fwrite($fp, json_encode($currData, JSON_UNESCAPED_UNICODE)."\n");
  170. $bar->advance();
  171. $done[$id] = 1;
  172. }
  173. fclose($fp);
  174. }
  175. fclose($fpIndex);
  176. $this->info((time() - $start).' seconds');
  177. $this->call('export:zip2', [
  178. 'id' => 'ai-translating-training-data',
  179. 'filename' => $dirname,
  180. 'title' => 'wikipali ai translating training data',
  181. 'format' => $this->option('format'),
  182. ]);
  183. sleep(5);
  184. File::deleteDirectory($dirname);
  185. return 0;
  186. }
  187. private function isEmpty(?string $input): bool
  188. {
  189. if (empty($input)) {
  190. return true;
  191. }
  192. $result = preg_replace('/[\s\d\p{P}]/u', '', $input);
  193. return empty($result);
  194. }
  195. }