Skip to content

Commit 340c0af

Browse files
ai models code review, added ai actions flow support, added ecommerce flow + frontend ai chat block
1 parent ff2ea44 commit 340c0af

File tree

18 files changed

+1599
-140
lines changed

18 files changed

+1599
-140
lines changed
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
<?php
2+
3+
/**
4+
* SiteBase
5+
* PHP Version 8.3
6+
*
7+
* @category CMS / Framework
8+
* @package Degami\Sitebase
9+
* @author Mirko De Grandis <degami@github.com>
10+
* @license MIT https://opensource.org/licenses/mit-license.php
11+
* @link https://github.com/degami/sitebase
12+
*/
13+
14+
namespace App\Base\Abstracts\Models;
15+
16+
use App\App;
17+
use App\Base\Interfaces\AI\AIModelInterface;
18+
use App\Base\Abstracts\ContainerAwareObject;
19+
use Exception;
20+
21+
/**
22+
* Abstract LLM Adapter
23+
*/
24+
abstract class AbstractLLMAdapter extends ContainerAwareObject implements AIModelInterface
25+
{
26+
27+
public function sendRaw(array $payload) : array
28+
{
29+
$client = $this->getGuzzle();
30+
31+
$prepared = $this->prepareRequest($payload);
32+
33+
$resp = $client->post(
34+
$this->getEndpoint(),
35+
$prepared
36+
);
37+
38+
$responseBody = $resp->getBody()->getContents();
39+
40+
if (!isJson($responseBody)) {
41+
throw new Exception("Invalid response from LLM API");
42+
}
43+
44+
return json_decode($responseBody, true) ?? [];
45+
}
46+
47+
public function ask(string $prompt, ?string $model = null, ?array $previousMessages = null) : string
48+
{
49+
$contents = $this->buildConversation(
50+
$previousMessages ?? [],
51+
$prompt
52+
);
53+
54+
$raw = $this->sendRaw($contents);
55+
$norm = $this->normalizeResponse($raw);
56+
57+
return trim($norm['assistantText'] ?? '');
58+
}
59+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
/**
4+
* SiteBase
5+
* PHP Version 8.3
6+
*
7+
* @category CMS / Framework
8+
* @package Degami\Sitebase
9+
* @author Mirko De Grandis <degami@github.com>
10+
* @license MIT https://opensource.org/licenses/mit-license.php
11+
* @link https://github.com/degami/sitebase
12+
*/
13+
14+
namespace App\Base\AI\Actions;
15+
16+
use GuzzleHttp\Client;
17+
use Exception;
18+
19+
class GraphQLExecutor
20+
{
21+
protected string $endpoint;
22+
protected ?string $authHeader; // Bearer token or null
23+
protected Client $http;
24+
25+
public function __construct(string $endpoint, ?string $authHeader = null)
26+
{
27+
$this->endpoint = $endpoint;
28+
$this->authHeader = $authHeader;
29+
$this->http = new Client(['timeout' => 30, 'verify' => false]);
30+
}
31+
32+
/**
33+
* Execute query/mutation and return decoded JSON (data or errors)
34+
*
35+
* @param string $query
36+
* @param array $variables
37+
* @return array
38+
* @throws Exception
39+
*/
40+
public function execute(string $query, array $variables = []): array
41+
{
42+
$headers = ['Content-Type' => 'application/json'];
43+
if (!empty($this->authHeader)) {
44+
$headers['Authorization'] = $this->authHeader;
45+
}
46+
47+
$resp = $this->http->post($this->endpoint, [
48+
'headers' => $headers,
49+
'json' => ['query' => $query, 'variables' => $variables]
50+
]);
51+
52+
$body = json_decode($resp->getBody()->getContents(), true);
53+
if ($body === null) {
54+
throw new Exception("Invalid JSON response from GraphQL endpoint");
55+
}
56+
return $body;
57+
}
58+
}
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
<?php
2+
3+
/**
4+
* SiteBase
5+
* PHP Version 8.3
6+
*
7+
* @category CMS / Framework
8+
* @package Degami\Sitebase
9+
* @author Mirko De Grandis <degami@github.com>
10+
* @license MIT https://opensource.org/licenses/mit-license.php
11+
* @link https://github.com/degami/sitebase
12+
*/
13+
14+
namespace App\Base\AI\Actions;
15+
16+
class GraphQLSchemaProvider
17+
{
18+
const INTROSPECTION_QUERY = '
19+
query IntrospectionQuery {
20+
__schema {
21+
types {
22+
kind
23+
name
24+
description
25+
fields {
26+
name
27+
description
28+
args {
29+
name
30+
type {
31+
kind
32+
name
33+
ofType {
34+
kind
35+
name
36+
}
37+
}
38+
}
39+
type {
40+
kind
41+
name
42+
ofType {
43+
kind
44+
name
45+
}
46+
}
47+
}
48+
inputFields {
49+
name
50+
type {
51+
kind
52+
name
53+
ofType {
54+
kind
55+
name
56+
}
57+
}
58+
}
59+
enumValues {
60+
name
61+
description
62+
}
63+
}
64+
queryType { name }
65+
mutationType { name }
66+
subscriptionType { name }
67+
}
68+
}
69+
';
70+
protected GraphQLExecutor $executor;
71+
72+
public function __construct(GraphQLExecutor $executor)
73+
{
74+
$this->executor = $executor;
75+
}
76+
77+
public function getFullSchema(): string
78+
{
79+
$result = $this->executor->execute(self::INTROSPECTION_QUERY);
80+
81+
if (!isset($result['data']['__schema'])) {
82+
return '';
83+
}
84+
85+
return "```json\n" . json_encode($result['data'], JSON_PRETTY_PRINT) . "\n```";
86+
}
87+
88+
public function getReducedSchema(): string
89+
{
90+
$result = $this->executor->execute(self::INTROSPECTION_QUERY);
91+
92+
if (!isset($result['data']['__schema'])) {
93+
return '';
94+
}
95+
96+
$schema = $result['data']['__schema'];
97+
98+
// 1. filtra solo tipi utili
99+
$types = array_filter($schema['types'], function ($type) {
100+
return !str_starts_with($type['name'], "__"); // skip system types
101+
});
102+
103+
// 2. ricomponi un testo leggibile
104+
$schemaText = "";
105+
106+
foreach ($types as $type) {
107+
$schemaText .= "type {$type['name']} {\n";
108+
if (!empty($type['fields'])) {
109+
foreach ($type['fields'] as $field) {
110+
$schemaText .= " {$field['name']} ";
111+
if (isset($field['type']['name'])) {
112+
$schemaText .= ": {$field['type']['name']}";
113+
}
114+
$schemaText .= "\n";
115+
}
116+
}
117+
$schemaText .= "}\n\n";
118+
}
119+
120+
return $schemaText;
121+
}
122+
}

0 commit comments

Comments
 (0)