2025-07-22 18:15:36 +00:00
using AIStudio.Tools.PluginSystem.Assistants.DataModel ;
2026-03-10 17:57:48 +00:00
using AIStudio.Tools.PluginSystem.Assistants.DataModel.Layout ;
2025-07-22 18:15:36 +00:00
using Lua ;
2026-03-17 16:20:16 +00:00
using System.Text ;
2025-07-21 12:38:08 +00:00
2025-07-21 13:10:40 +00:00
namespace AIStudio.Tools.PluginSystem.Assistants ;
2025-07-21 12:38:08 +00:00
2025-09-29 19:04:16 +00:00
public sealed class PluginAssistants ( bool isInternal , LuaState state , PluginType type ) : PluginBase ( isInternal , state , type )
2025-07-21 12:38:08 +00:00
{
2026-03-03 14:11:03 +00:00
private static string TB ( string fallbackEn ) = > I18N . I . T ( fallbackEn , typeof ( PluginAssistants ) . Namespace , nameof ( PluginAssistants ) ) ;
2025-07-22 18:15:36 +00:00
2025-07-21 12:38:08 +00:00
private static readonly ILogger < PluginAssistants > LOGGER = Program . LOGGER_FACTORY . CreateLogger < PluginAssistants > ( ) ;
2026-03-03 14:11:03 +00:00
public AssistantForm ? RootComponent { get ; private set ; }
public string AssistantTitle { get ; private set ; } = string . Empty ;
public string AssistantDescription { get ; private set ; } = string . Empty ;
public string SystemPrompt { get ; private set ; } = string . Empty ;
public string SubmitText { get ; private set ; } = string . Empty ;
public bool AllowProfiles { get ; private set ; } = true ;
2026-02-23 14:01:00 +00:00
public bool HasEmbeddedProfileSelection { get ; private set ; }
2026-03-02 14:24:18 +00:00
public bool HasCustomPromptBuilder = > this . buildPromptFunction is not null ;
2026-03-09 17:56:38 +00:00
public const int TEXT_AREA_MAX_VALUE = 524288 ;
2026-03-02 14:24:18 +00:00
private LuaFunction ? buildPromptFunction ;
2025-07-21 12:38:08 +00:00
2025-09-29 19:04:16 +00:00
public void TryLoad ( )
2025-07-21 12:38:08 +00:00
{
2025-09-29 19:04:16 +00:00
if ( ! this . TryProcessAssistant ( out var issue ) )
this . pluginIssues . Add ( issue ) ;
2025-07-22 18:15:36 +00:00
}
/// <summary>
/// Tries to parse the assistant table into our internal assistant render tree data model. It follows this process:
/// <list type="number">
2026-03-02 22:29:02 +00:00
/// <item><description>ASSISTANT ? Title/Description ? UI</description></item>
/// <item><description>UI: Root element ? required Children ? Components</description></item>
/// <item><description>Components: Type ? Props ? Children (recursively)</description></item>
2025-07-22 18:15:36 +00:00
/// </list>
/// </summary>
/// <param name="message">The error message, when parameters from the table could not be read.</param>
/// <returns>True, when the assistant could be read successfully indicating the data model is populated.</returns>
private bool TryProcessAssistant ( out string message )
{
message = string . Empty ;
2026-02-23 14:01:00 +00:00
this . HasEmbeddedProfileSelection = false ;
2026-03-02 14:24:18 +00:00
this . buildPromptFunction = null ;
2026-03-02 22:29:02 +00:00
this . RegisterLuaHelpers ( ) ;
2025-07-22 18:15:36 +00:00
// Ensure that the main ASSISTANT table exists and is a valid Lua table:
if ( ! this . state . Environment [ "ASSISTANT" ] . TryRead < LuaTable > ( out var assistantTable ) )
{
2026-02-24 13:42:57 +00:00
message = TB ( "The ASSISTANT lua table does not exist or is not a valid table." ) ;
2025-07-22 18:15:36 +00:00
return false ;
}
2025-07-21 12:38:08 +00:00
2025-07-22 18:15:36 +00:00
if ( ! assistantTable . TryGetValue ( "Title" , out var assistantTitleValue ) | |
! assistantTitleValue . TryRead < string > ( out var assistantTitle ) )
{
2026-02-24 13:42:57 +00:00
message = TB ( "The provided ASSISTANT lua table does not contain a valid title." ) ;
2025-07-22 18:15:36 +00:00
return false ;
}
if ( ! assistantTable . TryGetValue ( "Description" , out var assistantDescriptionValue ) | |
! assistantDescriptionValue . TryRead < string > ( out var assistantDescription ) )
{
2026-02-24 13:42:57 +00:00
message = TB ( "The provided ASSISTANT lua table does not contain a valid description." ) ;
2025-07-22 18:15:36 +00:00
return false ;
}
2025-09-30 19:54:24 +00:00
if ( ! assistantTable . TryGetValue ( "SystemPrompt" , out var assistantSystemPromptValue ) | |
! assistantSystemPromptValue . TryRead < string > ( out var assistantSystemPrompt ) )
{
2026-02-24 13:42:57 +00:00
message = TB ( "The provided ASSISTANT lua table does not contain a valid system prompt." ) ;
2025-09-30 19:54:24 +00:00
return false ;
}
2025-11-10 16:01:49 +00:00
if ( ! assistantTable . TryGetValue ( "SubmitText" , out var assistantSubmitTextValue ) | |
! assistantSubmitTextValue . TryRead < string > ( out var assistantSubmitText ) )
{
message = TB ( "The ASSISTANT table does not contain a valid system prompt." ) ;
return false ;
}
2025-09-30 19:54:24 +00:00
if ( ! assistantTable . TryGetValue ( "AllowProfiles" , out var assistantAllowProfilesValue ) | |
! assistantAllowProfilesValue . TryRead < bool > ( out var assistantAllowProfiles ) )
{
2026-02-24 13:42:57 +00:00
message = TB ( "The provided ASSISTANT lua table does not contain the boolean flag to control the allowance of profiles." ) ;
2025-09-30 19:54:24 +00:00
return false ;
}
2025-07-22 18:15:36 +00:00
2026-03-02 14:24:18 +00:00
if ( assistantTable . TryGetValue ( "BuildPrompt" , out var buildPromptValue ) )
{
if ( buildPromptValue . TryRead < LuaFunction > ( out var buildPrompt ) )
this . buildPromptFunction = buildPrompt ;
else
message = TB ( "ASSISTANT.BuildPrompt exists but is not a Lua function or has invalid syntax." ) ;
}
2025-07-22 18:15:36 +00:00
this . AssistantTitle = assistantTitle ;
this . AssistantDescription = assistantDescription ;
2025-09-30 19:54:24 +00:00
this . SystemPrompt = assistantSystemPrompt ;
2025-11-10 16:01:49 +00:00
this . SubmitText = assistantSubmitText ;
2025-09-30 19:54:24 +00:00
this . AllowProfiles = assistantAllowProfiles ;
2025-07-22 18:15:36 +00:00
// Ensure that the UI table exists nested in the ASSISTANT table and is a valid Lua table:
if ( ! assistantTable . TryGetValue ( "UI" , out var uiVal ) | | ! uiVal . TryRead < LuaTable > ( out var uiTable ) )
{
2026-02-24 13:42:57 +00:00
message = TB ( "The provided ASSISTANT lua table does not contain a valid UI table." ) ;
2025-07-22 18:15:36 +00:00
return false ;
}
if ( ! this . TryReadRenderTree ( uiTable , out var rootComponent ) )
{
2026-02-24 13:42:57 +00:00
message = TB ( "Failed to parse the UI render tree from the ASSISTANT lua table." ) ;
2025-07-22 18:15:36 +00:00
return false ;
}
this . RootComponent = ( AssistantForm ) rootComponent ;
return true ;
}
2026-03-02 14:24:18 +00:00
public async Task < string? > TryBuildPromptAsync ( LuaTable input , CancellationToken cancellationToken = default )
{
if ( this . buildPromptFunction is null )
return null ;
try
{
2026-03-03 19:43:14 +00:00
cancellationToken . ThrowIfCancellationRequested ( ) ;
var results = await this . state . CallAsync ( this . buildPromptFunction , [ input ] ) ;
2026-03-02 14:24:18 +00:00
if ( results . Length = = 0 )
return string . Empty ;
if ( results [ 0 ] . TryRead < string > ( out var prompt ) )
return prompt ;
LOGGER . LogWarning ( "ASSISTANT.BuildPrompt returned a non-string value." ) ;
return string . Empty ;
}
catch ( Exception e )
{
LOGGER . LogError ( e , "ASSISTANT.BuildPrompt failed to execute." ) ;
return string . Empty ;
}
}
2026-03-10 14:43:40 +00:00
public async Task < LuaTable ? > TryInvokeButtonActionAsync ( AssistantButton button , LuaTable input , CancellationToken cancellationToken = default )
{
2026-03-16 13:15:29 +00:00
return await this . TryInvokeComponentCallbackAsync ( button . Action , AssistantComponentType . BUTTON , button . Name , input , cancellationToken ) ;
}
public async Task < LuaTable ? > TryInvokeSwitchChangedAsync ( AssistantSwitch switchComponent , LuaTable input , CancellationToken cancellationToken = default )
{
return await this . TryInvokeComponentCallbackAsync ( switchComponent . OnChanged , AssistantComponentType . SWITCH , switchComponent . Name , input , cancellationToken ) ;
}
private async Task < LuaTable ? > TryInvokeComponentCallbackAsync ( LuaFunction ? callback , AssistantComponentType componentType , string componentName , LuaTable input , CancellationToken cancellationToken = default )
{
if ( callback is null )
2026-03-10 14:43:40 +00:00
return null ;
try
{
cancellationToken . ThrowIfCancellationRequested ( ) ;
2026-03-16 13:15:29 +00:00
var results = await this . state . CallAsync ( callback , [ input ] ) ;
2026-03-10 14:43:40 +00:00
if ( results . Length = = 0 )
return null ;
if ( results [ 0 ] . Type is LuaValueType . Nil )
return null ;
if ( results [ 0 ] . TryRead < LuaTable > ( out var updateTable ) )
return updateTable ;
2026-03-16 13:15:29 +00:00
LOGGER . LogWarning ( "Assistant plugin '{PluginName}' {ComponentType} '{ComponentName}' callback returned a non-table value. The result is ignored." , this . Name , componentType , componentName ) ;
2026-03-10 14:43:40 +00:00
return null ;
}
catch ( Exception e )
{
2026-03-16 13:15:29 +00:00
LOGGER . LogError ( e , "Assistant plugin '{PluginName}' {ComponentType} '{ComponentName}' callback failed to execute." , this . Name , componentType , componentName ) ;
2026-03-10 14:43:40 +00:00
return null ;
}
}
2025-07-22 18:15:36 +00:00
/// <summary>
/// Parses the root <c>FORM</c> component and start to parse its required children (main ui components)
/// </summary>
/// <param name="uiTable">The <c>LuaTable</c> containing all UI components</param>
/// <param name="root">Outputs the root <c>FORM</c> component, if the parsing is successful. </param>
/// <returns>True, when the UI table could be read successfully.</returns>
private bool TryReadRenderTree ( LuaTable uiTable , out IAssistantComponent root )
{
root = null ! ;
2026-03-03 14:11:03 +00:00
2025-07-22 18:15:36 +00:00
if ( ! uiTable . TryGetValue ( "Type" , out var typeVal )
| | ! typeVal . TryRead < string > ( out var typeText )
2026-02-24 10:39:17 +00:00
| | ! Enum . TryParse < AssistantComponentType > ( typeText , true , out var type )
| | type ! = AssistantComponentType . FORM )
2025-07-22 18:15:36 +00:00
{
LOGGER . LogWarning ( "UI table of the ASSISTANT table has no valid Form type." ) ;
return false ;
}
if ( ! uiTable . TryGetValue ( "Children" , out var childrenVal ) | |
! childrenVal . TryRead < LuaTable > ( out var childrenTable ) )
{
LOGGER . LogWarning ( "Form has no valid Children table." ) ;
return false ;
}
var children = new List < IAssistantComponent > ( ) ;
var count = childrenTable . ArrayLength ;
for ( var idx = 1 ; idx < = count ; idx + + )
{
var childVal = childrenTable [ idx ] ;
if ( ! childVal . TryRead < LuaTable > ( out var childTable ) )
{
LOGGER . LogWarning ( $"Child #{idx} is not a table." ) ;
continue ;
}
if ( ! this . TryReadComponentTable ( idx , childTable , out var comp ) )
{
LOGGER . LogWarning ( $"Child #{idx} could not be parsed." ) ;
continue ;
}
children . Add ( comp ) ;
}
2026-02-24 10:39:17 +00:00
root = AssistantComponentFactory . CreateComponent ( AssistantComponentType . FORM , new Dictionary < string , object > ( ) , children ) ;
2025-07-22 18:15:36 +00:00
return true ;
}
/// <summary>
/// Parses the components' table containing all members and properties.
/// Recursively calls itself, if the component has a children table
/// </summary>
/// <param name="idx">Current index inside the <c>FORM</c> children</param>
/// <param name="componentTable">The <c>LuaTable</c> containing all component properties</param>
/// <param name="component">Outputs the component if the parsing is successful</param>
/// <returns>True, when the component table could be read successfully.</returns>
private bool TryReadComponentTable ( int idx , LuaTable componentTable , out IAssistantComponent component )
{
component = null ! ;
if ( ! componentTable . TryGetValue ( "Type" , out var typeVal )
| | ! typeVal . TryRead < string > ( out var typeText )
2026-02-24 10:39:17 +00:00
| | ! Enum . TryParse < AssistantComponentType > ( typeText , true , out var type ) )
2025-07-22 18:15:36 +00:00
{
LOGGER . LogWarning ( $"Component #{idx} missing valid Type." ) ;
return false ;
}
2026-02-23 14:01:00 +00:00
2026-02-24 10:39:17 +00:00
if ( type = = AssistantComponentType . PROFILE_SELECTION )
2026-02-23 14:01:00 +00:00
this . HasEmbeddedProfileSelection = true ;
2025-07-22 18:15:36 +00:00
Dictionary < string , object > props = new ( ) ;
if ( componentTable . TryGetValue ( "Props" , out var propsVal )
& & propsVal . TryRead < LuaTable > ( out var propsTable ) )
{
if ( ! this . TryReadComponentProps ( type , propsTable , out props ) )
LOGGER . LogWarning ( $"Component #{idx} Props could not be fully read." ) ;
}
2025-09-30 21:12:16 +00:00
2025-07-22 18:15:36 +00:00
var children = new List < IAssistantComponent > ( ) ;
if ( componentTable . TryGetValue ( "Children" , out var childVal )
& & childVal . TryRead < LuaTable > ( out var childTable ) )
{
var cnt = childTable . ArrayLength ;
for ( var i = 1 ; i < = cnt ; i + + )
{
var cv = childTable [ i ] ;
if ( cv . TryRead < LuaTable > ( out var ct )
& & this . TryReadComponentTable ( i , ct , out var childComp ) )
{
children . Add ( childComp ) ;
}
}
}
component = AssistantComponentFactory . CreateComponent ( type , props , children ) ;
2026-03-09 17:56:38 +00:00
if ( component is AssistantTextArea textArea )
{
if ( ! string . IsNullOrWhiteSpace ( textArea . AdornmentIcon ) & & ! string . IsNullOrWhiteSpace ( textArea . AdornmentText ) )
LOGGER . LogWarning ( $"Assistant plugin '{this.Name}' TEXT_AREA '{textArea.Name}' defines both '[\" AdornmentIcon \ "]' and '[\"AdornmentText\"]', thus both will be ignored by the renderer. You`re only allowed to use either one of them." ) ;
if ( textArea . MaxLength = = 0 )
{
LOGGER . LogWarning ( $"Assistant plugin '{this.Name}' TEXT_AREA '{textArea.Name}' defines a MaxLength of `0`. This is not applicable, if you want a readonly Textfield, set the [\" ReadOnly \ "] field to `true`. MAXLENGTH IS SET TO DEFAULT {TEXT_AREA_MAX_VALUE}." ) ;
textArea . MaxLength = TEXT_AREA_MAX_VALUE ;
}
if ( textArea . MaxLength ! = 0 & & textArea . MaxLength ! = TEXT_AREA_MAX_VALUE )
textArea . Counter = textArea . MaxLength ;
if ( textArea . Counter ! = null )
textArea . IsImmediate = true ;
}
2026-03-10 15:12:00 +00:00
if ( component is AssistantButtonGroup buttonGroup )
{
var invalidChildren = buttonGroup . Children . Where ( child = > child . Type ! = AssistantComponentType . BUTTON ) . ToList ( ) ;
if ( invalidChildren . Count > 0 )
{
LOGGER . LogWarning ( "Assistant plugin '{PluginName}' BUTTON_GROUP contains non-BUTTON children. Only BUTTON children are supported and invalid children are ignored." , this . Name ) ;
buttonGroup . Children = buttonGroup . Children . Where ( child = > child . Type = = AssistantComponentType . BUTTON ) . ToList ( ) ;
}
}
2026-03-10 17:57:48 +00:00
if ( component is AssistantGrid grid )
{
var invalidChildren = grid . Children . Where ( child = > child . Type ! = AssistantComponentType . LAYOUT_ITEM ) . ToList ( ) ;
if ( invalidChildren . Count > 0 )
{
LOGGER . LogWarning ( "Assistant plugin '{PluginName}' LAYOUT_GRID contains non-LAYOUT_ITEM children. Only LAYOUT_ITEM children are supported and invalid children are ignored." , this . Name ) ;
grid . Children = grid . Children . Where ( child = > child . Type = = AssistantComponentType . LAYOUT_ITEM ) . ToList ( ) ;
}
}
2025-07-22 18:15:36 +00:00
return true ;
}
2026-03-09 17:56:38 +00:00
private bool TryReadComponentProps ( AssistantComponentType type , LuaTable propsTable , out Dictionary < string , object > props )
2025-07-22 18:15:36 +00:00
{
props = new Dictionary < string , object > ( ) ;
if ( ! ComponentPropSpecs . SPECS . TryGetValue ( type , out var spec ) )
{
LOGGER . LogWarning ( $"No PropSpec defined for component type {type}" ) ;
return false ;
}
foreach ( var key in spec . Required )
{
if ( ! propsTable . TryGetValue ( key , out var luaVal ) )
{
LOGGER . LogWarning ( $"Component {type} missing required prop '{key}'." ) ;
return false ;
}
2026-03-10 14:43:40 +00:00
if ( ! this . TryConvertComponentPropValue ( type , key , luaVal , out var dotNetVal ) )
2025-07-22 18:15:36 +00:00
{
LOGGER . LogWarning ( $"Component {type}: prop '{key}' has wrong type." ) ;
return false ;
}
props [ key ] = dotNetVal ;
}
foreach ( var key in spec . Optional )
{
if ( ! propsTable . TryGetValue ( key , out var luaVal ) )
continue ;
2026-03-10 14:43:40 +00:00
if ( ! this . TryConvertComponentPropValue ( type , key , luaVal , out var dotNetVal ) )
2025-07-22 18:15:36 +00:00
{
LOGGER . LogWarning ( $"Component {type}: optional prop '{key}' has wrong type, skipping." ) ;
continue ;
}
props [ key ] = dotNetVal ;
}
return true ;
2025-07-21 12:38:08 +00:00
}
2026-03-10 14:43:40 +00:00
private bool TryConvertComponentPropValue ( AssistantComponentType type , string key , LuaValue val , out object result )
{
if ( type = = AssistantComponentType . BUTTON & & key = = "Action" & & val . TryRead < LuaFunction > ( out var action ) )
{
result = action ;
return true ;
}
2026-03-16 14:44:15 +00:00
if ( type = = AssistantComponentType . SWITCH & & key = = "OnChanged" & & val . TryRead < LuaFunction > ( out var onChanged ) )
{
result = onChanged ;
return true ;
}
2026-03-10 14:43:40 +00:00
return this . TryConvertLuaValue ( val , out result ) ;
}
2025-07-21 12:38:08 +00:00
2025-07-22 18:15:36 +00:00
private bool TryConvertLuaValue ( LuaValue val , out object result )
{
if ( val . TryRead < string > ( out var s ) )
{
result = s ;
return true ;
}
if ( val . TryRead < bool > ( out var b ) )
{
result = b ;
return true ;
}
if ( val . TryRead < double > ( out var d ) )
{
result = d ;
return true ;
}
2025-09-30 21:12:16 +00:00
if ( val . TryRead < LuaTable > ( out var table ) & & this . TryParseDropdownItem ( table , out var item ) )
{
result = item ;
return true ;
}
if ( val . TryRead < LuaTable > ( out var listTable ) & & this . TryParseDropdownItemList ( listTable , out var itemList ) )
{
result = itemList ;
return true ;
}
2026-02-10 16:06:45 +00:00
if ( val . TryRead < LuaTable > ( out var listItemListTable ) & & this . TryParseListItemList ( listItemListTable , out var listItemList ) )
{
result = listItemList ;
return true ;
}
2025-09-30 21:12:16 +00:00
2025-07-22 18:15:36 +00:00
result = null ! ;
return false ;
}
2025-09-30 21:12:16 +00:00
private bool TryParseDropdownItem ( LuaTable table , out AssistantDropdownItem item )
{
item = new AssistantDropdownItem ( ) ;
if ( ! table . TryGetValue ( "Value" , out var valueVal ) | | ! valueVal . TryRead < string > ( out var value ) )
return false ;
if ( ! table . TryGetValue ( "Display" , out var displayVal ) | | ! displayVal . TryRead < string > ( out var display ) )
return false ;
item . Value = value ;
item . Display = display ;
return true ;
}
private bool TryParseDropdownItemList ( LuaTable table , out List < AssistantDropdownItem > items )
{
items = new List < AssistantDropdownItem > ( ) ;
var length = table . ArrayLength ;
for ( var i = 1 ; i < = length ; i + + )
{
var value = table [ i ] ;
if ( value . TryRead < LuaTable > ( out var subTable ) & & this . TryParseDropdownItem ( subTable , out var item ) )
{
items . Add ( item ) ;
}
else
{
items = null ! ;
return false ;
}
}
return true ;
}
2026-02-10 16:06:45 +00:00
private bool TryParseListItem ( LuaTable table , out AssistantListItem item )
{
item = new AssistantListItem ( ) ;
if ( ! table . TryGetValue ( "Text" , out var textVal ) | | ! textVal . TryRead < string > ( out var text ) )
return false ;
if ( ! table . TryGetValue ( "Type" , out var typeVal ) | | ! typeVal . TryRead < string > ( out var type ) )
return false ;
2026-03-17 16:20:16 +00:00
table . TryGetValue ( "Icon" , out var iconVal ) ;
iconVal . TryRead < string > ( out var icon ) ;
icon ? ? = string . Empty ;
table . TryGetValue ( "IconColor" , out var iconColorVal ) ;
iconColorVal . TryRead < string > ( out var iconColor ) ;
iconColor ? ? = string . Empty ;
2026-02-10 16:06:45 +00:00
2026-03-17 16:20:16 +00:00
2026-02-10 16:06:45 +00:00
item . Text = text ;
item . Type = type ;
2026-03-17 16:20:16 +00:00
item . Icon = icon ;
item . IconColor = iconColor ;
2026-02-10 16:06:45 +00:00
if ( table . TryGetValue ( "Href" , out var hrefVal ) & & hrefVal . TryRead < string > ( out var href ) )
{
item . Href = href ;
}
return true ;
}
private bool TryParseListItemList ( LuaTable table , out List < AssistantListItem > items )
{
items = new List < AssistantListItem > ( ) ;
var length = table . ArrayLength ;
for ( var i = 1 ; i < = length ; i + + )
{
var value = table [ i ] ;
if ( value . TryRead < LuaTable > ( out var subTable ) & & this . TryParseListItem ( subTable , out var item ) )
{
items . Add ( item ) ;
}
else
{
items = null ! ;
return false ;
}
}
return true ;
}
2026-03-02 22:29:02 +00:00
private void RegisterLuaHelpers ( )
{
2026-03-03 19:43:14 +00:00
this . state . Environment [ "LogInfo" ] = new LuaFunction ( ( context , _ ) = >
2026-03-02 22:29:02 +00:00
{
if ( context . ArgumentCount = = 0 ) return new ( 0 ) ;
var message = context . GetArgument < string > ( 0 ) ;
LOGGER . LogInformation ( $"[Lua] [Assistants] [{this.Name}]: {message}" ) ;
2026-03-03 19:43:14 +00:00
return new ( 0 ) ;
2026-03-02 22:29:02 +00:00
} ) ;
2026-03-03 19:43:14 +00:00
this . state . Environment [ "LogDebug" ] = new LuaFunction ( ( context , _ ) = >
2026-03-02 22:29:02 +00:00
{
if ( context . ArgumentCount = = 0 ) return new ( 0 ) ;
var message = context . GetArgument < string > ( 0 ) ;
LOGGER . LogDebug ( $"[Lua] [Assistants] [{this.Name}]: {message}" ) ;
2026-03-03 19:43:14 +00:00
return new ( 0 ) ;
2026-03-02 22:29:02 +00:00
} ) ;
2026-03-03 19:43:14 +00:00
this . state . Environment [ "LogWarning" ] = new LuaFunction ( ( context , _ ) = >
2026-03-02 22:29:02 +00:00
{
if ( context . ArgumentCount = = 0 ) return new ( 0 ) ;
var message = context . GetArgument < string > ( 0 ) ;
LOGGER . LogWarning ( $"[Lua] [Assistants] [{this.Name}]: {message}" ) ;
2026-03-03 19:43:14 +00:00
return new ( 0 ) ;
2026-03-02 22:29:02 +00:00
} ) ;
2026-03-03 19:43:14 +00:00
this . state . Environment [ "LogError" ] = new LuaFunction ( ( context , _ ) = >
2026-03-02 22:29:02 +00:00
{
if ( context . ArgumentCount = = 0 ) return new ( 0 ) ;
var message = context . GetArgument < string > ( 0 ) ;
LOGGER . LogError ( $"[Lua] [Assistants] [{this.Name}]: {message}" ) ;
2026-03-03 19:43:14 +00:00
return new ( 0 ) ;
2026-03-02 22:29:02 +00:00
} ) ;
2026-03-03 19:43:14 +00:00
this . state . Environment [ "DateTime" ] = new LuaFunction ( ( context , _ ) = >
2026-03-02 22:29:02 +00:00
{
var format = context . ArgumentCount > 0 ? context . GetArgument < string > ( 0 ) : "yyyy-MM-dd HH:mm:ss" ;
var now = DateTime . Now ;
var formattedDate = now . ToString ( format ) ;
var table = new LuaTable
{
["year"] = now . Year ,
["month"] = now . Month ,
["day"] = now . Day ,
["hour"] = now . Hour ,
["minute"] = now . Minute ,
["second"] = now . Second ,
["millisecond"] = now . Millisecond ,
["formatted"] = formattedDate ,
} ;
2026-03-03 19:43:14 +00:00
return new ( context . Return ( table ) ) ;
2026-03-02 22:29:02 +00:00
} ) ;
2026-03-03 19:43:14 +00:00
this . state . Environment [ "Timestamp" ] = new LuaFunction ( ( context , _ ) = >
2026-03-02 22:29:02 +00:00
{
var timestamp = DateTime . UtcNow . ToString ( "o" ) ;
2026-03-03 19:43:14 +00:00
return new ( context . Return ( timestamp ) ) ;
2026-03-02 22:29:02 +00:00
} ) ;
}
2026-02-23 14:01:00 +00:00
}