WebOrbiton
v3.0.0.0

FlatlyPage

114 lines · 4.3 KB
  1. <?php
  2. require_once __DIR__ . '/../config.php';
  3. require_once __DIR__ . '/../admin/ai-helper-lib.php';
  4. ​
  5. header('Content-Type: application/json; charset=utf-8');
  6. header('Cache-Control: no-store');
  7. header('X-Content-Type-Options: nosniff');
  8. ​
  9. function ai_respond(int $status, array $payload): void
  10. {
  11. http_response_code($status);
  12. echo json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PARTIAL_OUTPUT_ON_ERROR);
  13. exit;
  14. }
  15. ​
  16. if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') {
  17. header('Allow: POST');
  18. ai_respond(405, ['ok' => false, 'error' => 'Method not allowed.']);
  19. }
  20. ​
  21. if (!is_logged_in()) {
  22. ai_respond(401, ['ok' => false, 'error' => 'Your session has expired. Reload the page and log in again.']);
  23. }
  24. check_ip_ban();
  25. ​
  26. if (!verify_csrf_token((string) ($_SERVER['HTTP_X_CSRF_TOKEN'] ?? ''))) {
  27. ai_respond(403, ['ok' => false, 'error' => 'Invalid security token. Reload the page and try again.']);
  28. }
  29. ​
  30. $maxBody = 400000;
  31. if ((int) ($_SERVER['CONTENT_LENGTH'] ?? 0) > $maxBody) {
  32. ai_respond(413, ['ok' => false, 'error' => 'The request is too large.']);
  33. }
  34. $input = json_decode((string) file_get_contents('php://input', false, null, 0, $maxBody + 1), true);
  35. if (!is_array($input)) {
  36. ai_respond(400, ['ok' => false, 'error' => 'Invalid request.']);
  37. }
  38. ​
  39. $now = time();
  40. $hits = array_values(array_filter((array) ($_SESSION['ai_helper_hits'] ?? []), static fn($time) => is_int($time) && $now - $time < 600));
  41. if (count($hits) >= 30) {
  42. ai_respond(429, ['ok' => false, 'error' => 'Too many AI requests. Please wait a few minutes.']);
  43. }
  44. $hits[] = $now;
  45. $_SESSION['ai_helper_hits'] = $hits;
  46. session_write_close();
  47. set_time_limit(180);
  48. ​
  49. $settings = AiHelper::settings();
  50. if (!$settings['enabled'] || $settings['key'] === '') {
  51. ai_respond(400, ['ok' => false, 'error' => 'The AI helper is not enabled. Add an API key in Settings > AI Helper.']);
  52. }
  53. ​
  54. $action = (string) ($input['action'] ?? 'chat');
  55. ​
  56. if ($action === 'test') {
  57. $result = AiHelper::test($settings);
  58. ai_respond($result['ok'] ? 200 : 502, $result);
  59. }
  60. ​
  61. if ($action !== 'chat') {
  62. ai_respond(400, ['ok' => false, 'error' => 'Unknown action.']);
  63. }
  64. ​
  65. $prompt = trim((string) ($input['prompt'] ?? ''));
  66. if ($prompt === '' || mb_strlen($prompt) > 4000) {
  67. ai_respond(400, ['ok' => false, 'error' => 'Write a request of up to 4000 characters.']);
  68. }
  69. ​
  70. if (($input['mode'] ?? '') === 'settings') {
  71. $tab = (string) ($input['tab'] ?? '');
  72. $history = [];
  73. foreach (array_slice(is_array($input['history'] ?? null) ? array_values($input['history']) : [], -6) as $entry) {
  74. if (is_array($entry) && in_array($entry['role'] ?? '', ['user', 'assistant'], true) && is_string($entry['text'] ?? null)) {
  75. $history[] = ['role' => $entry['role'], 'text' => mb_substr($entry['text'], 0, 1500)];
  76. }
  77. }
  78. $language = AiHelper::cleanLanguage($input['language'] ?? null);
  79. $result = AiHelper::proposeSettings($settings, $tab, $input['settings'] ?? [], $prompt, $history, $language, is_array($input['source'] ?? null) ? $input['source'] : null);
  80. ai_respond($result['ok'] ? 200 : 502, $result);
  81. }
  82. ​
  83. $scope = (string) ($input['scope'] ?? 'all');
  84. if ($scope !== 'all' && preg_match('/^b\d{1,3}$/', $scope) !== 1) {
  85. ai_respond(400, ['ok' => false, 'error' => 'Invalid scope.']);
  86. }
  87. ​
  88. $blocks = $input['blocks'] ?? [];
  89. if (!is_array($blocks) || count($blocks) > 100) {
  90. ai_respond(400, ['ok' => false, 'error' => 'Invalid page data.']);
  91. }
  92. $blocks = array_values($blocks);
  93. ​
  94. $page = is_array($input['page'] ?? null) ? $input['page'] : [];
  95. ​
  96. $history = [];
  97. foreach (array_slice(is_array($input['history'] ?? null) ? array_values($input['history']) : [], -6) as $entry) {
  98. if (is_array($entry) && in_array($entry['role'] ?? '', ['user', 'assistant'], true) && is_string($entry['text'] ?? null)) {
  99. $history[] = ['role' => $entry['role'], 'text' => mb_substr($entry['text'], 0, 1500)];
  100. }
  101. }
  102. ​
  103. $language = AiHelper::cleanLanguage($input['language'] ?? null);
  104. $source = null;
  105. if ($language !== null && is_array($input['source'] ?? null)) {
  106. $source = $input['source'];
  107. if (!is_array($source['blocks'] ?? null) || count($source['blocks']) > 100) {
  108. $source = null;
  109. }
  110. }
  111. ​
  112. $result = AiHelper::propose($settings, $page, $blocks, $prompt, $scope, $history, $language, $source);
  113. ai_respond($result['ok'] ? 200 : 502, $result);
  114. ​