Passed
Pull Request — master (#7304)
by Yannick
10:52 queued 01:00
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
use Symfony\Component\Routing\Attribute\Route;
22
23
use const FILTER_SANITIZE_NUMBER_INT;
24
25
/**
26
 * Controller to handle AI-related functionalities.
27
 */
28
#[Route('/ai')]
29
class AiController extends AbstractController
30
{
31
    public function __construct(
32
        private readonly AiProviderFactory $aiProviderFactory,
33
        private readonly TrackEAttemptRepository $attemptRepo,
34
        private readonly EntityManagerInterface $em
35
    ) {}
36
37
    #[Route('/generate_aiken', name: 'chamilo_core_ai_generate_aiken', methods: ['POST'])]
38
    public function generateAiken(Request $request): JsonResponse
39
    {
40
        try {
41
            $data = json_decode($request->getContent(), true);
42
            $nQ = (int) ($data['nro_questions'] ?? 0);
43
            $language = (string) ($data['language'] ?? 'en');
44
            $topic = trim((string) ($data['quiz_name'] ?? ''));
45
            $questionType = $data['question_type'] ?? 'multiple_choice';
46
            $aiProvider = $data['ai_provider'] ?? null;
47
48
            if ($nQ <= 0 || empty($topic)) {
49
                return new JsonResponse([
50
                    'success' => false,
51
                    'text' => 'Invalid request parameters. Ensure all fields are filled correctly.',
52
                ], 400);
53
            }
54
55
            $aiService = $this->aiProviderFactory->getProvider($aiProvider, 'text');
56
            $questions = $aiService->generateQuestions($topic, $nQ, $questionType, $language);
57
58
            if (str_starts_with($questions, 'Error:')) {
59
                return new JsonResponse([
60
                    'success' => false,
61
                    'text' => $questions,
62
                ], 500);
63
            }
64
65
            return new JsonResponse([
66
                'success' => true,
67
                'text' => trim($questions),
68
            ]);
69
        } catch (Exception $e) {
70
            error_log('AI Request failed: '.$e->getMessage());
71
72
            return new JsonResponse([
73
                'success' => false,
74
                'text' => 'An error occurred while generating questions. Please contact the administrator.',
75
            ], 500);
76
        }
77
    }
78
79
    #[Route('/open_answer_grade', name: 'chamilo_core_ai_open_answer_grade', methods: ['POST'])]
80
    public function openAnswerGrade(Request $request): JsonResponse
81
    {
82
        $exeId = $request->request->getInt('exeId', 0);
83
        $questionId = $request->request->getInt('questionId', 0);
84
        $courseId = $request->request->getInt('courseId', 0);
85
86
        if (0 === $exeId || 0 === $questionId || 0 === $courseId) {
87
            return $this->json(['error' => 'Missing parameters'], 400);
88
        }
89
90
        /** @var TrackEExercise $trackExercise */
91
        $trackExercise = $this->em
92
            ->getRepository(TrackEExercise::class)
93
            ->find($exeId)
94
        ;
95
        if (null === $trackExercise) {
96
            return $this->json(['error' => 'Exercise attempt not found'], 404);
97
        }
98
99
        $attempt = $this->attemptRepo->findOneBy([
100
            'trackExercise' => $trackExercise,
101
            'questionId' => $questionId,
102
            'user' => $trackExercise->getUser(),
103
        ]);
104
        if (null === $attempt) {
105
            return $this->json(['error' => 'Attempt not found'], 404);
106
        }
107
108
        $answerText = $attempt->getAnswer();
109
110
        if (ctype_digit($answerText)) {
111
            $cqa = $this->em
112
                ->getRepository(CQuizAnswer::class)
113
                ->find((int) $answerText)
114
            ;
115
            if ($cqa) {
116
                $answerText = $cqa->getAnswer();
117
            }
118
        }
119
120
        $courseInfo = api_get_course_info_by_id($courseId);
121
        if (empty($courseInfo['real_id'])) {
122
            return $this->json(['error' => 'Course info not found'], 500);
123
        }
124
125
        $question = Question::read($questionId, $courseInfo);
126
        if (false === $question) {
127
            return $this->json(['error' => 'Question not found'], 404);
128
        }
129
130
        $language = $courseInfo['language'] ?? 'en';
131
        $courseTitle = $courseInfo['title'] ?? '';
132
        $maxScore = $question->selectWeighting();
133
        $questionText = $question->selectTitle();
134
        $prompt = \sprintf(
135
            "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'.",
136
            $language,
137
            $questionText,
138
            $courseTitle,
139
            $maxScore,
140
            $answerText
141
        );
142
143
        $raw = trim((string) $this->aiProviderFactory
144
            ->getProvider()
145
            ->gradeOpenAnswer($prompt, 'open_answer_grade'));
146
147
        if ('' === $raw) {
148
            return $this->json(['error' => 'AI request failed'], 500);
149
        }
150
151
        if (str_contains($raw, "\n")) {
152
            [$scoreLine, $feedback] = explode("\n", $raw, 2);
153
        } else {
154
            $scoreLine = (string) $maxScore;
155
            $feedback = $raw;
156
        }
157
158
        $score = (int) filter_var($scoreLine, FILTER_SANITIZE_NUMBER_INT);
159
160
        $track = new TrackEDefault();
161
        $track
162
            ->setDefaultUserId($this->getUser()->getId())
163
            ->setDefaultEventType('ai_use_question_feedback')
164
            ->setDefaultValueType('attempt_id')
165
            ->setDefaultValue((string) $attempt->getId())
166
            ->setDefaultDate(new DateTime())
167
            ->setCId($courseId)
168
            ->setSessionId(api_get_session_id())
169
        ;
170
        $this->em->persist($track);
171
        $this->em->flush();
172
173
        return $this->json([
174
            'score' => $score,
175
            'feedback' => $feedback,
176
        ]);
177
    }
178
179
    #[Route('/generate_image', name: 'chamilo_core_ai_generate_image', methods: ['POST'])]
180
    public function generateImage(Request $request): JsonResponse
181
    {
182
        try {
183
            $data = json_decode($request->getContent(), true);
184
            $n = (int) ($data['n'] ?? 1);
185
            $language = (string) ($data['language'] ?? 'en');
186
            $prompt = trim((string) ($data['prompt'] ?? ''));
187
            $toolName = trim((string) ($data['tool'] ?? 'document'));
188
            $aiProvider = $data['ai_provider'] ?? null;
189
190
            if ($n <= 0 || empty($prompt) || empty($toolName)) {
191
                return new JsonResponse([
192
                    'success' => false,
193
                    'text' => 'Invalid request parameters. Ensure all fields are filled correctly.',
194
                ], 400);
195
            }
196
197
            $aiService = $this->aiProviderFactory->getProvider($aiProvider, 'image');
198
            $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

198
            /** @scrutinizer ignore-call */ 
199
            $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...
199
200
            if (str_starts_with($questions, 'Error:')) {
201
                return new JsonResponse([
202
                    'success' => false,
203
                    'text' => $questions,
204
                ], 500);
205
            }
206
207
            return new JsonResponse([
208
                'success' => true,
209
                'text' => trim($questions),
210
            ]);
211
        } catch (Exception $e) {
212
            error_log('AI Request failed: '.$e->getMessage());
213
214
            return new JsonResponse([
215
                'success' => false,
216
                'text' => 'An error occurred while generating questions. Please contact the administrator.',
217
            ], 500);
218
        }
219
    }
220
}
221