diff --git a/docs/site/.vitepress/config.ts b/docs/site/.vitepress/config.ts index 483ca4d2..da13d892 100644 --- a/docs/site/.vitepress/config.ts +++ b/docs/site/.vitepress/config.ts @@ -55,6 +55,15 @@ export default defineConfig({ { text: 'Inertia.js Integration', link: '/guide/inertia' }, ], }, + { + text: 'Infrastructure', + items: [ + { text: 'Background Jobs', link: '/guide/background-jobs' }, + { text: 'File Storage', link: '/guide/file-storage' }, + { text: 'Localization', link: '/guide/localization' }, + { text: 'AI Agents & RAG', link: '/guide/ai-agents' }, + ], + }, ], '/frontend/': [ diff --git a/docs/site/getting-started/introduction.md b/docs/site/getting-started/introduction.md index ac291a10..449ba309 100644 --- a/docs/site/getting-started/introduction.md +++ b/docs/site/getting-started/introduction.md @@ -100,6 +100,32 @@ sm new feature Products/Browse # add a feature to an existing module sm doctor --fix # validate project structure, auto-fix issues ``` +### AI Agents & RAG + +Build AI-powered features with built-in support for multiple LLM providers, tool calling, and retrieval-augmented generation: + +- **Multi-provider AI** -- Anthropic (Claude), OpenAI, Azure OpenAI, and Ollama behind a unified `IChatClient` interface +- **Agent runtime** -- define agents with custom instructions, tools, and guardrails. Supports streaming (SSE) responses +- **Tool discovery** -- mark methods with `[AgentTool]` for automatic tool registration +- **RAG pipeline** -- index documents into a vector store and inject relevant context into agent conversations +- **Built-in safety** -- rate limiting, token tracking, PII redaction, and prompt injection detection + +### File Storage + +A pluggable file storage abstraction with providers for local filesystem, AWS S3, and Azure Blob Storage. The FileStorage module adds HTTP upload/download endpoints, folder browsing, and a database-backed file registry. + +### Background Jobs + +Schedule and monitor long-running tasks with the BackgroundJobs module: + +- Immediate, delayed, and CRON-based recurring job scheduling +- Real-time progress reporting and structured logging +- Admin dashboard with job monitoring, retry, and cancellation + +### Localization + +Built-in multi-language support with embedded JSON locale files, automatic locale resolution (user settings, Accept-Language header), and a React `useTranslation()` hook for frontend integration. + ### Multi-Provider Database SimpleModule supports multiple database providers with automatic schema isolation: @@ -169,8 +195,12 @@ You don't write registration code, startup configuration, or reflection-based di | Build tooling | Vite, Tailwind CSS 4 | | Source generation | Roslyn incremental generators | | Component library | Radix UI | -| Testing | xUnit.v3, FluentAssertions, Bogus | +| AI | Anthropic, OpenAI, Azure OpenAI, Ollama via Microsoft.Extensions.AI | +| RAG | Vector search with PostgreSQL or in-memory stores | +| Testing | xUnit.v3, FluentAssertions, Bogus, Playwright, BenchmarkDotNet, NBomber | | Database | SQLite, PostgreSQL, SQL Server via EF Core | +| File storage | Local, AWS S3, Azure Blob Storage | +| Job scheduling | TickerQ with CRON support | | CLI | System.CommandLine | ## Next Steps diff --git a/docs/site/getting-started/project-structure.md b/docs/site/getting-started/project-structure.md index e3028395..35d68968 100644 --- a/docs/site/getting-started/project-structure.md +++ b/docs/site/getting-started/project-structure.md @@ -10,32 +10,60 @@ A SimpleModule solution follows a consistent directory layout that separates the ``` MyApp/ -├── framework/ # Core framework packages +├── framework/ # Core framework packages │ ├── SimpleModule.Core/ │ ├── SimpleModule.Generator/ │ ├── SimpleModule.Database/ │ ├── SimpleModule.Blazor/ │ ├── SimpleModule.Hosting/ -│ └── SimpleModule.DevTools/ -├── modules/ # Your feature modules -│ ├── Products/ +│ ├── SimpleModule.DevTools/ +│ ├── SimpleModule.Agents/ # AI agent runtime and registry +│ ├── SimpleModule.AI.Anthropic/ # Claude API provider +│ ├── SimpleModule.AI.OpenAI/ # OpenAI API provider +│ ├── SimpleModule.AI.AzureOpenAI/ # Azure OpenAI provider +│ ├── SimpleModule.AI.Ollama/ # Ollama local model provider +│ ├── SimpleModule.Rag/ # RAG pipeline and knowledge store +│ ├── SimpleModule.Rag.StructuredRag/ # Structured RAG pipeline +│ ├── SimpleModule.Rag.VectorStore.InMemory/ # In-memory vector store (dev) +│ ├── SimpleModule.Rag.VectorStore.Postgres/ # PostgreSQL vector store +│ ├── SimpleModule.Storage/ # Storage provider abstraction +│ ├── SimpleModule.Storage.Local/ # Local filesystem storage +│ ├── SimpleModule.Storage.S3/ # AWS S3 storage +│ └── SimpleModule.Storage.Azure/ # Azure Blob storage +├── modules/ # Your feature modules +│ ├── Admin/ +│ ├── Agents/ +│ ├── AuditLogs/ +│ ├── BackgroundJobs/ +│ ├── Dashboard/ +│ ├── FeatureFlags/ +│ ├── FileStorage/ +│ ├── Localization/ +│ ├── Marketplace/ +│ ├── OpenIddict/ │ ├── Orders/ -│ └── Settings/ -├── packages/ # Frontend npm packages +│ ├── PageBuilder/ +│ ├── Permissions/ +│ ├── Products/ +│ ├── Rag/ +│ ├── Settings/ +│ ├── Tenants/ +│ └── Users/ +├── packages/ # Frontend npm packages │ ├── SimpleModule.Client/ │ ├── SimpleModule.UI/ │ └── SimpleModule.Theme.Default/ ├── template/ -│ └── SimpleModule.Host/ # The host application +│ └── SimpleModule.Host/ # The host application │ ├── ClientApp/ │ ├── Program.cs │ └── wwwroot/ ├── cli/ -│ └── SimpleModule.Cli/ # The sm CLI tool -├── tests/ # Framework-level test projects -├── SimpleModule.slnx # Solution file -├── package.json # Root npm workspace config -└── Directory.Build.props # Shared MSBuild properties +│ └── SimpleModule.Cli/ # The sm CLI tool +├── tests/ # Framework-level test projects +├── SimpleModule.slnx # Solution file +├── package.json # Root npm workspace config +└── Directory.Build.props # Shared MSBuild properties ``` ## Framework Projects @@ -98,6 +126,35 @@ Module registration infrastructure. Provides the runtime plumbing that the gener Development utilities including hot reload support, diagnostic middleware, and developer experience tooling that is excluded from production builds. +### SimpleModule.Agents + +AI agent runtime and orchestration. Provides `IAgentRegistry` for agent discovery, `AgentChatService` for chat (streaming and non-streaming), `IAgentToolProvider` with `[AgentTool]` attribute for tool discovery, and middleware for rate limiting, token tracking, and guardrails (PII redaction, prompt injection detection). + +### SimpleModule.AI.* + +AI provider integrations implementing `IChatClient` from Microsoft.Extensions.AI: + +- **SimpleModule.AI.Anthropic** -- Claude API via the Anthropic SDK +- **SimpleModule.AI.OpenAI** -- OpenAI API +- **SimpleModule.AI.AzureOpenAI** -- Azure OpenAI Service +- **SimpleModule.AI.Ollama** -- Ollama for local model inference + +### SimpleModule.Rag + +Retrieval-augmented generation pipeline. Defines `IRagPipeline` for querying a knowledge base and `IKnowledgeStore` for indexing documents. Includes `KnowledgeIndexingHostedService` for background indexing with deduplication. + +- **SimpleModule.Rag.StructuredRag** -- Structured RAG implementation (table, graph, algorithm, catalogue, chunk formats) +- **SimpleModule.Rag.VectorStore.InMemory** -- In-memory vector store for development and testing +- **SimpleModule.Rag.VectorStore.Postgres** -- PostgreSQL-backed vector store for production + +### SimpleModule.Storage + +File storage abstraction with `IStorageProvider` interface (save, get, delete, exists, list). Three provider implementations: + +- **SimpleModule.Storage.Local** -- local filesystem storage +- **SimpleModule.Storage.S3** -- AWS S3 and S3-compatible services +- **SimpleModule.Storage.Azure** -- Azure Blob Storage + ## Module Structure Every module follows a **three-project pattern**: implementation, contracts, and tests. diff --git a/docs/site/guide/ai-agents.md b/docs/site/guide/ai-agents.md new file mode 100644 index 00000000..a03822ed --- /dev/null +++ b/docs/site/guide/ai-agents.md @@ -0,0 +1,224 @@ +--- +outline: deep +--- + +# AI Agents + +SimpleModule includes a framework for building AI-powered agents with tool calling, RAG (retrieval-augmented generation), and streaming responses. The agent system supports multiple AI providers and is configured through the standard settings system. + +## Architecture + +The agent stack has three layers: + +1. **AI Providers** (`SimpleModule.AI.*`) -- `IChatClient` implementations for different LLM providers +2. **Agent Runtime** (`SimpleModule.Agents`) -- orchestration, tool discovery, session management, and middleware +3. **RAG Pipeline** (`SimpleModule.Rag`) -- knowledge indexing and retrieval for context injection + +## Setting Up an AI Provider + +Register one AI provider in your host application. Each provider reads from a dedicated configuration section. + +### Anthropic (Claude) + +```csharp +builder.Services.AddAnthropicAI(builder.Configuration); +``` + +```json +{ + "AI": { + "Anthropic": { + "ApiKey": "sk-ant-...", + "Model": "claude-sonnet-4-20250514" + } + } +} +``` + +### OpenAI + +```csharp +builder.Services.AddOpenAI(builder.Configuration); +``` + +```json +{ + "AI": { + "OpenAI": { + "ApiKey": "sk-...", + "Model": "gpt-4o" + } + } +} +``` + +### Azure OpenAI + +```csharp +builder.Services.AddAzureOpenAI(builder.Configuration); +``` + +```json +{ + "AI": { + "AzureOpenAI": { + "Endpoint": "https://your-resource.openai.azure.com", + "DeploymentName": "gpt-4o", + "ApiKey": "your-key" + } + } +} +``` + +### Ollama (Local) + +```csharp +builder.Services.AddOllamaAI(builder.Configuration); +``` + +```json +{ + "AI": { + "Ollama": { + "BaseUrl": "http://localhost:11434", + "Model": "llama3" + } + } +} +``` + +## Registering the Agent Runtime + +```csharp +builder.Services.AddSimpleModuleAgents(builder.Configuration); +``` + +This registers: +- `AgentChatService` -- handles chat requests (streaming and non-streaming) +- `IAgentRegistry` -- discovers and serves agent definitions +- `IAgentSessionStore` -- persists conversation history +- Middleware pipeline: logging, rate limiting, token tracking, retry +- Guardrails: content length limits, PII redaction, prompt injection detection + +## Defining an Agent + +Implement `IAgentDefinition` in your module: + +```csharp +public class ProductAssistant : IAgentDefinition +{ + public string Name => "product-assistant"; + public string Description => "Helps users find and manage products"; + public string Instructions => """ + You are a product catalog assistant. + Help users search, compare, and manage products. + """; + + // Optional overrides + public int? MaxTokens => 2048; + public double? Temperature => 0.5; + public bool? EnableRag => true; + public string? RagCollectionName => "products"; +} +``` + +## Creating Agent Tools + +Implement `IAgentToolProvider` and mark methods with `[AgentTool]`: + +```csharp +public class ProductTools(IProductContracts products) : IAgentToolProvider +{ + [AgentTool(Name = "search_products", Description = "Search the product catalog")] + public async Task> SearchAsync(string query, CancellationToken ct) + { + return await products.SearchAsync(query, ct); + } + + [AgentTool(Name = "get_product", Description = "Get product details by ID")] + public async Task GetByIdAsync(int id, CancellationToken ct) + { + return await products.GetByIdAsync(id, ct); + } +} +``` + +Tools are automatically discovered via DI and converted to AI function definitions that the LLM can call. + +## Chat API + +### Non-Streaming + +```csharp +var response = await agentChatService.ChatAsync( + "product-assistant", + new AgentChatRequest { Message = "Find me all products under $50" }, + cancellationToken); +``` + +### Streaming (SSE) + +```csharp +await foreach (var chunk in agentChatService.ChatStreamAsync( + "product-assistant", + new AgentChatRequest { Message = "Compare these two products" }, + cancellationToken)) +{ + // Send chunk to client via SSE +} +``` + +## RAG Integration + +When `EnableRag` is `true` on an agent definition, the runtime automatically: + +1. Queries the knowledge base via `IRagPipeline.QueryAsync()` +2. Injects matching knowledge chunks into the system message +3. Sends the enriched context to the LLM + +### Setting Up RAG + +```csharp +builder.Services.AddSimpleModuleRag(builder.Configuration); +``` + +```json +{ + "Rag": { + "DefaultTopK": 5, + "MinScore": 0.7, + "EmbeddingDimension": 1536, + "IndexOnStartup": true + } +} +``` + +Choose a vector store: + +```csharp +// Development +builder.Services.AddInMemoryVectorStore(); + +// Production +builder.Services.AddPostgresVectorStore(builder.Configuration); +``` + +## Agent Settings + +The module registers these settings (manageable via the admin UI): + +| Setting | Default | Description | +|---------|---------|-------------| +| `Agents.Enabled` | `true` | Global kill switch | +| `Agents.MaxTokens` | `4096` | Default max tokens per response | +| `Agents.Temperature` | `0.7` | Default sampling temperature | +| `Agents.EnableRag` | `true` | Enable RAG context injection | +| `Agents.EnableStreaming` | `true` | Allow streaming responses | +| `Agents.RateLimit.RequestsPerMinute` | `60` | Rate limit per user | +| `Agents.RateLimit.TokensPerMinute` | `100000` | Token rate limit per user | + +## Next Steps + +- [File Storage](/guide/file-storage) -- storing files for RAG knowledge indexing +- [Settings](/guide/settings) -- runtime configuration for agent behavior +- [Modules](/guide/modules) -- structuring agent tools within modules diff --git a/docs/site/guide/background-jobs.md b/docs/site/guide/background-jobs.md new file mode 100644 index 00000000..7daac4f4 --- /dev/null +++ b/docs/site/guide/background-jobs.md @@ -0,0 +1,162 @@ +--- +outline: deep +--- + +# Background Jobs + +SimpleModule provides a background job system built on [TickerQ](https://github.com/nickofc/TickerQ) for scheduling and executing long-running tasks outside the HTTP request pipeline. Jobs support progress reporting, structured logging, retries, and CRON-based recurring schedules. + +## Defining a Job + +Implement `IModuleJob` and register it in your module: + +```csharp +public class GenerateReportJob : IModuleJob +{ + public async Task ExecuteAsync( + IJobExecutionContext context, + CancellationToken cancellationToken) + { + var data = context.GetData(); + context.ReportProgress(0, "Starting report generation"); + + // Do work... + context.Log("Processing 500 records"); + context.ReportProgress(50, "Halfway done"); + + // More work... + context.ReportProgress(100, "Report complete"); + } +} +``` + +Register the job in your module's `ConfigureServices`: + +```csharp +services.AddModuleJob(); +``` + +## Scheduling Jobs + +Inject `IBackgroundJobs` to enqueue, schedule, or create recurring jobs: + +### Immediate Execution + +```csharp +var jobId = await backgroundJobs.EnqueueAsync( + new ReportRequest { Type = "monthly" }); +``` + +### Delayed Execution + +```csharp +var jobId = await backgroundJobs.ScheduleAsync( + executeAt: DateTimeOffset.UtcNow.AddHours(1), + data: new ReportRequest { Type = "monthly" }); +``` + +### Recurring Jobs (CRON) + +```csharp +var recurringId = await backgroundJobs.AddRecurringAsync( + name: "Monthly Report", + cronExpression: "0 0 1 * *", // 1st of every month at midnight + data: new ReportRequest { Type = "monthly" }); +``` + +CRON expressions use the standard 5-field format: `minute hour day month dayOfWeek`. + +### Managing Jobs + +```csharp +// Cancel a running or pending job +await backgroundJobs.CancelAsync(jobId); + +// Check job status +var status = await backgroundJobs.GetStatusAsync(jobId); + +// Toggle a recurring job on/off +await backgroundJobs.ToggleRecurringAsync(recurringId); + +// Remove a recurring job +await backgroundJobs.RemoveRecurringAsync(recurringId); +``` + +## Job Execution Context + +During execution, `IJobExecutionContext` provides: + +| Method | Purpose | +|--------|---------| +| `GetData()` | Deserialize the job's input data | +| `ReportProgress(percentage, message?)` | Update progress (0-100) with optional message | +| `Log(message)` | Add a timestamped log entry | +| `JobId` | The unique identifier for this execution | + +Progress updates are batched and flushed to the database periodically (configurable via `ProgressFlushInterval`). + +## Job States + +| State | Description | +|-------|-------------| +| `Pending` | Queued, waiting to execute | +| `Running` | Currently executing | +| `Completed` | Finished successfully | +| `Failed` | Execution threw an exception | +| `Cancelled` | Cancelled by user or system | +| `Skipped` | Skipped (e.g., overlapping recurring run) | + +## API Endpoints + +All endpoints require `BackgroundJobs.ViewJobs` or `BackgroundJobs.ManageJobs` permissions. + +| Method | Route | Description | +|--------|-------|-------------| +| `GET` | `/api/jobs/` | List jobs with optional state/type filters | +| `GET` | `/api/jobs/{id}` | Job detail with logs and progress | +| `POST` | `/api/jobs/{id}/cancel` | Cancel a pending or running job | +| `POST` | `/api/jobs/{id}/retry` | Retry a failed job | +| `GET` | `/api/jobs/recurring` | List all recurring jobs | +| `POST` | `/api/jobs/recurring/{id}/toggle` | Enable/disable a recurring job | +| `DELETE` | `/api/jobs/recurring/{id}` | Delete a recurring job | + +## Admin UI + +The module includes four admin pages at `/admin/jobs`: + +- **Dashboard** -- overview with active, failed, and recurring job counts +- **Job List** -- paginated, filterable list of all jobs with state and progress +- **Job Detail** -- real-time progress bar, logs, error messages, and retry/cancel actions (auto-refreshes every 2 seconds while running) +- **Recurring Jobs** -- manage recurring schedules with toggle and delete actions + +## Configuration + +```csharp +public class BackgroundJobsModuleOptions : IModuleOptions +{ + public int MaxConcurrency { get; set; } = Environment.ProcessorCount; + public int ProgressFlushBatchSize { get; set; } = 50; + public TimeSpan ProgressFlushInterval { get; set; } = TimeSpan.FromSeconds(2); + public int MaxLogEntries { get; set; } = 1000; +} +``` + +## Contract Interface + +Query job data from other modules via `IBackgroundJobsContracts`: + +```csharp +public interface IBackgroundJobsContracts +{ + Task> GetJobsAsync(JobFilter filter, CancellationToken ct); + Task GetJobDetailAsync(JobId id, CancellationToken ct); + Task> GetRecurringJobsAsync(CancellationToken ct); + Task RetryAsync(JobId id, CancellationToken ct); +} +``` + +## Next Steps + +- [Modules](/guide/modules) -- module structure and service registration +- [Permissions](/guide/permissions) -- protecting job management endpoints +- [Events](/guide/events) -- triggering jobs from event handlers diff --git a/docs/site/guide/file-storage.md b/docs/site/guide/file-storage.md new file mode 100644 index 00000000..18176d9d --- /dev/null +++ b/docs/site/guide/file-storage.md @@ -0,0 +1,151 @@ +--- +outline: deep +--- + +# File Storage + +SimpleModule provides a file storage abstraction with pluggable providers for local filesystem, AWS S3, and Azure Blob Storage. The FileStorage module adds HTTP endpoints, database tracking, and an admin UI on top of this abstraction. + +## Storage Providers + +### IStorageProvider Interface + +All providers implement a common interface: + +```csharp +public interface IStorageProvider +{ + Task SaveAsync(string path, Stream content, string contentType); + Task GetAsync(string path); + Task DeleteAsync(string path); + Task ExistsAsync(string path); + Task> ListAsync(string prefix); +} +``` + +### Local Storage + +Stores files on the local filesystem. Best for development and single-server deployments. + +```csharp +builder.Services.AddLocalStorage(builder.Configuration); +``` + +```json +{ + "Storage": { + "Provider": "Local", + "Local": { + "BasePath": "./storage" + } + } +} +``` + +### AWS S3 + +```csharp +builder.Services.AddS3Storage(builder.Configuration); +``` + +```json +{ + "Storage": { + "Provider": "S3", + "S3": { + "BucketName": "my-bucket", + "AccessKey": "your-access-key", + "SecretKey": "your-secret-key", + "Region": "us-east-1", + "ServiceUrl": "", + "ForcePathStyle": false + } + } +} +``` + +Set `ServiceUrl` for S3-compatible services (MinIO, DigitalOcean Spaces). Set `ForcePathStyle` to `true` for path-style URL access. + +### Azure Blob Storage + +```csharp +builder.Services.AddAzureBlobStorage(builder.Configuration); +``` + +```json +{ + "Storage": { + "Provider": "Azure", + "Azure": { + "ConnectionString": "DefaultEndpointsProtocol=https;AccountName=...", + "ContainerName": "files" + } + } +} +``` + +## FileStorage Module + +The FileStorage module provides HTTP endpoints and a database-backed file registry on top of the storage abstraction. + +### API Endpoints + +| Method | Route | Permission | Description | +|--------|-------|------------|-------------| +| `POST` | `/api/files/` | `FileStorage.Upload` | Upload a file (multipart form) | +| `GET` | `/api/files/` | `FileStorage.View` | List files (optional `?folder=` filter) | +| `GET` | `/api/files/{id}` | `FileStorage.View` | Get file metadata by ID | +| `GET` | `/api/files/{id}/download` | `FileStorage.View` | Download file content | +| `DELETE` | `/api/files/{id}` | `FileStorage.Delete` | Delete a file | +| `GET` | `/api/files/folders` | `FileStorage.View` | List folders (optional `?parent=` filter) | + +### Browse UI + +A file browser view at `/files/browse` lets users navigate folders, upload files, and download or delete existing files. + +### Module Settings + +| Setting | Default | Description | +|---------|---------|-------------| +| `FileStorage.MaxFileSizeMb` | `50` | Maximum upload size in megabytes | +| `FileStorage.AllowedExtensions` | `.jpg,.jpeg,.png,.gif,.pdf,.doc,.docx,.xls,.xlsx,.zip` | Comma-separated allowed file extensions | + +### Using from Other Modules + +Inject `IFileStorageContracts` to interact with file storage from any module: + +```csharp +public interface IFileStorageContracts +{ + Task> GetFilesAsync(string? folder = null); + Task GetFileByIdAsync(FileStorageId id); + Task UploadFileAsync( + Stream content, string fileName, string contentType, string? folder = null); + Task DeleteFileAsync(FileStorageId id); + Task DownloadFileAsync(FileStorageId id); + Task> GetFoldersAsync(string? parentFolder = null); +} +``` + +## Provider Comparison + +| Feature | Local | S3 | Azure | +|---------|-------|----|-------| +| External dependencies | None | AWSSDK.S3 | Azure.Storage.Blobs | +| Folder support | Physical directories | Prefix-based | Prefix-based | +| Pagination | N/A | ListObjectsV2 | GetBlobsByHierarchyAsync | +| Best for | Development, single server | Production, multi-region | Production, Azure ecosystem | + +## Path Handling + +All paths are normalized to forward slashes internally. The `StoragePathHelper` utility provides safe path operations: + +- Path traversal attacks are blocked (paths cannot escape the base directory) +- Leading/trailing slashes are normalized +- File names and folder names are extracted consistently across providers + +## Next Steps + +- [AI Agents](/guide/ai-agents) -- using file storage with RAG knowledge indexing +- [Configuration](/reference/configuration) -- all storage configuration options +- [Deployment](/advanced/deployment) -- production storage configuration diff --git a/docs/site/guide/localization.md b/docs/site/guide/localization.md new file mode 100644 index 00000000..aa5a3769 --- /dev/null +++ b/docs/site/guide/localization.md @@ -0,0 +1,170 @@ +--- +outline: deep +--- + +# Localization + +SimpleModule includes a built-in localization system that provides multi-language support across both the .NET backend and the React frontend. Translations are stored as embedded JSON resources in each module and automatically discovered at startup. + +## How It Works + +1. Each module embeds locale JSON files (e.g., `en.json`, `es.json`) as assembly resources +2. At startup, the framework scans all module assemblies and loads translations into frozen (immutable) dictionaries +3. On each request, middleware resolves the user's preferred locale +4. Translations are injected into Inertia shared props, making them available to all React components + +## Adding Translations to a Module + +### 1. Create Locale Files + +Add a `Locales/` directory to your module with JSON files named by locale code: + +``` +modules/Products/src/Products/ +├── Locales/ +│ ├── en.json +│ └── es.json +``` + +Translation keys use dot notation: + +```json +// Locales/en.json +{ + "Browse.Title": "Products", + "Browse.Description": "Browse the product catalog.", + "Manage.DeleteConfirm": "Are you sure you want to delete \"{name}\"?" +} +``` + +```json +// Locales/es.json +{ + "Browse.Title": "Productos", + "Browse.Description": "Navegar el catálogo de productos.", + "Manage.DeleteConfirm": "¿Estás seguro de que deseas eliminar \"{name}\"?" +} +``` + +### 2. Embed as Resources + +In your module's `.csproj`, mark the locale files as embedded resources: + +```xml + + + +``` + +### 3. Use Translations in React + +Import the `useTranslation` hook from `@simplemodule/client`: + +```tsx +import { useTranslation } from '@simplemodule/client'; + +export default function Browse({ products }) { + const { t, locale } = useTranslation('Products'); + + return ( +
+

{t('Browse.Title')}

+

{t('Browse.Description')}

+
+ ); +} +``` + +The hook accepts a namespace (your module name) and returns: +- **`t(key, params?)`** -- translates a key, with optional parameter interpolation +- **`locale`** -- the current locale string (e.g., `"en"`, `"es"`) + +### Parameter Interpolation + +Use `{paramName}` placeholders in translation values: + +```tsx +// Translation: "Are you sure you want to delete \"{name}\"?" +t('Manage.DeleteConfirm', { name: product.name }) +``` + +### 4. Type-Safe Keys (Optional) + +Create a `keys.ts` file for compile-time key safety: + +```typescript +// Locales/keys.ts +export const ProductsKeys = { + Browse: { + Title: 'Browse.Title', + Description: 'Browse.Description', + }, + Manage: { + DeleteConfirm: 'Manage.DeleteConfirm', + }, +} as const; +``` + +Then use it in components: + +```tsx +import { ProductsKeys } from '../Locales/keys'; + +const { t } = useTranslation('Products'); +t(ProductsKeys.Browse.Title); +``` + +## Locale Resolution + +The `LocaleResolutionMiddleware` determines the user's locale using this priority order: + +1. **User setting** -- the `app.language` setting stored in the database (cached for 5 minutes) +2. **Accept-Language header** -- parsed with quality values (cached for 30 minutes) +3. **Configuration default** -- `Localization:DefaultLocale` from `appsettings.json` +4. **Hardcoded fallback** -- `"en"` + +## Backend Usage + +The localization system integrates with .NET's `IStringLocalizer`: + +```csharp +public class MyService(IStringLocalizer localizer) +{ + public string GetWelcome(string name) => + localizer["welcome", name]; +} +``` + +## Fallback Behavior + +- If a key is missing for the requested locale, the system falls back to English (`"en"`) +- If the key is missing in English too, the raw key string is returned + +## Configuration + +```json +{ + "Localization": { + "DefaultLocale": "en" + } +} +``` + +## Contract Interface + +Other modules can access translations programmatically through `ILocalizationContracts`: + +```csharp +public interface ILocalizationContracts +{ + string? GetTranslation(string key, string locale); + IReadOnlyDictionary GetAllTranslations(string locale); + IReadOnlyList GetSupportedLocales(); +} +``` + +## Next Steps + +- [Settings](/guide/settings) -- user-scoped settings that store language preferences +- [Inertia.js Integration](/guide/inertia) -- how shared props deliver translations to React +- [Modules](/guide/modules) -- module structure and embedded resources diff --git a/docs/site/index.md b/docs/site/index.md index 0e09b76b..5a229ec5 100644 --- a/docs/site/index.md +++ b/docs/site/index.md @@ -32,5 +32,14 @@ features: - icon: 🗄️ title: Multi-Provider Database details: SQLite for development, PostgreSQL or SQL Server for production. Schema isolation per module with automatic table prefix or schema management. + - icon: 🤖 + title: AI Agents & RAG + details: Build AI-powered features with multi-provider LLM support (Claude, OpenAI, Ollama), tool calling, and retrieval-augmented generation with vector search. + - icon: 📁 + title: File Storage & Background Jobs + details: Pluggable file storage (local, S3, Azure), background job scheduling with CRON support, real-time progress tracking, and admin dashboards. + - icon: 🌐 + title: Localization + details: Built-in i18n with embedded JSON locale files, automatic locale resolution, and a React useTranslation() hook. Supports parameter interpolation and fallback chains. --- diff --git a/docs/site/reference/configuration.md b/docs/site/reference/configuration.md index 30a0c963..4c2ea36d 100644 --- a/docs/site/reference/configuration.md +++ b/docs/site/reference/configuration.md @@ -200,6 +200,182 @@ ASP.NET loads configuration from multiple sources. Later sources override earlie For the `Development` environment, the effective configuration merges `appsettings.json` with `appsettings.Development.json`, with the Development file winning on conflicts. +## AI Provider Configuration + +SimpleModule supports multiple AI providers. Configure one in `appsettings.json`: + +### Anthropic (Claude) + +```json +{ + "AI": { + "Anthropic": { + "ApiKey": "sk-ant-...", + "Model": "claude-sonnet-4-20250514" + } + } +} +``` + +### OpenAI + +```json +{ + "AI": { + "OpenAI": { + "ApiKey": "sk-...", + "Model": "gpt-4o" + } + } +} +``` + +### Azure OpenAI + +```json +{ + "AI": { + "AzureOpenAI": { + "Endpoint": "https://your-resource.openai.azure.com", + "DeploymentName": "gpt-4o", + "ApiKey": "your-key" + } + } +} +``` + +### Ollama (Local) + +```json +{ + "AI": { + "Ollama": { + "BaseUrl": "http://localhost:11434", + "Model": "llama3" + } + } +} +``` + +::: tip +Use environment variables for API keys in production: `AI__Anthropic__ApiKey`, `AI__OpenAI__ApiKey`, etc. +::: + +## Agent Configuration + +The agent runtime is configured under the `Agents` section: + +```json +{ + "Agents": { + "Enabled": true, + "MaxTokens": 4096, + "Temperature": 0.7, + "EnableRag": true, + "EnableStreaming": true, + "SessionTimeout": "00:30:00", + "RateLimit": { + "RequestsPerMinute": 60, + "TokensPerMinute": 100000 + } + } +} +``` + +| Key | Default | Description | +|-----|---------|-------------| +| `Agents:Enabled` | `true` | Global kill switch for AI agents | +| `Agents:MaxTokens` | `4096` | Default max tokens per response | +| `Agents:Temperature` | `0.7` | Default sampling temperature | +| `Agents:EnableRag` | `true` | Enable RAG context injection | +| `Agents:EnableStreaming` | `true` | Allow SSE streaming responses | +| `Agents:SessionTimeout` | `00:30:00` | Session inactivity timeout | +| `Agents:RateLimit:RequestsPerMinute` | `60` | Per-user request rate limit | +| `Agents:RateLimit:TokensPerMinute` | `100000` | Per-user token rate limit | + +## RAG Configuration + +```json +{ + "Rag": { + "DefaultTopK": 5, + "MinScore": 0.7, + "EmbeddingDimension": 1536, + "IndexOnStartup": true + } +} +``` + +| Key | Default | Description | +|-----|---------|-------------| +| `Rag:DefaultTopK` | `5` | Number of results per query | +| `Rag:MinScore` | `0.7` | Minimum similarity score threshold | +| `Rag:EmbeddingDimension` | `1536` | Vector embedding dimension | +| `Rag:IndexOnStartup` | `true` | Index knowledge base on application start | + +## File Storage Configuration + +### Local Storage + +```json +{ + "Storage": { + "Provider": "Local", + "Local": { + "BasePath": "./storage" + } + } +} +``` + +### AWS S3 + +```json +{ + "Storage": { + "Provider": "S3", + "S3": { + "BucketName": "my-bucket", + "AccessKey": "your-access-key", + "SecretKey": "your-secret-key", + "Region": "us-east-1", + "ServiceUrl": "", + "ForcePathStyle": false + } + } +} +``` + +### Azure Blob Storage + +```json +{ + "Storage": { + "Provider": "Azure", + "Azure": { + "ConnectionString": "DefaultEndpointsProtocol=https;AccountName=...", + "ContainerName": "files" + } + } +} +``` + +::: warning +Never commit storage credentials to source control. Use environment variables: `Storage__S3__AccessKey`, `Storage__Azure__ConnectionString`, etc. +::: + +## Localization Configuration + +```json +{ + "Localization": { + "DefaultLocale": "en" + } +} +``` + +The default locale is used when no user setting or Accept-Language header matches a supported locale. + ## Next Steps - [API Reference](/reference/api) -- complete interface and type documentation diff --git a/docs/site/testing/e2e-tests.md b/docs/site/testing/e2e-tests.md index 38e25853..1671b43e 100644 --- a/docs/site/testing/e2e-tests.md +++ b/docs/site/testing/e2e-tests.md @@ -271,6 +271,23 @@ pages/ viewer.page.ts openiddict/ clients.page.ts + admin/ + roles.page.ts + users.page.ts + audit-logs/ + browse.page.ts + dashboard.page.ts + background-jobs/ + dashboard.page.ts + list.page.ts + recurring.page.ts + marketplace/ + browse.page.ts + tenants/ + list.page.ts + users/ + list.page.ts + profile.page.ts ``` ## CI Integration diff --git a/docs/site/testing/overview.md b/docs/site/testing/overview.md index e51d69f0..8cbc265c 100644 --- a/docs/site/testing/overview.md +++ b/docs/site/testing/overview.md @@ -81,6 +81,45 @@ CI runs tests against two database providers: The `SimpleModuleWebApplicationFactory` automatically uses SQLite in-memory with a shared connection kept open for the lifetime of the test run. +## Benchmarks (BenchmarkDotNet) + +Micro-benchmarks measure endpoint latency and JSON serialization performance for every module: + +```bash +# Run all benchmarks +dotnet run -c Release --project tests/SimpleModule.Benchmarks + +# Run benchmarks for a specific module +dotnet run -c Release --project tests/SimpleModule.Benchmarks -- --filter "*Products*" +``` + +Benchmarks use `SimpleModuleWebApplicationFactory` with test auth headers for low-overhead measurement of CRUD operations via an in-process TestServer. + +## Load Tests (NBomber) + +HTTP load tests using real OAuth Bearer tokens acquired via password grant from OpenIddict: + +```bash +# Run all 11 scenarios (50 concurrent users, ~5 min) +dotnet test tests/SimpleModule.LoadTests + +# Run a single scenario +dotnet test tests/SimpleModule.LoadTests --filter "Products_Crud" +``` + +Scenarios cover all modules at 50 concurrent copies: + +- **CRUD lifecycle** -- Products, Orders, Users, PageBuilder (create, read, update, delete) +- **Read operations** -- Settings, AuditLogs, FileStorage, FeatureFlags +- **Admin operations** -- role create/delete (handles 302 Blazor SSR redirects) +- **Anonymous** -- Marketplace search and browse +- **Mixed Realistic** -- weighted workload (70% reads, 20% creates, 10% updates) + +Key infrastructure: +- `LoadTestWebApplicationFactory` with file-based SQLite + WAL mode +- `PasswordGrantTokenHandler` for OpenIddict ROPC grant +- `SqliteBusyTimeoutInterceptor` for concurrent access + ## Next Steps - [Unit tests](./unit-tests) -- testing services, validators, and event handlers in isolation diff --git a/package.json b/package.json index ff681481..e97616de 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "website:dev": "npm run dev -w website", "website:build": "npm run build -w website", "website:preview": "npm run preview -w website", - "prepare": "husky" + "prepare": "husky || true" }, "devDependencies": { "@biomejs/biome": "^2.4.10",