Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1441 ariadna 1
<?php
2
// This file is part of Moodle - http://moodle.org/
3
//
4
// Moodle is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// Moodle is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
 
17
namespace aiprovider_azureai;
18
 
19
use core\http_client;
20
use core_ai\ai_image;
21
use GuzzleHttp\Psr7\Request;
22
use GuzzleHttp\Psr7\Uri;
23
use Psr\Http\Message\RequestInterface;
24
use Psr\Http\Message\ResponseInterface;
25
use Psr\Http\Message\UriInterface;
26
 
27
/**
28
 * Class process image generation.
29
 *
30
 * @package    aiprovider_azureai
31
 * @copyright  2024 Matt Porritt <matt.porritt@moodle.com>
32
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
33
 */
34
class process_generate_image extends abstract_processor {
35
    /** @var int The number of images to generate dall-e-3 only supports 1 */
36
    private int $numberimages = 1;
37
 
38
    #[\Override]
39
    protected function get_endpoint(): UriInterface {
40
        $url = rtrim($this->provider->config['endpoint'], '/')
41
            . '/openai/deployments/'
42
            . $this->get_deployment_name()
43
            . '/images/generations?api-version='
44
            . $this->get_api_version();
45
 
46
        return new Uri($url);
47
    }
48
 
49
    #[\Override]
50
    protected function query_ai_api(): array {
51
        $response = parent::query_ai_api();
52
 
53
        // If the request was successful, save the URL to a file.
54
        if ($response['success']) {
55
            $fileobj = $this->url_to_file(
56
                $this->action->get_configuration('userid'),
57
                $response['sourceurl']
58
            );
59
            // Add the file to the response, so the calling placement can do whatever they want with it.
60
            $response['draftfile'] = $fileobj;
61
        }
62
 
63
        return $response;
64
    }
65
 
66
    /**
67
     * Convert the given aspect ratio to an image size
68
     * that is compatible with the azureai API.
69
     *
70
     * @param string $ratio The aspect ratio of the image.
71
     * @return string The size of the image.
72
     * @throws \coding_exception
73
     */
74
    private function calculate_size(string $ratio): string {
75
        if ($ratio === 'square') {
76
            $size = '1024x1024';
77
        } else if ($ratio === 'landscape') {
78
            $size = '1792x1024';
79
        } else if ($ratio === 'portrait') {
80
            $size = '1024x1792';
81
        } else {
82
            throw new \coding_exception('Invalid aspect ratio: ' . $ratio);
83
        }
84
        return $size;
85
    }
86
 
87
    #[\Override]
88
    protected function create_request_object(string $userid): RequestInterface {
89
        return new Request(
90
            method: 'POST',
91
            uri: '',
92
            headers: [
93
                'Content-Type' => 'application/json',
94
            ],
95
            body: json_encode((object) [
96
                'prompt' => $this->action->get_configuration('prompttext'),
97
                'n' => $this->numberimages,
98
                'quality' => $this->action->get_configuration('quality'),
99
                'size' => $this->calculate_size($this->action->get_configuration('aspectratio')),
100
                'style' => $this->action->get_configuration('style'),
101
                'user' => $userid,
102
            ]),
103
        );
104
    }
105
 
106
    #[\Override]
107
    protected function handle_api_success(ResponseInterface $response): array {
108
        $responsebody = $response->getBody();
109
        $bodyobj = json_decode($responsebody->getContents());
110
 
111
        return [
112
            'success' => true,
113
            'sourceurl' => $bodyobj->data[0]->url,
114
            'revisedprompt' => $bodyobj->data[0]->revised_prompt,
115
        ];
116
    }
117
 
118
    /**
119
     * Convert the url for the image  to a file.
120
     *
121
     * Placements can't interact with the provider AI directly,
122
     * therefore we need to provide the image file in a format that can
123
     * be used by placements. So we use the file API.
124
     *
125
     * @param int $userid The user id.
126
     * @param string $url The URL to the image.
127
     * @return \stored_file The file object.
128
     */
129
    private function url_to_file(int $userid, string $url): \stored_file {
130
        global $CFG;
131
 
132
        require_once("{$CFG->libdir}/filelib.php");
133
 
134
        // Azure AI doesn't always return unique file names, but does return unique URLS.
135
        // Therefore, some processing is needed to get a unique filename.
136
        $parsedurl = parse_url($url, PHP_URL_PATH); // Parse the URL to get the path.
137
        $fileext = pathinfo($parsedurl, PATHINFO_EXTENSION); // Get the file extension.
138
        $filename = substr(hash('sha512', ($url . $userid)), 0, 16) . '.' . $fileext;
139
 
140
        $client = \core\di::get(http_client::class);
141
 
142
        // Download the image and add the watermark.
143
        $downloadtmpdir = make_request_directory();
144
        $tempdst = $downloadtmpdir . $filename;
145
        $client->get($url, [
146
            'sink' => $tempdst,
147
            'timeout' => $CFG->repositorygetfiletimeout,
148
        ]);
149
        $image = new ai_image($tempdst);
150
        $image->add_watermark()->save();
151
 
152
        // We put the file in the user draft area initially.
153
        // Placements (on behalf of the user) can then move it to the correct location.
154
        $fileinfo = new \stdClass();
155
        $fileinfo->contextid = \context_user::instance($userid)->id;
156
        $fileinfo->filearea = 'draft';
157
        $fileinfo->component = 'user';
158
        $fileinfo->itemid = file_get_unused_draft_itemid();
159
        $fileinfo->filepath = '/';
160
        $fileinfo->filename = $filename;
161
 
162
        $fs = get_file_storage();
163
        return $fs->create_file_from_string($fileinfo, file_get_contents($tempdst));
164
    }
165
}