Passa al contenuto principale

Architettura dell'Applicazione

L'architettura di Tidiko AI è progettata per essere modulare, scalabile e facilmente estendibile. Il sistema è basato su un'architettura monolitica con separazione delle responsabilità tra diversi componenti.

🏗️ Panoramica Architetturale

Componenti Principali

📁 Struttura del Progetto

Backend Laravel

app/
├── Http/Controllers/ # Controller per API e web
├── Models/ # Modelli Eloquent
├── Services/ # Servizi di business logic
├── Jobs/ # Job per processamento asincrono
├── Notifications/ # Sistema di notifiche
├── Policies/ # Autorizzazioni e permessi
└── Support/ # Classi di supporto

Frontend Widget

resources/
├── js/
│ └── shadowDomWidget.js # Widget principale
├── scss/
│ ├── agentWidget.scss # Stili widget
│ └── agentProductCard.scss # Stili product card
└── views/ # Template Blade

🔄 Flusso di Dati

1. Inizializzazione Widget

resources/js/shadowDomWidget.js
class TidikoAiWidget {
constructor(containerId = null, params = {}) {
// Configurazione iniziale
this.options = { ...platformSettings, ...overrideSettings };
this.jwt = jwt;
this.appUrl = appUrl;

// Inizializzazione Shadow DOM
this.init(containerId);
}

async init(containerId) {
const host = containerId
? document.getElementById(containerId)
: this._createDefaultContainer();

// Attach Shadow DOM per isolamento
this.shadowRoot = host.attachShadow({ mode: "open" });
await this._injectContent();
this._setupEventListeners();
this._initializeChat();
}
}

2. Autenticazione e Autorizzazione

app/Http/Controllers/JWTController.php
class JWTController extends Controller
{
public static function refreshToken(Request $request)
{
try {
$validator = Validator::make($request->all(), [
'refresh_token' => 'required|string'
]);

if ($validator->fails()) {
return response()->json([
'error' => 'Validation failed',
'details' => $validator->errors()
], 400);
}

$refreshToken = $request->input('refresh_token');
$tokens = JWTService::refreshJwtTokens($refreshToken);

return response()->json([
'success' => true,
'data' => $tokens
]);
} catch (\Exception $e) {
return response()->json([
'success' => false,
'error' => $e->getMessage()
], 401);
}
}
}

3. Gestione Conversazioni

app/Http/Controllers/ConversationController.php
class ConversationController extends Controller
{
public function store(Request $request)
{
$uuid = $request->assistant_or_agent_uuid;
$type = $request->type;

if ($type === 'assistant') {
$assistant = Assistant::where('assistant_uuid', $uuid)->first();
} else if ($type === 'agent') {
$assistant = Agent::where('agent_uuid', $uuid)->first();
}

$conversation = Conversation::create([
'conversation_uuid' => $request->conversation_uuid,
'assistant_id' => $assistant->id,
'started_at' => Carbon::now(),
'domain' => $request->domain,
'location' => json_encode($request->location)
]);

return response()->json($conversation, 201);
}
}

🗄️ Database Schema

Tabelle Principali

database/migrations/create_assistants_table.php
Schema::create('assistants', function (Blueprint $table) {
$table->id();
$table->foreignId('company_id')->constrained()->onDelete('cascade');
$table->string('assistant_uiid')->nullable();
$table->string('vector_store_uiid')->nullable();
$table->string('name');
$table->text('instructions')->nullable();
$table->string('model');
$table->boolean('file_search')->nullable()->default(true);
$table->boolean('code_interpreter')->nullable()->default(false);
$table->enum('response_format', ['text', 'json_object', 'json_schema'])->default('text');
$table->decimal('temperature', total: 3, places: 2)->default(1);
$table->decimal('topP', total: 3, places: 2)->default(1);
$table->timestamps();
$table->softDeletes();
});

Relazioni tra Modelli

app/Models/Assistant.php
class Assistant extends Model
{
public function company()
{
return $this->belongsTo(Company::class);
}

public function conversations()
{
return $this->hasMany(Conversation::class, 'assistant_id');
}

public function files()
{
return $this->hasMany(File::class, 'vector_store_uuid', 'vector_store_uuid');
}
}

🔧 Servizi e Integrazioni

NodeApiService

Servizio per la comunicazione con il worker Node.js:

app/Services/NodeApiService.php
class NodeApiService
{
public static function createCollection($model)
{
$jwt = self::getJWT($model);
$url = config('app.node_server_url') . "/collections";

$response = Http::withHeaders([
'Authorization' => "Bearer {$jwt}",
])->post($url);

return $response->json();
}

public static function upload($model, $vectorStoreUuid, $parameters)
{
$jwt = self::getJWT($model);

switch ($parameters['type']) {
case 'file':
$url = config('app.node_server_url') . "/upload/files/{$vectorStoreUuid}";
$file = Storage::disk('public')->get($parameters['file']);
$response = Http::attach('file', $file, basename($parameters['file']))
->withHeaders(['Authorization' => "Bearer {$jwt}"])
->post($url);
break;

case 'single':
case 'sitemap':
$url = config('app.node_server_url') . "/upload/url/{$vectorStoreUuid}";
$response = Http::withHeaders(['Authorization' => "Bearer {$jwt}"])
->post($url, $parameters);
break;
}

return $response->json();
}
}

SubscriptionService

Gestione degli abbonamenti e fatturazione:

app/Services/SubscriptionService.php
class SubscriptionService
{
protected $company;

public function __construct($companyId)
{
$this->company = Company::find($companyId);
}

public function canCreateAssistant(): bool
{
if (!$this->company || !$this->isSubscriptionActive()) {
return false;
}

$subscriptionPlan = $this->company->subscriptionPlan();
if (!$subscriptionPlan) {
return false;
}

$assistantCount = $this->company->assistants()->count();
$agentsCount = $this->company->agents()->count();

return $assistantCount + $agentsCount < $subscriptionPlan->max_assistants;
}

public function canSendMessage(): bool
{
// Implementazione controllo limiti messaggi
return true;
}
}

🌐 Sistema di Widget

Shadow DOM Implementation

resources/js/shadowDomWidget.js
class TidikoAiWidget {
async _injectContent() {
// Iniezione degli stili
if (isDevelopment) {
await this._loadDevStyles();
} else {
const style = document.createElement("style");
style.textContent = CSS_STYLES;
this.shadowRoot.appendChild(style);
}

// Iniezione dell'HTML
this.shadowRoot.innerHTML += HTML_TEMPLATE;
}

_setupEventListeners() {
// Form submit
this.dom.messageForm.addEventListener("submit", (e) => {
e.preventDefault();
if (!this.isRunning) this.sendMessage();
});

// WebSocket events
this.socket.on("chat_response_chunk", ({ response }) => {
this.updateLastSystemMessage(response);
});

this.socket.on("chat_response_end", () => {
this.isRunning = false;
this.dom.messageInput.disabled = false;
this.dom.messageSend.disabled = false;
});
}
}

WebSocket Communication

resources/js/shadowDomWidget.js
_initSocket() {
const token = window.localStorage.getItem("tidiko_jwt");
this.socket = io(this.socketUrl, {
auth: { token },
reconnection: true,
reconnectionDelay: 1000,
});

this.socket.on("chat_response_start", () => {
this.tmpMsg = "";
});

this.socket.on("chat_response_chunk", ({ response }) => {
this.updateLastSystemMessage(response);
});

this.socket.on("chat_response_end", () => {
this.tmpMsg = "";
this.isRunning = false;
});
}

📊 Sistema di Caching

Redis Integration

config/cache.php
'redis' => [
'driver' => 'redis',
'connection' => 'cache',
'lock_connection' => 'default',
],

Cache Strategies

app/Http/Controllers/AssistantController.php
public function index($module = 'assistants')
{
$cacheKey = "assistants_{$user->company_id}";

$assistants = Cache::remember($cacheKey, 3600, function () use ($user) {
return Assistant::where('company_id', $user->company_id)
->with(['files', 'conversations'])
->get();
});

return view('modules.assistants.index', compact('assistants'));
}

🔒 Sicurezza e Autenticazione

JWT Token Management

app/Services/JWTService.php
class JWTService
{
public static function generateJWT(array $payload): string
{
$key = config('app.jwt_secret');
$algorithm = 'HS256';

return JWT::encode($payload, $key, $algorithm);
}

public static function validateJWT(string $token): ?array
{
try {
$key = config('app.jwt_secret');
$decoded = JWT::decode($token, new Key($key, 'HS256'));
return (array) $decoded;
} catch (Exception $e) {
return null;
}
}
}

Policy e Autorizzazioni

app/Policies/AssistantPolicy.php
class AssistantPolicy
{
public function view(User $user, Assistant $assistant)
{
return $user->role === 'admin' ||
($user->role === 'editor' && $user->company_id === $assistant->company_id);
}

public function update(User $user, Assistant $assistant)
{
return $user->role === 'admin' ||
($user->role === 'editor' && $user->company_id === $assistant->company_id);
}

public function delete(User $user, Assistant $assistant)
{
return $user->role === 'admin' ||
($user->role === 'editor' && $user->company_id === $assistant->company_id);
}
}

📈 Monitoring e Logging

Log Configuration

config/logging.php
'channels' => [
'single' => [
'driver' => 'single',
'path' => storage_path('logs/laravel.log'),
'level' => 'debug',
],

'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => 'debug',
'days' => 14,
],
],

Error Handling

app/Http/Controllers/MessageController.php
public function store(Request $request)
{
DB::beginTransaction();
try {
$message = Message::create([
'conversation_id' => $conversation->id,
'message_uuid' => $request->message_uuid,
'sender' => $request->sender,
'message' => $request->message,
'tokens_used' => $tokens_used,
'model' => $request->model,
'cost' => $cost,
]);

DB::commit();
return response()->json($message, 201);
} catch (\Exception $e) {
DB::rollBack();
Log::error('Error creating message', ['error' => $e->getMessage()]);
return response()->json(['error' => $e->getMessage()], 500);
}
}

🚀 Performance e Scalabilità

Database Optimization

app/Models/Conversation.php
class Conversation extends Model
{
protected $fillable = [
'conversation_uuid',
'assistant_id',
'agent_id',
'started_at',
'ended_at',
'tokens_used',
'domain',
'location'
];

protected $casts = [
'location' => 'array',
'started_at' => 'datetime',
'ended_at' => 'datetime',
];
}

Query Optimization

app/Http/Controllers/ConversationController.php
public function list($conversationId)
{
$messages = Message::where('conversation_id', $conversationId)
->orderBy('created_at', 'asc')
->orderBy('sender', 'asc')
->get();

// Processamento efficiente dei messaggi
$htmlContent = '';
foreach ($messages as $message) {
$sanitizedMessage = Purifier::clean($message->message);
$htmlContent .= view('components.chat-bubble', [
'type' => $message->sender,
'message' => $sanitizedMessage,
'time' => $message->created_at->format('j M Y H:i'),
'cost' => number_format($message->cost, 6, ',')
])->render();
}

return response()->json($htmlContent);
}

Questa architettura garantisce scalabilità, manutenibilità e performance ottimali per la gestione di chatbot intelligenti su larga scala.