AiController.php 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575
  1. <?php
  2. namespace App\Http\Controllers\V1\Ai;
  3. use App\Auth;
  4. use App\Http\Controllers\V1\Controller;
  5. use App\libs\helpers\Helper;
  6. use App\libs\helpers\LogHelper;
  7. use App\libs\helpers\Response;
  8. use App\Models\Config;
  9. use App\Models\Keyword;
  10. use App\Models\TaskList;
  11. use App\Models\User;
  12. use App\Models\UserRole;
  13. use Illuminate\Http\Request;
  14. use Illuminate\Support\Facades\Log;
  15. use App\Exceptions\ApiException;
  16. use Overtrue\Pinyin\Pinyin;
  17. class AiController extends Controller
  18. {
  19. public $code = '_Ai';
  20. /**
  21. * 用户提交任务
  22. */
  23. public function submitTask(Request $request): \Illuminate\Http\JsonResponse
  24. {
  25. try {
  26. (int) $plot = Config::query()->where('key', 'plot')->value('value');
  27. if (!$plot) {
  28. return Response::fail('系统错误,请联系管理员#');
  29. }
  30. if (Auth::$user->diamond < $plot) {
  31. return Response::fail('钻石不够了,请先充值');
  32. }
  33. $roleId = $request->post('roleId', 0);
  34. if ($roleId) {
  35. $role = UserRole::query()->find($roleId);
  36. if (!$role) {
  37. return Response::fail('没有该角色');
  38. }
  39. } else {
  40. $role = UserRole::query()->create([
  41. 'user_id' => Auth::$userId,
  42. 'name' => $request->post('name', ''),
  43. 'sex' => $request->post('sex', ''),
  44. 'age' => $request->post('age', ''),
  45. 'star' => $request->post('star', ''),
  46. 'level' => $request->post('level', ''),
  47. // 'is_piny' => $request->post('is_piny', 1),
  48. ]);
  49. if (!$role) {
  50. return Response::fail('提交失败,请稍后再试!');
  51. }
  52. }
  53. User::query()->where('id', Auth::$userId)->decrement('diamond', $plot);
  54. $task = TaskList::query()->create([
  55. 'user_id' => Auth::$userId,
  56. 'role_id' => $role->id,
  57. 'image' => $request->post('image', ''),
  58. 'surplus_diamond' => Auth::$user->diamond - $plot,
  59. 'nickname' => 'AI绘本生成',
  60. 'plot' => $plot,
  61. 'is_piny' => $request->post('pinYin', 1),
  62. ]);
  63. if (!$task) {
  64. return Response::fail('任务提交失败');
  65. }
  66. return Response::success(['id' => $task->id], '提交成功,请稍等');
  67. } catch (\Exception $exception) {
  68. LogHelper::exceptionLog($exception, $this->code);
  69. return Response::fail($exception->getMessage());
  70. }
  71. }
  72. /**
  73. * 获取任务详情.
  74. *
  75. * @return \Illuminate\Http\JsonResponse
  76. */
  77. public function getTaskDetail(Request $request)
  78. {
  79. try {
  80. $id = $request->get('id', 0);
  81. return Response::success(TaskList::query()->where('user_id', Auth::$userId)->find($id));
  82. } catch (\Exception $exception) {
  83. LogHelper::exceptionLog($exception, $this->code);
  84. return Response::fail($exception->getMessage());
  85. }
  86. }
  87. /**
  88. * 获取字数.
  89. *
  90. * @return int|mixed
  91. */
  92. private function getBuildCountString($level = 1)
  93. {
  94. $string = Config::query()->where('key', 'class_count')->first()->toArray();
  95. foreach ($string['value'] ?? [] as $value) {
  96. if ($level == $value['lv']) {
  97. return $value['count'];
  98. }
  99. }
  100. return 200;
  101. }
  102. /**
  103. * 生成故事.
  104. */
  105. public function buildStory(Request $request): \Illuminate\Http\JsonResponse
  106. {
  107. try {
  108. $task = TaskList::query()->with('role')->where('state', 0)->inRandomOrder()->first();
  109. if (!$task) {
  110. return Response::fail('暂无任务');
  111. }
  112. $key = [];
  113. // $keyword = Keyword::query()->pluck('keyword');
  114. // foreach ($keyword as $value) {
  115. // $key[] = $value;
  116. // }
  117. // $keyword = implode(',', $key);
  118. $keyword = Config::query()->where('key', 'prompt_gushi')->value('value');
  119. $task->state = 1;
  120. $task->save();
  121. Log::warning('这个是当前的次数:' . $this->getBuildCountString($task->level));
  122. $prompt = "一个{$task->role->sex},叫{$task->role->name},{$task->role->age}岁。{$keyword},要求不低于{$this->getBuildCountString($task->role->level)}字。备注:不要加什么特殊符号只需要正常的逗号句号叹号这些。";
  123. $messages[] = ['role' => 'user', 'content' => $prompt];
  124. $data = [
  125. 'messages' => $messages,
  126. ];
  127. $postData = json_encode($data);
  128. $complete = $this->host($postData);
  129. $task->init_content = $complete['result'];
  130. $task->save();
  131. return Response::success($complete);
  132. } catch (\Exception $exception) {
  133. LogHelper::exceptionLog($exception, $this->code);
  134. return Response::fail($exception->getMessage());
  135. }
  136. }
  137. /**
  138. * 生成标题.
  139. *
  140. * @return \Illuminate\Http\JsonResponse
  141. */
  142. public function buildTitle(Request $request)
  143. {
  144. try {
  145. $task = TaskList::query()->with('role')->where('state', 1)->inRandomOrder()->first();
  146. if (!$task) {
  147. return Response::fail('暂无任务');
  148. }
  149. $task->state = 2;
  150. $task->save();
  151. $prompt = $task->init_content . '。将这段童话故事生成一个标题,标题不能有特殊符号,只需要纯文字标题即可,不需要其他任何文字,标题文字控制在10个字以内。不能超过10个字';
  152. $messages[] = ['role' => 'user', 'content' => $prompt];
  153. $data = [
  154. 'messages' => $messages,
  155. ];
  156. $postData = json_encode($data);
  157. $complete = $this->host($postData);
  158. // $task->state = 2;
  159. $task->title = $complete['result'];
  160. $task->save();
  161. return Response::success($complete);
  162. } catch (\Exception $exception) {
  163. LogHelper::exceptionLog($exception, $this->code);
  164. return Response::fail($exception->getMessage());
  165. }
  166. }
  167. /**
  168. * 生成关键词.
  169. *
  170. * @return \Illuminate\Http\JsonResponse
  171. */
  172. public function buildKeyword(Request $request)
  173. {
  174. try {
  175. $task = TaskList::query()->with('role')->where('state', 2)->inRandomOrder()->first();
  176. if (!$task) {
  177. return Response::fail('暂无任务');
  178. }
  179. $keyword = Config::query()->where('key', 'prompt_keyword')->value('value');
  180. $prompt = $task->init_content . $keyword;
  181. $messages[] = ['role' => 'user', 'content' => $prompt];
  182. $data = [
  183. 'messages' => $messages,
  184. ];
  185. $task->state = 3;
  186. $task->save();
  187. $postData = json_encode($data);
  188. $complete = $this->host($postData);
  189. // $task->state = 3;
  190. $task->keyword = $complete['result'];
  191. $task->save();
  192. return Response::success($complete);
  193. } catch (\Exception $exception) {
  194. LogHelper::exceptionLog($exception, $this->code);
  195. return Response::fail($exception->getMessage());
  196. }
  197. }
  198. /**
  199. * 翻译英文.
  200. */
  201. private function toEnglish($prompt)
  202. {
  203. $keyword = Config::query()->where('key', 'prompt_style')->value('value');
  204. $prompt = $prompt . $keyword;
  205. $promptTo = '我希望你能担任英语翻译、拼写校对和修辞改进的角色。我会将翻译的结果用于如stable diffusion、midjourney等绘画场景生成图片,语言要求尽量优美。我会用任何语言和你交流,你会识别语言,将其翻译为英语并仅回答翻译的最终结果,不要写解释。我的第一句话是:' . $prompt . '。请立刻翻译,不要回复其它内容。';
  206. $messages[] = ['role' => 'user', 'content' => $promptTo];
  207. $data = [
  208. 'messages' => $messages,
  209. ];
  210. $postData = json_encode($data);
  211. $complete = $this->host($postData);
  212. return $complete['result'];
  213. }
  214. /**
  215. * 提交sd生成插图.
  216. *
  217. * @return \Illuminate\Http\JsonResponse
  218. */
  219. public function buildSd(Request $request)
  220. {
  221. try {
  222. $task = TaskList::query()->with('role')->where('state', 3)->inRandomOrder()->first();
  223. if (!$task) {
  224. return Response::fail('暂无任务');
  225. }
  226. $task->state = 4;
  227. $task->save();
  228. $keyword = $this->toEnglish($task->keyword);
  229. $param = ['name' => '甜瓜(动漫风格)',
  230. 'init_image' => '',
  231. 'prompt' => $keyword,
  232. 'width' => '512',
  233. 'height' => '512',
  234. 'guidance_scale' => '7',
  235. 'samples' => '1',
  236. 'model_id' => '3',
  237. 'scheduler' => 'DDPMScheduler',
  238. 'type' => 'text2img',
  239. 'num_inference_steps' => '30',
  240. 'keywords' => $keyword,
  241. ];
  242. $result = (new Helper())->opensd($param);
  243. $result = json_decode($result, true);
  244. Log::warning('这个是SD回调信息:' . json_encode($result, 256));
  245. if (isset($result['data']['id'])) {
  246. $task->sd_id = $result['data']['id'];
  247. } else {
  248. $task->state = 3;
  249. }
  250. $task->save();
  251. return Response::success($result['data']);
  252. } catch (\Exception $exception) {
  253. LogHelper::exceptionLog($exception, $this->code);
  254. return Response::fail($exception->getMessage());
  255. }
  256. }
  257. /**
  258. * 查询生成进度.
  259. *
  260. * @return \Illuminate\Http\JsonResponse
  261. */
  262. public function getOpensdDetail(Request $request)
  263. {
  264. $task = TaskList::query()->where('state', 4)->inRandomOrder()->first();
  265. if (!$task) {
  266. return $this->error('暂无任务');
  267. }
  268. $res = (new Helper())->opensdDetail($task->sd_id);
  269. $res = @json_decode($res, true);
  270. if (200 != $res['code']) {
  271. return Response::fail($res['msg']);
  272. }
  273. if ('fail' == $res['data']['state']) {
  274. $task->state = 3;
  275. $task->desc = $res['data']['fail_reason'];
  276. }
  277. $task->save();
  278. if ('success' == $res['data']['state']) {
  279. $path = $this->saveImage($res['data']['gen_img']);
  280. $task->sd_image = $path;
  281. $task->state = 5;
  282. $task->save();
  283. }
  284. return $this->success('操作成功', $res['data']);
  285. }
  286. /**
  287. * 获取品牌排版.
  288. *
  289. * @return \Illuminate\Http\JsonResponse
  290. */
  291. public function buildPinyin(Request $request)
  292. {
  293. try {
  294. $task = TaskList::query()->where('state', 5)->inRandomOrder()->first();
  295. if (!$task) {
  296. return $this->error('暂无任务');
  297. }
  298. if (empty($file_name)) {
  299. $file_name = md5(time()) . '.png';
  300. }
  301. $path = public_path() . '/images/' . $file_name;
  302. $shell = 'wkhtmltoimage https://hb.swdz.com/api/ai/Pinyin?id=' . $task->id . ' ' . $path;
  303. exec($shell, $result, $status);
  304. if ($status) {
  305. $this->exit_out('生成图片失败', ['生成图片失败' => [$shell, $result, $status]]);
  306. }
  307. $domain = request()->getScheme() . '://' . request()->getHost();
  308. $pdfPath = $domain . '/images/' . $file_name;
  309. $task->image_path = $pdfPath;
  310. $result = $this->extracted($task);
  311. $task->pinyin_content = $result;
  312. $task->state = 6;
  313. $task->save();
  314. return Response::success($result);
  315. } catch (\Exception $exception) {
  316. LogHelper::exceptionLog($exception, $this->code);
  317. return Response::fail($exception->getMessage());
  318. }
  319. }
  320. /**
  321. * 下载PDF.
  322. *
  323. * @return \Illuminate\Http\JsonResponse
  324. */
  325. public function downloadPdf(Request $request)
  326. {
  327. try {
  328. $id = $request->input('id', 0);
  329. $task = TaskList::query()->find($id);
  330. if (!$task) {
  331. return Response::fail('没有该任务');
  332. }
  333. if (!empty($task->pdf_path)) {
  334. return Response::success(['url' => $task->pdf_path]);
  335. }
  336. if (empty($file_name)) {
  337. $file_name = md5(time()) . '.pdf';
  338. }
  339. $path = public_path() . '/pdf/' . $file_name;
  340. $shell = 'wkhtmltopdf https://hb.swdz.com/api/ai/Pinyin?id=' . $task->id . ' ' . $path;
  341. exec($shell, $result, $status);
  342. if ($status) {
  343. $this->exit_out('生成PDF失败', ['生成PDF失败' => [$shell, $result, $status]]);
  344. }
  345. $domain = request()->getScheme() . '://' . request()->getHost();
  346. $pdfPath = $domain . '/pdf/' . $file_name;
  347. $task->pdf_path = $pdfPath;
  348. $task->save();
  349. return Response::success(['url' => $pdfPath]);
  350. } catch (\Exception $exception) {
  351. LogHelper::exceptionLog($exception, $this->code);
  352. return Response::fail($exception->getMessage());
  353. }
  354. }
  355. private function splitChineseCharacters($inputString, $type = 0)
  356. {
  357. if (0 == $type) {
  358. $inputString = str_replace("\n", '', $inputString);
  359. }
  360. $inputString = str_replace('“', '', $inputString);
  361. $inputString = str_replace('”', '', $inputString);
  362. // $inputString = preg_replace('/[^\w\s-]+/u', '', $inputString);
  363. $length = mb_strlen($inputString, 'utf-8');
  364. $result = [];
  365. for ($i = 0; $i < $length; $i++) {
  366. $result[] = mb_substr($inputString, $i, 1, 'utf-8');
  367. }
  368. return $result;
  369. // return $inputString;
  370. }
  371. public function Pinyin(Request $request)
  372. {
  373. try {
  374. $id = $request->input('id');
  375. $task = TaskList::query()->find($id);
  376. $result = $this->extracted($task);
  377. return view('pdf', ['data' => $result, 'img' => $task->image, 'title' => $task->title]);
  378. } catch (\Exception $exception) {
  379. LogHelper::exceptionLog($exception, $this->code);
  380. return Response::fail($exception->getMessage());
  381. }
  382. }
  383. public function containsPunctuation($str)
  384. {
  385. // 定义要检测的符号
  386. $punctuation = '/[,。!、]/u'; // 中文逗号、中文句号、中文感叹号
  387. // 使用 preg_match 函数进行匹配
  388. return 1 === preg_match($punctuation, $str);
  389. }
  390. public function generate_pdf($html_path = '', $file_name = '')
  391. {
  392. if (empty($file_name)) {
  393. $file_name = md5(time()) . '.pdf';
  394. }
  395. $path = public_path() . '/pdf/' . $file_name;
  396. var_dump($path);
  397. exit;
  398. $shell = 'wkhtmltopdf --outline --minimum-font-size 9 ' . $html_path . ' ' . $path;
  399. exec($shell, $result, $status);
  400. if ($status) {
  401. $this->exit_out('生成PDF失败', ['生成PDF失败' => [$shell, $result, $status]]);
  402. }
  403. $domain = request()->getScheme() . '://' . request()->getHost();
  404. return $domain . '/pdf/' . $file_name;
  405. }
  406. public function exit_out($msg, $exceptionData = false, $code = 1, $data = [])
  407. {
  408. $out = ['code' => $code, 'msg' => $msg, 'data' => $data];
  409. if (false !== $exceptionData) {
  410. $this->trace([$msg => $exceptionData], 'error');
  411. }
  412. $json = json_encode($out, JSON_UNESCAPED_UNICODE);
  413. throw new ApiException($json);
  414. }
  415. public function trace($log = '', $level = 'info')
  416. {
  417. Log::log($level, $log);
  418. }
  419. /**
  420. * 保存图片.
  421. *
  422. * @return \Illuminate\Http\JsonResponse|string
  423. */
  424. private function saveImage($image_url)
  425. {
  426. $imageContent = file_get_contents($image_url);
  427. $imageDir = $_SERVER['DOCUMENT_ROOT'] . '/static/images/' . date('Ymd') . '/';
  428. $imageName = uniqid() . time() . '_image.png';
  429. $imagePath = $imageDir . $imageName;
  430. if (!is_dir($imageDir)) {
  431. mkdir($imageDir, 0777, true);
  432. }
  433. if (false !== $imageContent) {
  434. $savedImages = file_put_contents($imagePath, $imageContent);
  435. if (false !== $savedImages) {
  436. return $_SERVER['HTTP_HOST'] . '/static/images/' . date('Ymd') . '/' . $imageName;
  437. }
  438. return false;
  439. }
  440. return false;
  441. }
  442. /**
  443. * 执行请求
  444. */
  445. private function host($postData)
  446. {
  447. $ch = curl_init();
  448. curl_setopt_array($ch, [
  449. CURLOPT_URL => getenv('BAIDU_ERNIE_BOT40_URL') . '?access_token=' . Helper::getAccessToken(),
  450. CURLOPT_RETURNTRANSFER => true,
  451. CURLOPT_POST => true,
  452. CURLOPT_POSTFIELDS => $postData,
  453. ]);
  454. $res = curl_exec($ch);
  455. curl_close($ch);
  456. Log::warning('请求头:' .$postData);
  457. Log::warning('请求返回值:' .$res);
  458. return json_decode(trim($res), true);
  459. }
  460. public function extracted($result, $type = 0, $sdImage = ''): string
  461. {
  462. if (0 == $type) {
  463. $inputString = $result->init_content;
  464. $sdImage = $result->sd_image;
  465. } else {
  466. $inputString = $result;
  467. }
  468. $inputString = str_replace('“', '', $inputString);
  469. $inputString = str_replace('”', '', $inputString);
  470. $inputString = str_replace('——', '', $inputString);
  471. $inputString = str_replace('—', '', $inputString);
  472. $inputStringFor = $this->splitChineseCharacters($inputString, 1);
  473. $arr = [];
  474. $i = 0;
  475. foreach ($inputStringFor as $key => $value) {
  476. if ("\n" == $value) {
  477. $arr[] = $key - $i;
  478. $i++;
  479. }
  480. }
  481. $splitCharacters = $this->splitChineseCharacters($inputString);
  482. $pinyin = Pinyin::Sentence($inputString)->toArray();
  483. $string = '';
  484. $count = 0;
  485. foreach ($pinyin as $value) {
  486. $count += mb_strlen($value);
  487. }
  488. $img = '<img style="width: 100%;height:auto;float: right;margin-right: 10px;margin-top: 10px; margin-bottom: 5px" src="https://' . $sdImage . '"/>';
  489. $stringCount = 0;
  490. foreach ($pinyin as $key => $value) {
  491. if ($this->containsPunctuation($value)) {
  492. $value = '&nbsp;';
  493. }
  494. $stringCount += mb_strlen($value);
  495. if (in_array($count - $stringCount, [158, 159, 160, 161, 162, 163]) && !strstr($string, '<img')) {
  496. $string = $string . $img;
  497. }
  498. if (in_array($key + 1, $arr)) {
  499. if ($result->is_piny){
  500. $string = $string . "<span><sup>{$value}</sup>{$splitCharacters[$key]}</span><br style='clear: both'><br style='clear: both'>";
  501. }else{
  502. $string = $string . "<span>{$splitCharacters[$key]}</span><br style='clear: both'><br style='clear: both'>";
  503. }
  504. } else {
  505. $style = '';
  506. // if (in_array($key, $arr) || 0 == $key) {
  507. // $style = 'style="margin-left: 5rem"';
  508. // }
  509. if ($result->is_piny){
  510. $string = $string . '<span ' . $style . "><sup>{$value}</sup>{$splitCharacters[$key]}</span>";
  511. }else{
  512. $string = $string . '<span ' . $style . ">{$splitCharacters[$key]}</span>";
  513. }
  514. }
  515. }
  516. return $string;
  517. }
  518. }