2024-08-01 19:53:28 +00:00
using AIStudio.Agents ;
2024-04-19 19:19:13 +00:00
using AIStudio.Settings ;
2025-03-30 18:34:30 +00:00
using AIStudio.Tools.PluginSystem ;
2024-08-21 06:30:01 +00:00
using AIStudio.Tools.Services ;
2024-04-20 15:03:57 +00:00
2024-09-01 18:10:03 +00:00
using Microsoft.AspNetCore.Server.Kestrel.Core ;
using Microsoft.Extensions.Logging.Console ;
2024-04-05 14:16:33 +00:00
using MudBlazor.Services ;
2024-03-28 21:26:48 +00:00
2024-12-03 20:02:37 +00:00
using MudExtensions.Services ;
2024-05-19 14:05:06 +00:00
#if ! DEBUG
using System.Reflection ;
using Microsoft.Extensions.FileProviders ;
#endif
2024-09-01 18:10:03 +00:00
namespace AIStudio ;
internal sealed class Program
2024-05-04 08:41:14 +00:00
{
2024-09-01 18:10:03 +00:00
public static RustService RUST_SERVICE = null ! ;
public static Encryption ENCRYPTION = null ! ;
public static string API_TOKEN = null ! ;
2025-02-17 11:33:34 +00:00
public static IServiceProvider SERVICE_PROVIDER = null ! ;
2025-03-22 20:12:14 +00:00
public static ILoggerFactory LOGGER_FACTORY = null ! ;
2024-09-01 18:10:03 +00:00
2025-05-17 11:36:28 +00:00
public static async Task Main ( )
2024-04-05 14:16:33 +00:00
{
2025-05-17 11:36:28 +00:00
#if DEBUG
// Read the environment variables from the .env file:
var envFilePath = Path . Combine ( ".." , ".." , "startup.env" ) ;
await EnvFile . Apply ( envFilePath ) ;
#endif
2024-09-01 18:10:03 +00:00
// Read the secret key for the IPC from the AI_STUDIO_SECRET_KEY environment variable:
var secretPasswordEncoded = Environment . GetEnvironmentVariable ( "AI_STUDIO_SECRET_PASSWORD" ) ;
if ( string . IsNullOrWhiteSpace ( secretPasswordEncoded ) )
{
Console . WriteLine ( "Error: The AI_STUDIO_SECRET_PASSWORD environment variable is not set." ) ;
return ;
}
2024-06-02 18:51:26 +00:00
2024-09-01 18:10:03 +00:00
var secretPassword = Convert . FromBase64String ( secretPasswordEncoded ) ;
var secretKeySaltEncoded = Environment . GetEnvironmentVariable ( "AI_STUDIO_SECRET_KEY_SALT" ) ;
if ( string . IsNullOrWhiteSpace ( secretKeySaltEncoded ) )
{
Console . WriteLine ( "Error: The AI_STUDIO_SECRET_KEY_SALT environment variable is not set." ) ;
return ;
}
2024-03-28 21:26:48 +00:00
2024-09-01 18:10:03 +00:00
var secretKeySalt = Convert . FromBase64String ( secretKeySaltEncoded ) ;
var certificateFingerprint = Environment . GetEnvironmentVariable ( "AI_STUDIO_CERTIFICATE_FINGERPRINT" ) ;
if ( string . IsNullOrWhiteSpace ( certificateFingerprint ) )
{
Console . WriteLine ( "Error: The AI_STUDIO_CERTIFICATE_FINGERPRINT environment variable is not set." ) ;
return ;
}
2025-05-17 11:36:28 +00:00
var rustApiPort = Environment . GetEnvironmentVariable ( "AI_STUDIO_API_PORT" ) ;
if ( string . IsNullOrWhiteSpace ( rustApiPort ) )
{
Console . WriteLine ( "Error: The AI_STUDIO_API_PORT environment variable is not set." ) ;
return ;
}
2024-09-01 18:10:03 +00:00
var apiToken = Environment . GetEnvironmentVariable ( "AI_STUDIO_API_TOKEN" ) ;
if ( string . IsNullOrWhiteSpace ( apiToken ) )
{
Console . WriteLine ( "Error: The AI_STUDIO_API_TOKEN environment variable is not set." ) ;
return ;
}
API_TOKEN = apiToken ;
using var rust = new RustService ( rustApiPort , certificateFingerprint ) ;
var appPort = await rust . GetAppPort ( ) ;
if ( appPort = = 0 )
{
Console . WriteLine ( "Error: Failed to get the app port from Rust." ) ;
return ;
}
var builder = WebApplication . CreateBuilder ( ) ;
builder . WebHost . ConfigureKestrel ( kestrelServerOptions = >
{
kestrelServerOptions . ConfigureEndpointDefaults ( listenOptions = >
{
listenOptions . Protocols = HttpProtocols . Http1AndHttp2AndHttp3 ;
} ) ;
} ) ;
builder . Logging . ClearProviders ( ) ;
builder . Logging . SetMinimumLevel ( LogLevel . Debug ) ;
builder . Logging . AddFilter ( "Microsoft" , LogLevel . Information ) ;
builder . Logging . AddFilter ( "Microsoft.AspNetCore.Hosting.Diagnostics" , LogLevel . Warning ) ;
builder . Logging . AddFilter ( "Microsoft.AspNetCore.Routing.EndpointMiddleware" , LogLevel . Warning ) ;
builder . Logging . AddFilter ( "Microsoft.AspNetCore.StaticFiles" , LogLevel . Warning ) ;
builder . Logging . AddFilter ( "MudBlazor" , LogLevel . Information ) ;
builder . Logging . AddConsole ( options = >
{
options . FormatterName = TerminalLogger . FORMATTER_NAME ;
} ) . AddConsoleFormatter < TerminalLogger , ConsoleFormatterOptions > ( ) ;
2024-05-18 19:50:46 +00:00
2024-12-03 20:02:37 +00:00
builder . Services . AddMudExtensions ( ) ;
2024-09-01 18:10:03 +00:00
builder . Services . AddMudServices ( config = >
{
config . SnackbarConfiguration . PositionClass = Defaults . Classes . Position . BottomLeft ;
config . SnackbarConfiguration . PreventDuplicates = false ;
config . SnackbarConfiguration . NewestOnTop = false ;
config . SnackbarConfiguration . ShowCloseIcon = true ;
config . SnackbarConfiguration . VisibleStateDuration = 6_000 ; //milliseconds aka 6 seconds
config . SnackbarConfiguration . HideTransitionDuration = 500 ;
config . SnackbarConfiguration . ShowTransitionDuration = 500 ;
config . SnackbarConfiguration . SnackbarVariant = Variant . Outlined ;
} ) ;
2024-05-18 19:50:46 +00:00
2025-05-01 10:54:04 +00:00
builder . Services . AddMemoryCache ( ) ; // Needed for the Markdown library
2024-09-01 18:10:03 +00:00
builder . Services . AddMudMarkdownServices ( ) ;
2024-09-15 10:30:07 +00:00
builder . Services . AddSingleton ( new MudTheme ( ) ) ;
2024-09-01 18:10:03 +00:00
builder . Services . AddSingleton ( MessageBus . INSTANCE ) ;
builder . Services . AddSingleton ( rust ) ;
builder . Services . AddMudMarkdownClipboardService < MarkdownClipboardService > ( ) ;
builder . Services . AddSingleton < SettingsManager > ( ) ;
builder . Services . AddSingleton < ThreadSafeRandom > ( ) ;
2025-02-15 14:41:12 +00:00
builder . Services . AddSingleton < DataSourceService > ( ) ;
2025-06-01 18:46:33 +00:00
builder . Services . AddSingleton < SettingsLocker > ( ) ;
2024-09-01 18:10:03 +00:00
builder . Services . AddTransient < HTMLParser > ( ) ;
2025-02-17 11:33:34 +00:00
builder . Services . AddTransient < AgentDataSourceSelection > ( ) ;
2025-02-22 19:51:06 +00:00
builder . Services . AddTransient < AgentRetrievalContextValidation > ( ) ;
2024-09-01 18:10:03 +00:00
builder . Services . AddTransient < AgentTextContentCleaner > ( ) ;
builder . Services . AddHostedService < UpdateService > ( ) ;
builder . Services . AddHostedService < TemporaryChatService > ( ) ;
2025-06-01 18:48:49 +00:00
builder . Services . AddHostedService < EnterpriseEnvironmentService > ( ) ;
2024-09-01 18:10:03 +00:00
builder . Services . AddRazorComponents ( )
. AddInteractiveServerComponents ( )
. AddHubOptions ( options = >
{
options . MaximumReceiveMessageSize = null ;
2025-04-24 07:50:03 +00:00
options . ClientTimeoutInterval = TimeSpan . FromDays ( 14 ) ;
2024-09-01 18:10:03 +00:00
options . HandshakeTimeout = TimeSpan . FromSeconds ( 30 ) ;
} ) ;
2024-05-18 19:50:46 +00:00
2024-09-01 18:10:03 +00:00
builder . Services . AddSingleton ( new HttpClient
{
BaseAddress = new Uri ( $"http://localhost:{appPort}" )
} ) ;
builder . WebHost . UseUrls ( $"http://localhost:{appPort}" ) ;
2024-05-10 19:52:37 +00:00
2024-09-01 18:10:03 +00:00
#if DEBUG
builder . WebHost . UseWebRoot ( "wwwroot" ) ;
builder . WebHost . UseStaticWebAssets ( ) ;
#endif
// Execute the builder to get the app:
var app = builder . Build ( ) ;
2025-03-22 20:12:14 +00:00
// Get the logging factory for e.g., static classes:
LOGGER_FACTORY = app . Services . GetRequiredService < ILoggerFactory > ( ) ;
2025-02-15 14:41:12 +00:00
// Get a program logger:
var programLogger = app . Services . GetRequiredService < ILogger < Program > > ( ) ;
programLogger . LogInformation ( "Starting the AI Studio server." ) ;
2025-02-17 11:33:34 +00:00
// Store the service provider (DI). We need it later for some classes,
// which are not part of the request pipeline:
SERVICE_PROVIDER = app . Services ;
2024-09-01 18:10:03 +00:00
// Initialize the encryption service:
2025-02-15 14:41:12 +00:00
programLogger . LogInformation ( "Initializing the encryption service." ) ;
2024-09-01 18:10:03 +00:00
var encryptionLogger = app . Services . GetRequiredService < ILogger < Encryption > > ( ) ;
var encryption = new Encryption ( encryptionLogger , secretPassword , secretKeySalt ) ;
var encryptionInitializer = encryption . Initialize ( ) ;
// Set the logger for the Rust service:
2025-02-15 14:41:12 +00:00
programLogger . LogInformation ( "Initializing the Rust service." ) ;
2024-09-01 18:10:03 +00:00
var rustLogger = app . Services . GetRequiredService < ILogger < RustService > > ( ) ;
rust . SetLogger ( rustLogger ) ;
rust . SetEncryptor ( encryption ) ;
RUST_SERVICE = rust ;
ENCRYPTION = encryption ;
2025-02-15 14:41:12 +00:00
programLogger . LogInformation ( "Initialize internal file system." ) ;
2024-09-01 18:10:03 +00:00
app . Use ( Redirect . HandlerContentAsync ) ;
#if DEBUG
app . UseStaticFiles ( ) ;
app . UseDeveloperExceptionPage ( ) ;
#else
var fileProvider = new ManifestEmbeddedFileProvider ( Assembly . GetAssembly ( type : typeof ( Program ) ) ! , "wwwroot" ) ;
app . UseStaticFiles ( new StaticFileOptions
{
FileProvider = fileProvider ,
RequestPath = string . Empty ,
} ) ;
2024-05-18 19:50:46 +00:00
#endif
2024-09-01 18:10:03 +00:00
app . UseAntiforgery ( ) ;
app . MapRazorComponents < App > ( )
. AddInteractiveServerRenderMode ( ) ;
2024-03-28 21:26:48 +00:00
2024-09-01 18:10:03 +00:00
var serverTask = app . RunAsync ( ) ;
2025-02-15 14:41:12 +00:00
programLogger . LogInformation ( "Server was started successfully." ) ;
2024-05-12 12:32:34 +00:00
2024-09-01 18:10:03 +00:00
await encryptionInitializer ;
await rust . AppIsReady ( ) ;
2025-02-15 14:41:12 +00:00
programLogger . LogInformation ( "The AI Studio server is ready." ) ;
2025-05-31 16:57:40 +00:00
//
// Read the enterprise environment for the current user's configuration:
//
var enterpriseConfigServerUrl = await RUST_SERVICE . EnterpriseEnvConfigServerUrl ( ) ;
var enterpriseConfigId = await RUST_SERVICE . EnterpriseEnvConfigId ( ) ;
switch ( enterpriseConfigServerUrl )
{
case null when enterpriseConfigId = = Guid . Empty :
programLogger . LogInformation ( "AI Studio runs without an enterprise configuration." ) ;
break ;
case null :
programLogger . LogWarning ( $"AI Studio runs with an enterprise configuration id ('{enterpriseConfigId}'), but the configuration server URL is not set." ) ;
break ;
case not null when enterpriseConfigId = = Guid . Empty :
programLogger . LogWarning ( $"AI Studio runs with an enterprise configuration server URL ('{enterpriseConfigServerUrl}'), but the configuration ID is not set." ) ;
break ;
default :
programLogger . LogInformation ( $"AI Studio runs with an enterprise configuration id ('{enterpriseConfigId}') and configuration server URL ('{enterpriseConfigServerUrl}')." ) ;
break ;
}
2025-02-15 14:41:12 +00:00
TaskScheduler . UnobservedTaskException + = ( sender , taskArgs ) = >
{
programLogger . LogError ( taskArgs . Exception , $"Unobserved task exception by sender '{sender ?? " n / a "}'." ) ;
taskArgs . SetObserved ( ) ;
} ;
2024-09-01 18:10:03 +00:00
await serverTask ;
2025-02-17 11:33:34 +00:00
RUST_SERVICE . Dispose ( ) ;
2025-03-30 18:34:30 +00:00
PluginFactory . Dispose ( ) ;
2025-02-17 11:33:34 +00:00
programLogger . LogInformation ( "The AI Studio server was stopped." ) ;
2024-09-01 18:10:03 +00:00
}
}