WebOrbiton
v1.0.0.1

StocketBase

98 lines · 3.6 KB
  1. <?php
  2. ​
  3. declare(strict_types=1);
  4. ​
  5. require_once __DIR__ . '/includes/config.php';
  6. require_once __DIR__ . '/includes/database.php';
  7. require_once __DIR__ . '/includes/auth.php';
  8. require_once __DIR__ . '/includes/csrf.php';
  9. require_once __DIR__ . '/includes/ai.php';
  10. ​
  11. const AI_MAX_BODY_BYTES = 600000;
  12. ​
  13. function aiRespond(int $status, array $payload)
  14. {
  15. http_response_code($status);
  16. header('Content-Type: application/json; charset=utf-8');
  17. header('Cache-Control: no-store');
  18. header('X-Content-Type-Options: nosniff');
  19. echo json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_INVALID_UTF8_SUBSTITUTE);
  20. exit;
  21. }
  22. ​
  23. if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') {
  24. header('Allow: POST');
  25. aiRespond(405, ['ok' => false, 'error' => 'Something went wrong. Please refresh the page.']);
  26. }
  27. ​
  28. Auth::boot();
  29. ​
  30. if (!Auth::check()) {
  31. aiRespond(401, ['ok' => false, 'error' => 'You were logged out. Refresh the page and log in again.']);
  32. }
  33. ​
  34. if (!Csrf::verify($_SERVER['HTTP_X_CSRF_TOKEN'] ?? null)) {
  35. aiRespond(403, ['ok' => false, 'error' => 'Your session expired. Refresh the page and try again.']);
  36. }
  37. ​
  38. if (!Ai::availableFor(Auth::role())) {
  39. aiRespond(403, ['ok' => false, 'error' => 'The AI helper is not available for your account.']);
  40. }
  41. ​
  42. $declaredLength = (int) ($_SERVER['CONTENT_LENGTH'] ?? 0);
  43. if ($declaredLength > AI_MAX_BODY_BYTES) {
  44. aiRespond(413, ['ok' => false, 'error' => 'That\'s too much text at once. Try a shorter message or a smaller part.']);
  45. }
  46. ​
  47. $rawBody = (string) file_get_contents('php://input', false, null, 0, AI_MAX_BODY_BYTES + 1);
  48. if (strlen($rawBody) > AI_MAX_BODY_BYTES) {
  49. aiRespond(413, ['ok' => false, 'error' => 'That\'s too much text at once. Try a shorter message or a smaller part.']);
  50. }
  51. ​
  52. $input = json_decode($rawBody, true);
  53. if (!is_array($input)) {
  54. aiRespond(400, ['ok' => false, 'error' => 'Something went wrong. Please refresh the page and try again.']);
  55. }
  56. ​
  57. $message = is_string($input['message'] ?? null) ? trim($input['message']) : '';
  58. if ($message === '' || mb_strlen($message) > 4000) {
  59. aiRespond(400, ['ok' => false, 'error' => 'Please keep your message under 4000 characters.']);
  60. }
  61. ​
  62. $rawBlocks = is_array($input['blocks'] ?? null) ? array_slice($input['blocks'], 0, 300) : [];
  63. $blocks = Ai::normalizeBlocks($rawBlocks);
  64. $fields = Ai::normalizeFields(is_array($input['fields'] ?? null) ? $input['fields'] : []);
  65. if ($blocks === [] && $fields === []) {
  66. aiRespond(400, ['ok' => false, 'error' => 'There\'s nothing written yet for me to work with.']);
  67. }
  68. ​
  69. $scope = ($input['scope'] ?? 'article') === 'block' ? 'block' : 'article';
  70. $focusId = is_string($input['focus_id'] ?? null) ? substr($input['focus_id'], 0, 40) : '';
  71. $history = is_array($input['history'] ?? null) ? array_slice($input['history'], -10) : [];
  72. ​
  73. $user = Auth::user();
  74. $userId = (int) ($user['id'] ?? 0);
  75. ​
  76. try {
  77. if (Ai::rateLimited($userId)) {
  78. aiRespond(429, ['ok' => false, 'error' => 'You\'ve used up this hour\'s AI messages. Try again a bit later.']);
  79. }
  80. Ai::recordRequest($userId);
  81. } catch (Throwable $exception) {
  82. aiRespond(500, ['ok' => false, 'error' => 'The AI helper is temporarily unavailable.']);
  83. }
  84. ​
  85. session_write_close();
  86. set_time_limit(130);
  87. ignore_user_abort(false);
  88. ​
  89. try {
  90. $result = Ai::chat($blocks, $message, $history, $scope, $focusId, $scope === 'block' ? [] : $fields);
  91. aiRespond(200, ['ok' => true] + $result);
  92. } catch (AiException $exception) {
  93. aiRespond(502, ['ok' => false, 'error' => $exception->getMessage()]);
  94. } catch (Throwable $exception) {
  95. error_log('StocketBase AI: ' . $exception->getMessage());
  96. aiRespond(500, ['ok' => false, 'error' => 'The AI helper failed unexpectedly.']);
  97. }
  98. ​