Passed
Pull Request — master (#7304)
by Yannick
09:41
created

AiController::generateImage()   B

Complexity

Conditions 6
Paths 15

Size

Total Lines 39
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 29
c 1
b 0
f 0
nc 15
nop 1
dl 0
loc 39
rs 8.8337
1
<?php
2
3
declare(strict_types=1);
4
5
/* For licensing terms, see /license.txt */
6
7
namespace Chamilo\CoreBundle\Controller;
8
9
use Chamilo\CoreBundle\AiProvider\AiProviderFactory;
10
use Chamilo\CoreBundle\Entity\TrackEDefault;
11
use Chamilo\CoreBundle\Entity\TrackEExercise;
12
use Chamilo\CoreBundle\Repository\TrackEAttemptRepository;
13
use Chamilo\CourseBundle\Entity\CQuizAnswer;
14
use DateTime;
15
use Doctrine\ORM\EntityManagerInterface;
16
use Exception;
17
use Question;
18
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
19
use Symfony\Component\HttpFoundation\JsonResponse;
20
use Symfony\Component\HttpFoundation\Request;
21
22
use Symfony\Component\Routing\Attribute\Route;
23
24
use const FILTER_SANITIZE_NUMBER_INT;
25
26
/**
27
 * Controller to handle AI-related functionalities.
28
 */
29
#[Route('/ai')]
30
class AiController extends AbstractController
31
{
32
    public function __construct(
33
        private readonly AiProviderFactory $aiProviderFactory,
34
        private readonly TrackEAttemptRepository $attemptRepo,
35
        private readonly EntityManagerInterface $em
36
    ) {}
37
38
    #[Route('/generate_aiken', name: 'chamilo_core_ai_generate_aiken', methods: ['POST'])]
39
    public function generateAiken(Request $request): JsonResponse
40
    {
41
        try {
42
            $data = json_decode($request->getContent(), true);
43
            $nQ = (int) ($data['nro_questions'] ?? 0);
44
            $language = (string) ($data['language'] ?? 'en');
45
            $topic = trim((string) ($data['quiz_name'] ?? ''));
46
            $questionType = $data['question_type'] ?? 'multiple_choice';
47
            $aiProvider = $data['ai_provider'] ?? null;
48
49
            if ($nQ <= 0 || empty($topic)) {
50
                return new JsonResponse([
51
                    'success' => false,
52
                    'text' => 'Invalid request parameters. Ensure all fields are filled correctly.',
53
                ], 400);
54
            }
55
56
            $aiService = $this->aiProviderFactory->getProvider($aiProvider, 'text');
57
            $questions = $aiService->generateQuestions($topic, $nQ, $questionType, $language);
58
59
            if (str_starts_with($questions, 'Error:')) {
60
                return new JsonResponse([
61
                    'success' => false,
62
                    'text' => $questions,
63
                ], 500);
64
            }
65
66
            return new JsonResponse([
67
                'success' => true,
68
                'text' => trim($questions),
69
            ]);
70
        } catch (Exception $e) {
71
            error_log('AI Request failed: '.$e->getMessage());
72
73
            return new JsonResponse([
74
                'success' => false,
75
                'text' => 'An error occurred while generating questions. Please contact the administrator.',
76
            ], 500);
77
        }
78
    }
79
80
    #[Route('/open_answer_grade', name: 'chamilo_core_ai_open_answer_grade', methods: ['POST'])]
81
    public function openAnswerGrade(Request $request): JsonResponse
82
    {
83
        $exeId = $request->request->getInt('exeId', 0);
84
        $questionId = $request->request->getInt('questionId', 0);
85
        $courseId = $request->request->getInt('courseId', 0);
86
87
        if (0 === $exeId || 0 === $questionId || 0 === $courseId) {
88
            return $this->json(['error' => 'Missing parameters'], 400);
89
        }
90
91
        /** @var TrackEExercise $trackExercise */
92
        $trackExercise = $this->em
93
            ->getRepository(TrackEExercise::class)
94
            ->find($exeId)
95
        ;
96
        if (null === $trackExercise) {
97
            return $this->json(['error' => 'Exercise attempt not found'], 404);
98
        }
99
100
        $attempt = $this->attemptRepo->findOneBy([
101
            'trackExercise' => $trackExercise,
102
            'questionId' => $questionId,
103
            'user' => $trackExercise->getUser(),
104
        ]);
105
        if (null === $attempt) {
106
            return $this->json(['error' => 'Attempt not found'], 404);
107
        }
108
109
        $answerText = $attempt->getAnswer();
110
111
        if (ctype_digit($answerText)) {
112
            $cqa = $this->em
113
                ->getRepository(CQuizAnswer::class)
114
                ->find((int) $answerText)
115
            ;
116
            if ($cqa) {
117
                $answerText = $cqa->getAnswer();
118
            }
119
        }
120
121
        $courseInfo = api_get_course_info_by_id($courseId);
122
        if (empty($courseInfo['real_id'])) {
123
            return $this->json(['error' => 'Course info not found'], 500);
124
        }
125
126
        $question = Question::read($questionId, $courseInfo);
127
        if (false === $question) {
128
            return $this->json(['error' => 'Question not found'], 404);
129
        }
130
131
        $language = $courseInfo['language'] ?? 'en';
132
        $courseTitle = $courseInfo['title'] ?? '';
133
        $maxScore = $question->selectWeighting();
134
        $questionText = $question->selectTitle();
135
        $prompt = \sprintf(
136
            "In language %s, for the question: '%s', in the context of %s, provide a score between 0 and %d on one line, then feedback on the next line for the following answer: '%s'.",
137
            $language,
138
            $questionText,
139
            $courseTitle,
140
            $maxScore,
141
            $answerText
142
        );
143
144
        $raw = trim((string) $this->aiProviderFactory
145
            ->getProvider()
146
            ->gradeOpenAnswer($prompt, 'open_answer_grade'));
147
148
        if ('' === $raw) {
149
            return $this->json(['error' => 'AI request failed'], 500);
150
        }
151
152
        if (str_contains($raw, "\n")) {
153
            [$scoreLine, $feedback] = explode("\n", $raw, 2);
154
        } else {
155
            $scoreLine = (string) $maxScore;
156
            $feedback = $raw;
157
        }
158
159
        $score = (int) filter_var($scoreLine, FILTER_SANITIZE_NUMBER_INT);
160
161
        $track = new TrackEDefault();
162
        $track
163
            ->setDefaultUserId($this->getUser()->getId())
164
            ->setDefaultEventType('ai_use_question_feedback')
165
            ->setDefaultValueType('attempt_id')
166
            ->setDefaultValue((string) $attempt->getId())
167
            ->setDefaultDate(new DateTime())
168
            ->setCId($courseId)
169
            ->setSessionId(api_get_session_id())
170
        ;
171
        $this->em->persist($track);
172
        $this->em->flush();
173
174
        return $this->json([
175
            'score' => $score,
176
            'feedback' => $feedback,
177
        ]);
178
    }
179
180
    #[Route('/generate_image', name: 'chamilo_core_ai_generate_image', methods: ['POST'])]
181
    public function generateImage(Request $request): JsonResponse
182
    {
183
        try {
184
            $data = json_decode($request->getContent(), true);
185
            $n = (int) ($data['n'] ?? 1);
186
            $language = (string) ($data['language'] ?? 'en');
187
            $prompt = trim((string) ($data['prompt'] ?? ''));
188
            $toolName = trim((string) ($data['tool'] ?? 'document'));
189
            $aiProvider = $data['ai_provider'] ?? null;
190
191
            if ($n <= 0 || empty($prompt) || empty($toolName)) {
192
                return new JsonResponse([
193
                    'success' => false,
194
                    'text' => 'Invalid request parameters. Ensure all fields are filled correctly.',
195
                ], 400);
196
            }
197
198
            $aiService = $this->aiProviderFactory->getProvider($aiProvider, 'image');
199
            $questions = $aiService->generateImage($prompt, $toolName, ['language' => $language, 'n' => $n]);
0 ignored issues
show
Bug introduced by
The method generateImage() does not exist on Chamilo\CoreBundle\AiProvider\AiProviderInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

199
            /** @scrutinizer ignore-call */ 
200
            $questions = $aiService->generateImage($prompt, $toolName, ['language' => $language, 'n' => $n]);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
200
201
            if (str_starts_with($questions, 'Error:')) {
202
                return new JsonResponse([
203
                    'success' => false,
204
                    'text' => $questions,
205
                ], 500);
206
            }
207
208
            return new JsonResponse([
209
                'success' => true,
210
                'text' => trim($questions),
211
            ]);
212
        } catch (Exception $e) {
213
            error_log('AI Request failed: '.$e->getMessage());
214
215
            return new JsonResponse([
216
                'success' => false,
217
                'text' => 'An error occurred while generating questions. Please contact the administrator.',
218
            ], 500);
219
        }
220
    }
221
}
222