/** Datei: plugins/agents/library/ARUNOX/Plugins/Agents/API/v1/Routes.php **/
* @copyright 2025 ARUNOX
* @version 1.1.0
*
* @since 1.0.0 File is created
*/
namespace ARUNOX\Plugins\Agents\API\v1;
use ARUNOX\Core\Hooks;
use ARUNOX\Core\Router;
use ARUNOX\Plugins\REST;
class Routes {
public static function register() : void {
/* ### MAIN ROUTES ############################## # */
Router::addAll( Controller::class );
/* ### MENU ROUTES ############################## # */
Hooks::add('router::get->api.v1.rest.routes', function( $routes ) {
$routes[] = [
"path" => "/agents",
"label" => "Assistenten",
"icon" => "bi bi-person-gear",
"menu" => "content",
];
$routes[] = [
"path" => "/agents/{id}",
"label" => "Bearbeiten",
];
return $routes;
});
}
}
/** Datei: plugins/agents/library/ARUNOX/Plugins/Agents/API/v1/Controller.php **/
* @copyright 2025 ARUNOX
* @version 1.1.0
*
* @since 1.0.0 File is created
*/
namespace ARUNOX\Plugins\Agents\API\v1;
use ARUNOX\HTTP;
use ARUNOX\Core\Hooks;
use ARUNOX\API\OpenAI;
use ARUNOX\Plugins\REST;
use ARUNOX\Plugins\Users;
use ARUNOX\Plugins\Agents;
use ARUNOX\API\BaseController;
use ARUNOX\Database\BaseEntity;
class Controller extends BaseController {
/* ### VARIABLES ################################################ # */
protected static array $NOTIFY = [
"notfound" => "Assistent nicht gefunden",
"saved" => "Assistent wurde erfolgreich gespeichert",
"deleted" => "Assistent wurde erfolgreich gelöscht",
"created" => "Assistent wurde erfolgreich erstellt",
"statuschanged.0" => "Assistent wurde erfolgreich deaktiviert",
"statuschanged.1" => "Assistent wurde erfolgreich aktiviert",
"notcreated" => "Assistent konnte nicht erstellt werden",
"notsaved" => "Assistent konnte nicht gespeichert werden",
];
/* ### PUBLIC GET FUNCTIONS ##################################### # */
/**
* @api
* @method GET
*
* @route.path /agents
* @route.description Listet alle Agents
*/
public static function getAll() : array {
return Agents\Model::getAll();
}
/**
* @api
* @method GET
*
* @route.path /agents/{hashid}
* @route.description Gibt einen einzelnen Agent anhand der ID zurück
*/
public static function get( int|string $hashid ) : BaseEntity | HTTP\Response {
if( $result = Agents\Model::get( $hashid ) ) return $result;
return REST\Response::autoResponse( 404, static::$NOTIFY["notfound"] );
}
/* ### PUBLIC SAVE FUNCTIONS #################################### # */
/**
* @api
* @method PUT
*
* @route.path /agents/{hashid}
* @route.description Speichert Änderungen an einem bestehenden Agent
*/
public static function save( int|string $hashid, array $data ) : HTTP\Response {
if( $result = Agents\Model::get( $hashid ) ) {
if( $result->save( $data ) ) {
return REST\Response::autoResponse( 200, static::$NOTIFY["saved"] );
}
return REST\Response::autoResponse( 500, static::$NOTIFY["notsaved"] );
}
return REST\Response::autoResponse( 404, static::$NOTIFY["notfound"] );
}
/**
* @api
* @method POST
*
* @route.path /agents
* @route.description Erstellt einen neuen Agent
*/
public static function create( array $data ) : HTTP\Response {
if( $hashid = Agents\Model::save( $data ) ) {
$result = Agents\Model::get( $hashid );
return REST\Response::autoResponse( 200, static::$NOTIFY["created"], [ "data" => $result ] );
}
return REST\Response::autoResponse( 500, static::$NOTIFY["notfound"] );
}
/**
* @api
* @method DELETE
*
* @route.path /agents/{hashid}
* @route.description Löscht einen Agent anhand der ID
*/
public static function delete( int|string $hashid ) : HTTP\Response {
if( Agents\Model::delete( $hashid ) ) {
return REST\Response::autoResponse( 200, static::$NOTIFY["deleted"] );
}
return REST\Response::autoResponse( 500, static::$NOTIFY["notfound"] );
}
/**
* @api
* @method PATCH
*
* @route.path /agents/{hashid}/status
* @route.description Wechselt den Status (aktiv/inaktiv) eines Agenten
*/
public static function toggleStatus( int|string $hashid ) : HTTP\Response {
if( $result = Agents\Model::get( $hashid ) ) {
$result->toggleStatus( TRUE );
return REST\Response::autoResponse( 200,
static::$NOTIFY["statuschanged.{$result->status->getValue()}"],
[ "data" => $result ]
);
}
return REST\Response::autoResponse( 404, static::$NOTIFY["notfound"] );
}
/* ### PUBLIC VALIDATE FUNCTIONS ################################ # */
/* ### PUBLIC REST FUNCTIONS #################################### # */
/* ### PUBLIC REST RESPONSE FUNCTIONS ########################### # */
/**
* @internal rest
* @method PUT
*
* @route.path /agents/{hashid}
* @route.description REST-Antwort nach dem Speichern eines Agenten
*/
public static function restResponseSave( int|string $hashid, array $data ) : HTTP\Response {
$result = static::save( $hashid, $data );
if( $result->isStatus( 200 ) ) {
$data = Agents\Model::get( $hashid );
REST\Response::closeModals();
REST\Response::addUpdater( [ "data/table/row", $hashid ], $data );
}
return static::restResponse( $result );
}
/**
* @internal rest
* @method POST
*
* @route.path /agents
* @route.description REST-Antwort nach dem Erstellen eines Agenten (inkl. Notifikation, Redirect etc.)
*/
public static function restResponseCreate( array $data ) : HTTP\Response {
$result = static::create( $data );
if( $result->isStatus( 200 ) ) {
REST\Response::closeModals();
REST\Response::redirect( "/agents/{$result->getBody("data", "hashid")}" );
}
return static::restResponse( $result );
}
/**
* @internal rest
* @method DELETE
*
* @route.path /agents/{hashid}
* @route.description REST-Antwort nach dem Löschen eines Agenten
*/
public static function restResponseDelete( int|string $hashid ) : HTTP\Response {
REST\Response::closeModals();
$result = static::delete( $hashid );
if( $result->isStatus( 200 ) ) {
REST\Response::redirect( "/agents" );
}
return static::restResponse( $result );
}
/**
* @internal rest
* @method PATCH
*
* @route.path /agents/{hashid}/status
* @route.description REST-Antwort zur Status-Umschaltung eines Agenten (z. B. für Notifications)
*/
public static function restResponseToggleStatus( int|string $hashid ) : HTTP\Response {
$result = static::toggleStatus( $hashid );
if( $result->isStatus( 200 ) ) {
$data = $result->getBody("data");
REST\Response::addUpdater( [ "data/table/row", $hashid ], $data );
}
return static::restResponse( $result );
}
/* ### PUBLIC REST CONFIG FUNCTIONS ############################# # */
/**
* @internal rest
* @method GET
*
* @route.path /agents
* @route.description REST-Konfiguration für Agenten-Tabelle
*/
public static function restConfigOverview() : REST\Generator\Base\BaseGenerator {
return REST\Response::addWrapper( "overview", "Assistenten" )
->get()
->setApi( "/agents" )
->setAction( "/agents/{hashid}" )
->setOrder( "name", "asc" )
->setSearch( TRUE )
->addButton(...Hooks::filter( "rest.generator::button.name->create", [], "agents" ))
->addColumn(...Hooks::filter( "rest.generator::column.type->text", [], "name", TRUE, TRUE, TRUE, "agents" ))
->addColumn(...Hooks::filter( "rest.generator::column.type->text", [], "voice", TRUE, TRUE, TRUE, "agents" ))
->addColumn(...Hooks::filter( "rest.generator::column.type->status", [], "status", [], "agents", "hashid" ))
->addColumn(...Hooks::filter( "rest.generator::column.type->text", [], "phone", TRUE, FALSE, TRUE, "agents" ))
->addColumn(...Hooks::filter( "rest.generator::column.type->datetime", [], "date_edit", FALSE, FALSE, TRUE, "agents" ))
->addColumn(...Hooks::filter( "rest.generator::column.type->actions", [], [], FALSE, "agents" ))->get()
->addButton(...Hooks::filter( "rest.generator::action.name->edit", [], "agents", "hashid" ))
->addButton(...Hooks::filter( "rest.generator::action.name->delete", [], "agents", "hashid" ));
}
/**
* @internal rest
* @method GET
*
* @route.path /agents/{hashid}
* @route.alternative.0 /agents/{hashid}#kontext
* @route.alternative.1 /agents/{hashid}#chat
* @route.alternative.2 /agents/{hashid}#telefon
* @route.description REST-Konfiguration für Detailansicht eines Agenten
*/
public static function restConfigDetails( int|string $hashid ) : REST\Generator\Base\BaseGenerator {
$data = Agents\Model::get( $hashid );
return REST\Response::addWrapper( "details", "Assistent bearbeiten" )
->setBacklink( "/agents" )
->get()
->setApi( "/agents/{$hashid}" )
->setAction( "/agents/{$hashid}", "PUT" )
->addTab( "Assistent" )->get()
->addHeadline( "Assistent", 2 )
->addText( "Mit diesen Informationen wird das Verhalten des Assistenten gesteuert." )
->addField(...Hooks::filter( "rest.generator::control.type->text", [], "name", TRUE, "agents" ))
->addField(...Hooks::filter( "rest.generator::control.type->text", [
"readonly" => TRUE,
], "uuid", FALSE, "agents" ))
->parent()
->addTab( "Kontext" )->get()
->addHeadline( "Kontext", 2 )
->addText( "Kontext der sowohl für Chat als auch Telefon gültig ist. Zur besseren Struktur kann Markdown genutzt werden." )
->addHeadline( "Persönlichkeit", 3 )
->addText( "Informationen über den Assistenten sowie welche Persönlichkeit der Assistent haben soll." )
->addField(...Hooks::filter( "rest.generator::control.type->textarea", [
"label" => "Persönlichkeit"
], "prompt_personality", FALSE, "agents" ))
->addHeadline( "Allgemeine Informationen", 3 )
->addText( "Allgemeine Informationen für den Assistenten, z.B. was er machen soll." )
->addField(...Hooks::filter( "rest.generator::control.type->textarea", [
"label" => "Allgemeine Informationen"
], "prompt_tasks", FALSE, "agents" ))
->addHeadline( "Verhalten bei Unklarheiten", 3 )
->addText( "Falls du das Anliegen des Anrufers nicht beantworten kannst:" )
->addField(...Hooks::filter( "rest.generator::control.type->textarea", [
"label" => "Verhalten bei Unklarheiten"
], "prompt_uncertainties", FALSE, "agents" ))
->addHeadline( "Sensible Themen", 3 )
->addText( "Falls der Kunde über folgende Themen spricht, sage, dass du nicht weiterhelfen kannst: " )
->addField(...Hooks::filter( "rest.generator::control.type->textarea", [
"label" => "Sensible Themen"
], "prompt_sensitives", FALSE, "agents" ))
->addHeadline( "Gesprächsablauf", 3 )
->addText( "Ein grober Ablauf eines Gespräches, damit der Assistent weiß wie er das Gespräch führen soll." )
->addField(...Hooks::filter( "rest.generator::control.type->textarea", [
"label" => "Gesprächsablauf"
], "prompt_conversation_flow", FALSE, "agents" ))
->addHeadline( "Häufige Fragen", 3 )
->addText( "Hier eine Übersicht über häufig gestellte Fragen und deren Antworten, die als Orientierungshilfe dienen soll:" )
->addField(...Hooks::filter( "rest.generator::control.type->textarea", [
"label" => "Häufige Fragen"
], "prompt_faq", FALSE, "agents" ))
->addHeadline( "Wissensdatenbank", 3 )
->addText( "Hier eine generelle Auflistung an Informationen, die bei der Gesprächsführung helfen sollen:" )
->addField(...Hooks::filter( "rest.generator::control.type->textarea", [
"label" => "Wissensdatenbank"
], "prompt_knowledge_base", FALSE, "agents" ))
->parent()
->addTab( "Chat" )->get()
->addHeadline( "Einstellung für Chat-KI", 2 )
->addField(...Hooks::filter( "rest.generator::control.type->code", [
"label" => "Einbettungscode",
"value" => implode( "\n", [
"",
"domain . "/api/v1/agents/chat/{$data->uuid}/styles\">",
]),
"readonly" => TRUE,
], "embed_code", FALSE, "agents" ))
->addField(...Hooks::filter( "rest.generator::control.type->textarea", [], "start_message_chat", FALSE, "agents" ))
->addField(...Hooks::filter( "rest.generator::control.type->textarea", [
"label" => "Zusätzlicher Kontext",
], "prompt_chat", FALSE, "agents" ))
->parent()
->addTab( "Telefon" )->get()
->addHeadline( "Einstellung für Telefon-KI", 2 )
->addGroup()
->addField(...Hooks::filter( "rest.generator::control.type->select", [], "phone_number", FALSE, FALSE, "agents" ))
->addField(...Hooks::filter( "rest.generator::control.type->select", [], "phone_voice", FALSE, FALSE, "agents" ))
->parent()
->addGroup()
->addField(...Hooks::filter( "rest.generator::control.type->textarea", [], "start_message_phone", FALSE, "agents" ))
->addField(...Hooks::filter( "rest.generator::control.type->textarea", [
"label" => "Zusätzlicher Kontext",
], "prompt_phone", FALSE, "agents" ))
->parent( 2 )
->addTab( "Tools" )->get()
->addText( "Tools sind Werkzeuge die der Assistent während des Anrufs verwenden kann." )
->addContent(
Agents\Tools\API\v1\Controller::restConfigTable( $hashid )
)
->parent()
->addTab( "Nachverarbeitung" )->get()
->addText( "Nachverarbeitungen des Assistenten werden nach dem Anruf parallel ausgeführt." )
->addContent(
Agents\Processing\API\v1\Controller::restConfigTable()
)
->parent()
->addTab( "Speichern" )->get()
->setColor( REST\Generator\Config\Color::Primary )
->setIcon( "bi bi-save" )
->setType( "submit" );
}
/**
* @internal rest
* @method GET
*
* @route.path /agents/create
* @route.description REST-Konfiguration für das Erstellen eines Agenten (Formular)
*/
public static function restConfigCreate() : REST\Generator\Base\BaseGenerator {
return REST\Response::addModal( "details", "Assistent erstellen" )
->get()
->setAction( "/agents", "POST" )
->addButton(...Hooks::filter( "rest.generator::button.type->submit", [], "Erstellen", "agents" ))
->addField(...Hooks::filter( "rest.generator::control.type->text", [], "name", TRUE, "agents" ));
}
/**
* @internal rest
* @method GET
*
* @route.path /agents/{hashid}/delete
* @route.description REST-Konfiguration für die Lösch-Bestätigung eines Agenten
*/
public static function restConfigDelete( int|string $hashid ) : REST\Generator\Base\BaseGenerator {
return REST\Response::addModal( "details", "Assistent Löschen" )
->setSize( REST\Wrapper\Modal\Size::sm )
->get()
->setAction( "/agents/{$hashid}", "DELETE" )
->addText( "Möchtest du wirklich den Datensatz (#{$hashid}) löschen? " )
->addButton(...Hooks::filter( "rest.generator::button.type->cancel", [], NULL, "agents" ))
->addButton(...Hooks::filter( "rest.generator::button.type->delete", [], NULL, "agents" ));
}
/* ### PUBLIC CHAT FUNCTIONS #################################### # */
/**
* @api
* @method GET
*
* @route.path /agents/chat/{uuid}/reset
* @route.description Resetten den Chat des Users zum aktuellen Agenten
*/
public static function resetChat( string $uuid ) : HTTP\Response {
$agent = Agents\Model::getByColumn( "uuid", $uuid );
$user = Users\Model::get();
$hashid = hashid( "chat", $agent->hashid, $user->id );
\ARUNOX\API::setAuthToken( ARUNOX_API_KEY );
\ARUNOX\API::setApiUrl( "https://" . SYSTEM->domain ."/api/v1/agents/{$agent->hashid}" );
$prompt = \ARUNOX\API::get( "?columns=prompt" );
$prompt = $prompt->prompt;
$tools = \ARUNOX\API::get( "/tools?status=1&columns=id,type,description,url,defaults,header,method,parameters" );
$tools = array_map( fn( $item ) => (object)array_merge( (array)$item, [
"name" => "applyTool-{$item->id}",
"openai" => [
"type" => "function",
"function" => (object)[
"name" => "applyTool-{$item->id}",
"description" => $item->description,
"parameters" => (object)json_decode( $item->parameters ?? "{}" ),
],
],
]), $tools );
$tools = array_column( $tools, NULL, "name" );
// show( $tools );
$config = [
"model" => "gpt-4o-mini",
// "instructions" => $prompt,
"tools" => array_values( array_column( $tools, "openai" ) ),
"tool_choice" => "auto",
"user" => $hashid,
"store" => FALSE,
];
// show( $config );
$calls = [
[
"role" => OpenAI\Messages\Role::Developer,
"content" => $prompt,
],
[
"role" => OpenAI\Messages\Role::User,
"content" => "Ich möchte ein Tool für Agent sam erstellen",
],
];
function runOpenAiResponseLoop( array $config, array &$calls, array $tools, array &$logging ) {
$response = OpenAI\Responses::create(array_merge( $config, [ "input" => $calls ]));
$toolCall = FALSE;
if( OpenAI::hasError() ) show( OpenAI::getError(), "STOP" );
// show( $response, FALSE );
$logging[] = $response;
// show( $response );
foreach( $response->output as $output ) {
if( $output->type == Agents\Calls\Type::ToolCall ) {
$output->role = OpenAI\Messages\Role::Tool;
$calls[] = [
"call_id" => $output->call_id,
"arguments" => $output->arguments,
"name" => $output->name,
"type" => $output->type,
"id" => $output->id,
"status" => $output->status,
];
if( ! $tool = $tools[ $output->name ] ?? NULL ) continue;
$toolCall = TRUE;
if( $tool->type == 3 ) {
$defaults = json_decode( $tool->defaults ?? "[]", TRUE );
$arguments = array_replace( $defaults, json_decode( $output->arguments ?? "[]", TRUE ) );
$toolUrl = preg_replace_callback('/\{(\w+)\}/', function( $m ) use( $arguments ) { return trim( $arguments[ $m[1] ] ?? $m[0], "/" ); }, $tool->url );
$result = \ARUNOX\API::{$tool->method}( $toolUrl, $arguments , $tool->header );
if( !is_scalar( $result ) ) {
$result = json_encode( $result, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES );
}
$calls[] = [
"call_id" => $output->call_id,
"output" => $result,
"type" => Agents\Calls\Type::ToolResult,
"id" => $output->id,
"status" => "completed",
];
}
// show( $output, $calls );
}
if( $output->type == Agents\Calls\Type::Message ) {
$calls[] = [
"content" => $output->content[0]->text,
"id" => $output->id,
"role" => $output->role,
"status" => $output->status,
"type" => $output->type,
];
}
}
if( $toolCall ) return runOpenAiResponseLoop( $config, $calls, $tools, $logging );
return $response;
}
function runOpenAiChatLoop( array $config, array &$calls, array $tools, array &$logging ) {
$response = OpenAI\Chat\Completions::create(array_merge( $config, [ "messages" => $calls ]));
if( OpenAI::hasError() ) show( OpenAI::getError(), "STOP" );
$logging[] = $response;
$choice = $response->choices[0];
$message = $choice->message;
$calls[] = (array)$message;
// show( $response );
if( $choice->finish_reason == "tool_calls" ) {
foreach( $message->tool_calls as $tool_call ) {
if( ! $tool = $tools[ $tool_call->function->name ] ?? NULL ) continue;
if( $tool->type == 3 ) {
$defaults = json_decode( $tool->defaults ?? "[]", TRUE );
$arguments = array_replace( $defaults, json_decode( $tool_call->function->arguments ?? "[]", TRUE ) );
$toolUrl = preg_replace_callback('/\{(\w+)\}/', function( $m ) use( $arguments ) { return trim( $arguments[ $m[1] ] ?? $m[0], "/" ); }, $tool->url );
$result = \ARUNOX\API::{$tool->method}( $toolUrl, $arguments , $tool->header );
if( !is_scalar( $result ) ) {
$result = json_encode( $result, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES );
}
$calls[] = [
"tool_call_id" => $tool_call->id,
"content" => $result,
"role" => OpenAI\Messages\Role::Tool,
];
}
}
return runOpenAiChatLoop( $config, $calls, $tools, $logging );
}
return $response;
}
$logging = [];
// $response = runOpenAiResponseLoop( $config, $calls, $tools, $logging );
$response = runOpenAiChatLoop( $config, $calls, $tools, $logging );
show( $response->choices[0]->message->content, $calls, $logging, "STOP" );
$data = [
"origin_id" => $agent->hashid,
"chat_hashid" => $response->user,
"status" => Agents\Calls\Status::getByConstant( $response->output[0]->status ),
"role" => $response->output[0]->role,
"type" => $response->output[0]->type,
"msg_id" => $response->output[0]->id,
"model" => $response->model,
"content" => $response->output[0]->content[0]->text,
"tool_name" => $response->output[0]->name,
"tool_args" => $response->output[0]->arguments,
"tool_call_id" => $response->output[0]->call_id,
"usage_prompt" => $response->usage->prompt_tokens,
"usage_completion" => $response->usage->completion_tokens,
"usage_total" => $response->usage->total_tokens,
// "cost_usd" => $response->user,
"source" => Agents\Calls\Source::Webchat,
// "hashid" => $response->user,
];
show( $response, $data, "STOP" );
$call_id = \ARUNOX\API::post( "/calls/{$hashid}", [
"content" => $agent->start_message_chat,
"role" => OpenAI\Messages\Role::Assistant,
"type" => Agents\Calls\Type::Message,
"status" => Agents\Calls\Status::Complete,
]);
show( $call_id, "X");
}
/**
* @api
* @method GET
*
* @route.path /agents/chat/{uuid}/data
* @route.description Lädt den aktuellen Chat-Verlauf für einen Agenten
*/
public static function getChatData( string $uuid, array $data ) : BaseEntity | bool | array {
return Agents\Model::getByColumn( "uuid", $uuid );
}
/**
* @api
* @method GET
*
* @route.path /agents/chat/{uuid}/script
* @route.description Liefert das benötigte JavaScript für den Chat
*/
public static function includeChatScript( string $uuid, array $data ) : never {
$agent = Agents\Model::getByColumn( "uuid", $uuid );
$user = Users\Model::get();
$icons = glob( PLUGINS_DIR . "/agents/assets/icons/*.svg" );
$icons = array_map( fn( $icon ) => [
"file" => file_get_contents( $icon ),
"name" => preg_replace_callback('/-([a-z])/', fn( $m ) => strtoupper( $m[1] ), basename( $icon, ".svg" ) ),
], $icons );
$icons = array_column( $icons, "file", "name" );
$openai = json_decode( $user->openai_chats ?? "{}", TRUE );
$hashid = hashid( "chat", $agent->hashid, $user->id );
if( !in_array( $hashid, $openai ) ) {
$openai[] = $hashid;
$user->save([ "openai_chats" => $openai ]);
}
// show( $icons );
header("Content-Type: text/javascript ");
// echo "const chatAI_MD = '" . base64_encode(file_get_contents( PLUGINS_DIR . "/agents/assets/demo.md" )) . "';";
echo "const chatAI_NAME = '{$agent->name}';";
echo "const chatAI_API = 'https://" . SYSTEM->domain ."/api/v1/agents/{$agent->hashid}';\n";
echo "const chatAI_WS_URL = 'wss://ws.arunox.dev';";
echo "const chatAI_WS_Port = '2347';";
// echo "const chatAI_WS_Token = 'ws_" . session_id(). "';";
echo "const chatAI_minWidth = '350px';";
echo "const chatAI_minHeight = '250px';";
echo "const chatAI_maxWidth = '33vw';";
echo "const chatAI_maxHeight = '66vh';";
echo "const chatAI_icons = " . json_encode( $icons, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE ) .";";
echo "const chatAI_CHAT_ID = '{$hashid}';";
echo file_get_contents( PLUGINS_DIR . "/agents/assets/js/marked.min.js" );
echo file_get_contents( PLUGINS_DIR . "/agents/assets/js/chatai.js" );
echo file_get_contents( PLUGINS_DIR . "/agents/assets/js/chatai_ws.js" );
exit;
}
/**
* @api
* @method GET
*
* @route.path /agents/chat/{id}/styles
* @route.description Liefert die benötigten Stylesheets für den Chat
*/
public static function includeChatStyles( string $uuid, array $data ) : never {
header("Content-Type: text/css");
include PLUGINS_DIR . "/agents/assets/css/chatai.css";
exit;
}
}