/** Datei: plugins/agents/library/ARUNOX/Plugins/Agents/Model.php **/ openai_assistant_id ?? FALSE ); if( SYSTEM->domain ) { $domain = "https://" . SYSTEM->domain . "/api/v1/agents/{$agent->id}/tools"; } $tools = $agent->getTools( TRUE ) ; $tools = array_map( fn( $item ) => [ "type" => "function", "function" => [ "name" => "applyTool-{$item->id}", "description" => $item->description, // "strict" => TRUE, "parameters" => (object)json_decode( $item->parameters ?? "{}" ), ], ], $tools ); $prompt = array_filter([ "#Über Dich" => $agent->prompt_personality, "#Allgemeine Informationen" => $agent->prompt_tasks, "#Verhalten bei Unklarheiten" => $agent->prompt_uncertainties, "#Sensible Themen" => $agent->prompt_sensitives, "#Gesprächsablauf" => $agent->prompt_conversation_flow, "#Häufige Fragen" => $agent->prompt_faq, "#Wissensdatenbank" => $agent->prompt_knowledge_base, "# [!important] Wenn Du im Chat-Modus bist\n" => $agent->prompt_chat, "# [!important] Wenn Du im Telefon-Modus bist\n" => $agent->prompt_phone, ]); $prompt = array_map( "trim", $prompt ); $prompt = array_map( fn( $title, $prompt ) => "{$title}\n{$prompt}", array_keys( $prompt ), array_values( $prompt ) ); $prompt = implode( "\n\n---\n\n", $prompt ); $config = [ "model" => "gpt-4o", "name" => $agent->name, "description" => $domain, "instructions" => $prompt, "tools" => $tools, ]; if( $create ) $response = OpenAI\Assistants::create( $config ); else $response = OpenAI\Assistants::modify( $agent->openai_assistant_id, $config ); if( $response ) parent::save([ "id" => $id, "openai_assistant_id" => $response->id ]); else show( OpenAI::getError() ); // show( $create, $response, OpenAI::getError(), $agent, "X" ); } return $id; } public static function delete( array | int | string $value ) : int { $agent = parent::get( $value ); if( parent::delete( $value ) ) { if( class_exists( OpenAI::class ) ) { if( $agent->openai_assistant_id ) { OpenAI\Assistants::delete( $agent->openai_assistant_id ); } } return TRUE; } return FALSE; } } /** Datei: plugins/agents/library/ARUNOX/Plugins/Agents/Calls/Model.php **/ Agents\Model::getValue( $agentId, "id" ), "chat_hashid" => $chatHashId, ])); } } /** Datei: plugins/agents/library/ARUNOX/Plugins/Agents/Calls/API/v1/Routes.php **/ * @copyright 2025 ARUNOX * @version 1.1.0 * * @since 1.0.0 File is created */ namespace ARUNOX\Plugins\Agents\Calls\API\v1; use ARUNOX\Core\Hooks; use ARUNOX\Core\Router; use ARUNOX\Plugins\REST; class Routes { public static function register() : void { /* ### MAIN ROUTES ############################## # */ // Router::add([ Controller::class, "getAll" ]); // Router::add([ Controller::class, "get" ]); // Router::add([ Controller::class, "getTypeAsOptions" ]); // // Router::add([ Controller::class, "create" ]); // Router::add([ Controller::class, "save" ]); // Router::add([ Controller::class, "delete" ]); // Router::add([ Controller::class, "toggleStatus" ]); // // /* ### REST ROUTES ############################## # */ // Router::add([ Controller::class, "restConfigDetails" ]); // Router::add([ Controller::class, "restConfigCreate" ]); // Router::add([ Controller::class, "restConfigDelete" ]); // // Router::add([ Controller::class, "restResponseCreate" ]); // Router::add([ Controller::class, "restResponseSave" ]); // Router::add([ Controller::class, "restResponseDelete" ]); // Router::add([ Controller::class, "restResponseToggleStatus" ]); } } /** Datei: plugins/agents/library/ARUNOX/Plugins/Agents/Calls/API/v1/Controller.php **/ * @copyright 2025 ARUNOX * @version 1.1.0 * * @since 1.0.0 File is created */ namespace ARUNOX\Plugins\Agents\Calls\API\v1; use ARUNOX\HTTP; use ARUNOX\Plugins\REST; use ARUNOX\Plugins\Agents; use ARUNOX\API\BaseController; class Controller extends BaseController { /* ### VARIABLES ################################################ # */ protected static array $NOTIFY = [ "notfound" => "Call nicht gefunden", "saved" => "Call wurde erfolgreich gespeichert", "deleted" => "Call wurde erfolgreich gelöscht", "created" => "Call wurde erfolgreich erstellt", "statuschanged.0" => "Call wurde erfolgreich deaktiviert", "statuschanged.1" => "Call wurde erfolgreich aktiviert", "notcreated" => "Call konnte nicht erstellt werden", "notsaved" => "Call konnte nicht gespeichert werden", ]; /* ### PUBLIC FUNCTIONS ######################################### # */ /** * @api * @method GET * * @route.path /agents/{id}/calls/{id} * @route.description Listet alle Calls eines Chats auf */ public static function getAllOwn( string $agentHashId, string $chatHashId, array $where = [] ) : array { return Agents\Calls\Model::getByAgent( $agentHashId, $chatHashId, $where ); } /* ### PUBLIC SAVE FUNCTIONS #################################### # */ /** * @api * @method PUT * * @route.path /agents/{id}/tools/{id} * @route.description Aktualisiert die Eigenschaften eines Tools */ public static function save( string $agentHashId, string $chatHashId, array $data ) : HTTP\Response { if( $result = Calls\Tools\Model::get([ "id" => $id, "origin_id" => $origin ]) ) { if( $result->save( $data ) ) { return REST\Response::jsonResponse( 200, static::getNotify("saved") ); } return REST\Response::jsonResponse( 500, static::getNotify("notsaved") ); } return REST\Response::jsonResponse( 404, static::getNotify("notfound") ); } /** * @api * @method POST * * @route.path /agents/{id}/calls/{id} * @route.description Erstellt einen neuen Call für einen Chat */ public static function create( string $agentHashId, string $chatHashId, array $data ) : HTTP\Response { $data[ "origin_id" ] = Agents\Model::getValue( $agentHashId ); $data[ "chat_hashid" ] = $chatHashId; if( $hashid = Agents\Calls\Model::save( $data ) ) { $result = Agents\Calls\Model::get( $hashid ); return REST\Response::jsonResponse( 200, static::getNotify("created"), [ "data" => $result ] ); } return REST\Response::jsonResponse( 500, static::getNotify("notfound") ); } /** * @api * @method DELETE * * @route.path /agents/{id}/calls/{id} * @route.description Löscht ein Tool eines Agenten */ public static function delete( string $agentHashId, string $callHashId ) : HTTP\Response { $agent = Agents\Model::getValue( $agentHashId ); if( Agents\Calls\Model::delete([ "hashid" => $callHashId, "origin_id" => $agent ]) ) { return REST\Response::jsonResponse( 200, static::getNotify("deleted") ); } return REST\Response::jsonResponse( 500, static::getNotify("notfound") ); } /* ### PUBLIC VALIDATE FUNCTIONS ################################ # */ /* ### PUBLIC REST FUNCTIONS #################################### # */ /* ### PUBLIC REST RESPONSE FUNCTIONS ########################### # */ /* ### PUBLIC REST CONFIG FUNCTIONS ############################# # */ /* ### PUBLIC REST TYPE FUNCTIONS ############################### # */ } /** Datei: plugins/agents/library/ARUNOX/Plugins/Agents/Calls/Source.php **/ [ "color" => REST\Generator\Config\Color::Info, "label" => "API", "icon" => "bi bi-code" ], self::Cron => [ "color" => REST\Generator\Config\Color::Info, "label" => "Cron", "icon" => "bi bi-clock" ], self::Phone => [ "color" => REST\Generator\Config\Color::Info, "label" => "Phone", "icon" => "bi bi-telephone" ], self::Admin => [ "color" => REST\Generator\Config\Color::Info, "label" => "Admin", "icon" => "bi bi-shield-lock" ], self::Webchat => [ "color" => REST\Generator\Config\Color::Info, "label" => "Webchat", "icon" => "bi bi-globe" ], self::Backend => [ "color" => REST\Generator\Config\Color::Info, "label" => "Backend", "icon" => "bi bi-cpu" ], ]; /* ### PUBLIC FUNCTIONS ######################################### # */ } /** Datei: plugins/agents/library/ARUNOX/Plugins/Agents/Calls/Type.php **/ [ "color" => REST\Generator\Config\Color::Info, "label" => "Text", "icon" => "bi bi-chat-dots" ], self::Image => [ "color" => REST\Generator\Config\Color::Info, "label" => "Image", "icon" => "bi bi-image" ], self::AudioIn => [ "color" => REST\Generator\Config\Color::Info, "label" => "Audio in", "icon" => "bi bi-mic" ], self::AudioOut => [ "color" => REST\Generator\Config\Color::Info, "label" => "Audio out", "icon" => "bi bi-volume-up" ], self::ToolCall => [ "color" => REST\Generator\Config\Color::Info, "label" => "Tool Call", "icon" => "bi bi-plug" ], self::ToolResult => [ "color" => REST\Generator\Config\Color::Info, "label" => "Tool Result", "icon" => "bi bi-check-circle" ], self::System => [ "color" => REST\Generator\Config\Color::Info, "label" => "System", "icon" => "bi bi-terminal" ], ]; /* ### PUBLIC FUNCTIONS ######################################### # */ } /** Datei: plugins/agents/library/ARUNOX/Plugins/Agents/Calls/Entity.php **/ Status::class, "type" => Type::class, "role" => Role::class, "source" => Source::class, "date_create" => Attribute\DateTime::class, "date_edit" => Attribute\DateTime::class, ]; /* ### PUBLIC FUNCTIONS ######################################### # */ public function toggleStatus( bool $save = FALSE ) { $this->status->toggle(); if( $save ) $this->save(); } } /** Datei: plugins/agents/library/ARUNOX/Plugins/Agents/Calls/Status.php **/ [ "color" => REST\Generator\Config\Color::Default, "label" => "Draft", "icon" => "bi bi-dash-circle" ], self::Waiting => [ "color" => REST\Generator\Config\Color::Info, "label" => "Waiting", "icon" => "bi bi-hourglass-split" ], self::InProgress => [ "color" => REST\Generator\Config\Color::Warning, "label" => "InProgress", "icon" => "bi bi-cpu" ], self::Completed => [ "color" => REST\Generator\Config\Color::Success, "label" => "Completed", "icon" => "bi bi-check-circle" ], self::Incomplete => [ "color" => REST\Generator\Config\Color::Danger, "label" => "Incomplete", "icon" => "bi bi-exclamation-triangle" ], self::Failed => [ "color" => REST\Generator\Config\Color::Danger, "label" => "Error", "icon" => "bi bi-exclamation-triangle" ], self::Skipped => [ "color" => REST\Generator\Config\Color::Info, "label" => "Skipped", "icon" => "bi bi-slash-circle" ], ]; /* ### PUBLIC FUNCTIONS ######################################### # */ /* ### MAGIC FUNCTIONS ########################################## # */ } /** 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; } } /** Datei: plugins/agents/library/ARUNOX/Plugins/Agents/Tools/Model.php **/ Agents\Model::getValue( $agentId, "id" ), ])); } } /** Datei: plugins/agents/library/ARUNOX/Plugins/Agents/Tools/API/v1/Routes.php **/ * @copyright 2025 ARUNOX * @version 1.1.0 * * @since 1.0.0 File is created */ namespace ARUNOX\Plugins\Agents\Tools\API\v1; use ARUNOX\Core\Hooks; use ARUNOX\Core\Router; use ARUNOX\Plugins\REST; class Routes { public static function register() : void { Router::addAll( Controller::class ); Hooks::add('router::get->api.v1.rest.routes', function( $routes ) { $routes[] = [ "path" => "/agents/tools/{id}", "label" => "Bearbeiten", ]; return $routes; }); } } /** Datei: plugins/agents/library/ARUNOX/Plugins/Agents/Tools/API/v1/Controller.php **/ * @copyright 2025 ARUNOX * @version 1.1.0 * * @since 1.0.0 File is created */ namespace ARUNOX\Plugins\Agents\Tools\API\v1; use ARUNOX\API; use ARUNOX\HTTP; use ARUNOX\Core\Hooks; use ARUNOX\Plugins\REST; use ARUNOX\Plugins\Agents; use ARUNOX\Database\Attribute; use ARUNOX\API\BaseController; use ARUNOX\Database\BaseEntity; class Controller extends BaseController { /* ### VARIABLES ################################################ # */ protected static array $NOTIFY = [ "notfound" => "Tool nicht gefunden", "saved" => "Tool wurde erfolgreich gespeichert", "deleted" => "Tool wurde erfolgreich gelöscht", "created" => "Tool wurde erfolgreich erstellt", "statuschanged.0" => "Tool wurde erfolgreich deaktiviert", "statuschanged.1" => "Tool wurde erfolgreich aktiviert", "notcreated" => "Tool konnte nicht erstellt werden", "notsaved" => "Tool konnte nicht gespeichert werden", ]; /* ### PUBLIC FUNCTIONS ######################################### # */ /** * @api * @method GET * * @route.path /agents/{hashid}/tools * @route.link /agents/{hashid}#tools * @route.query.status integer|options=/agents/tools/options/status * @route.description Listet alle Tools eines Agenten */ public static function getAllOwn( string $hashid, array $where = [] ) : array { return Agents\Tools\Model::getByAgent( $hashid, $where ); } /** * @api * @method GET * * @route.path /agents/{hashid}/tools/{id} * @route.description Gibt ein einzelnes Tool eines Agenten zurück */ public static function get( int $origin, int $id ) : BaseEntity | HTTP\Response { if( $result = Agents\Tools\Model::get([ "id" => $id, "origin_id" => $origin ]) ) { return $result; } return REST\Response::jsonResponse( 404, static::$NOTIFY["notfound"] ); } /** * @api * @method GET * * @route.path /agents/tools/options/type * @route.description Gibt ein alle Optionen für type zurück */ public static function getTypeAsOptions() : array { return Agents\Tools\Type::getAll(); } /** * @api * @method GET * * @route.path /agents/tools/options/status * @route.description Gibt ein alle Optionen für status zurück */ public static function getStatusAsOptions() : array { return Attribute\Status::getAll(); } /* ### PUBLIC SAVE FUNCTIONS #################################### # */ /** * @api * @method PUT * * @route.path /agents/{hashid}/tools/{id} * @route.description Aktualisiert die Eigenschaften eines Tools */ public static function save( string $origin, int $id, array $data ) : HTTP\Response { $agent = Agents\Model::getValue( $origin ); if( $result = Agents\Tools\Model::get([ "id" => $id, "origin_id" => $agent ]) ) { if( $result->save( $data ) ) { return REST\Response::jsonResponse( 200, static::$NOTIFY["saved"] ); } return REST\Response::jsonResponse( 500, static::$NOTIFY["notsaved"] ); } return REST\Response::jsonResponse( 404, static::$NOTIFY["notfound"] ); } /** * @api * @method POST * * @route.path /agents/{hashid}/tools * @route.description Erstellt ein neues Tool für einen Agenten */ public static function create( string $origin, array $data ) : HTTP\Response { $data[ "origin_id" ] = Agents\Model::getValue( $origin ); if( $id = Agents\Tools\Model::save( $data ) ) { $result = Agents\Tools\Model::get( $id ); return REST\Response::jsonResponse( 200, static::$NOTIFY["created"], [ "data" => $result ] ); } return REST\Response::jsonResponse( 500, static::$NOTIFY["notfound"] ); } /** * @api * @method DELETE * * @route.path /agents/{hashid}/tools/{id} * @route.description Löscht ein Tool eines Agenten */ public static function delete( string $origin, int $id ) : HTTP\Response { $agent = Agents\Model::getValue( $origin ); if( Agents\Tools\Model::delete([ "id" => $id, "origin_id" => $agent ]) ) { return REST\Response::jsonResponse( 200, static::$NOTIFY["deleted"] ); } return REST\Response::jsonResponse( 500, static::$NOTIFY["notfound"] ); } /** * @api * @method PATCH * * @route.path /agents/{hashid}/tools/{id}/status * @route.description Umschalten des Status eines Tools */ public static function toggleStatus( string $origin, int $id ) : HTTP\Response { $agent = Agents\Model::getValue( $origin ); if( $result = Agents\Tools\Model::get([ "id" => $id, "origin_id" => $agent ]) ) { $result->toggleStatus( TRUE ); return REST\Response::jsonResponse( 200, static::$NOTIFY["statuschanged.{$result->status->getValue()}"], [ "data" => $result ] ); } return REST\Response::jsonResponse( 404, static::$NOTIFY["notfound"] ); } /* ### PUBLIC VALIDATE FUNCTIONS ################################ # */ /* ### PUBLIC REST FUNCTIONS #################################### # */ /* ### PUBLIC REST RESPONSE FUNCTIONS ########################### # */ /** * @internal rest * @method PUT * * @route.path /agents/{hashid}/tools/{id} * @route.description REST-Antwort nach dem Speichern eines Tools */ public static function restResponseSave( string $origin, int $id, array $data ) : HTTP\Response { $agent = Agents\Model::getValue( $origin ); $result = static::save( $agent, $id, $data ); if( $result->isStatus( 200 ) ) { $data = Agents\Tools\Model::get([ "id" => $id, "origin_id" => $agent ]); REST\Response::closeModals(); REST\Response::addUpdater( [ "data/table/row", "/api/v1/agents/{$origin}/tools", $id ], $data ); } return static::restResponse( $result ); } /** * @internal rest * @method POST * * @route.path /agents/{hashid}/tools * @route.description REST-Antwort nach dem Erstellen eines Tools */ public static function restResponseCreate( string $origin, array $data ) : HTTP\Response { $agent = Agents\Model::getValue( $origin ); $result = static::create( $agent, $data ); if( $result->isStatus( 200 ) ) { REST\Response::closeModals(); REST\Response::redirect( "/agents/{$origin}#tools" ); } return static::restResponse( $result ); } /** * @internal rest * @method DELETE * * @route.path /agents/{hashid}/tools/{id} * @route.description REST-Antwort nach dem Löschen eines Tools */ public static function restResponseDelete( string $origin, int $id ) : HTTP\Response { REST\Response::closeModals(); $agent = Agents\Model::getValue( $origin ); $result = static::delete( $agent, $id ); if( $result->isStatus( 200 ) ) { REST\Response::redirect( "/agents/{$origin}#tools" ); } return static::restResponse( $result ); } /** * @internal rest * @method PATCH * * @route.path /agents/{hashid}/tools/{id}/status * @route.description REST-Antwort zur Statusänderung eines Tools */ public static function restResponseToggleStatus( string $origin, int $id ) : HTTP\Response { $agent = Agents\Model::getValue( $origin ); $result = static::toggleStatus( $agent, $id ); if( $result->isStatus( 200 ) ) { $data = $result->getBody("data"); REST\Response::addUpdater( [ "data/table/row", "/api/v1/agents/{$origin}/tools", $id ], $data ); } return static::restResponse( $result ); } /* ### PUBLIC REST CONFIG FUNCTIONS ############################# # */ /** * @internal rest * @method GET * * @route.path /agents/{hashid}/tools/{id} * @route.description REST-Detailansicht für ein Tool */ public static function restConfigDetails( string $origin, int $id ) : REST\Generator\Base\BaseGenerator { $agent = Agents\Model::getValue( $origin ); $tool = Agents\Tools\Model::get([ "id" => $id, "origin_id" => $agent ]); $form = REST\Response::addModal( "details", "Tool bearbeiten" ) ->setData( $tool ) ->setTitle( $tool->type->getLabel() ) ->setIcon( $tool->type->getIcon() ) ->setColor( $tool->type->getColor() ) ->get() ->setAction( "/agents/{$origin}/tools/{id}", "PUT" ) ->addButton(...Hooks::filter( "rest.generator::button.type->submit", [], "Speichern", "agents/{origin_id}/tools" )) ->addText( "Geben Sie diesem Tool eine für Sie passende Beschreibung, zur besseren Wiedererkennung." ) ->addField(...Hooks::filter( "rest.generator::control.type->text", [], "name", TRUE, "agents/tools" )) ->addField(...Hooks::filter( "rest.generator::control.type->text", [], "description", TRUE, "agents/{origin_id}/tools" )) ->addField(...Hooks::filter( "rest.generator::control.type->text", [], "context", FALSE, "agents/tools" )) ->addHeadline( "Einstellungen", "3" ); switch( $tool->type->getValue() ) { case Agents\Tools\Type::SystemTool : static::restConfigFormTypeSystemTool( $form ); break; case Agents\Tools\Type::CallForward : static::restConfigFormTypeCallForward( $form ); break; case Agents\Tools\Type::Calendar : static::restConfigFormTypeCalendar( $form ); break; case Agents\Tools\Type::APIRequest : static::restConfigFormTypeAPIRequest( $form ); break; case Agents\Tools\Type::ConversionFlow : static::restConfigFormTypeConversionFlow( $form ); break; } return $form; } /** * @internal rest * @method GET * * @route.path /agents/{hashid}/tools/create * @route.query.type integer|required|options=/agents/tools/options/type * @route.description REST-Formularkonfiguration zum Erstellen eines Tools */ public static function restConfigCreate( string $origin, $args ) : REST\Generator\Base\BaseGenerator { $agent = Agents\Model::getValue( $origin ); $option = Agents\Tools\Type::get( $args["type"] ?? 3 ); $form = REST\Response::addModal( "details", "Tool erstellen" ) ->setTitle( $option["label"] ) ->setIcon( $option["icon"] ) ->setColor( $option["color"] ) ->get() ->setAction( "/agents/{$origin}/tools", "POST" ) ->addButton(...Hooks::filter( "rest.generator::button.type->submit", [], "Erstellen", "agents/tools" )) ->addText( "Geben Sie diesem Tool eine für Sie passende Beschreibung, zur besseren Wiedererkennung." ) ->addField(...Hooks::filter( "rest.generator::control.type->text", [], "name", TRUE, "agents/tools" )) ->addField(...Hooks::filter( "rest.generator::control.type->text", [], "description", TRUE, "agents/tools" )) ->addField(...Hooks::filter( "rest.generator::control.type->text", [], "context", FALSE, "agents/tools" )) ->addField(...Hooks::filter( "rest.generator::control.type->hidden", [], "type", $args["type"], "agents/tools" )) ->addHeadline( "Einstellungen", "3" ); switch( $args["type"] ) { case Agents\Tools\Type::SystemTool : static::restConfigFormTypeSystemTool( $form ); break; case Agents\Tools\Type::CallForward : static::restConfigFormTypeCallForward( $form ); break; case Agents\Tools\Type::Calendar : static::restConfigFormTypeCalendar( $form ); break; case Agents\Tools\Type::APIRequest : static::restConfigFormTypeAPIRequest( $form ); break; case Agents\Tools\Type::ConversionFlow : static::restConfigFormTypeConversionFlow( $form ); break; } return $form; } /** * @internal rest * @method GET * * @route.path /agents/{hashid}/tools/{id}/delete * @route.description REST-Bestätigungsdialog zum Löschen eines Tools */ public static function restConfigDeleteOwn( string $origin, int $id ) : REST\Generator\Base\BaseGenerator { $agent = Agents\Model::getValue( $origin ); return REST\Response::addModal( "details", "Tool Löschen" ) ->setSize( REST\Wrapper\Modal\Size::sm ) ->get() ->setAction( "/agents/{$origin}/tools/{$id}", "DELETE" ) ->addText( "Möchtest du wirklich den Datensatz (#{$id}) löschen? " ) ->addButton(...Hooks::filter( "rest.generator::button.type->cancel", [], NULL, "agents/{origin_id}/tools" )) ->addButton(...Hooks::filter( "rest.generator::button.type->delete", [], NULL, "agents/{origin_id}/tools" )); } /* ### PUBLIC REST EXTENSION FUNCTIONS ########################## # */ public static function restConfigTable( string $hashid ) : REST\Generator\Base\BaseGenerator { $table = REST\Generator\Data\Table::create() ->setApi( "/agents/{$hashid}/tools" ) ->setAction( "/agents/{$hashid}/tools/{id}" ) ->setOrder( "name", "asc" ) ->setSearch( TRUE ) ->addButton(...Hooks::filter( "rest.generator::button.name->create", [], "agents/{$hashid}/tools" )) ->addColumn(...Hooks::filter( "rest.generator::column.type->icon", [ "icon" => "bi-ui-checks-grid", "options" => Agents\Tools\Type::getAll(), ], "type", [], "agents/{origin_id}/tools" )) ->addColumn(...Hooks::filter( "rest.generator::column.type->status", [], "status", [], "agents/{$hashid}/tools" )) ->addColumn(...Hooks::filter( "rest.generator::column.type->text", [ "label" => "Description", ], "output", TRUE, TRUE, TRUE, "agents/{origin_id}/tools" )) ->addColumn(...Hooks::filter( "rest.generator::column.type->text", [ "label" => "Kontext", ], "context", TRUE, TRUE, TRUE, "agents/{origin_id}/tools" )) ->addColumn(...Hooks::filter( "rest.generator::column.type->datetime", [], "date_edit", FALSE, FALSE, TRUE, "agents/{id}/tools" )) ->addColumn(...Hooks::filter( "rest.generator::column.type->actions", [], [], FALSE, "agents/{id}/tools" ))->get() ->addButton(...Hooks::filter( "rest.generator::action.name->edit", [], "agents/{$hashid}/tools" )) ->addButton(...Hooks::filter( "rest.generator::action.name->delete", [], "agents/{$hashid}/tools" )) ; $buttons = $table->top()->getButtons(); foreach( Agents\Tools\Type::getAll() as $type ) { $type["name"] = "type"; $buttons->addDropdown( ...Hooks::filter( "rest.generator::button.name->create", $type, "agents/{$hashid}/tools" ) ); } return $table; } /* ### PUBLIC REST TYPE FUNCTIONS ############################### # */ public static function restConfigFormTypeSystemTool( REST\Generator\Base\BaseGenerator $form ) { $form->top()->get() ->addField(...Hooks::filter( "rest.generator::control.type->textarea", [ "label" => "Dynamische Parameter", "value" => "[]", ], "parameters", FALSE, "agents/tools" )) ; } public static function restConfigFormTypeCallForward( REST\Generator\Base\BaseGenerator $form ) { $form->top()->get() ->addField(...Hooks::filter( "rest.generator::control.type->tel", [], "phone", TRUE, "agents/{origin_id}/tools" )) ; } public static function restConfigFormTypeCalendar( REST\Generator\Base\BaseGenerator $form ) { $form ->get()->setTitle( "Aktuell nur Platzhalter / Muster") ->top(); } public static function restConfigFormTypeAPIRequest( REST\Generator\Base\BaseGenerator $form ) { $form->top()->get() ->addField(...Hooks::filter( "rest.generator::control.type->text", [ "label" => "Ausführungstext", "hint" => "Text der während Ausführung gesprochen wird. Leer lassen für keine Antwort.", ], "response", FALSE, "agents/tools" )) ->addField(...Hooks::filter( "rest.generator::control.type->url", [], "url", TRUE, "agents/tools" )) ->addField(...Hooks::filter( "rest.generator::control.type->select", [ "choose_id" => API\Method::GET, "options" => API\Method::getAll(), ], "method", TRUE, FALSE, "agents/tools" )) ->addField(...Hooks::filter( "rest.generator::control.type->textarea", [ "value" => "[]", ], "header", FALSE, "agents/tools" )) ->addField(...Hooks::filter( "rest.generator::control.type->textarea", [ "label" => "Dynamische Parameter", "value" => "[]", ], "parameters", FALSE, "agents/tools" )) ->addField(...Hooks::filter( "rest.generator::control.type->textarea", [ "label" => "Default / Fixe Parameter", "value" => "[]", ], "defaults", FALSE, "agents/tools" )) ; } public static function restConfigFormTypeConversionFlow( REST\Generator\Base\BaseGenerator $form ) { $form->top()->get() ->addField(...Hooks::filter( "rest.generator::control.type->textarea", [ "label" => "Step", "value" => "[]", ], "steps", FALSE, "agents/tools" )) ; } } /** Datei: plugins/agents/library/ARUNOX/Plugins/Agents/Tools/Type.php **/ [ "color" => REST\Generator\Config\Color::Default, "icon" => "bi bi-tools", "label" => "Systemtool" ], self::CallForward => [ "color" => REST\Generator\Config\Color::Primary, "icon" => "bi bi-telephone-forward", "label" => "Anrufweiterleitung" ], self::Calendar => [ "color" => REST\Generator\Config\Color::Secondary, "icon" => "bi bi-calendar-plus", "label" => "Terminbuchung" ], self::APIRequest => [ "color" => REST\Generator\Config\Color::Danger, "icon" => "bi bi-plugin", "label" => "API Request" ], self::ConversionFlow => [ "color" => REST\Generator\Config\Color::Info, "icon" => "bi bi-chat-dots", "label" => "Gesprächsablauf" ], ]; /* ### PUBLIC FUNCTIONS ######################################### # */ } /** Datei: plugins/agents/library/ARUNOX/Plugins/Agents/Tools/Entity.php **/ Attribute\Status::class, "date_create" => Attribute\DateTime::class, "date_edit" => Attribute\DateTime::class, "type" => Type::class, "output" => "output", ]; /* ### PUBLIC FUNCTIONS ######################################### # */ public function getOutput() { return match( $this->type->getValue() ) { Type::CallForward => "{$this->phone} » {$this->description}", default => $this->description, }; } public function toggleStatus( bool $save = FALSE ) { $this->status->toggle(); if( $save ) $this->save(); } } /** Datei: plugins/agents/library/ARUNOX/Plugins/Agents/Entity.php **/ Attribute\Status::class, "date_create" => Attribute\DateTime::class, "date_edit" => Attribute\DateTime::class, "prompt" => "prompt", ]; /* ### PUBLIC FUNCTIONS ######################################### # */ public function toggleStatus( bool $save = FALSE ) { $this->status->toggle(); if( $save ) $this->save(); } public function getTools( bool $onlyActive = FALSE ) { if( $onlyActive ) $where = [ "status" => Attribute\Status::Active ]; else $where = []; return Tools\Model::getAll( array_merge( $where, [ "origin_id" => $this->id, ])); } public function getPrompt() { $prompt = array_filter([ "#Über Dich" => $this->prompt_personality, "#Allgemeine Informationen" => $this->prompt_tasks, "#Verhalten bei Unklarheiten" => $this->prompt_uncertainties, "#Sensible Themen" => $this->prompt_sensitives, "#Gesprächsablauf" => $this->prompt_conversation_flow, "#Häufige Fragen" => $this->prompt_faq, "#Wissensdatenbank" => $this->prompt_knowledge_base, "# Chat-Modus (aktiv)\n" => $this->prompt_chat, // "# Telefon-Modus (aktiv))\n" => $this->prompt_phone, ]); $prompt = array_map( "trim", $prompt ); $prompt = array_map( fn( $title, $prompt ) => "{$title}\n{$prompt}", array_keys( $prompt ), array_values( $prompt ) ); $prompt = implode( "\n\n---\n\n", $prompt ); return $prompt; } } /** Datei: plugins/agents/library/ARUNOX/Plugins/Agents/Processing/Model.php **/ Agents\Model::getValue( $agentId, "id" ), ])); } } /** Datei: plugins/agents/library/ARUNOX/Plugins/Agents/Processing/API/v1/Routes.php **/ * @copyright 2025 ARUNOX * @version 1.1.0 * * @since 1.0.0 File is created */ namespace ARUNOX\Plugins\Agents\Processing\API\v1; use ARUNOX\Core\Hooks; use ARUNOX\Core\Router; class Routes { public static function register() : void { Router::get('/api/v1/agents/{id}/processing', [ Controller::class, 'getByAgent' ]); Router::get('/api/v1/agents/{id}/processing/create', [ Controller::class, 'restConfigCreate' ]); Router::post('/api/v1/agents/{id}/processing', [ Controller::class, 'create' ]); Router::get('/api/v1/agents/{id}/processing/{id}', [ Controller::class, 'get' ]); Router::put('/api/v1/agents/{id}/processing/{id}', [ Controller::class, 'save' ]); Router::patch('/api/v1/agents/{id}/processing/{id}/status', [ Controller::class, 'toggleStatus' ]); Router::get('/api/v1/agents/{id}/processing/{id}/delete', [ Controller::class, 'restConfigDelete' ]); Router::delete('/api/v1/agents/{id}/processing/{id}', [ Controller::class, 'delete' ]); Hooks::add('router::get->api.v1.rest.routes', function( $routes ) { $routes[] = [ "path" => "/agents/processing/{id}", "label" => "Bearbeiten", ]; return $routes; }); } } /** Datei: plugins/agents/library/ARUNOX/Plugins/Agents/Processing/API/v1/Controller.php **/ * @copyright 2025 ARUNOX * @version 1.1.0 * * @since 1.0.0 File is created */ namespace ARUNOX\Plugins\Agents\Processing\API\v1; use ARUNOX\HTTP; use ARUNOX\Core\Hooks; use ARUNOX\Plugins\REST; use ARUNOX\Plugins\Agents; use ARUNOX\API\BaseController; class Controller extends BaseController { /* ### VARIABLES ################################################ # */ protected static array $NOTIFY = [ "notfound" => "Tool nicht gefunden", "saved" => "Tool wurde erfolgreich gespeichert", "deleted" => "Tool wurde erfolgreich gelöscht", "created" => "Tool wurde erfolgreich erstellt", "notcreated" => "Tool konnte nicht erstellt werden", ]; /* ### PUBLIC FUNCTIONS ######################################### # */ public function getByAgent( int $id ) : HTTP\Response { $results = Agents\Processing\Model::getByAgent( $id ); return $this->jsonResponse( $results ); } public function get( int $origin, int $id ) : HTTP\Response { if( $result = Agents\Processing\Model::get([ "id" => $id, "origin_id" => $origin ]) ) { $this->restConfigDetails( $result ); } else REST\Response::addNotify( "danger", static::$NOTIFY["notfound"] ); return REST\Response::jsonResponse(); } /* ### PUBLIC SAVE FUNCTIONS #################################### # */ public function save( int $origin, int $id, array $data ) : HTTP\Response { if( $result = Agents\Processing\Model::get([ "id" => $id, "origin_id" => $origin ]) ) { $result->save( $data ); REST\Response::addNotify( "success", static::$NOTIFY["saved"] ); REST\Response::closeModals(); REST\Response::addUpdater( [ "data/table/row", "/api/v1/agents/{$result->origin_id}/processing", $id ], $result ); } else REST\Response::addNotify( "danger", static::$NOTIFY["notfound"] ); return REST\Response::jsonResponse(); } public function create( int $origin, array $data ) : HTTP\Response { $data[ "origin_id" ] = $origin; if( $id = Agents\Model::save( $data ) ) { $result = Agents\Processing\Model::get( $id ); REST\Response::addNotify( "success", static::$NOTIFY["created"] ); REST\Response::closeModals(); REST\Response::redirect( "/agents/agents/{$result->origin_id}#processing" ); } else REST\Response::addNotify( "danger", static::$NOTIFY["notcreated"] ); return REST\Response::jsonResponse(); } public function delete( int $origin, int $id ) : HTTP\Response { $result = Agents\Processing\Model::get([ "id" => $id, "origin_id" => $origin ]); REST\Response::closeModals(); if( $result && Agents\Model::delete( $id ) ) { REST\Response::addNotify( "success", static::$NOTIFY["deleted"] ); REST\Response::redirect( "/agents/agents/{$result->origin_id}#processing" ); } else REST\Response::addNotify( "danger", static::$NOTIFY["notfound"] ); return REST\Response::jsonResponse(); } public function toggleStatus( int $origin, int $id ) : HTTP\Response { if( $result = Agents\Processing\Model::get([ "id" => $id, "origin_id" => $origin ]) ) { $result->toggleStatus( TRUE ); REST\Response::addUpdater( [ "data/table/row", "/api/v1/agents/{$result->origin_id}/processing", $id ], $result ); } else REST\Response::addNotify( "danger", static::$NOTIFY["notfound"] ); return REST\Response::jsonResponse(); } /* ### PUBLIC VALIDATE FUNCTIONS ################################ # */ /* ### PUBLIC REST FUNCTIONS #################################### # */ public function restConfigDetails( Agents\Processing\Entity $data ) : HTTP\Response { $form = REST\Response::addModal( "details", "Tool bearbeiten" ) ->setData( $data ) ->setTitle( $data->type->getLabel() ) ->setIcon( $data->type->getIcon() ) ->setColor( $data->type->getColor() ) ->get() ->setAction( "/api/v1/agents/{origin_id}/processing/{id}", "PUT" ) ->addButton(...Hooks::filter( "rest.generator::button.type->submit", [], "Speichern", "agents/{origin_id}/processing" )) ->addText( "Geben Sie diesem Tool eine für Sie passende Beschreibung, zur besseren Wiedererkennung." ) ->addField(...Hooks::filter( "rest.generator::control.type->text", [], "description", TRUE, "agents/{origin_id}/processing" )) ->addHeadline( "Einstellungen", "3" ); switch( $data->type->getValue() ) { case Agents\Processing\Type::EmailDelivery : static::restConfigFormTypeEmailDelivery( $form ); break; case Agents\Processing\Type::SMSDelivery : static::restConfigFormTypeSMSDelivery( $form ); break; case Agents\Processing\Type::APIRequest : static::restConfigFormTypeApi( $form ); break; } return REST\Response::jsonResponse(); } public function restConfigCreate( $args ) : HTTP\Response { $option = Agents\Processing\Type::get( $args["type"] ); $form = REST\Response::addModal( "details", "Tool erstellen" ) ->setTitle( $option["label"] ) ->setIcon( $option["icon"] ) ->setColor( $option["color"] ) ->get() ->setAction( "/api/v1/agents/{id}/processing", "POST" ) ->addButton(...Hooks::filter( "rest.generator::button.type->submit", [], "Erstellen", "agents/{origin_id}/processing" )) ->addText( "Geben Sie diesem Tool eine für Sie passende Beschreibung, zur besseren Wiedererkennung." ) ->addField(...Hooks::filter( "rest.generator::control.type->text", [], "description", TRUE, "agents/{origin_id}/processing" )) ->addHeadline( "Einstellungen", "3" ); switch( $_GET["type"] ) { case Agents\Processing\Type::EmailDelivery : static::restConfigFormTypeEmailDelivery( $form ); break; case Agents\Processing\Type::SMSDelivery : static::restConfigFormTypeSMSDelivery( $form ); break; case Agents\Processing\Type::APIRequest : static::restConfigFormTypeApi( $form ); break; } return REST\Response::jsonResponse(); } public function restConfigDeleteOwn( int $origin, int $id ) : HTTP\Response { REST\Response::addModal( "details", "Tool Löschen" ) ->setData([ "id" => $id ]) ->setSize( REST\Wrapper\Modal\Size::sm ) ->get() ->setAction( "/api/v1/agents/{origin_id}/processing/{id}", "DELETE" ) ->addText( "Möchtest du wirklich den Datensatz (#{$id}) löschen? " ) ->addButton(...Hooks::filter( "rest.generator::button.type->cancel", [], NULL, "agents/{origin_id}/processing" )) ->addButton(...Hooks::filter( "rest.generator::button.type->delete", [], NULL, "agents/{origin_id}/processing" )); return REST\Response::jsonResponse(); } /* ### PUBLIC REST STATIC FUNCTIONS ############################# # */ public static function restConfigTable() : REST\Generator\Base\BaseGenerator { $table = REST\Generator\Data\Table::create() ->setApi( "/api/v1/agents/{id}/processing" ) ->setAction( "/api/v1/agents/{origin_id}/processing/{id}" ) ->setOrder( "name", "asc" ) ->setSearch( TRUE ) ->addButton(...Hooks::filter( "rest.generator::button.name->create", [], "agents/{id}/processing" )) ->addColumn(...Hooks::filter( "rest.generator::column.type->text", [], "type", TRUE, TRUE, TRUE, "agents/{origin_id}/processing" )) ->addColumn(...Hooks::filter( "rest.generator::column.type->status", [], "status", [], "agents/{origin_id}/processing" )) ->addColumn(...Hooks::filter( "rest.generator::column.type->text", [], "description", TRUE, FALSE, TRUE, "agents/{origin_id}/processing" )) ->addColumn(...Hooks::filter( "rest.generator::column.type->datetime", [], "date_edit", FALSE, FALSE, TRUE, "agents/{origin_id}/processing" )) ->addColumn(...Hooks::filter( "rest.generator::column.type->actions", [], [], FALSE, "agents/{origin_id}/processing" ))->get() ->addButton(...Hooks::filter( "rest.generator::action.name->edit", [], "agents/{origin_id}/processing" )) ->addButton(...Hooks::filter( "rest.generator::action.name->delete", [], "agents/{origin_id}/processing" )) ->top(); $buttons = $table->getButtons(); foreach( Agents\Processing\Type::getAll() as $type ) { $type["name"] = "type"; $buttons->addDropdown( ...Hooks::filter( "rest.generator::button.name->create", $type, "agents/{id}/processing" ) ); } return $table; } public static function restConfigFormTypeEmailDelivery( REST\Generator\Base\BaseGenerator $form ) : void { $form ->addField(...Hooks::filter( "rest.generator::control.type->tel", [], "phone", TRUE, "agents/{origin_id}/processing" )) ->top(); } public static function restConfigFormTypeSMSDelivery( REST\Generator\Base\BaseGenerator $form ) : void { $form ->get() ->addField(...Hooks::filter( "rest.generator::control.type->tel", [], "phone", TRUE, "agents/{origin_id}/processing" )) ->setTitle( "Aktuell nur Platzhalter / Muster") ->top(); } public static function restConfigFormTypeApi( REST\Generator\Base\BaseGenerator $form ) : void { $form ->get() ->setTitle( "Aktuell nur Platzhalter / Muster") ->top(); } } /** Datei: plugins/agents/library/ARUNOX/Plugins/Agents/Processing/Type.php **/ [ "color" => REST\Generator\Config\Color::Primary, "icon" => "bi bi-envelope-at", "label" => "E-Mail-Versand" ], 2 => [ "color" => REST\Generator\Config\Color::Secondary, "icon" => "bi bi-phone-vibrate", "label" => "SMS-Versand" ], 3 => [ "color" => REST\Generator\Config\Color::Danger, "icon" => "bi bi-plugin", "label" => "API Request" ], ]; /* ### PUBLIC FUNCTIONS ######################################### # */ } /** Datei: plugins/agents/library/ARUNOX/Plugins/Agents/Processing/Entity.php **/ Attribute\Status::class, "date_create" => Attribute\DateTime::class, "date_edit" => Attribute\DateTime::class, "type" => Type::class, ]; /* ### PUBLIC FUNCTIONS ######################################### # */ public function toggleStatus( bool $save = FALSE ) { $this->status->toggle(); if( $save ) $this->save(); } } /** Datei: plugins/agents/hooks/model/insert_agents.php **/ * @copyright 2025 ARUNOX * @version 1.0.0 * * @hook model::insert->query->agents * @param object $query Database\QueryBuilder Object * @param string $table Current Database Table Name * * @since 1.0.0 File is created */ use ARUNOX\Database\QueryBuilder; $query->addData( "uuid", guid() ); return $query; /** Datei: plugins/agents/hooks/generator/controls.php **/ voice.agents", function( array $config ) { return array_replace_recursive( $config, [ "options" => [ [ "value" => "alloy", "label" => "Luca" ], [ "value" => "echo", "label" => "Elias" ], [ "value" => "ash", "label" => "Lena" ], [ "value" => "onyx", "label" => "Julia" ], ], ]); }); Hooks::add("rest.generator::control.name->start_message.agents", function( array $config ) { return array_replace_recursive( $config, [ "lines" => 1, ]); }); Hooks::add("rest.generator::control.name->prompt_*.agents", function( array $config ) { return array_replace_recursive( $config, [ "lines" => 3, ]); }); /** Datei: plugins/agents/hooks/generator/columns.php **/ voice.agents", function( array $config ) { return array_replace_recursive( $config, [ "options" => [ [ "value" => "alloy", "label" => "Luca" ], [ "value" => "echo", "label" => "Elias" ], [ "value" => "ash", "label" => "Lena" ], [ "value" => "onyx", "label" => "Julia" ], ], ]); }); Hooks::add("rest.generator::column.name->type.agents.processing", function( array $config ) { return array_replace_recursive( $config, [ "options" => Agents\Processing\Type::getAll(), ]); }); /** Datei: plugins/agents/hooks/init.php **/ * @copyright 2025 ARUNOX * @version 1.0.0 * @since 1.0.0 File is created */ use ARUNOX\Core\Hooks; use ARUNOX\Plugins\Agents; Hooks::add('public::api->init', [ Agents\API\v1\Routes::class, "register" ] ); Hooks::add('public::api->init', [ Agents\Calls\API\v1\Routes::class, "register" ] ); Hooks::add('public::api->init', [ Agents\Processing\API\v1\Routes::class, "register" ] ); Hooks::add('public::api->init', [ Agents\Tools\API\v1\Routes::class, "register" ] ); /** Datei: plugins/agents/assets/js/chatai.js **/ const chatAI = { timeout : null, websocket : null, container : null, initialized : false, elements : {}, observer : new MutationObserver(() => { clearTimeout( chatAI.timeout ); chatAI.timeout = setTimeout(() => { chatAI.observer.disconnect(); chatAI.draw(); }, 300 ); }), iconMap : { '✅' : 'checkCircle', '❌' : 'xCircle', '🔁' : 'arrowClockwise', '🗑️' : 'trash', '⏳' : 'hourglassSplit', '⚡' : 'lightningChargeFill', '🚨' : 'exclamationTriangle', '❓' : 'questionCircle', '🟢' : 'wifi', '🔴' : 'wifiOff', '🎙️' : 'mic', '🔉' : 'volumeUp', '🗣️' : 'chatDots' }, init : () => { chatAI.container = document.querySelector('body'); chatAI.observer.observe( chatAI.container, { childList: true, subtree: true }); }, scrollToBottom : () => { chatAI.elements.output.scrollTop = chatAI.elements.output.scrollHeight; }, sanitizeMD : ( markdown ) => { return markdown.replace(/\[([^\]]+)\]\((javascript:\S+\([^\)]+\))\)/gi, ( match, text, href ) => { const safeHref = href.replace(/\s+/g, ''); return `[${text}](${safeHref})`; }); }, sendMethod : ( method, data ) => { chatAI.websocket.send({ // command : 'openai-response', command : 'openai-chat', api_url : chatAI_API, chat_id : chatAI_CHAT_ID, method : method, data : data, }); }, sendMessage : ( message ) => { chatAI.websocket.send({ // command : 'openai-response', command : 'openai-chat', api_url : chatAI_API, chat_id : chatAI_CHAT_ID, message : message, }); }, readResponse : ( response ) => { chatAI.drawStatus(); if( typeof response === 'string' ) { response = { role : 'human', message : response, }; } if( Array.isArray( response ) ) { for( const item of response ) { chatAI.drawMessage( item ); } return; } chatAI.drawMessage( response ); }, showBubble : () => { if( chatAI.elements.bubble ) chatAI.elements.bubble.classList.remove('hidden'); }, hideBubble : () => { if( chatAI.elements.bubble ) chatAI.elements.bubble.classList.add('hidden'); }, showChat : () => { chatAI.elements.wrapper.classList.remove('hidden'); chatAI.hideBubble(); }, hideChat : () => { chatAI.elements.wrapper.classList.add('hidden'); chatAI.showBubble(); }, clearChat : () => { const messages = document.querySelectorAll(`.chatai__message`); messages.forEach( message => message.remove() ); }, removeMessage : ( id ) => { const message = document.querySelector(`.chatai__message[data-id="${id}"]`); message.remove(); }, replaceEmoji : ( text ) => { for( const emoji in chatAI.iconMap ) { if( text.startsWith( emoji ) ) { const wrapper = document.createElement('span'); const iconKey = chatAI.iconMap[ emoji ]; const iconSVG = chatAI_icons?.[ iconKey ] || ''; return `${iconSVG} ${text.slice( emoji.length ).trim()}`; } } return text; }, drawMessage : ( data ) => { const container = document.createElement('div'); const button = document.createElement('button'); container.className = `chatai__message chatai__message--${data.role}`; container.innerHTML = marked.parse( chatAI.sanitizeMD( data.content || '' ) ); if( typeof data.id !== 'undefined' ) { container.dataset.id = data.id; } button.className = `chatai__button chatai__button--delete`; button.innerHTML = '🗑'; button.title = 'Nachricht aus Verlauf löschen'; button.addEventListener( 'click', () => { chatAI.sendMethod( 'remove', data.id ); }); container.append( button ); chatAI.elements.output.append( container ); chatAI.elements.input.value = ''; chatAI.scrollToBottom(); }, drawStatus : () => { if( typeof chatAI.elements.status === 'undefined' ) return; const data = chatAI.websocket.status; if( data.status === 'connected' ) { chatAI.elements.statusDot.classList.remove( 'error' ); chatAI.elements.statusDot.classList.add( 'success' ); chatAI.showBubble(); } if( data.status === 'disconnected' ) { chatAI.elements.statusDot.classList.remove( 'success' ); chatAI.elements.statusDot.classList.add( 'error' ); chatAI.hideBubble(); } switch( data.status ) { case 'thread_created' : chatAI.initialized = true; break; case 'thread_loaded' : chatAI.initialized = true; break; case 'thread_cleared' : chatAI.clearChat(); break; case 'message_removed' : chatAI.removeMessage( data.data.id ); break; case 'transcribe_started' : break; case 'transcribe_failed' : break; case 'transcribe_stopped' : break; case 'synthesis_startet' : break; case 'synthesis_stopped' : break; case 'run_started' : break; case 'run_in_progress' : break; case 'run_requires_action' : break; case 'run_cancelled' : break; case 'run_completed' : break; } chatAI.elements.statusText.innerHTML = chatAI.replaceEmoji( data.message || '' ); }, draw : () => { chatAI.elements.bubble = document.createElement('div'); chatAI.elements.wrapper = document.createElement('div'); chatAI.elements.header = document.createElement('div'); chatAI.elements.title = document.createElement('div'); chatAI.elements.btnClose = document.createElement('button'); chatAI.elements.btnClear = document.createElement('button'); chatAI.elements.btnReset = document.createElement('button'); chatAI.elements.output = document.createElement('div'); chatAI.elements.status = document.createElement('div'); chatAI.elements.statusDot = document.createElement('div'); chatAI.elements.statusText = document.createElement('div'); chatAI.elements.inputArea = document.createElement('div'); chatAI.elements.input = document.createElement('textarea'); chatAI.elements.submit = document.createElement('button'); // Klassen chatAI.elements.bubble.classList.add('chatai__bubble'); chatAI.elements.wrapper.classList.add('chatai__wrapper', 'hidden'); chatAI.elements.header.classList.add('chatai__header'); chatAI.elements.title.classList.add('chatai__title'); chatAI.elements.btnClose.classList.add('chatai__button', 'chatai__button--close'); chatAI.elements.btnClear.classList.add('chatai__button', 'chatai__button--clear'); chatAI.elements.btnReset.classList.add('chatai__button', 'chatai__button--reset'); chatAI.elements.output.classList.add('chatai__output'); chatAI.elements.status.classList.add('chatai__status'); chatAI.elements.statusDot.classList.add('chatai__status__dot'); chatAI.elements.statusText.classList.add('chatai__status__text'); chatAI.elements.inputArea.classList.add('chatai__inputarea'); chatAI.elements.input.classList.add('chatai__input'); chatAI.elements.submit.classList.add('chatai__submit'); if( !chatAI.websocket.connected ) { chatAI.elements.bubble.classList.add('hidden'); } // Styles chatAI.elements.wrapper.style.minWidth = chatAI_minWidth; chatAI.elements.wrapper.style.minHeight = chatAI_minHeight; chatAI.elements.wrapper.style.maxWidth = chatAI_maxWidth; chatAI.elements.wrapper.style.maxHeight = chatAI_maxHeight; // Icons chatAI.elements.bubble.innerHTML = chatAI_icons.chatFill; chatAI.elements.btnClose.innerHTML = chatAI_icons.xLg; chatAI.elements.btnClear.innerHTML = chatAI_icons.trash; chatAI.elements.btnReset.innerHTML = chatAI_icons.arrowClockwise; // Texte / Labels / title chatAI.elements.submit.textContent = 'Senden'; chatAI.elements.title.textContent = chatAI_NAME || 'Chat'; chatAI.elements.btnClose.title = 'Schließen'; chatAI.elements.btnClear.title = 'Verlauf löschen'; chatAI.elements.btnReset.title = 'Chat zurücksetzen'; // Struktur aufbauen chatAI.elements.status.append( chatAI.elements.statusText ); chatAI.elements.header.append( chatAI.elements.statusDot, chatAI.elements.title, chatAI.elements.btnClear, chatAI.elements.btnReset, chatAI.elements.btnClose ); chatAI.elements.inputArea.append( chatAI.elements.input, chatAI.elements.submit ); chatAI.elements.wrapper.append( chatAI.elements.header, chatAI.elements.output, chatAI.elements.status, chatAI.elements.inputArea ); // Events chatAI.elements.bubble.addEventListener('click', () => { chatAI.showChat(); chatAI.drawStatus(); chatAI.elements.input.focus(); if( !chatAI.initialized ) { chatAI.sendMethod( 'init' ); } }); chatAI.elements.btnClose.addEventListener('click', () => { chatAI.hideChat(); }); chatAI.elements.btnClear.addEventListener( 'click', () => { chatAI.sendMethod( 'clear' ); }); chatAI.elements.btnReset.addEventListener( 'click', () => { chatAI.sendMethod( 'reset' ); }); chatAI.elements.submit.addEventListener('click', () => { const message = chatAI.elements.input.value.trim(); if( !message || !chatAI.websocket.connected ) return; // chatAI.drawMessage({ role: 'user', content: message }); chatAI.sendMessage( message ); }); chatAI.elements.input.addEventListener('keydown', function (e) { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); chatAI.elements.submit.click(); } }); chatAI.elements.input.addEventListener('input', function () { this.style.height = 'auto'; this.style.height = Math.min(this.scrollHeight, 150) + 'px'; }); chatAI.container.append( chatAI.elements.bubble, chatAI.elements.wrapper ); // Resizen const resizeHandle = document.createElement('div'); resizeHandle.className = 'chatai__resize-handle'; chatAI.elements.wrapper.appendChild(resizeHandle); let resizing = false; let startX, startY, startWidth, startHeight; resizeHandle.addEventListener('mousedown', function(e) { e.preventDefault(); resizing = true; startX = e.clientX; startY = e.clientY; startWidth = parseInt(document.defaultView.getComputedStyle(chatAI.elements.wrapper).width, 10); startHeight = parseInt(document.defaultView.getComputedStyle(chatAI.elements.wrapper).height, 10); document.documentElement.addEventListener('mousemove', doResize, false); document.documentElement.addEventListener('mouseup', stopResize, false); }); function doResize(e) { if (!resizing) return; const newWidth = Math.max( parseInt( chatAI_minWidth ), startWidth - (e.clientX - startX)); const newHeight = Math.max(parseInt( chatAI_minHeight ), startHeight - (e.clientY - startY)); chatAI.elements.wrapper.style.width = newWidth + 'px'; chatAI.elements.wrapper.style.height = newHeight + 'px'; } function stopResize() { resizing = false; document.documentElement.removeEventListener('mousemove', doResize, false); document.documentElement.removeEventListener('mouseup', stopResize, false); } } }; document.addEventListener( 'DOMContentLoaded', chatAI.init ); /** Datei: plugins/agents/assets/js/chatai_ws.js **/ (function (root, factory) { if (typeof define === 'function' && define.amd) { define([], factory); } else if (typeof module !== 'undefined' && module.exports){ module.exports = factory(); } else { root.ReconnectingWebSocket = factory(); } })(typeof self !== 'undefined' ? self : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : this, function () { if (!('WebSocket' in window)) { return; } function ReconnectingWebSocket(url, protocols, options) { // Default settings var settings = { /** Whether this instance should log debug messages. */ debug: false, /** Whether or not the websocket should attempt to connect immediately upon instantiation. */ automaticOpen: true, /** The number of milliseconds to delay before attempting to reconnect. */ reconnectInterval: 1000, /** The maximum number of milliseconds to delay a reconnection attempt. */ maxReconnectInterval: 30000, /** The rate of increase of the reconnect delay. Allows reconnect attempts to back off when problems persist. */ reconnectDelay: 10, /** The maximum time in milliseconds to wait for a connection to succeed before closing and retrying. */ timeoutInterval: 2000, /** The maximum number of reconnection attempts to make. Unlimited if null. */ maxReconnectAttempts: null, /** The binary type, possible values 'blob' or 'arraybuffer', default 'blob'. */ binaryType: 'blob' } if (!options) { options = {}; } // Overwrite and define settings with options if they exist. for (var key in settings) { if (typeof options[key] !== 'undefined') { this[key] = options[key]; } else { this[key] = settings[key]; } } // These should be treated as read-only properties /** The URL as resolved by the constructor. This is always an absolute URL. Read only. */ this.url = url; /** The number of attempted reconnects since starting, or the last successful connection. Read only. */ this.reconnectAttempts = 0; /** * The current state of the connection. * Can be one of: WebSocket.CONNECTING, WebSocket.OPEN, WebSocket.CLOSING, WebSocket.CLOSED * Read only. */ this.readyState = WebSocket.CONNECTING; /** * A string indicating the name of the sub-protocol the server selected; this will be one of * the strings specified in the protocols parameter when creating the WebSocket object. * Read only. */ this.protocol = null; // Private state variables var self = this; var ws; var forcedClose = false; var timedOut = false; var eventTarget = document.createElement('div'); // Wire up "on*" properties as event handlers eventTarget.addEventListener('open', function(event) { self.onopen(event); }); eventTarget.addEventListener('close', function(event) { self.onclose(event); }); eventTarget.addEventListener('connecting', function(event) { self.onconnecting(event); }); eventTarget.addEventListener('message', function(event) { self.onmessage(event); }); eventTarget.addEventListener('error', function(event) { self.onerror(event); }); // Expose the API required by EventTarget this.addEventListener = eventTarget.addEventListener.bind(eventTarget); this.removeEventListener = eventTarget.removeEventListener.bind(eventTarget); this.dispatchEvent = eventTarget.dispatchEvent.bind(eventTarget); /** * This function generates an event that is compatible with standard * compliant browsers and IE9 - IE11 * * This will prevent the error: * Object doesn't support this action * * http://stackoverflow.com/questions/19345392/why-arent-my-parameters-getting-passed-through-to-a-dispatched-event/19345563#19345563 * @param s String The name that the event should use * @param args Object an optional object that the event will use */ function generateEvent(s, args) { var evt = document.createEvent("CustomEvent"); evt.initCustomEvent(s, false, false, args); return evt; }; this.open = function (reconnectAttempt) { ws = new WebSocket(self.url, protocols || []); ws.binaryType = this.binaryType; if (reconnectAttempt) { if (this.maxReconnectAttempts && this.reconnectAttempts > this.maxReconnectAttempts) { return; } } else { eventTarget.dispatchEvent(generateEvent('connecting')); this.reconnectAttempts = 0; } if (self.debug || ReconnectingWebSocket.debugAll) { console.debug('ReconnectingWebSocket', 'attempt-connect', self.url); } var localWs = ws; var timeout = setTimeout(function() { if (self.debug || ReconnectingWebSocket.debugAll) { console.debug('ReconnectingWebSocket', 'connection-timeout', self.url); } timedOut = true; localWs.close(); timedOut = false; }, self.timeoutInterval); ws.onopen = function(event) { clearTimeout(timeout); if (self.debug || ReconnectingWebSocket.debugAll) { console.debug('ReconnectingWebSocket', 'onopen', self.url); } self.protocol = ws.protocol; self.readyState = WebSocket.OPEN; self.reconnectAttempts = 0; var e = generateEvent('open'); e.isReconnect = reconnectAttempt; reconnectAttempt = false; eventTarget.dispatchEvent(e); }; ws.onclose = function(event) { clearTimeout(timeout); ws = null; if (forcedClose) { self.readyState = WebSocket.CLOSED; eventTarget.dispatchEvent(generateEvent('close')); } else { self.readyState = WebSocket.CONNECTING; var e = generateEvent('connecting'); e.code = event.code; e.reason = event.reason; e.wasClean = event.wasClean; eventTarget.dispatchEvent(e); if (!reconnectAttempt && !timedOut) { if (self.debug || ReconnectingWebSocket.debugAll) { console.debug('ReconnectingWebSocket', 'onclose', self.url); } eventTarget.dispatchEvent(generateEvent('close')); } var timeout = self.reconnectInterval * Math.pow(self.reconnectDelay, self.reconnectAttempts); setTimeout(function() { self.reconnectAttempts++; self.open(true); }, timeout > self.maxReconnectInterval ? self.maxReconnectInterval : timeout); } }; ws.onmessage = function(event) { if (self.debug || ReconnectingWebSocket.debugAll) { console.debug('ReconnectingWebSocket', 'onmessage', self.url, event.data); } var e = generateEvent('message'); e.data = event.data; eventTarget.dispatchEvent(e); }; ws.onerror = function(event) { if (self.debug || ReconnectingWebSocket.debugAll) { console.debug('ReconnectingWebSocket', 'onerror', self.url, event); } eventTarget.dispatchEvent(generateEvent('error')); }; } // Whether or not to create a websocket upon instantiation if (this.automaticOpen == true) { this.open(false); } /** * Transmits data to the server over the WebSocket connection. * * @param data a text string, ArrayBuffer or Blob to send to the server. */ this.send = function(data) { if (ws) { if (self.debug || ReconnectingWebSocket.debugAll) { console.debug('ReconnectingWebSocket', 'send', self.url, data); } return ws.send(JSON.stringify( data )); } else { throw 'INVALID_STATE_ERR : Pausing to reconnect websocket'; } }; /** * Closes the WebSocket connection or connection attempt, if any. * If the connection is already CLOSED, this method does nothing. */ this.close = function(code, reason) { // Default CLOSE_NORMAL code if (typeof code == 'undefined') { code = 1000; } forcedClose = true; if (ws) { ws.close(code, reason); } }; /** * Additional public API method to refresh the connection if still open (close, re-open). * For example, if the app suspects bad data / missed heart beats, it can try to refresh. */ this.refresh = function() { if (ws) { ws.close(); } }; } /** * An event listener to be called when the WebSocket connection's readyState changes to OPEN; * this indicates that the connection is ready to send and receive data. */ ReconnectingWebSocket.prototype.onopen = function(event) {}; /** An event listener to be called when the WebSocket connection's readyState changes to CLOSED. */ ReconnectingWebSocket.prototype.onclose = function(event) {}; /** An event listener to be called when a connection begins being attempted. */ ReconnectingWebSocket.prototype.onconnecting = function(event) {}; /** An event listener to be called when a message is received from the server. */ ReconnectingWebSocket.prototype.onmessage = function(event) {}; /** An event listener to be called when an error occurs. */ ReconnectingWebSocket.prototype.onerror = function(event) {}; /** * Whether all instances of ReconnectingWebSocket should log debug messages. * Setting this to true is the equivalent of setting all instances of ReconnectingWebSocket.debug to true. */ ReconnectingWebSocket.debugAll = false; ReconnectingWebSocket.CONNECTING = WebSocket.CONNECTING; ReconnectingWebSocket.OPEN = WebSocket.OPEN; ReconnectingWebSocket.CLOSING = WebSocket.CLOSING; ReconnectingWebSocket.CLOSED = WebSocket.CLOSED; return ReconnectingWebSocket; }); chatAI.websocket = new ReconnectingWebSocket( `${chatAI_WS_URL}:${chatAI_WS_Port}` ); chatAI.websocket.connected = false; chatAI.websocket.status = false; chatAI.websocket.onopen = event => { if( typeof chatAI_WS_ID !== 'undefined' && chatAI_WS_ID ) { chatAI.websocket.send({ command : 'login', id : chatAI_WS_ID, }); } if( typeof chatAI_WS_Token !== 'undefined' && chatAI_WS_Token ) { chatAI.websocket.send({ command : 'login', token : chatAI_WS_Token, }); } }; chatAI.websocket.onclose = event => { chatAI.websocket.connected = false; event.data = JSON.stringify({ command : 'status', status : 'disconnected', message : '🔴 Verbindung wurde geschlossen.', }); chatAI.websocket.onmessage( event ); }; chatAI.websocket.onerror = event => {}; chatAI.websocket.onmessage = event => { let data = JSON.parse( event.data ); console.log( 'WebSocket: onMessage', data ); if( data.command === 'error' ) { console.log( 'WebSocketError', data ); return; } if( data.command === 'status' ) { chatAI.websocket.status = data; if( data.status === 'connected' ) chatAI.websocket.connected = true; if( data.status === 'disconnected' ) chatAI.websocket.connected = false; chatAI.drawStatus(); return; } if( data.command === 'message' ) { chatAI.readResponse( data ); return; } if( data.command === 'messages' ) { chatAI.readResponse( data.messages ); return; } // console.log( 'Unknown WebSocket Message', data ); }; /** Datei: plugins/agents/assets/js/marked.min.js **/ /** * marked v15.0.8 - a markdown parser * Copyright (c) 2011-2025, Christopher Jeffrey. (MIT Licensed) * https://github.com/markedjs/marked */ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).marked={})}(this,(function(e){"use strict";function t(){return{async:!1,breaks:!1,extensions:null,gfm:!0,hooks:null,pedantic:!1,renderer:null,silent:!1,tokenizer:null,walkTokens:null}}function n(t){e.defaults=t}e.defaults={async:!1,breaks:!1,extensions:null,gfm:!0,hooks:null,pedantic:!1,renderer:null,silent:!1,tokenizer:null,walkTokens:null};const s={exec:()=>null};function r(e,t=""){let n="string"==typeof e?e:e.source;const s={replace:(e,t)=>{let r="string"==typeof t?t:t.source;return r=r.replace(i.caret,"$1"),n=n.replace(e,r),s},getRegex:()=>new RegExp(n,t)};return s}const i={codeRemoveIndent:/^(?: {1,4}| {0,3}\t)/gm,outputLinkReplace:/\\([\[\]])/g,indentCodeCompensation:/^(\s+)(?:```)/,beginningSpace:/^\s+/,endingHash:/#$/,startingSpaceChar:/^ /,endingSpaceChar:/ $/,nonSpaceChar:/[^ ]/,newLineCharGlobal:/\n/g,tabCharGlobal:/\t/g,multipleSpaceGlobal:/\s+/g,blankLine:/^[ \t]*$/,doubleBlankLine:/\n[ \t]*\n[ \t]*$/,blockquoteStart:/^ {0,3}>/,blockquoteSetextReplace:/\n {0,3}((?:=+|-+) *)(?=\n|$)/g,blockquoteSetextReplace2:/^ {0,3}>[ \t]?/gm,listReplaceTabs:/^\t+/,listReplaceNesting:/^ {1,4}(?=( {4})*[^ ])/g,listIsTask:/^\[[ xX]\] /,listReplaceTask:/^\[[ xX]\] +/,anyLine:/\n.*\n/,hrefBrackets:/^<(.*)>$/,tableDelimiter:/[:|]/,tableAlignChars:/^\||\| *$/g,tableRowBlankLine:/\n[ \t]*$/,tableAlignRight:/^ *-+: *$/,tableAlignCenter:/^ *:-+: *$/,tableAlignLeft:/^ *:-+ *$/,startATag:/^/i,startPreScriptTag:/^<(pre|code|kbd|script)(\s|>)/i,endPreScriptTag:/^<\/(pre|code|kbd|script)(\s|>)/i,startAngleBracket:/^$/,pedanticHrefTitle:/^([^'"]*[^\s])\s+(['"])(.*)\2/,unicodeAlphaNumeric:/[\p{L}\p{N}]/u,escapeTest:/[&<>"']/,escapeReplace:/[&<>"']/g,escapeTestNoEncode:/[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/,escapeReplaceNoEncode:/[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/g,unescapeTest:/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/gi,caret:/(^|[^\[])\^/g,percentDecode:/%25/g,findPipe:/\|/g,splitPipe:/ \|/,slashPipe:/\\\|/g,carriageReturn:/\r\n|\r/g,spaceLine:/^ +$/gm,notSpaceStart:/^\S*/,endingNewline:/\n$/,listItemRegex:e=>new RegExp(`^( {0,3}${e})((?:[\t ][^\\n]*)?(?:\\n|$))`),nextBulletRegex:e=>new RegExp(`^ {0,${Math.min(3,e-1)}}(?:[*+-]|\\d{1,9}[.)])((?:[ \t][^\\n]*)?(?:\\n|$))`),hrRegex:e=>new RegExp(`^ {0,${Math.min(3,e-1)}}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)`),fencesBeginRegex:e=>new RegExp(`^ {0,${Math.min(3,e-1)}}(?:\`\`\`|~~~)`),headingBeginRegex:e=>new RegExp(`^ {0,${Math.min(3,e-1)}}#`),htmlBeginRegex:e=>new RegExp(`^ {0,${Math.min(3,e-1)}}<(?:[a-z].*>|!--)`,"i")},l=/^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/,o=/(?:[*+-]|\d{1,9}[.)])/,a=/^(?!bull |blockCode|fences|blockquote|heading|html|table)((?:.|\n(?!\s*?\n|bull |blockCode|fences|blockquote|heading|html|table))+?)\n {0,3}(=+|-+) *(?:\n+|$)/,c=r(a).replace(/bull/g,o).replace(/blockCode/g,/(?: {4}| {0,3}\t)/).replace(/fences/g,/ {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g,/ {0,3}>/).replace(/heading/g,/ {0,3}#{1,6}/).replace(/html/g,/ {0,3}<[^\n>]+>\n/).replace(/\|table/g,"").getRegex(),h=r(a).replace(/bull/g,o).replace(/blockCode/g,/(?: {4}| {0,3}\t)/).replace(/fences/g,/ {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g,/ {0,3}>/).replace(/heading/g,/ {0,3}#{1,6}/).replace(/html/g,/ {0,3}<[^\n>]+>\n/).replace(/table/g,/ {0,3}\|?(?:[:\- ]*\|)+[\:\- ]*\n/).getRegex(),p=/^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table| +\n)[^\n]+)*)/,u=/(?!\s*\])(?:\\.|[^\[\]\\])+/,g=r(/^ {0,3}\[(label)\]: *(?:\n[ \t]*)?([^<\s][^\s]*|<.*?>)(?:(?: +(?:\n[ \t]*)?| *\n[ \t]*)(title))? *(?:\n+|$)/).replace("label",u).replace("title",/(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/).getRegex(),k=r(/^( {0,3}bull)([ \t][^\n]+?)?(?:\n|$)/).replace(/bull/g,o).getRegex(),d="address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|search|section|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul",f=/|$))/,x=r("^ {0,3}(?:<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:[^\\n]*\\n+|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?(?:\\?>\\n*|$)|\\n*|$)|\\n*|$)|)[\\s\\S]*?(?:(?:\\n[ \t]*)+\\n|$)|<(?!script|pre|style|textarea)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ \t]*)+\\n|$)|(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ \t]*)+\\n|$))","i").replace("comment",f).replace("tag",d).replace("attribute",/ +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex(),b=r(p).replace("hr",l).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("|lheading","").replace("|table","").replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html",")|<(?:script|pre|style|textarea|!--)").replace("tag",d).getRegex(),w={blockquote:r(/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/).replace("paragraph",b).getRegex(),code:/^((?: {4}| {0,3}\t)[^\n]+(?:\n(?:[ \t]*(?:\n|$))*)?)+/,def:g,fences:/^ {0,3}(`{3,}(?=[^`\n]*(?:\n|$))|~{3,})([^\n]*)(?:\n|$)(?:|([\s\S]*?)(?:\n|$))(?: {0,3}\1[~`]* *(?=\n|$)|$)/,heading:/^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/,hr:l,html:x,lheading:c,list:k,newline:/^(?:[ \t]*(?:\n|$))+/,paragraph:b,table:s,text:/^[^\n]+/},m=r("^ *([^\\n ].*)\\n {0,3}((?:\\| *)?:?-+:? *(?:\\| *:?-+:? *)*(?:\\| *)?)(?:\\n((?:(?! *\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)").replace("hr",l).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("blockquote"," {0,3}>").replace("code","(?: {4}| {0,3}\t)[^\\n]").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html",")|<(?:script|pre|style|textarea|!--)").replace("tag",d).getRegex(),y={...w,lheading:h,table:m,paragraph:r(p).replace("hr",l).replace("heading"," {0,3}#{1,6}(?:\\s|$)").replace("|lheading","").replace("table",m).replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html",")|<(?:script|pre|style|textarea|!--)").replace("tag",d).getRegex()},$={...w,html:r("^ *(?:comment *(?:\\n|\\s*$)|<(tag)[\\s\\S]+? *(?:\\n{2,}|\\s*$)|\\s]*)*?/?> *(?:\\n{2,}|\\s*$))").replace("comment",f).replace(/tag/g,"(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b").getRegex(),def:/^ *\[([^\]]+)\]: *]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/,heading:/^(#{1,6})(.*)(?:\n+|$)/,fences:s,lheading:/^(.+?)\n {0,3}(=+|-+) *(?:\n+|$)/,paragraph:r(p).replace("hr",l).replace("heading"," *#{1,6} *[^\n]").replace("lheading",c).replace("|table","").replace("blockquote"," {0,3}>").replace("|fences","").replace("|list","").replace("|html","").replace("|tag","").getRegex()},R=/^( {2,}|\\)\n(?!\s*$)/,S=/[\p{P}\p{S}]/u,T=/[\s\p{P}\p{S}]/u,z=/[^\s\p{P}\p{S}]/u,A=r(/^((?![*_])punctSpace)/,"u").replace(/punctSpace/g,T).getRegex(),_=/(?!~)[\p{P}\p{S}]/u,P=/^(?:\*+(?:((?!\*)punct)|[^\s*]))|^_+(?:((?!_)punct)|([^\s_]))/,I=r(P,"u").replace(/punct/g,S).getRegex(),L=r(P,"u").replace(/punct/g,_).getRegex(),B="^[^_*]*?__[^_*]*?\\*[^_*]*?(?=__)|[^*]+(?=[^*])|(?!\\*)punct(\\*+)(?=[\\s]|$)|notPunctSpace(\\*+)(?!\\*)(?=punctSpace|$)|(?!\\*)punctSpace(\\*+)(?=notPunctSpace)|[\\s](\\*+)(?!\\*)(?=punct)|(?!\\*)punct(\\*+)(?!\\*)(?=punct)|notPunctSpace(\\*+)(?=notPunctSpace)",C=r(B,"gu").replace(/notPunctSpace/g,z).replace(/punctSpace/g,T).replace(/punct/g,S).getRegex(),q=r(B,"gu").replace(/notPunctSpace/g,/(?:[^\s\p{P}\p{S}]|~)/u).replace(/punctSpace/g,/(?!~)[\s\p{P}\p{S}]/u).replace(/punct/g,_).getRegex(),E=r("^[^_*]*?\\*\\*[^_*]*?_[^_*]*?(?=\\*\\*)|[^_]+(?=[^_])|(?!_)punct(_+)(?=[\\s]|$)|notPunctSpace(_+)(?!_)(?=punctSpace|$)|(?!_)punctSpace(_+)(?=notPunctSpace)|[\\s](_+)(?!_)(?=punct)|(?!_)punct(_+)(?!_)(?=punct)","gu").replace(/notPunctSpace/g,z).replace(/punctSpace/g,T).replace(/punct/g,S).getRegex(),Z=r(/\\(punct)/,"gu").replace(/punct/g,S).getRegex(),v=r(/^<(scheme:[^\s\x00-\x1f<>]*|email)>/).replace("scheme",/[a-zA-Z][a-zA-Z0-9+.-]{1,31}/).replace("email",/[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/).getRegex(),D=r(f).replace("(?:--\x3e|$)","--\x3e").getRegex(),M=r("^comment|^|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>|^<\\?[\\s\\S]*?\\?>|^|^").replace("comment",D).replace("attribute",/\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/).getRegex(),O=/(?:\[(?:\\.|[^\[\]\\])*\]|\\.|`[^`]*`|[^\[\]\\`])*?/,Q=r(/^!?\[(label)\]\(\s*(href)(?:\s+(title))?\s*\)/).replace("label",O).replace("href",/<(?:\\.|[^\n<>\\])+>|[^\s\x00-\x1f]*/).replace("title",/"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/).getRegex(),j=r(/^!?\[(label)\]\[(ref)\]/).replace("label",O).replace("ref",u).getRegex(),N=r(/^!?\[(ref)\](?:\[\])?/).replace("ref",u).getRegex(),G={_backpedal:s,anyPunctuation:Z,autolink:v,blockSkip:/\[[^[\]]*?\]\((?:\\.|[^\\\(\)]|\((?:\\.|[^\\\(\)])*\))*\)|`[^`]*?`|<[^<>]*?>/g,br:R,code:/^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/,del:s,emStrongLDelim:I,emStrongRDelimAst:C,emStrongRDelimUnd:E,escape:/^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/,link:Q,nolink:N,punctuation:A,reflink:j,reflinkSearch:r("reflink|nolink(?!\\()","g").replace("reflink",j).replace("nolink",N).getRegex(),tag:M,text:/^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\":">",'"':""","'":"'"},V=e=>K[e];function W(e,t){if(t){if(i.escapeTest.test(e))return e.replace(i.escapeReplace,V)}else if(i.escapeTestNoEncode.test(e))return e.replace(i.escapeReplaceNoEncode,V);return e}function Y(e){try{e=encodeURI(e).replace(i.percentDecode,"%")}catch{return null}return e}function ee(e,t){const n=e.replace(i.findPipe,((e,t,n)=>{let s=!1,r=t;for(;--r>=0&&"\\"===n[r];)s=!s;return s?"|":" |"})).split(i.splitPipe);let s=0;if(n[0].trim()||n.shift(),n.length>0&&!n.at(-1)?.trim()&&n.pop(),t)if(n.length>t)n.splice(t);else for(;n.length0)return{type:"space",raw:t[0]}}code(e){const t=this.rules.block.code.exec(e);if(t){const e=t[0].replace(this.rules.other.codeRemoveIndent,"");return{type:"code",raw:t[0],codeBlockStyle:"indented",text:this.options.pedantic?e:te(e,"\n")}}}fences(e){const t=this.rules.block.fences.exec(e);if(t){const e=t[0],n=function(e,t,n){const s=e.match(n.other.indentCodeCompensation);if(null===s)return t;const r=s[1];return t.split("\n").map((e=>{const t=e.match(n.other.beginningSpace);if(null===t)return e;const[s]=t;return s.length>=r.length?e.slice(r.length):e})).join("\n")}(e,t[3]||"",this.rules);return{type:"code",raw:e,lang:t[2]?t[2].trim().replace(this.rules.inline.anyPunctuation,"$1"):t[2],text:n}}}heading(e){const t=this.rules.block.heading.exec(e);if(t){let e=t[2].trim();if(this.rules.other.endingHash.test(e)){const t=te(e,"#");this.options.pedantic?e=t.trim():t&&!this.rules.other.endingSpaceChar.test(t)||(e=t.trim())}return{type:"heading",raw:t[0],depth:t[1].length,text:e,tokens:this.lexer.inline(e)}}}hr(e){const t=this.rules.block.hr.exec(e);if(t)return{type:"hr",raw:te(t[0],"\n")}}blockquote(e){const t=this.rules.block.blockquote.exec(e);if(t){let e=te(t[0],"\n").split("\n"),n="",s="";const r=[];for(;e.length>0;){let t=!1;const i=[];let l;for(l=0;l1,r={type:"list",raw:"",ordered:s,start:s?+n.slice(0,-1):"",loose:!1,items:[]};n=s?`\\d{1,9}\\${n.slice(-1)}`:`\\${n}`,this.options.pedantic&&(n=s?n:"[*+-]");const i=this.rules.other.listItemRegex(n);let l=!1;for(;e;){let n=!1,s="",o="";if(!(t=i.exec(e)))break;if(this.rules.block.hr.test(e))break;s=t[0],e=e.substring(s.length);let a=t[2].split("\n",1)[0].replace(this.rules.other.listReplaceTabs,(e=>" ".repeat(3*e.length))),c=e.split("\n",1)[0],h=!a.trim(),p=0;if(this.options.pedantic?(p=2,o=a.trimStart()):h?p=t[1].length+1:(p=t[2].search(this.rules.other.nonSpaceChar),p=p>4?1:p,o=a.slice(p),p+=t[1].length),h&&this.rules.other.blankLine.test(c)&&(s+=c+"\n",e=e.substring(c.length+1),n=!0),!n){const t=this.rules.other.nextBulletRegex(p),n=this.rules.other.hrRegex(p),r=this.rules.other.fencesBeginRegex(p),i=this.rules.other.headingBeginRegex(p),l=this.rules.other.htmlBeginRegex(p);for(;e;){const u=e.split("\n",1)[0];let g;if(c=u,this.options.pedantic?(c=c.replace(this.rules.other.listReplaceNesting," "),g=c):g=c.replace(this.rules.other.tabCharGlobal," "),r.test(c))break;if(i.test(c))break;if(l.test(c))break;if(t.test(c))break;if(n.test(c))break;if(g.search(this.rules.other.nonSpaceChar)>=p||!c.trim())o+="\n"+g.slice(p);else{if(h)break;if(a.replace(this.rules.other.tabCharGlobal," ").search(this.rules.other.nonSpaceChar)>=4)break;if(r.test(a))break;if(i.test(a))break;if(n.test(a))break;o+="\n"+c}h||c.trim()||(h=!0),s+=u+"\n",e=e.substring(u.length+1),a=g.slice(p)}}r.loose||(l?r.loose=!0:this.rules.other.doubleBlankLine.test(s)&&(l=!0));let u,g=null;this.options.gfm&&(g=this.rules.other.listIsTask.exec(o),g&&(u="[ ] "!==g[0],o=o.replace(this.rules.other.listReplaceTask,""))),r.items.push({type:"list_item",raw:s,task:!!g,checked:u,loose:!1,text:o,tokens:[]}),r.raw+=s}const o=r.items.at(-1);if(!o)return;o.raw=o.raw.trimEnd(),o.text=o.text.trimEnd(),r.raw=r.raw.trimEnd();for(let e=0;e"space"===e.type)),n=t.length>0&&t.some((e=>this.rules.other.anyLine.test(e.raw)));r.loose=n}if(r.loose)for(let e=0;e({text:e,tokens:this.lexer.inline(e),header:!1,align:i.align[t]}))));return i}}lheading(e){const t=this.rules.block.lheading.exec(e);if(t)return{type:"heading",raw:t[0],depth:"="===t[2].charAt(0)?1:2,text:t[1],tokens:this.lexer.inline(t[1])}}paragraph(e){const t=this.rules.block.paragraph.exec(e);if(t){const e="\n"===t[1].charAt(t[1].length-1)?t[1].slice(0,-1):t[1];return{type:"paragraph",raw:t[0],text:e,tokens:this.lexer.inline(e)}}}text(e){const t=this.rules.block.text.exec(e);if(t)return{type:"text",raw:t[0],text:t[0],tokens:this.lexer.inline(t[0])}}escape(e){const t=this.rules.inline.escape.exec(e);if(t)return{type:"escape",raw:t[0],text:t[1]}}tag(e){const t=this.rules.inline.tag.exec(e);if(t)return!this.lexer.state.inLink&&this.rules.other.startATag.test(t[0])?this.lexer.state.inLink=!0:this.lexer.state.inLink&&this.rules.other.endATag.test(t[0])&&(this.lexer.state.inLink=!1),!this.lexer.state.inRawBlock&&this.rules.other.startPreScriptTag.test(t[0])?this.lexer.state.inRawBlock=!0:this.lexer.state.inRawBlock&&this.rules.other.endPreScriptTag.test(t[0])&&(this.lexer.state.inRawBlock=!1),{type:"html",raw:t[0],inLink:this.lexer.state.inLink,inRawBlock:this.lexer.state.inRawBlock,block:!1,text:t[0]}}link(e){const t=this.rules.inline.link.exec(e);if(t){const e=t[2].trim();if(!this.options.pedantic&&this.rules.other.startAngleBracket.test(e)){if(!this.rules.other.endAngleBracket.test(e))return;const t=te(e.slice(0,-1),"\\");if((e.length-t.length)%2==0)return}else{const e=function(e,t){if(-1===e.indexOf(t[1]))return-1;let n=0;for(let s=0;s-1){const n=(0===t[0].indexOf("!")?5:4)+t[1].length+e;t[2]=t[2].substring(0,e),t[0]=t[0].substring(0,n).trim(),t[3]=""}}let n=t[2],s="";if(this.options.pedantic){const e=this.rules.other.pedanticHrefTitle.exec(n);e&&(n=e[1],s=e[3])}else s=t[3]?t[3].slice(1,-1):"";return n=n.trim(),this.rules.other.startAngleBracket.test(n)&&(n=this.options.pedantic&&!this.rules.other.endAngleBracket.test(e)?n.slice(1):n.slice(1,-1)),ne(t,{href:n?n.replace(this.rules.inline.anyPunctuation,"$1"):n,title:s?s.replace(this.rules.inline.anyPunctuation,"$1"):s},t[0],this.lexer,this.rules)}}reflink(e,t){let n;if((n=this.rules.inline.reflink.exec(e))||(n=this.rules.inline.nolink.exec(e))){const e=t[(n[2]||n[1]).replace(this.rules.other.multipleSpaceGlobal," ").toLowerCase()];if(!e){const e=n[0].charAt(0);return{type:"text",raw:e,text:e}}return ne(n,e,n[0],this.lexer,this.rules)}}emStrong(e,t,n=""){let s=this.rules.inline.emStrongLDelim.exec(e);if(!s)return;if(s[3]&&n.match(this.rules.other.unicodeAlphaNumeric))return;if(!(s[1]||s[2]||"")||!n||this.rules.inline.punctuation.exec(n)){const n=[...s[0]].length-1;let r,i,l=n,o=0;const a="*"===s[0][0]?this.rules.inline.emStrongRDelimAst:this.rules.inline.emStrongRDelimUnd;for(a.lastIndex=0,t=t.slice(-1*e.length+n);null!=(s=a.exec(t));){if(r=s[1]||s[2]||s[3]||s[4]||s[5]||s[6],!r)continue;if(i=[...r].length,s[3]||s[4]){l+=i;continue}if((s[5]||s[6])&&n%3&&!((n+i)%3)){o+=i;continue}if(l-=i,l>0)continue;i=Math.min(i,i+l+o);const t=[...s[0]][0].length,a=e.slice(0,n+s.index+t+i);if(Math.min(n,i)%2){const e=a.slice(1,-1);return{type:"em",raw:a,text:e,tokens:this.lexer.inlineTokens(e)}}const c=a.slice(2,-2);return{type:"strong",raw:a,text:c,tokens:this.lexer.inlineTokens(c)}}}}codespan(e){const t=this.rules.inline.code.exec(e);if(t){let e=t[2].replace(this.rules.other.newLineCharGlobal," ");const n=this.rules.other.nonSpaceChar.test(e),s=this.rules.other.startingSpaceChar.test(e)&&this.rules.other.endingSpaceChar.test(e);return n&&s&&(e=e.substring(1,e.length-1)),{type:"codespan",raw:t[0],text:e}}}br(e){const t=this.rules.inline.br.exec(e);if(t)return{type:"br",raw:t[0]}}del(e){const t=this.rules.inline.del.exec(e);if(t)return{type:"del",raw:t[0],text:t[2],tokens:this.lexer.inlineTokens(t[2])}}autolink(e){const t=this.rules.inline.autolink.exec(e);if(t){let e,n;return"@"===t[2]?(e=t[1],n="mailto:"+e):(e=t[1],n=e),{type:"link",raw:t[0],text:e,href:n,tokens:[{type:"text",raw:e,text:e}]}}}url(e){let t;if(t=this.rules.inline.url.exec(e)){let e,n;if("@"===t[2])e=t[0],n="mailto:"+e;else{let s;do{s=t[0],t[0]=this.rules.inline._backpedal.exec(t[0])?.[0]??""}while(s!==t[0]);e=t[0],n="www."===t[1]?"http://"+t[0]:t[0]}return{type:"link",raw:t[0],text:e,href:n,tokens:[{type:"text",raw:e,text:e}]}}}inlineText(e){const t=this.rules.inline.text.exec(e);if(t){const e=this.lexer.state.inRawBlock;return{type:"text",raw:t[0],text:t[0],escaped:e}}}}class re{tokens;options;state;tokenizer;inlineQueue;constructor(t){this.tokens=[],this.tokens.links=Object.create(null),this.options=t||e.defaults,this.options.tokenizer=this.options.tokenizer||new se,this.tokenizer=this.options.tokenizer,this.tokenizer.options=this.options,this.tokenizer.lexer=this,this.inlineQueue=[],this.state={inLink:!1,inRawBlock:!1,top:!0};const n={other:i,block:U.normal,inline:J.normal};this.options.pedantic?(n.block=U.pedantic,n.inline=J.pedantic):this.options.gfm&&(n.block=U.gfm,this.options.breaks?n.inline=J.breaks:n.inline=J.gfm),this.tokenizer.rules=n}static get rules(){return{block:U,inline:J}}static lex(e,t){return new re(t).lex(e)}static lexInline(e,t){return new re(t).inlineTokens(e)}lex(e){e=e.replace(i.carriageReturn,"\n"),this.blockTokens(e,this.tokens);for(let e=0;e!!(s=n.call({lexer:this},e,t))&&(e=e.substring(s.raw.length),t.push(s),!0))))continue;if(s=this.tokenizer.space(e)){e=e.substring(s.raw.length);const n=t.at(-1);1===s.raw.length&&void 0!==n?n.raw+="\n":t.push(s);continue}if(s=this.tokenizer.code(e)){e=e.substring(s.raw.length);const n=t.at(-1);"paragraph"===n?.type||"text"===n?.type?(n.raw+="\n"+s.raw,n.text+="\n"+s.text,this.inlineQueue.at(-1).src=n.text):t.push(s);continue}if(s=this.tokenizer.fences(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.heading(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.hr(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.blockquote(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.list(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.html(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.def(e)){e=e.substring(s.raw.length);const n=t.at(-1);"paragraph"===n?.type||"text"===n?.type?(n.raw+="\n"+s.raw,n.text+="\n"+s.raw,this.inlineQueue.at(-1).src=n.text):this.tokens.links[s.tag]||(this.tokens.links[s.tag]={href:s.href,title:s.title});continue}if(s=this.tokenizer.table(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.lheading(e)){e=e.substring(s.raw.length),t.push(s);continue}let r=e;if(this.options.extensions?.startBlock){let t=1/0;const n=e.slice(1);let s;this.options.extensions.startBlock.forEach((e=>{s=e.call({lexer:this},n),"number"==typeof s&&s>=0&&(t=Math.min(t,s))})),t<1/0&&t>=0&&(r=e.substring(0,t+1))}if(this.state.top&&(s=this.tokenizer.paragraph(r))){const i=t.at(-1);n&&"paragraph"===i?.type?(i.raw+="\n"+s.raw,i.text+="\n"+s.text,this.inlineQueue.pop(),this.inlineQueue.at(-1).src=i.text):t.push(s),n=r.length!==e.length,e=e.substring(s.raw.length)}else if(s=this.tokenizer.text(e)){e=e.substring(s.raw.length);const n=t.at(-1);"text"===n?.type?(n.raw+="\n"+s.raw,n.text+="\n"+s.text,this.inlineQueue.pop(),this.inlineQueue.at(-1).src=n.text):t.push(s)}else if(e){const t="Infinite loop on byte: "+e.charCodeAt(0);if(this.options.silent){console.error(t);break}throw new Error(t)}}return this.state.top=!0,t}inline(e,t=[]){return this.inlineQueue.push({src:e,tokens:t}),t}inlineTokens(e,t=[]){let n=e,s=null;if(this.tokens.links){const e=Object.keys(this.tokens.links);if(e.length>0)for(;null!=(s=this.tokenizer.rules.inline.reflinkSearch.exec(n));)e.includes(s[0].slice(s[0].lastIndexOf("[")+1,-1))&&(n=n.slice(0,s.index)+"["+"a".repeat(s[0].length-2)+"]"+n.slice(this.tokenizer.rules.inline.reflinkSearch.lastIndex))}for(;null!=(s=this.tokenizer.rules.inline.anyPunctuation.exec(n));)n=n.slice(0,s.index)+"++"+n.slice(this.tokenizer.rules.inline.anyPunctuation.lastIndex);for(;null!=(s=this.tokenizer.rules.inline.blockSkip.exec(n));)n=n.slice(0,s.index)+"["+"a".repeat(s[0].length-2)+"]"+n.slice(this.tokenizer.rules.inline.blockSkip.lastIndex);let r=!1,i="";for(;e;){let s;if(r||(i=""),r=!1,this.options.extensions?.inline?.some((n=>!!(s=n.call({lexer:this},e,t))&&(e=e.substring(s.raw.length),t.push(s),!0))))continue;if(s=this.tokenizer.escape(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.tag(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.link(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.reflink(e,this.tokens.links)){e=e.substring(s.raw.length);const n=t.at(-1);"text"===s.type&&"text"===n?.type?(n.raw+=s.raw,n.text+=s.text):t.push(s);continue}if(s=this.tokenizer.emStrong(e,n,i)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.codespan(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.br(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.del(e)){e=e.substring(s.raw.length),t.push(s);continue}if(s=this.tokenizer.autolink(e)){e=e.substring(s.raw.length),t.push(s);continue}if(!this.state.inLink&&(s=this.tokenizer.url(e))){e=e.substring(s.raw.length),t.push(s);continue}let l=e;if(this.options.extensions?.startInline){let t=1/0;const n=e.slice(1);let s;this.options.extensions.startInline.forEach((e=>{s=e.call({lexer:this},n),"number"==typeof s&&s>=0&&(t=Math.min(t,s))})),t<1/0&&t>=0&&(l=e.substring(0,t+1))}if(s=this.tokenizer.inlineText(l)){e=e.substring(s.raw.length),"_"!==s.raw.slice(-1)&&(i=s.raw.slice(-1)),r=!0;const n=t.at(-1);"text"===n?.type?(n.raw+=s.raw,n.text+=s.text):t.push(s)}else if(e){const t="Infinite loop on byte: "+e.charCodeAt(0);if(this.options.silent){console.error(t);break}throw new Error(t)}}return t}}class ie{options;parser;constructor(t){this.options=t||e.defaults}space(e){return""}code({text:e,lang:t,escaped:n}){const s=(t||"").match(i.notSpaceStart)?.[0],r=e.replace(i.endingNewline,"")+"\n";return s?'
'+(n?r:W(r,!0))+"
\n":"
"+(n?r:W(r,!0))+"
\n"}blockquote({tokens:e}){return`
\n${this.parser.parse(e)}
\n`}html({text:e}){return e}heading({tokens:e,depth:t}){return`${this.parser.parseInline(e)}\n`}hr(e){return"
\n"}list(e){const t=e.ordered,n=e.start;let s="";for(let t=0;t\n"+s+"\n"}listitem(e){let t="";if(e.task){const n=this.checkbox({checked:!!e.checked});e.loose?"paragraph"===e.tokens[0]?.type?(e.tokens[0].text=n+" "+e.tokens[0].text,e.tokens[0].tokens&&e.tokens[0].tokens.length>0&&"text"===e.tokens[0].tokens[0].type&&(e.tokens[0].tokens[0].text=n+" "+W(e.tokens[0].tokens[0].text),e.tokens[0].tokens[0].escaped=!0)):e.tokens.unshift({type:"text",raw:n+" ",text:n+" ",escaped:!0}):t+=n+" "}return t+=this.parser.parse(e.tokens,!!e.loose),`
  • ${t}
  • \n`}checkbox({checked:e}){return"'}paragraph({tokens:e}){return`

    ${this.parser.parseInline(e)}

    \n`}table(e){let t="",n="";for(let t=0;t${s}`),"\n\n"+t+"\n"+s+"
    \n"}tablerow({text:e}){return`\n${e}\n`}tablecell(e){const t=this.parser.parseInline(e.tokens),n=e.header?"th":"td";return(e.align?`<${n} align="${e.align}">`:`<${n}>`)+t+`\n`}strong({tokens:e}){return`${this.parser.parseInline(e)}`}em({tokens:e}){return`${this.parser.parseInline(e)}`}codespan({text:e}){return`${W(e,!0)}`}br(e){return"
    "}del({tokens:e}){return`${this.parser.parseInline(e)}`}link({href:e,title:t,tokens:n}){const s=this.parser.parseInline(n),r=Y(e);if(null===r)return s;let i='
    ",i}image({href:e,title:t,text:n}){const s=Y(e);if(null===s)return W(n);let r=`${n}{const r=e[s].flat(1/0);n=n.concat(this.walkTokens(r,t))})):e.tokens&&(n=n.concat(this.walkTokens(e.tokens,t)))}}return n}use(...e){const t=this.defaults.extensions||{renderers:{},childTokens:{}};return e.forEach((e=>{const n={...e};if(n.async=this.defaults.async||n.async||!1,e.extensions&&(e.extensions.forEach((e=>{if(!e.name)throw new Error("extension name required");if("renderer"in e){const n=t.renderers[e.name];t.renderers[e.name]=n?function(...t){let s=e.renderer.apply(this,t);return!1===s&&(s=n.apply(this,t)),s}:e.renderer}if("tokenizer"in e){if(!e.level||"block"!==e.level&&"inline"!==e.level)throw new Error("extension level must be 'block' or 'inline'");const n=t[e.level];n?n.unshift(e.tokenizer):t[e.level]=[e.tokenizer],e.start&&("block"===e.level?t.startBlock?t.startBlock.push(e.start):t.startBlock=[e.start]:"inline"===e.level&&(t.startInline?t.startInline.push(e.start):t.startInline=[e.start]))}"childTokens"in e&&e.childTokens&&(t.childTokens[e.name]=e.childTokens)})),n.extensions=t),e.renderer){const t=this.defaults.renderer||new ie(this.defaults);for(const n in e.renderer){if(!(n in t))throw new Error(`renderer '${n}' does not exist`);if(["options","parser"].includes(n))continue;const s=n,r=e.renderer[s],i=t[s];t[s]=(...e)=>{let n=r.apply(t,e);return!1===n&&(n=i.apply(t,e)),n||""}}n.renderer=t}if(e.tokenizer){const t=this.defaults.tokenizer||new se(this.defaults);for(const n in e.tokenizer){if(!(n in t))throw new Error(`tokenizer '${n}' does not exist`);if(["options","rules","lexer"].includes(n))continue;const s=n,r=e.tokenizer[s],i=t[s];t[s]=(...e)=>{let n=r.apply(t,e);return!1===n&&(n=i.apply(t,e)),n}}n.tokenizer=t}if(e.hooks){const t=this.defaults.hooks||new ae;for(const n in e.hooks){if(!(n in t))throw new Error(`hook '${n}' does not exist`);if(["options","block"].includes(n))continue;const s=n,r=e.hooks[s],i=t[s];ae.passThroughHooks.has(n)?t[s]=e=>{if(this.defaults.async)return Promise.resolve(r.call(t,e)).then((e=>i.call(t,e)));const n=r.call(t,e);return i.call(t,n)}:t[s]=(...e)=>{let n=r.apply(t,e);return!1===n&&(n=i.apply(t,e)),n}}n.hooks=t}if(e.walkTokens){const t=this.defaults.walkTokens,s=e.walkTokens;n.walkTokens=function(e){let n=[];return n.push(s.call(this,e)),t&&(n=n.concat(t.call(this,e))),n}}this.defaults={...this.defaults,...n}})),this}setOptions(e){return this.defaults={...this.defaults,...e},this}lexer(e,t){return re.lex(e,t??this.defaults)}parser(e,t){return oe.parse(e,t??this.defaults)}parseMarkdown(e){return(t,n)=>{const s={...n},r={...this.defaults,...s},i=this.onError(!!r.silent,!!r.async);if(!0===this.defaults.async&&!1===s.async)return i(new Error("marked(): The async option was set to true by an extension. Remove async: false from the parse options object to return a Promise."));if(null==t)return i(new Error("marked(): input parameter is undefined or null"));if("string"!=typeof t)return i(new Error("marked(): input parameter is of type "+Object.prototype.toString.call(t)+", string expected"));r.hooks&&(r.hooks.options=r,r.hooks.block=e);const l=r.hooks?r.hooks.provideLexer():e?re.lex:re.lexInline,o=r.hooks?r.hooks.provideParser():e?oe.parse:oe.parseInline;if(r.async)return Promise.resolve(r.hooks?r.hooks.preprocess(t):t).then((e=>l(e,r))).then((e=>r.hooks?r.hooks.processAllTokens(e):e)).then((e=>r.walkTokens?Promise.all(this.walkTokens(e,r.walkTokens)).then((()=>e)):e)).then((e=>o(e,r))).then((e=>r.hooks?r.hooks.postprocess(e):e)).catch(i);try{r.hooks&&(t=r.hooks.preprocess(t));let e=l(t,r);r.hooks&&(e=r.hooks.processAllTokens(e)),r.walkTokens&&this.walkTokens(e,r.walkTokens);let n=o(e,r);return r.hooks&&(n=r.hooks.postprocess(n)),n}catch(e){return i(e)}}}onError(e,t){return n=>{if(n.message+="\nPlease report this to https://github.com/markedjs/marked.",e){const e="

    An error occurred:

    "+W(n.message+"",!0)+"
    ";return t?Promise.resolve(e):e}if(t)return Promise.reject(n);throw n}}}const he=new ce;function pe(e,t){return he.parse(e,t)}pe.options=pe.setOptions=function(e){return he.setOptions(e),pe.defaults=he.defaults,n(pe.defaults),pe},pe.getDefaults=t,pe.defaults=e.defaults,pe.use=function(...e){return he.use(...e),pe.defaults=he.defaults,n(pe.defaults),pe},pe.walkTokens=function(e,t){return he.walkTokens(e,t)},pe.parseInline=he.parseInline,pe.Parser=oe,pe.parser=oe.parse,pe.Renderer=ie,pe.TextRenderer=le,pe.Lexer=re,pe.lexer=re.lex,pe.Tokenizer=se,pe.Hooks=ae,pe.parse=pe;const ue=pe.options,ge=pe.setOptions,ke=pe.use,de=pe.walkTokens,fe=pe.parseInline,xe=pe,be=oe.parse,we=re.lex;e.Hooks=ae,e.Lexer=re,e.Marked=ce,e.Parser=oe,e.Renderer=ie,e.TextRenderer=le,e.Tokenizer=se,e.getDefaults=t,e.lexer=we,e.marked=pe,e.options=ue,e.parse=xe,e.parseInline=fe,e.parser=be,e.setOptions=ge,e.use=ke,e.walkTokens=de})); /** Datei: plugins/agents/templates/login.php **/


    /** Datei: plugins/agents/config.php **/ * @copyright 2025 ARUNOX * @version 1.0.0 * @since 1.0.0 File is created */ /** Datei: plugins/agents/config/schema/v1.0.0.php **/ array ( 'columns' => array ( 'id' => 'INT NOT NULL AUTO_INCREMENT', 'date_create' => 'DATETIME DEFAULT_GENERATED DEFAULT \'CURRENT_TIMESTAMP\'', 'date_edit' => 'DATETIME DEFAULT_GENERATED ON UPDATE CURRENT_TIMESTAMP DEFAULT \'CURRENT_TIMESTAMP\'', 'status' => 'TINYINT(1) DEFAULT \'0\'', 'name' => 'VARCHAR(255)', 'phone' => 'VARCHAR(30)', 'voice' => 'VARCHAR(255)', ), 'primary' => array ( 0 => 'id', ), 'indexes' => array ( ), 'engine' => 'InnoDB', 'charset' => 'utf8mb3', ), 'agents_meta' => array ( 'columns' => array ( 'id' => 'INT NOT NULL AUTO_INCREMENT', 'date_create' => 'DATETIME DEFAULT_GENERATED DEFAULT \'CURRENT_TIMESTAMP\'', 'date_edit' => 'DATETIME DEFAULT_GENERATED ON UPDATE CURRENT_TIMESTAMP DEFAULT \'CURRENT_TIMESTAMP\'', 'origin_id' => 'INT NOT NULL', 'meta_key' => 'VARCHAR(255) NOT NULL', 'meta_value' => 'TEXT', ), 'primary' => array ( 0 => 'id', ), 'indexes' => array ( 'origin_id' => array ( 0 => 'origin_id', 1 => 'meta_key', ), ), 'engine' => 'InnoDB', 'charset' => 'utf8mb3', ), 'agents_processing' => array ( 'columns' => array ( 'id' => 'INT NOT NULL AUTO_INCREMENT', 'date_create' => 'DATETIME DEFAULT_GENERATED DEFAULT \'CURRENT_TIMESTAMP\'', 'date_edit' => 'DATETIME DEFAULT_GENERATED ON UPDATE CURRENT_TIMESTAMP DEFAULT \'CURRENT_TIMESTAMP\'', 'origin_id' => 'INT NOT NULL', 'status' => 'TINYINT(1) DEFAULT \'0\'', 'type' => 'TINYINT(1) NOT NULL', 'description' => 'TEXT', ), 'primary' => array ( 0 => 'id', ), 'indexes' => array ( 'origin_id' => array ( 0 => 'origin_id', ), ), 'engine' => 'InnoDB', 'charset' => 'utf8mb3', ), 'agents_processing_meta' => array ( 'columns' => array ( 'id' => 'INT NOT NULL AUTO_INCREMENT', 'date_create' => 'DATETIME DEFAULT_GENERATED DEFAULT \'CURRENT_TIMESTAMP\'', 'date_edit' => 'DATETIME DEFAULT_GENERATED ON UPDATE CURRENT_TIMESTAMP DEFAULT \'CURRENT_TIMESTAMP\'', 'origin_id' => 'INT NOT NULL', 'meta_key' => 'VARCHAR(255) NOT NULL', 'meta_value' => 'TEXT', ), 'primary' => array ( 0 => 'id', ), 'indexes' => array ( 'origin_id' => array ( 0 => 'origin_id', 1 => 'meta_key', ), ), 'engine' => 'InnoDB', 'charset' => 'utf8mb3', ), 'agents_tools' => array ( 'columns' => array ( 'id' => 'INT NOT NULL AUTO_INCREMENT', 'date_create' => 'DATETIME DEFAULT_GENERATED DEFAULT \'CURRENT_TIMESTAMP\'', 'date_edit' => 'DATETIME DEFAULT_GENERATED ON UPDATE CURRENT_TIMESTAMP DEFAULT \'CURRENT_TIMESTAMP\'', 'origin_id' => 'INT NOT NULL', 'status' => 'TINYINT(1) DEFAULT \'0\'', 'type' => 'TINYINT(1) NOT NULL', 'description' => 'TEXT', ), 'primary' => array ( 0 => 'id', ), 'indexes' => array ( 'origin_id' => array ( 0 => 'origin_id', ), ), 'engine' => 'InnoDB', 'charset' => 'utf8mb3', ), 'agents_tools_meta' => array ( 'columns' => array ( 'id' => 'INT NOT NULL AUTO_INCREMENT', 'date_create' => 'DATETIME DEFAULT_GENERATED DEFAULT \'CURRENT_TIMESTAMP\'', 'date_edit' => 'DATETIME DEFAULT_GENERATED ON UPDATE CURRENT_TIMESTAMP DEFAULT \'CURRENT_TIMESTAMP\'', 'origin_id' => 'INT NOT NULL', 'meta_key' => 'VARCHAR(255) NOT NULL', 'meta_value' => 'TEXT', ), 'primary' => array ( 0 => 'id', ), 'indexes' => array ( 'origin_id' => array ( 0 => 'origin_id', 1 => 'meta_key', ), ), 'engine' => 'InnoDB', 'charset' => 'utf8mb3', ), );