mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-10-21 20:58:00 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
09df19e6f5 | ||
|
|
6c4507ef82 | ||
|
|
e7707ba482 | ||
|
|
a8951fe58a | ||
|
|
af72a45035 | ||
|
|
6f76c845f1 | ||
|
|
ed8bd9d25c | ||
|
|
6150d49997 | ||
|
|
1aa27d217a | ||
|
|
5e603f9f4c | ||
|
|
21780ad481 | ||
|
|
b445600a52 | ||
|
|
6e33c361dc | ||
|
|
4562ed1c6a | ||
|
|
53f657da71 | ||
|
|
3671444d28 | ||
|
|
48f8cb3285 | ||
|
|
ea4e3f0199 | ||
|
|
891b90819b | ||
|
|
4eb58eb56d | ||
|
|
50d4e7e6dc | ||
|
|
7ae8f7b5ed | ||
|
|
4cf62672de | ||
|
|
5af6a8db3e | ||
|
|
e65874d99b | ||
|
|
8f9cd40d06 | ||
|
|
f1fa8fd8a0 | ||
|
|
0e14a3cb58 | ||
|
|
5ea92c9ced | ||
|
|
3d9e8a6f48 | ||
|
|
0f6a8c3247 | ||
|
|
9fc5c809b0 | ||
|
|
09f93c65c9 | ||
|
|
37293e4a7c | ||
|
|
8e85473b28 | ||
|
|
251b7973fa | ||
|
|
c9e1ae641f | ||
|
|
530b5f6cf8 | ||
|
|
3763b1e4a4 | ||
|
|
127778df42 | ||
|
|
c9f037bb2c | ||
|
|
f84ea772b4 | ||
|
|
a1f69c8dad | ||
|
|
cc3560fdd2 | ||
|
|
85af3ed651 | ||
|
|
3abc2bf0f1 | ||
|
|
0a504276d9 | ||
|
|
5f18cbe2bf | ||
|
|
eb39d130b9 | ||
|
|
4f8266f255 |
No files matched your search
@@ -173,6 +173,9 @@ jobs:
|
||||
pdfium_version=$(sed -n '11p' metadata.txt)
|
||||
pdfium_version=$(echo $pdfium_version | cut -d'.' -f3)
|
||||
|
||||
# Next line is the Qdrant version:
|
||||
qdrant_version="v$(sed -n '12p' metadata.txt)"
|
||||
|
||||
# Write the metadata to the environment:
|
||||
echo "APP_VERSION=${app_version}" >> $GITHUB_ENV
|
||||
echo "FORMATTED_APP_VERSION=${formatted_app_version}" >> $GITHUB_ENV
|
||||
@@ -185,6 +188,7 @@ jobs:
|
||||
echo "TAURI_VERSION=${tauri_version}" >> $GITHUB_ENV
|
||||
echo "ARCHITECTURE=${{ matrix.dotnet_runtime }}" >> $GITHUB_ENV
|
||||
echo "PDFIUM_VERSION=${pdfium_version}" >> $GITHUB_ENV
|
||||
echo "QDRANT_VERSION=${qdrant_version}" >> $GITHUB_ENV
|
||||
|
||||
# Log the metadata:
|
||||
echo "App version: '${formatted_app_version}'"
|
||||
@@ -197,6 +201,7 @@ jobs:
|
||||
echo "Tauri version: '${tauri_version}'"
|
||||
echo "Architecture: '${{ matrix.dotnet_runtime }}'"
|
||||
echo "PDFium version: '${pdfium_version}'"
|
||||
echo "Qdrant version: '${qdrant_version}'"
|
||||
|
||||
- name: Read and format metadata (Windows)
|
||||
if: matrix.platform == 'windows-latest'
|
||||
@@ -241,6 +246,9 @@ jobs:
|
||||
$pdfium_version = $metadata[10]
|
||||
$pdfium_version = $pdfium_version.Split('.')[2]
|
||||
|
||||
# Next line is the necessary Qdrant version:
|
||||
$qdrant_version = "v$($metadata[11])"
|
||||
|
||||
# Write the metadata to the environment:
|
||||
Write-Output "APP_VERSION=${app_version}" >> $env:GITHUB_ENV
|
||||
Write-Output "FORMATTED_APP_VERSION=${formatted_app_version}" >> $env:GITHUB_ENV
|
||||
@@ -252,6 +260,7 @@ jobs:
|
||||
Write-Output "MUD_BLAZOR_VERSION=${mud_blazor_version}" >> $env:GITHUB_ENV
|
||||
Write-Output "ARCHITECTURE=${{ matrix.dotnet_runtime }}" >> $env:GITHUB_ENV
|
||||
Write-Output "PDFIUM_VERSION=${pdfium_version}" >> $env:GITHUB_ENV
|
||||
Write-Output "QDRANT_VERSION=${qdrant_version}" >> $env:GITHUB_ENV
|
||||
|
||||
# Log the metadata:
|
||||
Write-Output "App version: '${formatted_app_version}'"
|
||||
@@ -264,6 +273,7 @@ jobs:
|
||||
Write-Output "Tauri version: '${tauri_version}'"
|
||||
Write-Output "Architecture: '${{ matrix.dotnet_runtime }}'"
|
||||
Write-Output "PDFium version: '${pdfium_version}'"
|
||||
Write-Output "Qdrant version: '${qdrant_version}'"
|
||||
|
||||
- name: Setup .NET
|
||||
uses: actions/setup-dotnet@v4
|
||||
@@ -334,7 +344,7 @@ jobs:
|
||||
echo "Cleaning up ..."
|
||||
rm -fr "$TMP"
|
||||
|
||||
- name: Install PDFium (Windows)
|
||||
- name: Deploy PDFium (Windows)
|
||||
if: matrix.platform == 'windows-latest'
|
||||
env:
|
||||
PDFIUM_VERSION: ${{ env.PDFIUM_VERSION }}
|
||||
@@ -385,6 +395,128 @@ jobs:
|
||||
Write-Host "Cleaning up ..."
|
||||
Remove-Item $ARCHIVE -Force -ErrorAction SilentlyContinue
|
||||
|
||||
# Try to remove the temporary directory, but ignore errors if files are still in use
|
||||
try {
|
||||
Remove-Item $TMP -Recurse -Force -ErrorAction Stop
|
||||
Write-Host "Successfully cleaned up temporary directory: $TMP"
|
||||
} catch {
|
||||
Write-Warning "Could not fully clean up temporary directory: $TMP. This is usually harmless as Windows will clean it up later. Error: $($_.Exception.Message)"
|
||||
}
|
||||
- name: Deploy Qdrant (Unix)
|
||||
if: matrix.platform != 'windows-latest'
|
||||
env:
|
||||
QDRANT_VERSION: ${{ env.QDRANT_VERSION }}
|
||||
DOTNET_RUNTIME: ${{ matrix.dotnet_runtime }}
|
||||
RUST_TARGET: ${{ matrix.rust_target }}
|
||||
run: |
|
||||
set -e
|
||||
|
||||
# Target directory:
|
||||
TDB_DIR="runtime/target/databases/qdrant"
|
||||
mkdir -p "$TDB_DIR"
|
||||
|
||||
case "${DOTNET_RUNTIME}" in
|
||||
linux-x64)
|
||||
QDRANT_FILE="x86_64-unknown-linux-gnu.tar.gz"
|
||||
DB_SOURCE="qdrant"
|
||||
DB_TARGET="qdrant-${RUST_TARGET}"
|
||||
;;
|
||||
linux-arm64)
|
||||
QDRANT_FILE="aarch64-unknown-linux-musl.tar.gz"
|
||||
DB_SOURCE="qdrant"
|
||||
DB_TARGET="qdrant-${RUST_TARGET}"
|
||||
;;
|
||||
osx-x64)
|
||||
QDRANT_FILE="x86_64-apple-darwin.tar.gz"
|
||||
DB_SOURCE="qdrant"
|
||||
DB_TARGET="qdrant-${RUST_TARGET}"
|
||||
;;
|
||||
osx-arm64)
|
||||
QDRANT_FILE="aarch64-apple-darwin.tar.gz"
|
||||
DB_SOURCE="qdrant"
|
||||
DB_TARGET="qdrant-${RUST_TARGET}"
|
||||
;;
|
||||
*)
|
||||
echo "Unknown platform: ${DOTNET_RUNTIME}"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
QDRANT_URL="https://github.com/qdrant/qdrant/releases/download/${QDRANT_VERSION}/qdrant-${QDRANT_FILE}"
|
||||
|
||||
echo "Download Qdrant $QDRANT_URL ..."
|
||||
TMP=$(mktemp -d)
|
||||
ARCHIVE="${TMP}/qdrant.tgz"
|
||||
|
||||
curl -fsSL -o "$ARCHIVE" "$QDRANT_URL"
|
||||
|
||||
echo "Extracting Qdrant ..."
|
||||
tar xzf "$ARCHIVE" -C "$TMP"
|
||||
SRC="${TMP}/${DB_SOURCE}"
|
||||
|
||||
if [ ! -f "$SRC" ]; then
|
||||
echo "Was not able to find Qdrant source: $SRC"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Copy Qdrant from ${DB_TARGET} to ${TDB_DIR}/"
|
||||
cp -f "$SRC" "$TDB_DIR/$DB_TARGET"
|
||||
|
||||
echo "Cleaning up ..."
|
||||
rm -fr "$TMP"
|
||||
|
||||
- name: Deploy Qdrant (Windows)
|
||||
if: matrix.platform == 'windows-latest'
|
||||
env:
|
||||
QDRANT_VERSION: ${{ env.QDRANT_VERSION }}
|
||||
DOTNET_RUNTIME: ${{ matrix.dotnet_runtime }}
|
||||
RUST_TARGET: ${{ matrix.rust_target }}
|
||||
run: |
|
||||
$TDB_DIR = "runtime\target\databases\qdrant"
|
||||
New-Item -ItemType Directory -Force -Path $TDB_DIR | Out-Null
|
||||
|
||||
switch ($env:DOTNET_RUNTIME) {
|
||||
"win-x64" {
|
||||
$QDRANT_FILE = "x86_64-pc-windows-msvc.zip"
|
||||
$DB_SOURCE = "qdrant.exe"
|
||||
$DB_TARGET = "qdrant-$($env:RUST_TARGET).exe"
|
||||
}
|
||||
"win-arm64" {
|
||||
$QDRANT_FILE = "x86_64-pc-windows-msvc.zip"
|
||||
$DB_SOURCE = "qdrant.exe"
|
||||
$DB_TARGET = "qdrant-$($env:RUST_TARGET).exe"
|
||||
}
|
||||
default {
|
||||
Write-Error "Unknown platform: $($env:DOTNET_RUNTIME)"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
$QDRANT_URL = "https://github.com/qdrant/qdrant/releases/download/$($env:QDRANT_VERSION)/qdrant-$QDRANT_FILE"
|
||||
Write-Host "Download $QDRANT_URL ..."
|
||||
|
||||
# Create a unique temporary directory (not just a file)
|
||||
$TMP = Join-Path ([System.IO.Path]::GetTempPath()) ([System.IO.Path]::GetRandomFileName())
|
||||
New-Item -ItemType Directory -Path $TMP -Force | Out-Null
|
||||
$ARCHIVE = Join-Path $TMP "qdrant.tgz"
|
||||
|
||||
Invoke-WebRequest -Uri $QDRANT_URL -OutFile $ARCHIVE
|
||||
|
||||
Write-Host "Extracting Qdrant ..."
|
||||
tar -xzf $ARCHIVE -C $TMP
|
||||
|
||||
$SRC = Join-Path $TMP $DB_SOURCE
|
||||
if (!(Test-Path $SRC)) {
|
||||
Write-Error "Cannot find Qdrant source: $SRC"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$DEST = Join-Path $TDB_DIR $DB_TARGET
|
||||
Copy-Item -Path $SRC -Destination $DEST -Force
|
||||
|
||||
Write-Host "Cleaning up ..."
|
||||
Remove-Item $ARCHIVE -Force -ErrorAction SilentlyContinue
|
||||
|
||||
# Try to remove the temporary directory, but ignore errors if files are still in use
|
||||
try {
|
||||
Remove-Item $TMP -Recurse -Force -ErrorAction Stop
|
||||
@@ -821,4 +953,4 @@ jobs:
|
||||
name: "Release ${{ env.FORMATTED_VERSION }}"
|
||||
fail_on_unmatched_files: true
|
||||
files: |
|
||||
release/assets/*
|
||||
release/assets/*
|
||||
@@ -6,6 +6,13 @@ libpdfium.dylib
|
||||
libpdfium.so
|
||||
libpdfium.dll
|
||||
|
||||
# Ignore qdrant database:
|
||||
qdrant-aarch64-apple-darwin
|
||||
qdrant-x86_64-apple-darwin
|
||||
qdrant-aarch64-unknown-linux-gnu
|
||||
qdrant-x86_64-unknown-linux-gnu
|
||||
qdrant-x86_64-pc-windows-msvc.exe
|
||||
|
||||
# User-specific files
|
||||
*.rsuser
|
||||
*.suo
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
# AGENTS.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
MindWork AI Studio is a cross-platform desktop application for interacting with Large Language Models (LLMs). The app uses a hybrid architecture combining a Rust Tauri runtime (for the native desktop shell) with a .NET Blazor Server web application (for the UI and business logic).
|
||||
|
||||
**Key Architecture Points:**
|
||||
- **Runtime:** Rust-based Tauri v1.8 application providing the native window, system integration, and IPC layer
|
||||
- **App:** .NET 9 Blazor Server application providing the UI and core functionality
|
||||
- **Communication:** The Rust runtime and .NET app communicate via HTTPS with TLS certificates generated at startup
|
||||
- **Providers:** Multi-provider architecture supporting OpenAI, Anthropic, Google, Mistral, Perplexity, self-hosted models, and others
|
||||
- **Plugin System:** Lua-based plugin system for language packs, configuration, and future assistant plugins
|
||||
|
||||
## Building
|
||||
|
||||
### Prerequisites
|
||||
- .NET 9 SDK
|
||||
- Rust toolchain (stable)
|
||||
- Tauri v1.6.2 CLI: `cargo install --version 1.6.2 tauri-cli`
|
||||
- Tauri prerequisites (platform-specific dependencies)
|
||||
- **Note:** Development on Linux is discouraged due to complex Tauri dependencies that vary by distribution
|
||||
|
||||
### Build
|
||||
```bash
|
||||
cd app/Build
|
||||
dotnet run build
|
||||
```
|
||||
This builds the .NET app as a Tauri "sidecar" binary, which is required even for development.
|
||||
|
||||
|
||||
### Running Tests
|
||||
Currently, no automated test suite exists in the repository.
|
||||
|
||||
## Architecture Details
|
||||
|
||||
### Rust Runtime (`runtime/`)
|
||||
**Entry point:** `runtime/src/main.rs`
|
||||
|
||||
Key modules:
|
||||
- `app_window.rs` - Tauri window management, updater integration
|
||||
- `dotnet.rs` - Launches and manages the .NET sidecar process
|
||||
- `runtime_api.rs` - Rocket-based HTTPS API for .NET ↔ Rust communication
|
||||
- `certificate.rs` - Generates self-signed TLS certificates for secure IPC
|
||||
- `secret.rs` - Secure secret storage using OS keyring (Keychain/Credential Manager)
|
||||
- `clipboard.rs` - Cross-platform clipboard operations
|
||||
- `file_data.rs` - File processing for RAG (extracts text from PDF, DOCX, XLSX, PPTX, etc.)
|
||||
- `encryption.rs` - AES-256-CBC encryption for sensitive data
|
||||
- `pandoc.rs` - Integration with Pandoc for document conversion
|
||||
- `log.rs` - Logging infrastructure using `flexi_logger`
|
||||
|
||||
### .NET App (`app/MindWork AI Studio/`)
|
||||
**Entry point:** `app/MindWork AI Studio/Program.cs`
|
||||
|
||||
Key structure:
|
||||
- **Program.cs** - Bootstraps Blazor Server, configures Kestrel, initializes encryption and Rust service
|
||||
- **Provider/** - LLM provider implementations (OpenAI, Anthropic, Google, Mistral, etc.)
|
||||
- `BaseProvider.cs` - Abstract base for all providers with streaming support
|
||||
- `IProvider.cs` - Provider interface defining capabilities and streaming methods
|
||||
- **Chat/** - Chat functionality and message handling
|
||||
- **Assistants/** - Pre-configured assistants (translation, summarization, coding, etc.)
|
||||
- `AssistantBase.razor` - Base component for all assistants
|
||||
- **Agents/** - contains all agents, e.g., for data source selection, context validation, etc.
|
||||
- `AgentDataSourceSelection.cs` - Selects appropriate data sources for queries
|
||||
- `AgentRetrievalContextValidation.cs` - Validates retrieved context relevance
|
||||
- **Tools/PluginSystem/** - Lua-based plugin system
|
||||
- **Tools/Services/** - Core background services (settings, message bus, data sources, updates)
|
||||
- **Tools/Rust/** - .NET wrapper for Rust API calls
|
||||
- **Settings/** - Application settings and data models
|
||||
- **Components/** - Reusable Blazor components
|
||||
- **Pages/** - Top-level page components
|
||||
|
||||
### IPC Communication Flow
|
||||
1. Rust runtime starts and generates TLS certificate
|
||||
2. Rust starts internal HTTPS API on random port
|
||||
3. Rust launches .NET sidecar, passing: API port, certificate fingerprint, API token, secret key
|
||||
4. .NET reads environment variables and establishes secure HTTPS connection to Rust
|
||||
5. .NET requests an app port from Rust, starts Blazor Server on that port
|
||||
6. Rust opens Tauri webview pointing to localhost:app_port
|
||||
7. Bi-directional communication: .NET ↔ Rust via HTTPS API
|
||||
|
||||
### Configuration and Metadata
|
||||
- `metadata.txt` - Build metadata (version, build time, component versions) read by both Rust and .NET
|
||||
- `startup.env` - Development environment variables (generated by build script)
|
||||
- `.NET project` reads metadata.txt at build time and injects as assembly attributes
|
||||
|
||||
## Plugin System
|
||||
|
||||
**Location:** `app/MindWork AI Studio/Plugins/`
|
||||
|
||||
Plugins are written in Lua and provide:
|
||||
- **Language plugins** - I18N translations (e.g., German language pack)
|
||||
- **Configuration plugins** - Enterprise IT configurations for centrally managed providers, settings
|
||||
- **Future:** Assistant plugins for custom assistants
|
||||
|
||||
**Example configuration plugin:** `app/MindWork AI Studio/Plugins/configuration/plugin.lua`
|
||||
|
||||
Plugins can configure:
|
||||
- Self-hosted LLM providers
|
||||
- Update behavior
|
||||
- Preview features visibility
|
||||
- Preselected profiles
|
||||
- Chat templates
|
||||
- etc.
|
||||
|
||||
When adding configuration options, update:
|
||||
- `app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs`: In method `TryProcessConfiguration` register new options.
|
||||
- `app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs`: In method `LoadAll` check for leftover configuration.
|
||||
- The corresponding data class in `app/MindWork AI Studio/Settings/DataModel/` to call `ManagedConfiguration.Register(...)`, when adding config options (in contrast to complex config. objects)
|
||||
- `app/MindWork AI Studio/Tools/PluginSystem/PluginConfigurationObject.cs` for parsing logic of complex configuration objects.
|
||||
- `app/MindWork AI Studio/Plugins/configuration/plugin.lua` to document the new configuration option.
|
||||
|
||||
## RAG (Retrieval-Augmented Generation)
|
||||
|
||||
RAG integration is currently in development (preview feature). Architecture:
|
||||
- **External Retrieval Interface (ERI)** - Contract for integrating external data sources
|
||||
- **Data Sources** - Local files and external data via ERI servers
|
||||
- **Agents** - AI agents select data sources and validate retrieval quality
|
||||
- **Embedding providers** - Support for various embedding models
|
||||
- **Vector database** - Planned integration with Qdrant for vector storage
|
||||
- **File processing** - Extracts text from PDF, DOCX, XLSX via Rust runtime
|
||||
|
||||
## Enterprise IT Support
|
||||
|
||||
AI Studio supports centralized configuration for enterprise environments:
|
||||
- **Registry (Windows)** or **environment variables** (all platforms) specify configuration server URL and ID
|
||||
- Configuration downloaded as ZIP containing Lua plugin
|
||||
- Checks for updates every ~16 minutes via ETag
|
||||
- Allows IT departments to pre-configure providers, settings, and chat templates
|
||||
|
||||
**Documentation:** `documentation/Enterprise IT.md`
|
||||
|
||||
## Provider Confidence System
|
||||
|
||||
Multi-level confidence scheme allows users to control which providers see which data:
|
||||
- Confidence levels: e.g. `NONE`, `LOW`, `MEDIUM`, `HIGH`, and some more granular levels
|
||||
- Each assistant/feature can require a minimum confidence level
|
||||
- Users assign confidence levels to providers based on trust
|
||||
|
||||
**Implementation:** `app/MindWork AI Studio/Provider/Confidence.cs`
|
||||
|
||||
## Dependencies and Frameworks
|
||||
|
||||
**Rust:**
|
||||
- Tauri 1.8 - Desktop application framework
|
||||
- Rocket 0.5 - HTTPS API server
|
||||
- tokio - Async runtime
|
||||
- keyring - OS keyring integration
|
||||
- pdfium-render - PDF text extraction
|
||||
- calamine - Excel file parsing
|
||||
|
||||
**.NET:**
|
||||
- Blazor Server - UI framework
|
||||
- MudBlazor 8.12 - Component library
|
||||
- LuaCSharp - Lua scripting engine
|
||||
- HtmlAgilityPack - HTML parsing
|
||||
- ReverseMarkdown - HTML to Markdown conversion
|
||||
|
||||
## Security
|
||||
|
||||
- **Encryption:** AES-256-CBC with PBKDF2 key derivation for sensitive data
|
||||
- **IPC:** TLS-secured communication with random ports and API tokens
|
||||
- **Secrets:** OS keyring for persistent secret storage (API keys, etc.)
|
||||
- **Sandboxing:** Tauri provides OS-level sandboxing
|
||||
|
||||
## Release Process
|
||||
|
||||
1. Create changelog file: `app/MindWork AI Studio/wwwroot/changelog/vX.Y.Z.md`
|
||||
2. Commit changelog
|
||||
3. Run from `app/Build`: `dotnet run release --action <patch|minor|major>`
|
||||
4. Create PR with version bump and changes
|
||||
5. After PR merge, maintainer creates git tag: `vX.Y.Z`
|
||||
6. GitHub Actions builds release binaries for all platforms
|
||||
7. Binaries uploaded to GitHub Releases
|
||||
|
||||
## Important Development Notes
|
||||
|
||||
- **File changes require Write/Edit tools** - Never use bash commands like `cat <<EOF` or `echo >`
|
||||
- **Spaces in paths** - Always quote paths with spaces in bash commands
|
||||
- **Debug environment** - Reads `startup.env` file with IPC credentials
|
||||
- **Production environment** - Runtime launches .NET sidecar with environment variables
|
||||
- **MudBlazor** - Component library requires DI setup in Program.cs
|
||||
- **Encryption** - Initialized before Rust service is marked ready
|
||||
- **Message Bus** - Singleton event bus for cross-component communication inside the .NET app
|
||||
@@ -1,200 +1 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
MindWork AI Studio is a cross-platform desktop application for interacting with Large Language Models (LLMs). The app uses a hybrid architecture combining a Rust Tauri runtime (for the native desktop shell) with a .NET Blazor Server web application (for the UI and business logic).
|
||||
|
||||
**Key Architecture Points:**
|
||||
- **Runtime:** Rust-based Tauri v1.8 application providing the native window, system integration, and IPC layer
|
||||
- **App:** .NET 9 Blazor Server application providing the UI and core functionality
|
||||
- **Communication:** The Rust runtime and .NET app communicate via HTTPS with TLS certificates generated at startup
|
||||
- **Providers:** Multi-provider architecture supporting OpenAI, Anthropic, Google, Mistral, Perplexity, self-hosted models, and others
|
||||
- **Plugin System:** Lua-based plugin system for language packs, configuration, and future assistant plugins
|
||||
|
||||
## Building and Running
|
||||
|
||||
### Prerequisites
|
||||
- .NET 9 SDK
|
||||
- Rust toolchain (stable)
|
||||
- Tauri v1.6.2 CLI: `cargo install --version 1.6.2 tauri-cli`
|
||||
- Tauri prerequisites (platform-specific dependencies)
|
||||
- **Note:** Development on Linux is discouraged due to complex Tauri dependencies that vary by distribution
|
||||
|
||||
### One-Time Setup
|
||||
```bash
|
||||
cd app/Build
|
||||
dotnet run build
|
||||
```
|
||||
This builds the .NET app as a Tauri "sidecar" binary, which is required even for development.
|
||||
|
||||
### Development Workflow
|
||||
Run these commands in separate terminals:
|
||||
|
||||
**Terminal 1 - Start Rust runtime:**
|
||||
```bash
|
||||
cd runtime
|
||||
cargo tauri dev --no-watch
|
||||
```
|
||||
|
||||
**Terminal 2 - Start .NET app:**
|
||||
```bash
|
||||
cd "app/MindWork AI Studio"
|
||||
dotnet run
|
||||
```
|
||||
|
||||
The app will start in the Tauri window. Hot reload is supported for .NET code changes.
|
||||
|
||||
### Building for Production
|
||||
```bash
|
||||
cd app/Build
|
||||
dotnet run build
|
||||
```
|
||||
Creates a release build for the current platform and architecture. Output is in `runtime/target/release/`.
|
||||
|
||||
### Running Tests
|
||||
Currently no automated test suite exists in the repository.
|
||||
|
||||
## Architecture Details
|
||||
|
||||
### Rust Runtime (`runtime/`)
|
||||
**Entry point:** `runtime/src/main.rs`
|
||||
|
||||
Key modules:
|
||||
- `app_window.rs` - Tauri window management, updater integration
|
||||
- `dotnet.rs` - Launches and manages the .NET sidecar process
|
||||
- `runtime_api.rs` - Rocket-based HTTPS API for .NET ↔ Rust communication
|
||||
- `certificate.rs` - Generates self-signed TLS certificates for secure IPC
|
||||
- `secret.rs` - Secure secret storage using OS keyring (Keychain/Credential Manager)
|
||||
- `clipboard.rs` - Cross-platform clipboard operations
|
||||
- `file_data.rs` - File processing for RAG (extracts text from PDF, DOCX, XLSX, PPTX, etc.)
|
||||
- `encryption.rs` - AES-256-CBC encryption for sensitive data
|
||||
- `pandoc.rs` - Integration with Pandoc for document conversion
|
||||
- `log.rs` - Logging infrastructure using `flexi_logger`
|
||||
|
||||
### .NET App (`app/MindWork AI Studio/`)
|
||||
**Entry point:** `app/MindWork AI Studio/Program.cs`
|
||||
|
||||
Key structure:
|
||||
- **Program.cs** - Bootstraps Blazor Server, configures Kestrel, initializes encryption and Rust service
|
||||
- **Provider/** - LLM provider implementations (OpenAI, Anthropic, Google, Mistral, etc.)
|
||||
- `BaseProvider.cs` - Abstract base for all providers with streaming support
|
||||
- `IProvider.cs` - Provider interface defining capabilities and streaming methods
|
||||
- **Chat/** - Chat functionality and message handling
|
||||
- **Assistants/** - Pre-configured assistants (translation, summarization, coding, etc.)
|
||||
- `AssistantBase.razor` - Base component for all assistants
|
||||
- **Agents/** - contains all agents, e.g., for data source selection, context validation, etc.
|
||||
- `AgentDataSourceSelection.cs` - Selects appropriate data sources for queries
|
||||
- `AgentRetrievalContextValidation.cs` - Validates retrieved context relevance
|
||||
- **Tools/PluginSystem/** - Lua-based plugin system
|
||||
- **Tools/Services/** - Core background services (settings, message bus, data sources, updates)
|
||||
- **Tools/Rust/** - .NET wrapper for Rust API calls
|
||||
- **Settings/** - Application settings and data models
|
||||
- **Components/** - Reusable Blazor components
|
||||
- **Pages/** - Top-level page components
|
||||
|
||||
### IPC Communication Flow
|
||||
1. Rust runtime starts and generates TLS certificate
|
||||
2. Rust starts internal HTTPS API on random port
|
||||
3. Rust launches .NET sidecar, passing: API port, certificate fingerprint, API token, secret key
|
||||
4. .NET reads environment variables and establishes secure HTTPS connection to Rust
|
||||
5. .NET requests an app port from Rust, starts Blazor Server on that port
|
||||
6. Rust opens Tauri webview pointing to localhost:app_port
|
||||
7. Bi-directional communication: .NET ↔ Rust via HTTPS API
|
||||
|
||||
### Configuration and Metadata
|
||||
- `metadata.txt` - Build metadata (version, build time, component versions) read by both Rust and .NET
|
||||
- `startup.env` - Development environment variables (generated by build script)
|
||||
- `.NET project` reads metadata.txt at build time and injects as assembly attributes
|
||||
|
||||
## Plugin System
|
||||
|
||||
**Location:** `app/MindWork AI Studio/Plugins/`
|
||||
|
||||
Plugins are written in Lua and provide:
|
||||
- **Language plugins** - I18N translations (e.g., German language pack)
|
||||
- **Configuration plugins** - Enterprise IT configurations for centrally managed providers, settings
|
||||
- **Future:** Assistant plugins for custom assistants
|
||||
|
||||
**Example configuration plugin:** `app/MindWork AI Studio/Plugins/configuration/plugin.lua`
|
||||
|
||||
Plugins can configure:
|
||||
- Self-hosted LLM providers
|
||||
- Update behavior
|
||||
- Preview features visibility
|
||||
- Preselected profiles
|
||||
- Chat templates
|
||||
|
||||
## RAG (Retrieval-Augmented Generation)
|
||||
|
||||
RAG integration is currently in development (preview feature). Architecture:
|
||||
- **External Retrieval Interface (ERI)** - Contract for integrating external data sources
|
||||
- **Data Sources** - Local files and external data via ERI servers
|
||||
- **Agents** - AI agents select data sources and validate retrieval quality
|
||||
- **Embedding providers** - Support for various embedding models
|
||||
- **Vector database** - Planned integration with Qdrant for vector storage
|
||||
- **File processing** - Extracts text from PDF, DOCX, XLSX via Rust runtime
|
||||
|
||||
## Enterprise IT Support
|
||||
|
||||
AI Studio supports centralized configuration for enterprise environments:
|
||||
- **Registry (Windows)** or **environment variables** (all platforms) specify configuration server URL and ID
|
||||
- Configuration downloaded as ZIP containing Lua plugin
|
||||
- Checks for updates every ~16 minutes via ETag
|
||||
- Allows IT departments to pre-configure providers, settings, and chat templates
|
||||
|
||||
**Documentation:** `documentation/Enterprise IT.md`
|
||||
|
||||
## Provider Confidence System
|
||||
|
||||
Multi-level confidence scheme allows users to control which providers see which data:
|
||||
- Confidence levels: e.g. `NONE`, `LOW`, `MEDIUM`, `HIGH`, and some more granular levels
|
||||
- Each assistant/feature can require a minimum confidence level
|
||||
- Users assign confidence levels to providers based on trust
|
||||
|
||||
**Implementation:** `app/MindWork AI Studio/Provider/Confidence.cs`
|
||||
|
||||
## Dependencies and Frameworks
|
||||
|
||||
**Rust:**
|
||||
- Tauri 1.8 - Desktop application framework
|
||||
- Rocket 0.5 - HTTPS API server
|
||||
- tokio - Async runtime
|
||||
- keyring - OS keyring integration
|
||||
- pdfium-render - PDF text extraction
|
||||
- calamine - Excel file parsing
|
||||
|
||||
**.NET:**
|
||||
- Blazor Server - UI framework
|
||||
- MudBlazor 8.12 - Component library
|
||||
- LuaCSharp - Lua scripting engine
|
||||
- HtmlAgilityPack - HTML parsing
|
||||
- ReverseMarkdown - HTML to Markdown conversion
|
||||
|
||||
## Security
|
||||
|
||||
- **Encryption:** AES-256-CBC with PBKDF2 key derivation for sensitive data
|
||||
- **IPC:** TLS-secured communication with random ports and API tokens
|
||||
- **Secrets:** OS keyring for persistent secret storage (API keys, etc.)
|
||||
- **Sandboxing:** Tauri provides OS-level sandboxing
|
||||
|
||||
## Release Process
|
||||
|
||||
1. Create changelog file: `app/MindWork AI Studio/wwwroot/changelog/vX.Y.Z.md`
|
||||
2. Commit changelog
|
||||
3. Run from `app/Build`: `dotnet run release --action <patch|minor|major>`
|
||||
4. Create PR with version bump and changes
|
||||
5. After PR merge, maintainer creates git tag: `vX.Y.Z`
|
||||
6. GitHub Actions builds release binaries for all platforms
|
||||
7. Binaries uploaded to GitHub Releases
|
||||
|
||||
## Important Development Notes
|
||||
|
||||
- **File changes require Write/Edit tools** - Never use bash commands like `cat <<EOF` or `echo >`
|
||||
- **Spaces in paths** - Always quote paths with spaces in bash commands
|
||||
- **Debug environment** - Reads `startup.env` file with IPC credentials
|
||||
- **Production environment** - Runtime launches .NET sidecar with environment variables
|
||||
- **MudBlazor** - Component library requires DI setup in Program.cs
|
||||
- **Encryption** - Initialized before Rust service is marked ready
|
||||
- **Message Bus** - Singleton event bus for cross-component communication inside the .NET app
|
||||
@AGENTS.md
|
||||
@@ -32,7 +32,7 @@ Since November 2024: Work on RAG (integration of your data and files) has begun.
|
||||
- [x] ~~App: Implement dialog for checking & handling [pandoc](https://pandoc.org/) installation ([PR #393](https://github.com/MindWorkAI/AI-Studio/pull/393), [PR #487](https://github.com/MindWorkAI/AI-Studio/pull/487))~~
|
||||
- [ ] App: Implement external embedding providers
|
||||
- [ ] App: Implement the process to vectorize one local file using embeddings
|
||||
- [ ] Runtime: Integration of the vector database [Qdrant](https://github.com/qdrant/qdrant)
|
||||
- [x] ~~Runtime: Integration of the vector database [Qdrant](https://github.com/qdrant/qdrant) ([PR #580](https://github.com/MindWorkAI/AI-Studio/pull/580))~~
|
||||
- [ ] App: Implement the continuous process of vectorizing data
|
||||
- [x] ~~App: Define a common retrieval context interface for the integration of RAG processes in chats (PR [#281](https://github.com/MindWorkAI/AI-Studio/pull/281), [#284](https://github.com/MindWorkAI/AI-Studio/pull/284), [#286](https://github.com/MindWorkAI/AI-Studio/pull/286), [#287](https://github.com/MindWorkAI/AI-Studio/pull/287))~~
|
||||
- [x] ~~App: Define a common augmentation interface for the integration of RAG processes in chats (PR [#288](https://github.com/MindWorkAI/AI-Studio/pull/288), [#289](https://github.com/MindWorkAI/AI-Studio/pull/289))~~
|
||||
@@ -105,6 +105,7 @@ MindWork AI Studio is a free desktop app for macOS, Windows, and Linux. It provi
|
||||
|
||||
**Key advantages:**
|
||||
- **Free of charge**: The app is free to use, both for personal and commercial purposes.
|
||||
- **Democratization of AI**: We want to contribute to the democratization of AI. MindWork AI Studio runs even on low-cost hardware, including computers around 100 € such as Raspberry Pi. This makes the app and its full feature set accessible to people and families with limited budgets. You can start with local LLMs or use affordable cloud models.
|
||||
- **Independence**: You are not tied to any single provider. Instead, you can choose the providers that best suit your needs. Right now, we support:
|
||||
- [OpenAI](https://openai.com/) (GPT5, GPT4.1, o1, o3, o4, etc.)
|
||||
- [Perplexity](https://www.perplexity.ai/)
|
||||
|
||||
@@ -69,7 +69,10 @@ public sealed partial class CollectI18NKeysCommand
|
||||
|
||||
var ns = this.DetermineNamespace(filePath);
|
||||
var fileInfo = new FileInfo(filePath);
|
||||
var name = fileInfo.Name.Replace(fileInfo.Extension, string.Empty).Replace(".razor", string.Empty);
|
||||
|
||||
var name = this.DetermineTypeName(filePath)
|
||||
?? fileInfo.Name.Replace(fileInfo.Extension, string.Empty).Replace(".razor", string.Empty);
|
||||
|
||||
var langNamespace = $"{ns}.{name}".ToUpperInvariant();
|
||||
foreach (var match in matches)
|
||||
{
|
||||
@@ -236,6 +239,14 @@ public sealed partial class CollectI18NKeysCommand
|
||||
Console.WriteLine($"- Error: The file '{filePath}' is neither a C# nor a Razor file. We can't determine the namespace.");
|
||||
return null;
|
||||
}
|
||||
|
||||
private string? DetermineTypeName(string filePath)
|
||||
{
|
||||
if (!filePath.EndsWith(".cs", StringComparison.OrdinalIgnoreCase))
|
||||
return null;
|
||||
|
||||
return this.ReadPartialTypeNameFromCSharp(filePath);
|
||||
}
|
||||
|
||||
private string? ReadNamespaceFromCSharp(string filePath)
|
||||
{
|
||||
@@ -254,6 +265,24 @@ public sealed partial class CollectI18NKeysCommand
|
||||
var match = matches[0];
|
||||
return match.Groups[1].Value;
|
||||
}
|
||||
|
||||
private string? ReadPartialTypeNameFromCSharp(string filePath)
|
||||
{
|
||||
var content = File.ReadAllText(filePath, Encoding.UTF8);
|
||||
var matches = CSharpPartialTypeRegex().Matches(content);
|
||||
|
||||
if (matches.Count == 0)
|
||||
return null;
|
||||
|
||||
if (matches.Count > 1)
|
||||
{
|
||||
Console.WriteLine($"The file '{filePath}' contains multiple partial type declarations. This scenario is not supported.");
|
||||
return null;
|
||||
}
|
||||
|
||||
var match = matches[0];
|
||||
return match.Groups[1].Value;
|
||||
}
|
||||
|
||||
private string? ReadNamespaceFromRazor(string filePath)
|
||||
{
|
||||
@@ -278,4 +307,7 @@ public sealed partial class CollectI18NKeysCommand
|
||||
|
||||
[GeneratedRegex("""namespace\s+([a-zA-Z0-9_.]+)""")]
|
||||
private static partial Regex CSharpNamespaceRegex();
|
||||
}
|
||||
|
||||
[GeneratedRegex("""\bpartial\s+(?:class|struct|interface|record(?:\s+(?:class|struct))?)\s+([A-Za-z_][A-Za-z0-9_]*)""")]
|
||||
private static partial Regex CSharpPartialTypeRegex();
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace Build.Commands;
|
||||
|
||||
public record Database(string Path, string Filename);
|
||||
@@ -0,0 +1,120 @@
|
||||
using System.Formats.Tar;
|
||||
using System.IO.Compression;
|
||||
|
||||
using SharedTools;
|
||||
|
||||
namespace Build.Commands;
|
||||
|
||||
public static class Qdrant
|
||||
{
|
||||
public static async Task InstallAsync(RID rid, string version)
|
||||
{
|
||||
Console.Write($"- Installing Qdrant {version} for {rid.ToUserFriendlyName()} ...");
|
||||
|
||||
var cwd = Environment.GetRustRuntimeDirectory();
|
||||
var qdrantTmpDownloadPath = Path.GetTempFileName();
|
||||
var qdrantTmpExtractPath = Directory.CreateTempSubdirectory();
|
||||
var qdrantUrl = GetQdrantDownloadUrl(rid, version);
|
||||
|
||||
//
|
||||
// Download the file:
|
||||
//
|
||||
Console.Write(" downloading ...");
|
||||
using (var client = new HttpClient())
|
||||
{
|
||||
var response = await client.GetAsync(qdrantUrl);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
{
|
||||
Console.WriteLine($" failed to download Qdrant {version} for {rid.ToUserFriendlyName()} from {qdrantUrl}");
|
||||
return;
|
||||
}
|
||||
|
||||
await using var fileStream = File.Create(qdrantTmpDownloadPath);
|
||||
await response.Content.CopyToAsync(fileStream);
|
||||
}
|
||||
|
||||
//
|
||||
// Extract the downloaded file:
|
||||
//
|
||||
Console.Write(" extracting ...");
|
||||
await using(var zStream = File.Open(qdrantTmpDownloadPath, FileMode.Open, FileAccess.Read, FileShare.Read))
|
||||
{
|
||||
if (rid == RID.WIN_X64)
|
||||
{
|
||||
using var archive = new ZipArchive(zStream, ZipArchiveMode.Read);
|
||||
archive.ExtractToDirectory(qdrantTmpExtractPath.FullName, overwriteFiles: true);
|
||||
}
|
||||
else
|
||||
{
|
||||
await using var uncompressedStream = new GZipStream(zStream, CompressionMode.Decompress);
|
||||
await TarFile.ExtractToDirectoryAsync(uncompressedStream, qdrantTmpExtractPath.FullName, true);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Copy the database to the target directory:
|
||||
//
|
||||
Console.Write(" deploying ...");
|
||||
var database = GetDatabasePath(rid);
|
||||
if (string.IsNullOrWhiteSpace(database.Path))
|
||||
{
|
||||
Console.WriteLine($" failed to find the database path for {rid.ToUserFriendlyName()}");
|
||||
return;
|
||||
}
|
||||
|
||||
var qdrantDbSourcePath = Path.Join(qdrantTmpExtractPath.FullName, database.Path);
|
||||
var qdrantDbTargetPath = Path.Join(cwd, "target", "databases", "qdrant",database.Filename);
|
||||
if (!File.Exists(qdrantDbSourcePath))
|
||||
{
|
||||
Console.WriteLine($" failed to find the database file '{qdrantDbSourcePath}'");
|
||||
return;
|
||||
}
|
||||
|
||||
Directory.CreateDirectory(Path.Join(cwd, "target", "databases", "qdrant"));
|
||||
if (File.Exists(qdrantDbTargetPath))
|
||||
File.Delete(qdrantDbTargetPath);
|
||||
|
||||
File.Copy(qdrantDbSourcePath, qdrantDbTargetPath);
|
||||
|
||||
//
|
||||
// Cleanup:
|
||||
//
|
||||
Console.Write(" cleaning up ...");
|
||||
File.Delete(qdrantTmpDownloadPath);
|
||||
Directory.Delete(qdrantTmpExtractPath.FullName, true);
|
||||
|
||||
Console.WriteLine(" done.");
|
||||
}
|
||||
|
||||
private static Database GetDatabasePath(RID rid) => rid switch
|
||||
{
|
||||
RID.OSX_ARM64 => new("qdrant", "qdrant-aarch64-apple-darwin"),
|
||||
RID.OSX_X64 => new("qdrant", "qdrant-x86_64-apple-darwin"),
|
||||
|
||||
RID.LINUX_ARM64 => new("qdrant", "qdrant-aarch64-unknown-linux-gnu"),
|
||||
RID.LINUX_X64 => new("qdrant", "qdrant-x86_64-unknown-linux-gnu"),
|
||||
|
||||
RID.WIN_X64 => new("qdrant.exe", "qdrant-x86_64-pc-windows-msvc.exe"),
|
||||
RID.WIN_ARM64 => new("qdrant.exe", "qdrant-aarch64-pc-windows-msvc.exe"),
|
||||
|
||||
_ => new(string.Empty, string.Empty),
|
||||
};
|
||||
|
||||
private static string GetQdrantDownloadUrl(RID rid, string version)
|
||||
{
|
||||
var baseUrl = $"https://github.com/qdrant/qdrant/releases/download/v{version}/qdrant-";
|
||||
return rid switch
|
||||
{
|
||||
RID.LINUX_ARM64 => $"{baseUrl}aarch64-unknown-linux-musl.tar.gz",
|
||||
RID.LINUX_X64 => $"{baseUrl}x86_64-unknown-linux-gnu.tar.gz",
|
||||
|
||||
RID.OSX_ARM64 => $"{baseUrl}aarch64-apple-darwin.tar.gz",
|
||||
RID.OSX_X64 => $"{baseUrl}x86_64-apple-darwin.tar.gz",
|
||||
|
||||
RID.WIN_X64 => $"{baseUrl}x86_64-pc-windows-msvc.zip",
|
||||
RID.WIN_ARM64 => $"{baseUrl}x86_64-pc-windows-msvc.zip",
|
||||
|
||||
_ => string.Empty,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -150,6 +150,9 @@ public sealed partial class UpdateMetadataCommands
|
||||
|
||||
var pdfiumVersion = await this.ReadPdfiumVersion();
|
||||
await Pdfium.InstallAsync(rid, pdfiumVersion);
|
||||
|
||||
var qdrantVersion = await this.ReadQdrantVersion();
|
||||
await Qdrant.InstallAsync(rid, qdrantVersion);
|
||||
|
||||
Console.Write($"- Start .NET build for {rid.ToUserFriendlyName()} ...");
|
||||
await this.ReadCommandOutput(pathApp, "dotnet", $"clean --configuration release --runtime {rid.AsMicrosoftRid()}");
|
||||
@@ -364,6 +367,16 @@ public sealed partial class UpdateMetadataCommands
|
||||
return shortVersion;
|
||||
}
|
||||
|
||||
private async Task<string> ReadQdrantVersion()
|
||||
{
|
||||
const int QDRANT_VERSION_INDEX = 11;
|
||||
var pathMetadata = Environment.GetMetadataPath();
|
||||
var lines = await File.ReadAllLinesAsync(pathMetadata, Encoding.UTF8);
|
||||
var currentQdrantVersion = lines[QDRANT_VERSION_INDEX].Trim();
|
||||
|
||||
return currentQdrantVersion;
|
||||
}
|
||||
|
||||
private async Task UpdateArchitecture(RID rid)
|
||||
{
|
||||
const int ARCHITECTURE_INDEX = 9;
|
||||
|
||||
@@ -27,4 +27,6 @@
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=mime/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=mwais/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=ollama/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Qdrant/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=qdrant/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=tauri_0027s/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
@@ -27,6 +27,7 @@
|
||||
<script src="system/MudBlazor.Markdown/MudBlazor.Markdown.min.js"></script>
|
||||
<script src="system/CodeBeam.MudBlazor.Extensions/MudExtensions.min.js"></script>
|
||||
<script src="app.js"></script>
|
||||
<script src="audio.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -7,7 +7,7 @@ namespace AIStudio.Assistants.Agenda;
|
||||
|
||||
public partial class AssistantAgenda : AssistantBaseCore<SettingsDialogAgenda>
|
||||
{
|
||||
public override Tools.Components Component => Tools.Components.AGENDA_ASSISTANT;
|
||||
protected override Tools.Components Component => Tools.Components.AGENDA_ASSISTANT;
|
||||
|
||||
protected override string Title => T("Agenda Planner");
|
||||
|
||||
|
||||
@@ -9,7 +9,10 @@
|
||||
@this.Title
|
||||
</MudText>
|
||||
|
||||
<MudIconButton Variant="Variant.Text" Icon="@Icons.Material.Filled.Settings" OnClick="@(async () => await this.OpenSettingsDialog())"/>
|
||||
@if (this.HasSettingsPanel)
|
||||
{
|
||||
<MudIconButton Variant="Variant.Text" Icon="@Icons.Material.Filled.Settings" OnClick="@(async () => await this.OpenSettingsDialog())"/>
|
||||
}
|
||||
</MudStack>
|
||||
|
||||
<InnerScrolling>
|
||||
@@ -22,7 +25,9 @@
|
||||
@if (this.Body is not null)
|
||||
{
|
||||
<CascadingValue Value="@this">
|
||||
@this.Body
|
||||
<CascadingValue Value="@this.Component">
|
||||
@this.Body
|
||||
</CascadingValue>
|
||||
</CascadingValue>
|
||||
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" StretchItems="StretchItems.Start" Class="mb-3">
|
||||
@@ -145,4 +150,4 @@
|
||||
</MudStack>
|
||||
</FooterContent>
|
||||
</InnerScrolling>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,6 +1,7 @@
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Provider;
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Dialogs.Settings;
|
||||
using AIStudio.Tools.Services;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
@@ -41,8 +42,8 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
|
||||
protected abstract string Description { get; }
|
||||
|
||||
protected abstract string SystemPrompt { get; }
|
||||
|
||||
public abstract Tools.Components Component { get; }
|
||||
|
||||
protected abstract Tools.Components Component { get; }
|
||||
|
||||
protected virtual Func<string> Result2Copy => () => this.resultingContentBlock is null ? string.Empty : this.resultingContentBlock.Content switch
|
||||
{
|
||||
@@ -81,6 +82,8 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
|
||||
protected virtual ChatThread ConvertToChatThread => this.chatThread ?? new();
|
||||
|
||||
protected virtual IReadOnlyList<IButtonData> FooterButtons => [];
|
||||
|
||||
protected virtual bool HasSettingsPanel => typeof(TSettings) != typeof(NoSettingsPanel);
|
||||
|
||||
protected AIStudio.Settings.Provider providerSettings = Settings.Provider.NONE;
|
||||
protected MudForm? form;
|
||||
@@ -185,11 +188,22 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
|
||||
this.inputIsValid = false;
|
||||
this.StateHasChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear all input issues.
|
||||
/// </summary>
|
||||
protected void ClearInputIssues()
|
||||
{
|
||||
this.inputIssues = [];
|
||||
this.inputIsValid = true;
|
||||
this.StateHasChanged();
|
||||
}
|
||||
|
||||
protected void CreateChatThread()
|
||||
{
|
||||
this.chatThread = new()
|
||||
{
|
||||
IncludeDateTime = false,
|
||||
SelectedProvider = this.providerSettings.Id,
|
||||
SelectedProfile = this.AllowProfiles ? this.currentProfile.Id : Profile.NO_PROFILE.Id,
|
||||
SystemPrompt = this.SystemPrompt,
|
||||
@@ -205,6 +219,7 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
|
||||
var chatId = Guid.NewGuid();
|
||||
this.chatThread = new()
|
||||
{
|
||||
IncludeDateTime = false,
|
||||
SelectedProvider = this.providerSettings.Id,
|
||||
SelectedProfile = this.AllowProfiles ? this.currentProfile.Id : Profile.NO_PROFILE.Id,
|
||||
SystemPrompt = this.SystemPrompt,
|
||||
@@ -216,6 +231,13 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
|
||||
|
||||
return chatId;
|
||||
}
|
||||
|
||||
protected virtual void ResetProviderAndProfileSelection()
|
||||
{
|
||||
this.providerSettings = this.SettingsManager.GetPreselectedProvider(this.Component);
|
||||
this.currentProfile = this.SettingsManager.GetPreselectedProfile(this.Component);
|
||||
this.currentChatTemplate = this.SettingsManager.GetPreselectedChatTemplate(this.Component);
|
||||
}
|
||||
|
||||
protected DateTimeOffset AddUserRequest(string request, bool hideContentFromUser = false, params List<FileAttachment> attachments)
|
||||
{
|
||||
@@ -308,6 +330,9 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
|
||||
|
||||
protected async Task OpenSettingsDialog()
|
||||
{
|
||||
if (!this.HasSettingsPanel)
|
||||
return;
|
||||
|
||||
var dialogParameters = new DialogParameters();
|
||||
await this.DialogService.ShowAsync<TSettings>(null, dialogParameters, DialogOptions.FULLSCREEN);
|
||||
}
|
||||
@@ -354,9 +379,7 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
|
||||
await this.JsRuntime.ClearDiv(AFTER_RESULT_DIV_ID);
|
||||
|
||||
this.ResetForm();
|
||||
this.providerSettings = this.SettingsManager.GetPreselectedProvider(this.Component);
|
||||
this.currentProfile = this.SettingsManager.GetPreselectedProfile(this.Component);
|
||||
this.currentChatTemplate = this.SettingsManager.GetPreselectedChatTemplate(this.Component);
|
||||
this.ResetProviderAndProfileSelection();
|
||||
|
||||
this.inputIsValid = false;
|
||||
this.inputIssues = [];
|
||||
@@ -396,4 +419,4 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ namespace AIStudio.Assistants.BiasDay;
|
||||
|
||||
public partial class BiasOfTheDayAssistant : AssistantBaseCore<SettingsDialogAssistantBias>
|
||||
{
|
||||
public override Tools.Components Component => Tools.Components.BIAS_DAY_ASSISTANT;
|
||||
protected override Tools.Components Component => Tools.Components.BIAS_DAY_ASSISTANT;
|
||||
|
||||
protected override string Title => T("Bias of the Day");
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace AIStudio.Assistants.Coding;
|
||||
|
||||
public partial class AssistantCoding : AssistantBaseCore<SettingsDialogCoding>
|
||||
{
|
||||
public override Tools.Components Component => Tools.Components.CODING_ASSISTANT;
|
||||
protected override Tools.Components Component => Tools.Components.CODING_ASSISTANT;
|
||||
|
||||
protected override string Title => T("Coding Assistant");
|
||||
|
||||
|
||||
+126
-64
@@ -1,9 +1,8 @@
|
||||
@attribute [Route(Routes.ASSISTANT_DOCUMENT_ANALYSIS)]
|
||||
@inherits AssistantBaseCore<AIStudio.Dialogs.Settings.SettingsDialogDocumentAnalysis>
|
||||
|
||||
@inherits AssistantBaseCore<AIStudio.Dialogs.Settings.NoSettingsPanel>
|
||||
@using AIStudio.Settings.DataModel
|
||||
|
||||
<PreviewPrototype ApplyInnerScrollingFix="true"/>
|
||||
<PreviewBeta ApplyInnerScrollingFix="true"/>
|
||||
<div class="mb-6"></div>
|
||||
|
||||
<MudText Typo="Typo.h4" Class="mb-3">
|
||||
@@ -25,9 +24,21 @@ else
|
||||
<MudList Color="Color.Primary" T="DataDocumentAnalysisPolicy" Class="mb-1" SelectedValue="@this.selectedPolicy" SelectedValueChanged="@this.SelectedPolicyChanged">
|
||||
@foreach (var policy in this.SettingsManager.ConfigurationData.DocumentAnalysis.Policies)
|
||||
{
|
||||
<MudListItem T="DataDocumentAnalysisPolicy" Icon="@Icons.Material.Filled.Policy" Value="@policy">
|
||||
@policy.PolicyName
|
||||
</MudListItem>
|
||||
@if (policy.IsEnterpriseConfiguration)
|
||||
{
|
||||
<MudListItem T="DataDocumentAnalysisPolicy" Icon="@Icons.Material.Filled.Policy" Value="@policy">
|
||||
@policy.PolicyName
|
||||
<MudTooltip Text="@T("This policy is managed by your organization.")" Placement="Placement.Right">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Business" Size="Size.Small" Class="ml-2" Style="vertical-align: middle;" />
|
||||
</MudTooltip>
|
||||
</MudListItem>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudListItem T="DataDocumentAnalysisPolicy" Icon="@Icons.Material.Filled.Policy" Value="@policy">
|
||||
@policy.PolicyName
|
||||
</MudListItem>
|
||||
}
|
||||
}
|
||||
</MudList>
|
||||
}
|
||||
@@ -36,76 +47,127 @@ else
|
||||
<MudButton OnClick="@this.AddPolicy" Variant="Variant.Filled" Color="Color.Primary">
|
||||
@T("Add policy")
|
||||
</MudButton>
|
||||
<MudButton OnClick="@this.RemovePolicy" Disabled="@(this.selectedPolicy?.IsProtected ?? true)" Variant="Variant.Filled" Color="Color.Error">
|
||||
<MudButton OnClick="@this.RemovePolicy" Disabled="@((this.selectedPolicy?.IsProtected ?? true) || (this.selectedPolicy?.IsEnterpriseConfiguration ?? true))" Variant="Variant.Filled" Color="Color.Error">
|
||||
@T("Delete this policy")
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
|
||||
<MudExpansionPanels Class="mb-3 mt-6" MultiExpansion="@false">
|
||||
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Policy" HeaderText="@(T("Policy definition") + $": {this.selectedPolicy?.PolicyName}")" IsExpanded="@(!this.selectedPolicy?.IsProtected ?? true)">
|
||||
<MudText Typo="Typo.h5" Class="mb-1">
|
||||
@T("Common settings")
|
||||
</MudText>
|
||||
|
||||
<MudTextField T="string" Disabled="@this.IsNoPolicySelectedOrProtected" @bind-Text="@this.policyName" Validation="@this.ValidatePolicyName" Immediate="@true" Label="@T("Policy name")" HelperText="@T("Please give your policy a name that provides information about the intended purpose. The name will be displayed to users in AI Studio.")" Counter="60" MaxLength="60" Variant="Variant.Outlined" Margin="Margin.Normal" UserAttributes="@USER_INPUT_ATTRIBUTES" Class="mb-3" OnKeyUp="() => this.PolicyNameWasChanged()"/>
|
||||
|
||||
<MudTextField T="string" Disabled="@this.IsNoPolicySelectedOrProtected" @bind-Text="@this.policyDescription" Validation="@this.ValidatePolicyDescription" Immediate="@true" Label="@T("Policy description")" HelperText="@T("Please provide a brief description of your policy. Describe or explain what your policy does. This description will be shown to users in AI Studio.")" Counter="512" MaxLength="512" Variant="Variant.Outlined" Margin="Margin.Normal" Lines="3" AutoGrow="@true" MaxLines="6" UserAttributes="@USER_INPUT_ATTRIBUTES" Class="mb-3"/>
|
||||
|
||||
<MudTextSwitch Disabled="@(this.IsNoPolicySelected || (this.selectedPolicy?.IsManaged ?? true))" Label="@T("Would you like to protect this policy so that you cannot accidentally edit or delete it?")" Value="@this.policyIsProtected" ValueChanged="async state => await this.PolicyProtectionWasChanged(state)" LabelOn="@T("Yes, protect this policy")" LabelOff="@T("No, the policy can be edited")" />
|
||||
<MudDivider Style="height: 0.25ch; margin: 1rem 0;" Class="mt-6" />
|
||||
|
||||
<MudText Typo="Typo.h5" Class="mt-6 mb-1">
|
||||
@T("Analysis and output rules")
|
||||
@if ((this.selectedPolicy?.HidePolicyDefinition ?? false) && (this.selectedPolicy?.IsEnterpriseConfiguration ?? false))
|
||||
{
|
||||
@* When HidePolicyDefinition is true AND the policy is an enterprise configuration, show only the document selection section without expansion panels *@
|
||||
<div class="mb-3 mt-3">
|
||||
<MudText Typo="Typo.h5" Class="mb-3">
|
||||
@T("Document selection - Policy"): @this.selectedPolicy?.PolicyName
|
||||
</MudText>
|
||||
|
||||
<MudJustifiedText Typo="Typo.body1" Class="mt-3">
|
||||
@T("Use the analysis and output rules to define how the AI evaluates your documents and formats the results.")
|
||||
</MudJustifiedText>
|
||||
|
||||
<MudJustifiedText Typo="Typo.body1" Class="mt-3">
|
||||
@T("The analysis rules specify what the AI should pay particular attention to while reviewing the documents you provide, and which aspects it should highlight or save. For example, if you want to extract the potential of green hydrogen for agriculture from a variety of general publications, you can explicitly define this in the analysis rules.")
|
||||
</MudJustifiedText>
|
||||
|
||||
<MudTextField T="string" Disabled="@this.IsNoPolicySelectedOrProtected" @bind-Text="@this.policyAnalysisRules" Validation="@this.ValidateAnalysisRules" Immediate="@true" Label="@T("Analysis rules")" HelperText="@T("Please provide a description of your analysis rules. This rules will be used to instruct the AI on how to analyze the documents.")" Variant="Variant.Outlined" Margin="Margin.Normal" Lines="5" AutoGrow="@true" MaxLines="26" UserAttributes="@USER_INPUT_ATTRIBUTES" Class="mb-3"/>
|
||||
|
||||
<ReadFileContent Text="@T("Load analysis rules from document")" @bind-FileContent="@this.policyAnalysisRules"/>
|
||||
|
||||
<MudJustifiedText Typo="Typo.body1" Class="mt-3">
|
||||
@T("After the AI has processed all documents, it needs your instructions on how the result should be formatted. Would you like a structured list with keywords or a continuous text? Should the output include emojis or be written in formal business language? You can specify all these preferences in the output rules. There, you can also predefine a desired structure—for example, by using Markdown formatting to define headings, paragraphs, or bullet points.")
|
||||
</MudJustifiedText>
|
||||
|
||||
<MudTextField T="string" Disabled="@this.IsNoPolicySelectedOrProtected" @bind-Text="@this.policyOutputRules" Validation="@this.ValidateOutputRules" Immediate="@true" Label="@T("Output rules")" HelperText="@T("Please provide a description of your output rules. This rules will be used to instruct the AI on how to format the output of the analysis.")" Variant="Variant.Outlined" Margin="Margin.Normal" Lines="5" AutoGrow="@true" MaxLines="26" UserAttributes="@USER_INPUT_ATTRIBUTES" Class="mb-3"/>
|
||||
|
||||
<ReadFileContent Text="@T("Load output rules from document")" @bind-FileContent="@this.policyOutputRules"/>
|
||||
|
||||
<MudText Typo="Typo.h5" Class="mt-6 mb-1">
|
||||
@T("Preparation for enterprise distribution")
|
||||
</MudText>
|
||||
|
||||
<MudTooltip Text="@T("Not implemented yet.")">
|
||||
<MudButton StartIcon="@Icons.Material.Filled.FileDownload" Disabled="true" Variant="Variant.Filled" Color="Color.Primary" OnClick="@this.ExportPolicyAsConfiguration">
|
||||
@T("Export policy as configuration section")
|
||||
</MudButton>
|
||||
</MudTooltip>
|
||||
</ExpansionPanel>
|
||||
|
||||
<MudDivider Style="height: 0.25ch; margin: 1rem 0;" Class="mt-6" />
|
||||
|
||||
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.DocumentScanner" HeaderText="@(T("Document selection - Policy") + $": {this.selectedPolicy?.PolicyName}")" IsExpanded="@(this.selectedPolicy?.IsProtected ?? false)">
|
||||
<MudText Typo="Typo.h5" Class="mb-1">
|
||||
|
||||
<MudText Typo="Typo.h6" Class="mb-1">
|
||||
@T("Policy Description")
|
||||
</MudText>
|
||||
|
||||
|
||||
<MudJustifiedText Typo="Typo.body1" Class="mb-3">
|
||||
@this.selectedPolicy?.PolicyDescription
|
||||
</MudJustifiedText>
|
||||
|
||||
<MudText Typo="Typo.h5" Class="mb-1 mt-6">
|
||||
|
||||
<MudText Typo="Typo.h6" Class="mb-1 mt-6">
|
||||
@T("Documents for the analysis")
|
||||
</MudText>
|
||||
|
||||
|
||||
<AttachDocuments Name="Document Analysis Files" Layer="@DropLayers.ASSISTANTS" @bind-DocumentPaths="@this.loadedDocumentPaths" CatchAllDocuments="true" UseSmallForm="false" Provider="@this.providerSettings"/>
|
||||
|
||||
</ExpansionPanel>
|
||||
</MudExpansionPanels>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
@* Standard view with expansion panels *@
|
||||
<MudExpansionPanels Class="mb-3 mt-3" MultiExpansion="@false">
|
||||
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Policy" HeaderText="@(T("Policy definition") + $": {this.selectedPolicy?.PolicyName}")" IsExpanded="@this.policyDefinitionExpanded" ExpandedChanged="@this.PolicyDefinitionExpandedChanged">
|
||||
@if (!this.policyDefinitionExpanded)
|
||||
{
|
||||
<MudJustifiedText Typo="Typo.body1" Class="mb-1">
|
||||
@T("Expand this section to view and edit the policy definition.")
|
||||
</MudJustifiedText>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudText Typo="Typo.h5" Class="mb-1">
|
||||
@T("Common settings")
|
||||
</MudText>
|
||||
|
||||
<MudTextField T="string" Disabled="@this.IsNoPolicySelectedOrProtected" @bind-Text="@this.policyName" Validation="@this.ValidatePolicyName" Immediate="@true" Label="@T("Policy name")" HelperText="@T("Please give your policy a name that provides information about the intended purpose. The name will be displayed to users in AI Studio.")" Counter="60" MaxLength="60" Variant="Variant.Outlined" Margin="Margin.Normal" UserAttributes="@USER_INPUT_ATTRIBUTES" Class="mb-3" OnKeyUp="@(() => this.PolicyNameWasChanged())"/>
|
||||
|
||||
<MudTextField T="string" Disabled="@this.IsNoPolicySelectedOrProtected" @bind-Text="@this.policyDescription" Validation="@this.ValidatePolicyDescription" Immediate="@true" Label="@T("Policy description")" HelperText="@T("Please provide a brief description of your policy. Describe or explain what your policy does. This description will be shown to users in AI Studio.")" Counter="512" MaxLength="512" Variant="Variant.Outlined" Margin="Margin.Normal" Lines="3" AutoGrow="@true" MaxLines="6" UserAttributes="@USER_INPUT_ATTRIBUTES" Class="mb-3"/>
|
||||
|
||||
<MudTextSwitch Disabled="@this.IsNoPolicySelectedOrProtected" Label="@T("Hide the policy definition when distributed via configuration plugin?")" Value="@this.policyHidePolicyDefinition" ValueChanged="async state => await this.PolicyHidePolicyDefinitionWasChanged(state)" LabelOn="@T("Yes, hide the policy definition")" LabelOff="@T("No, show the policy definition")" />
|
||||
|
||||
<MudJustifiedText Typo="Typo.body2" Class="mt-2 mb-3">
|
||||
@T("Note: This setting only takes effect when this policy is exported and distributed via a configuration plugin to other users. When enabled, users will only see the document selection interface and cannot view or modify the policy details. This setting does NOT affect your local view - you will always see the full policy definition for policies you create.")
|
||||
</MudJustifiedText>
|
||||
|
||||
<ConfigurationMinConfidenceSelection Disabled="@(() => this.IsNoPolicySelectedOrProtected)" RestrictToGlobalMinimumConfidence="true" SelectedValue="@(() => this.policyMinimumProviderConfidence)" SelectionUpdateAsync="@(async level => await this.PolicyMinimumConfidenceWasChangedAsync(level))" />
|
||||
|
||||
<ConfigurationProviderSelection Component="Components.DOCUMENT_ANALYSIS_ASSISTANT" Data="@this.availableLLMProviders" Disabled="@(() => this.IsNoPolicySelectedOrProtected)" SelectedValue="@(() => this.policyPreselectedProviderId)" SelectionUpdate="@(providerId => this.PolicyPreselectedProviderWasChanged(providerId))" ExplicitMinimumConfidence="@this.GetPolicyMinimumConfidenceLevel()"/>
|
||||
|
||||
<ProfileFormSelection Disabled="@this.IsNoPolicySelected" Profile="@this.currentProfile" ProfileChanged="@this.PolicyPreselectedProfileWasChangedAsync" />
|
||||
|
||||
<MudTextSwitch Disabled="@(this.IsNoPolicySelected || (this.selectedPolicy?.IsEnterpriseConfiguration ?? true))" Label="@T("Would you like to protect this policy so that you cannot accidentally edit or delete it?")" Value="@this.policyIsProtected" ValueChanged="async state => await this.PolicyProtectionWasChanged(state)" LabelOn="@T("Yes, protect this policy")" LabelOff="@T("No, the policy can be edited")" />
|
||||
|
||||
<MudText Typo="Typo.h5" Class="mt-6 mb-1">
|
||||
@T("Analysis and output rules")
|
||||
</MudText>
|
||||
|
||||
<MudJustifiedText Typo="Typo.body1" Class="mt-3">
|
||||
@T("Use the analysis and output rules to define how the AI evaluates your documents and formats the results.")
|
||||
</MudJustifiedText>
|
||||
|
||||
<MudJustifiedText Typo="Typo.body1" Class="mt-3">
|
||||
@T("The analysis rules specify what the AI should pay particular attention to while reviewing the documents you provide, and which aspects it should highlight or save. For example, if you want to extract the potential of green hydrogen for agriculture from a variety of general publications, you can explicitly define this in the analysis rules.")
|
||||
</MudJustifiedText>
|
||||
|
||||
<MudTextField T="string" Disabled="@this.IsNoPolicySelectedOrProtected" @bind-Text="@this.policyAnalysisRules" Validation="@this.ValidateAnalysisRules" Immediate="@true" Label="@T("Analysis rules")" HelperText="@T("Please provide a description of your analysis rules. This rules will be used to instruct the AI on how to analyze the documents.")" Variant="Variant.Outlined" Margin="Margin.Normal" Lines="5" AutoGrow="@true" MaxLines="26" UserAttributes="@USER_INPUT_ATTRIBUTES" Class="mb-3"/>
|
||||
|
||||
<ReadFileContent Text="@T("Load analysis rules from document")" @bind-FileContent="@this.policyAnalysisRules" Disabled="@this.IsNoPolicySelectedOrProtected"/>
|
||||
|
||||
<MudJustifiedText Typo="Typo.body1" Class="mt-3">
|
||||
@T("After the AI has processed all documents, it needs your instructions on how the result should be formatted. Would you like a structured list with keywords or a continuous text? Should the output include emojis or be written in formal business language? You can specify all these preferences in the output rules. There, you can also predefine a desired structure—for example, by using Markdown formatting to define headings, paragraphs, or bullet points.")
|
||||
</MudJustifiedText>
|
||||
|
||||
<MudTextField T="string" Disabled="@this.IsNoPolicySelectedOrProtected" @bind-Text="@this.policyOutputRules" Validation="@this.ValidateOutputRules" Immediate="@true" Label="@T("Output rules")" HelperText="@T("Please provide a description of your output rules. This rules will be used to instruct the AI on how to format the output of the analysis.")" Variant="Variant.Outlined" Margin="Margin.Normal" Lines="5" AutoGrow="@true" MaxLines="26" UserAttributes="@USER_INPUT_ATTRIBUTES" Class="mb-3"/>
|
||||
|
||||
<ReadFileContent Text="@T("Load output rules from document")" @bind-FileContent="@this.policyOutputRules" Disabled="@this.IsNoPolicySelectedOrProtected"/>
|
||||
|
||||
@if (this.SettingsManager.ConfigurationData.App.ShowAdminSettings)
|
||||
{
|
||||
<MudText Typo="Typo.h5" Class="mt-6 mb-1">
|
||||
@T("Preparation for enterprise distribution")
|
||||
</MudText>
|
||||
|
||||
<MudButton StartIcon="@Icons.Material.Filled.ContentCopy" Disabled="@(this.IsNoPolicySelected || (this.selectedPolicy?.IsEnterpriseConfiguration ?? false))" Variant="Variant.Filled" Color="Color.Default" OnClick="@this.ExportPolicyAsConfiguration">
|
||||
@T("Export policy as configuration section")
|
||||
</MudButton>
|
||||
}
|
||||
}
|
||||
</ExpansionPanel>
|
||||
|
||||
<MudDivider Style="height: 0.25ch; margin: 1rem 0;" Class="mt-6" />
|
||||
|
||||
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.DocumentScanner" HeaderText="@(T("Document selection - Policy") + $": {this.selectedPolicy?.PolicyName}")" IsExpanded="@(this.selectedPolicy?.IsProtected ?? false)">
|
||||
<MudText Typo="Typo.h5" Class="mb-1">
|
||||
@T("Policy Description")
|
||||
</MudText>
|
||||
|
||||
<MudJustifiedText Typo="Typo.body1" Class="mb-3">
|
||||
@this.selectedPolicy?.PolicyDescription
|
||||
</MudJustifiedText>
|
||||
|
||||
<MudText Typo="Typo.h5" Class="mb-1 mt-6">
|
||||
@T("Documents for the analysis")
|
||||
</MudText>
|
||||
|
||||
<AttachDocuments Name="Document Analysis Files" Layer="@DropLayers.ASSISTANTS" @bind-DocumentPaths="@this.loadedDocumentPaths" CatchAllDocuments="true" UseSmallForm="false" Provider="@this.providerSettings"/>
|
||||
|
||||
</ExpansionPanel>
|
||||
</MudExpansionPanels>
|
||||
}
|
||||
|
||||
<ProviderSelection @bind-ProviderSettings="@this.providerSettings" ValidateProvider="@this.ValidatingProvider"/>
|
||||
<ProviderSelection @bind-ProviderSettings="@this.providerSettings" ValidateProvider="@this.ValidatingProvider" ExplicitMinimumConfidence="@this.GetPolicyMinimumConfidenceLevel()"/>
|
||||
+364
-37
@@ -1,8 +1,11 @@
|
||||
using System.Text;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Dialogs;
|
||||
using AIStudio.Dialogs.Settings;
|
||||
using AIStudio.Provider;
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Settings.DataModel;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
@@ -11,12 +14,12 @@ using DialogOptions = AIStudio.Dialogs.DialogOptions;
|
||||
|
||||
namespace AIStudio.Assistants.DocumentAnalysis;
|
||||
|
||||
public partial class DocumentAnalysisAssistant : AssistantBaseCore<SettingsDialogDocumentAnalysis>
|
||||
public partial class DocumentAnalysisAssistant : AssistantBaseCore<NoSettingsPanel>
|
||||
{
|
||||
[Inject]
|
||||
private IDialogService DialogService { get; init; } = null!;
|
||||
|
||||
public override Tools.Components Component => Tools.Components.DOCUMENT_ANALYSIS_ASSISTANT;
|
||||
|
||||
protected override Tools.Components Component => Tools.Components.DOCUMENT_ANALYSIS_ASSISTANT;
|
||||
|
||||
protected override string Title => T("Document Analysis Assistant");
|
||||
|
||||
@@ -116,24 +119,77 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore<SettingsDialo
|
||||
|
||||
protected override Func<Task> SubmitAction => this.Analyze;
|
||||
|
||||
protected override bool SubmitDisabled => (this.IsNoPolicySelected || this.loadedDocumentPaths.Count==0);
|
||||
protected override bool SubmitDisabled => this.IsNoPolicySelected || this.loadedDocumentPaths.Count == 0;
|
||||
|
||||
protected override ChatThread ConvertToChatThread => (this.chatThread ?? new()) with
|
||||
protected override ChatThread ConvertToChatThread
|
||||
{
|
||||
SystemPrompt = SystemPrompts.DEFAULT,
|
||||
};
|
||||
get
|
||||
{
|
||||
if (this.chatThread is null || this.chatThread.Blocks.Count < 2)
|
||||
{
|
||||
return new ChatThread
|
||||
{
|
||||
SystemPrompt = SystemPrompts.DEFAULT
|
||||
};
|
||||
}
|
||||
|
||||
return new ChatThread
|
||||
{
|
||||
ChatId = Guid.NewGuid(),
|
||||
Name = string.Format(T("{0} - Document Analysis Session"), this.selectedPolicy?.PolicyName ?? T("Empty")),
|
||||
SystemPrompt = SystemPrompts.DEFAULT,
|
||||
Blocks =
|
||||
[
|
||||
// Replace the first "user block" (here, it was/is the block generated by the assistant) with a new one
|
||||
// that includes the loaded document paths and a standard message about the previous analysis session:
|
||||
new ContentBlock
|
||||
{
|
||||
Time = this.chatThread.Blocks.First().Time,
|
||||
Role = ChatRole.USER,
|
||||
HideFromUser = false,
|
||||
ContentType = ContentType.TEXT,
|
||||
Content = new ContentText
|
||||
{
|
||||
Text = this.T("The result of your previous document analysis session."),
|
||||
FileAttachments = this.loadedDocumentPaths.ToList(),
|
||||
}
|
||||
},
|
||||
|
||||
// Then, append the last block of the current chat thread
|
||||
// (which is expected to be the AI response):
|
||||
this.chatThread.Blocks.Last(),
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ResetForm()
|
||||
{
|
||||
this.loadedDocumentPaths.Clear();
|
||||
if (!this.MightPreselectValues())
|
||||
{
|
||||
this.policyName = string.Empty;
|
||||
this.policyDescription = string.Empty;
|
||||
this.policyIsProtected = false;
|
||||
this.policyHidePolicyDefinition = false;
|
||||
this.policyAnalysisRules = string.Empty;
|
||||
this.policyOutputRules = string.Empty;
|
||||
this.policyMinimumProviderConfidence = ConfidenceLevel.NONE;
|
||||
this.policyPreselectedProviderId = string.Empty;
|
||||
this.policyPreselectedProfileId = Profile.NO_PROFILE.Id;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ResetProviderAndProfileSelection()
|
||||
{
|
||||
if (this.selectedPolicy is null)
|
||||
{
|
||||
base.ResetProviderAndProfileSelection();
|
||||
return;
|
||||
}
|
||||
|
||||
this.ApplyPolicyPreselection(preferPolicyPreselection: true);
|
||||
}
|
||||
|
||||
protected override bool MightPreselectValues()
|
||||
{
|
||||
@@ -142,12 +198,16 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore<SettingsDialo
|
||||
this.policyName = this.selectedPolicy.PolicyName;
|
||||
this.policyDescription = this.selectedPolicy.PolicyDescription;
|
||||
this.policyIsProtected = this.selectedPolicy.IsProtected;
|
||||
this.policyHidePolicyDefinition = this.selectedPolicy.HidePolicyDefinition;
|
||||
this.policyAnalysisRules = this.selectedPolicy.AnalysisRules;
|
||||
this.policyOutputRules = this.selectedPolicy.OutputRules;
|
||||
|
||||
this.policyMinimumProviderConfidence = this.selectedPolicy.MinimumProviderConfidence;
|
||||
this.policyPreselectedProviderId = this.selectedPolicy.PreselectedProvider;
|
||||
this.policyPreselectedProfileId = string.IsNullOrWhiteSpace(this.selectedPolicy.PreselectedProfile) ? Profile.NO_PROFILE.Id : this.selectedPolicy.PreselectedProfile;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -166,12 +226,12 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore<SettingsDialo
|
||||
await this.AddPolicy();
|
||||
this.selectedPolicy = this.SettingsManager.ConfigurationData.DocumentAnalysis.Policies.First();
|
||||
}
|
||||
|
||||
var receivedDeferredContent = MessageBus.INSTANCE.CheckDeferredMessages<string>(Event.SEND_TO_DOCUMENT_ANALYSIS_ASSISTANT).FirstOrDefault();
|
||||
if (receivedDeferredContent is not null)
|
||||
this.deferredContent = receivedDeferredContent;
|
||||
|
||||
this.policyDefinitionExpanded = !this.selectedPolicy?.IsProtected ?? true;
|
||||
await base.OnInitializedAsync();
|
||||
this.ApplyFilters([], [ Event.CONFIGURATION_CHANGED, Event.PLUGINS_RELOADED ]);
|
||||
this.UpdateProviders();
|
||||
this.ApplyPolicyPreselection(preferPolicyPreselection: true);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -180,33 +240,43 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore<SettingsDialo
|
||||
{
|
||||
if(this.selectedPolicy is null)
|
||||
return;
|
||||
|
||||
if(!force && this.selectedPolicy.IsProtected)
|
||||
return;
|
||||
|
||||
if(!force && this.policyIsProtected)
|
||||
return;
|
||||
|
||||
this.selectedPolicy.PreselectedProvider = this.providerSettings.Id;
|
||||
|
||||
this.selectedPolicy.PolicyName = this.policyName;
|
||||
this.selectedPolicy.PolicyDescription = this.policyDescription;
|
||||
this.selectedPolicy.IsProtected = this.policyIsProtected;
|
||||
this.selectedPolicy.AnalysisRules = this.policyAnalysisRules;
|
||||
this.selectedPolicy.OutputRules = this.policyOutputRules;
|
||||
// The preselected profile is always user-adjustable, even for protected policies and enterprise configurations:
|
||||
this.selectedPolicy.PreselectedProfile = this.policyPreselectedProfileId;
|
||||
|
||||
// Enterprise configurations cannot be modified at all:
|
||||
if(this.selectedPolicy.IsEnterpriseConfiguration)
|
||||
return;
|
||||
|
||||
var canEditProtectedFields = force || (!this.selectedPolicy.IsProtected && !this.policyIsProtected);
|
||||
if (canEditProtectedFields)
|
||||
{
|
||||
this.selectedPolicy.PreselectedProvider = this.policyPreselectedProviderId;
|
||||
this.selectedPolicy.PolicyName = this.policyName;
|
||||
this.selectedPolicy.PolicyDescription = this.policyDescription;
|
||||
this.selectedPolicy.IsProtected = this.policyIsProtected;
|
||||
this.selectedPolicy.HidePolicyDefinition = this.policyHidePolicyDefinition;
|
||||
this.selectedPolicy.AnalysisRules = this.policyAnalysisRules;
|
||||
this.selectedPolicy.OutputRules = this.policyOutputRules;
|
||||
this.selectedPolicy.MinimumProviderConfidence = this.policyMinimumProviderConfidence;
|
||||
}
|
||||
|
||||
await this.SettingsManager.StoreSettings();
|
||||
}
|
||||
|
||||
private DataDocumentAnalysisPolicy? selectedPolicy;
|
||||
private bool policyIsProtected;
|
||||
private bool policyHidePolicyDefinition;
|
||||
private bool policyDefinitionExpanded;
|
||||
private string policyName = string.Empty;
|
||||
private string policyDescription = string.Empty;
|
||||
private string policyAnalysisRules = string.Empty;
|
||||
private string policyOutputRules = string.Empty;
|
||||
#warning Use deferred content for document analysis
|
||||
private string deferredContent = string.Empty;
|
||||
private ConfidenceLevel policyMinimumProviderConfidence = ConfidenceLevel.NONE;
|
||||
private string policyPreselectedProviderId = string.Empty;
|
||||
private string policyPreselectedProfileId = Profile.NO_PROFILE.Id;
|
||||
private HashSet<FileAttachment> loadedDocumentPaths = [];
|
||||
private readonly List<ConfigurationSelectData<string>> availableLLMProviders = new();
|
||||
|
||||
private bool IsNoPolicySelectedOrProtected => this.selectedPolicy is null || this.selectedPolicy.IsProtected;
|
||||
|
||||
@@ -216,18 +286,39 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore<SettingsDialo
|
||||
{
|
||||
this.selectedPolicy = policy;
|
||||
this.ResetForm();
|
||||
this.policyDefinitionExpanded = !this.selectedPolicy?.IsProtected ?? true;
|
||||
this.ApplyPolicyPreselection(preferPolicyPreselection: true);
|
||||
|
||||
this.form?.ResetValidation();
|
||||
this.ClearInputIssues();
|
||||
}
|
||||
|
||||
private Task PolicyDefinitionExpandedChanged(bool isExpanded)
|
||||
{
|
||||
this.policyDefinitionExpanded = isExpanded;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task AddPolicy()
|
||||
{
|
||||
this.SettingsManager.ConfigurationData.DocumentAnalysis.Policies.Add(new ()
|
||||
{
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
Num = this.SettingsManager.ConfigurationData.NextDocumentAnalysisPolicyNum++,
|
||||
PolicyName = string.Format(T("Policy {0}"), DateTimeOffset.UtcNow),
|
||||
});
|
||||
|
||||
await this.SettingsManager.StoreSettings();
|
||||
}
|
||||
|
||||
[SuppressMessage("Usage", "MWAIS0001:Direct access to `Providers` is not allowed")]
|
||||
private void UpdateProviders()
|
||||
{
|
||||
this.availableLLMProviders.Clear();
|
||||
foreach (var provider in this.SettingsManager.ConfigurationData.Providers)
|
||||
this.availableLLMProviders.Add(new ConfigurationSelectData<string>(provider.InstanceName, provider.Id));
|
||||
}
|
||||
|
||||
private async Task RemovePolicy()
|
||||
{
|
||||
if(this.selectedPolicy is null)
|
||||
@@ -236,6 +327,9 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore<SettingsDialo
|
||||
if(this.selectedPolicy.IsProtected)
|
||||
return;
|
||||
|
||||
if(this.selectedPolicy.IsEnterpriseConfiguration)
|
||||
return;
|
||||
|
||||
var dialogParameters = new DialogParameters<ConfirmDialog>
|
||||
{
|
||||
{ x => x.Message, string.Format(T("Are you sure you want to delete the document analysis policy '{0}'?"), this.selectedPolicy.PolicyName) },
|
||||
@@ -269,6 +363,9 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore<SettingsDialo
|
||||
if(this.selectedPolicy.IsProtected)
|
||||
return;
|
||||
|
||||
if(this.selectedPolicy.IsEnterpriseConfiguration)
|
||||
return;
|
||||
|
||||
this.selectedPolicy.PolicyName = this.policyName;
|
||||
}
|
||||
|
||||
@@ -276,14 +373,192 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore<SettingsDialo
|
||||
{
|
||||
if(this.selectedPolicy is null)
|
||||
return;
|
||||
|
||||
|
||||
if(this.selectedPolicy.IsEnterpriseConfiguration)
|
||||
return;
|
||||
|
||||
this.policyIsProtected = state;
|
||||
this.selectedPolicy.IsProtected = state;
|
||||
this.policyDefinitionExpanded = !state;
|
||||
await this.AutoSave(true);
|
||||
}
|
||||
|
||||
private async Task PolicyHidePolicyDefinitionWasChanged(bool state)
|
||||
{
|
||||
if(this.selectedPolicy is null)
|
||||
return;
|
||||
|
||||
if(this.selectedPolicy.IsEnterpriseConfiguration)
|
||||
return;
|
||||
|
||||
this.policyHidePolicyDefinition = state;
|
||||
this.selectedPolicy.HidePolicyDefinition = state;
|
||||
await this.AutoSave(true);
|
||||
}
|
||||
|
||||
[SuppressMessage("Usage", "MWAIS0001:Direct access to `Providers` is not allowed", Justification = "Policy-specific preselection needs to probe providers by id before falling back to SettingsManager APIs.")]
|
||||
private void ApplyPolicyPreselection(bool preferPolicyPreselection = false)
|
||||
{
|
||||
if (this.selectedPolicy is null)
|
||||
return;
|
||||
|
||||
this.policyPreselectedProviderId = this.selectedPolicy.PreselectedProvider;
|
||||
var minimumLevel = this.GetPolicyMinimumConfidenceLevel();
|
||||
|
||||
if (!preferPolicyPreselection)
|
||||
{
|
||||
// Keep the current provider if it still satisfies the minimum confidence:
|
||||
if (this.providerSettings != Settings.Provider.NONE &&
|
||||
this.providerSettings.UsedLLMProvider.GetConfidence(this.SettingsManager).Level >= minimumLevel)
|
||||
{
|
||||
this.currentProfile = this.ResolveProfileSelection();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Try to apply the policy preselection:
|
||||
var policyProvider = this.SettingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == this.selectedPolicy.PreselectedProvider);
|
||||
if (policyProvider is not null && policyProvider.UsedLLMProvider.GetConfidence(this.SettingsManager).Level >= minimumLevel)
|
||||
{
|
||||
this.providerSettings = policyProvider;
|
||||
this.currentProfile = this.ResolveProfileSelection();
|
||||
return;
|
||||
}
|
||||
|
||||
var fallbackProvider = this.SettingsManager.GetPreselectedProvider(this.Component, this.providerSettings.Id);
|
||||
if (fallbackProvider != Settings.Provider.NONE &&
|
||||
fallbackProvider.UsedLLMProvider.GetConfidence(this.SettingsManager).Level < minimumLevel)
|
||||
fallbackProvider = Settings.Provider.NONE;
|
||||
|
||||
this.providerSettings = fallbackProvider;
|
||||
this.currentProfile = this.ResolveProfileSelection();
|
||||
}
|
||||
|
||||
private ConfidenceLevel GetPolicyMinimumConfidenceLevel()
|
||||
{
|
||||
var minimumLevel = ConfidenceLevel.NONE;
|
||||
var llmSettings = this.SettingsManager.ConfigurationData.LLMProviders;
|
||||
var enforceGlobalMinimumConfidence = llmSettings is { EnforceGlobalMinimumConfidence: true, GlobalMinimumConfidence: not ConfidenceLevel.NONE and not ConfidenceLevel.UNKNOWN };
|
||||
if (enforceGlobalMinimumConfidence)
|
||||
minimumLevel = llmSettings.GlobalMinimumConfidence;
|
||||
|
||||
if (this.selectedPolicy is not null && this.selectedPolicy.MinimumProviderConfidence > minimumLevel)
|
||||
minimumLevel = this.selectedPolicy.MinimumProviderConfidence;
|
||||
|
||||
return minimumLevel;
|
||||
}
|
||||
|
||||
private Profile ResolveProfileSelection()
|
||||
{
|
||||
if (this.selectedPolicy is not null && !string.IsNullOrWhiteSpace(this.selectedPolicy.PreselectedProfile))
|
||||
{
|
||||
var policyProfile = this.SettingsManager.ConfigurationData.Profiles.FirstOrDefault(x => x.Id == this.selectedPolicy.PreselectedProfile);
|
||||
if (policyProfile is not null)
|
||||
return policyProfile;
|
||||
}
|
||||
|
||||
return this.SettingsManager.GetPreselectedProfile(this.Component);
|
||||
}
|
||||
|
||||
private async Task PolicyMinimumConfidenceWasChangedAsync(ConfidenceLevel level)
|
||||
{
|
||||
this.policyMinimumProviderConfidence = level;
|
||||
await this.AutoSave();
|
||||
|
||||
this.ApplyPolicyPreselection();
|
||||
}
|
||||
|
||||
private void PolicyPreselectedProviderWasChanged(string providerId)
|
||||
{
|
||||
if (this.selectedPolicy is null)
|
||||
return;
|
||||
|
||||
this.policyPreselectedProviderId = providerId;
|
||||
this.selectedPolicy.PreselectedProvider = providerId;
|
||||
this.providerSettings = Settings.Provider.NONE;
|
||||
this.ApplyPolicyPreselection();
|
||||
}
|
||||
|
||||
private async Task PolicyPreselectedProfileWasChangedAsync(Profile profile)
|
||||
{
|
||||
this.policyPreselectedProfileId = profile.Id;
|
||||
if (this.selectedPolicy is not null)
|
||||
this.selectedPolicy.PreselectedProfile = this.policyPreselectedProfileId;
|
||||
|
||||
this.currentProfile = this.ResolveProfileSelection();
|
||||
await this.AutoSave();
|
||||
}
|
||||
|
||||
#region Overrides of MSGComponentBase
|
||||
|
||||
protected override Task ProcessIncomingMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default
|
||||
{
|
||||
switch (triggeredEvent)
|
||||
{
|
||||
case Event.CONFIGURATION_CHANGED:
|
||||
this.UpdateProviders();
|
||||
this.StateHasChanged();
|
||||
break;
|
||||
|
||||
case Event.PLUGINS_RELOADED:
|
||||
this.HandlePluginsReloaded();
|
||||
this.StateHasChanged();
|
||||
break;
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void HandlePluginsReloaded()
|
||||
{
|
||||
// Check if the currently selected policy still exists after plugin reload:
|
||||
if (this.selectedPolicy is not null)
|
||||
{
|
||||
var stillExists = this.SettingsManager.ConfigurationData.DocumentAnalysis.Policies
|
||||
.Any(p => p.Id == this.selectedPolicy.Id);
|
||||
|
||||
if (!stillExists)
|
||||
{
|
||||
// Policy was removed, select a new one:
|
||||
this.selectedPolicy = this.SettingsManager.ConfigurationData.DocumentAnalysis.Policies.FirstOrDefault();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Policy still exists, update the reference to the potentially updated version:
|
||||
this.selectedPolicy = this.SettingsManager.ConfigurationData.DocumentAnalysis.Policies
|
||||
.First(p => p.Id == this.selectedPolicy.Id);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// No policy was selected, select the first one if available:
|
||||
this.selectedPolicy = this.SettingsManager.ConfigurationData.DocumentAnalysis.Policies.FirstOrDefault();
|
||||
}
|
||||
|
||||
// Update form values to reflect the current policy:
|
||||
this.ResetForm();
|
||||
|
||||
// Update the expansion state based on the policy protection:
|
||||
this.policyDefinitionExpanded = !this.selectedPolicy?.IsProtected ?? true;
|
||||
|
||||
// Update available providers:
|
||||
this.UpdateProviders();
|
||||
|
||||
// Apply policy preselection:
|
||||
this.ApplyPolicyPreselection(preferPolicyPreselection: true);
|
||||
|
||||
// Reset validation state:
|
||||
this.form?.ResetValidation();
|
||||
this.ClearInputIssues();
|
||||
}
|
||||
|
||||
private string? ValidatePolicyName(string name)
|
||||
{
|
||||
if(this.selectedPolicy?.IsEnterpriseConfiguration == true)
|
||||
return null;
|
||||
|
||||
if(string.IsNullOrWhiteSpace(name))
|
||||
return T("Please provide a name for your policy. This name will be used to identify the policy in AI Studio.");
|
||||
|
||||
@@ -298,6 +573,9 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore<SettingsDialo
|
||||
|
||||
private string? ValidatePolicyDescription(string description)
|
||||
{
|
||||
if(this.selectedPolicy?.IsEnterpriseConfiguration == true)
|
||||
return null;
|
||||
|
||||
if(string.IsNullOrWhiteSpace(description))
|
||||
return T("Please provide a description for your policy. This description will be used to inform users about the purpose of your document analysis policy.");
|
||||
|
||||
@@ -420,6 +698,7 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore<SettingsDialo
|
||||
return;
|
||||
|
||||
this.CreateChatThread();
|
||||
this.chatThread!.IncludeDateTime = true;
|
||||
|
||||
var userRequest = this.AddUserRequest(
|
||||
await this.PromptLoadDocumentsContent(),
|
||||
@@ -431,15 +710,63 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore<SettingsDialo
|
||||
|
||||
private async Task ExportPolicyAsConfiguration()
|
||||
{
|
||||
return;
|
||||
|
||||
# warning Implement the export function
|
||||
// do not allow the export of a protected policy
|
||||
if (this.IsNoPolicySelectedOrProtected)
|
||||
if (this.IsNoPolicySelected)
|
||||
{
|
||||
await this.MessageBus.SendError(new (Icons.Material.Filled.Policy, this.T("No policy is selected. Please select a policy to export.")));
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
await this.AutoSave();
|
||||
await this.form!.Validate();
|
||||
|
||||
if (!this.inputIsValid)
|
||||
{
|
||||
await this.MessageBus.SendError(new (Icons.Material.Filled.Policy, this.T("The selected policy contains invalid data. Please fix the issues before exporting the policy.")));
|
||||
return;
|
||||
}
|
||||
|
||||
var luaCode = this.GenerateLuaPolicyExport();
|
||||
await this.RustService.CopyText2Clipboard(this.Snackbar, luaCode);
|
||||
}
|
||||
}
|
||||
|
||||
private string GenerateLuaPolicyExport()
|
||||
{
|
||||
if(this.selectedPolicy is null)
|
||||
return string.Empty;
|
||||
|
||||
var preselectedProvider = string.IsNullOrWhiteSpace(this.selectedPolicy.PreselectedProvider) ? string.Empty : this.selectedPolicy.PreselectedProvider;
|
||||
var preselectedProfile = string.IsNullOrWhiteSpace(this.selectedPolicy.PreselectedProfile) ? Profile.NO_PROFILE.Id : this.selectedPolicy.PreselectedProfile;
|
||||
var id = string.IsNullOrWhiteSpace(this.selectedPolicy.Id) ? Guid.NewGuid().ToString() : this.selectedPolicy.Id;
|
||||
|
||||
return $$"""
|
||||
CONFIG["DOCUMENT_ANALYSIS_POLICIES"][#CONFIG["DOCUMENT_ANALYSIS_POLICIES"]+1] = {
|
||||
["Id"] = "{{id}}",
|
||||
["PolicyName"] = "{{this.selectedPolicy.PolicyName.Trim()}}",
|
||||
["PolicyDescription"] = "{{this.selectedPolicy.PolicyDescription.Trim()}}",
|
||||
|
||||
["AnalysisRules"] = [===[
|
||||
{{this.selectedPolicy.AnalysisRules.Trim()}}
|
||||
]===],
|
||||
|
||||
["OutputRules"] = [===[
|
||||
{{this.selectedPolicy.OutputRules.Trim()}}
|
||||
]===],
|
||||
|
||||
-- Optional: minimum provider confidence required for this policy.
|
||||
-- Allowed values are: NONE, VERY_LOW, LOW, MODERATE, MEDIUM, HIGH
|
||||
["MinimumProviderConfidence"] = "{{this.selectedPolicy.MinimumProviderConfidence}}",
|
||||
|
||||
-- Optional: preselect a provider or profile by ID.
|
||||
-- The IDs must exist in CONFIG["LLM_PROVIDERS"] or CONFIG["PROFILES"].
|
||||
["PreselectedProvider"] = "{{preselectedProvider}}",
|
||||
["PreselectedProfile"] = "{{preselectedProfile}}",
|
||||
|
||||
-- Optional: hide the policy definition section in the UI.
|
||||
-- When set to true, users will only see the document selection interface
|
||||
-- and cannot view or modify the policy settings.
|
||||
-- This is useful for enterprise configurations where policy details should remain hidden.
|
||||
-- Allowed values are: true, false (default: false)
|
||||
["HidePolicyDefinition"] = {{this.selectedPolicy.HidePolicyDefinition.ToString().ToLowerInvariant()}},
|
||||
}
|
||||
""";
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ namespace AIStudio.Assistants.EMail;
|
||||
|
||||
public partial class AssistantEMail : AssistantBaseCore<SettingsDialogWritingEMails>
|
||||
{
|
||||
public override Tools.Components Component => Tools.Components.EMAIL_ASSISTANT;
|
||||
protected override Tools.Components Component => Tools.Components.EMAIL_ASSISTANT;
|
||||
|
||||
protected override string Title => T("E-Mail");
|
||||
|
||||
|
||||
@@ -19,8 +19,8 @@ public partial class AssistantERI : AssistantBaseCore<SettingsDialogERIServer>
|
||||
|
||||
[Inject]
|
||||
private IDialogService DialogService { get; init; } = null!;
|
||||
|
||||
public override Tools.Components Component => Tools.Components.ERI_ASSISTANT;
|
||||
|
||||
protected override Tools.Components Component => Tools.Components.ERI_ASSISTANT;
|
||||
|
||||
protected override string Title => T("ERI Server");
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace AIStudio.Assistants.GrammarSpelling;
|
||||
|
||||
public partial class AssistantGrammarSpelling : AssistantBaseCore<SettingsDialogGrammarSpelling>
|
||||
{
|
||||
public override Tools.Components Component => Tools.Components.GRAMMAR_SPELLING_ASSISTANT;
|
||||
protected override Tools.Components Component => Tools.Components.GRAMMAR_SPELLING_ASSISTANT;
|
||||
|
||||
protected override string Title => T("Grammar & Spelling Checker");
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace AIStudio.Assistants.I18N;
|
||||
|
||||
public partial class AssistantI18N : AssistantBaseCore<SettingsDialogI18N>
|
||||
{
|
||||
public override Tools.Components Component => Tools.Components.I18N_ASSISTANT;
|
||||
protected override Tools.Components Component => Tools.Components.I18N_ASSISTANT;
|
||||
|
||||
protected override string Title => T("Localization");
|
||||
|
||||
|
||||
@@ -382,6 +382,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::CODING::COMMONCODINGLANGUAGEEXTENSIONS::T
|
||||
-- None
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::CODING::COMMONCODINGLANGUAGEEXTENSIONS::T810547195"] = "None"
|
||||
|
||||
-- {0} - Document Analysis Session
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T108097007"] = "{0} - Document Analysis Session"
|
||||
|
||||
-- Use the analysis and output rules to define how the AI evaluates your documents and formats the results.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T1155482668"] = "Use the analysis and output rules to define how the AI evaluates your documents and formats the results."
|
||||
|
||||
@@ -394,9 +397,6 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTA
|
||||
-- Please provide a description of your analysis rules. This rules will be used to instruct the AI on how to analyze the documents.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T1291179736"] = "Please provide a description of your analysis rules. This rules will be used to instruct the AI on how to analyze the documents."
|
||||
|
||||
-- Not implemented yet.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T1568777658"] = "Not implemented yet."
|
||||
|
||||
-- Yes, protect this policy
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T1762380857"] = "Yes, protect this policy"
|
||||
|
||||
@@ -406,9 +406,18 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTA
|
||||
-- Please provide a description for your policy. This description will be used to inform users about the purpose of your document analysis policy.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T1837166236"] = "Please provide a description for your policy. This description will be used to inform users about the purpose of your document analysis policy."
|
||||
|
||||
-- Hide the policy definition when distributed via configuration plugin?
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T1875622568"] = "Hide the policy definition when distributed via configuration plugin?"
|
||||
|
||||
-- Common settings
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T1963959073"] = "Common settings"
|
||||
|
||||
-- Note: This setting only takes effect when this policy is exported and distributed via a configuration plugin to other users. When enabled, users will only see the document selection interface and cannot view or modify the policy details. This setting does NOT affect your local view - you will always see the full policy definition for policies you create.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T1984494439"] = "Note: This setting only takes effect when this policy is exported and distributed via a configuration plugin to other users. When enabled, users will only see the document selection interface and cannot view or modify the policy details. This setting does NOT affect your local view - you will always see the full policy definition for policies you create."
|
||||
|
||||
-- This policy is managed by your organization.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2035084381"] = "This policy is managed by your organization."
|
||||
|
||||
-- The document analysis assistant helps you to analyze and extract information from documents based on predefined policies. You can create, edit, and manage document analysis policies that define how documents should be processed and what information should be extracted. Some policies might be protected by your organization and cannot be modified or deleted.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T206207667"] = "The document analysis assistant helps you to analyze and extract information from documents based on predefined policies. You can create, edit, and manage document analysis policies that define how documents should be processed and what information should be extracted. Some policies might be protected by your organization and cannot be modified or deleted."
|
||||
|
||||
@@ -436,12 +445,21 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTA
|
||||
-- Export policy as configuration section
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2556564432"] = "Export policy as configuration section"
|
||||
|
||||
-- The result of your previous document analysis session.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2570551055"] = "The result of your previous document analysis session."
|
||||
|
||||
-- Are you sure you want to delete the document analysis policy '{0}'?
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2582525917"] = "Are you sure you want to delete the document analysis policy '{0}'?"
|
||||
|
||||
-- Expand this section to view and edit the policy definition.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T277813037"] = "Expand this section to view and edit the policy definition."
|
||||
|
||||
-- Policy name
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2879019438"] = "Policy name"
|
||||
|
||||
-- No policy is selected. Please select a policy to export.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2929693091"] = "No policy is selected. Please select a policy to export."
|
||||
|
||||
-- Policy Description
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3023558273"] = "Policy Description"
|
||||
|
||||
@@ -457,6 +475,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTA
|
||||
-- Policy {0}
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3157740273"] = "Policy {0}"
|
||||
|
||||
-- No, show the policy definition
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3166091879"] = "No, show the policy definition"
|
||||
|
||||
-- The description of your policy must be between 32 and 512 characters long.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3285636934"] = "The description of your policy must be between 32 and 512 characters long."
|
||||
|
||||
@@ -469,6 +490,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTA
|
||||
-- Document Analysis Assistant
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T348883878"] = "Document Analysis Assistant"
|
||||
|
||||
-- Empty
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3512147854"] = "Empty"
|
||||
|
||||
-- Analysis and output rules
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3555314296"] = "Analysis and output rules"
|
||||
|
||||
@@ -496,6 +520,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTA
|
||||
-- After the AI has processed all documents, it needs your instructions on how the result should be formatted. Would you like a structured list with keywords or a continuous text? Should the output include emojis or be written in formal business language? You can specify all these preferences in the output rules. There, you can also predefine a desired structure—for example, by using Markdown formatting to define headings, paragraphs, or bullet points.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T726434276"] = "After the AI has processed all documents, it needs your instructions on how the result should be formatted. Would you like a structured list with keywords or a continuous text? Should the output include emojis or be written in formal business language? You can specify all these preferences in the output rules. There, you can also predefine a desired structure—for example, by using Markdown formatting to define headings, paragraphs, or bullet points."
|
||||
|
||||
-- The selected policy contains invalid data. Please fix the issues before exporting the policy.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T736334861"] = "The selected policy contains invalid data. Please fix the issues before exporting the policy."
|
||||
|
||||
-- Policy description
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T748735777"] = "Policy description"
|
||||
|
||||
@@ -505,6 +532,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTA
|
||||
-- Here you have the option to save different policies for various document analysis assistants and switch between them.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T848153710"] = "Here you have the option to save different policies for various document analysis assistants and switch between them."
|
||||
|
||||
-- Yes, hide the policy definition
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T940701960"] = "Yes, hide the policy definition"
|
||||
|
||||
-- Provide a list of bullet points and some basic information for an e-mail. The assistant will generate an e-mail based on that input.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::EMAIL::ASSISTANTEMAIL::T1143222914"] = "Provide a list of bullet points and some basic information for an e-mail. The assistant will generate an e-mail based on that input."
|
||||
|
||||
@@ -1666,6 +1696,15 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONPROVIDERSELECTION::T20906218
|
||||
-- Use app default
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONPROVIDERSELECTION::T3672477670"] = "Use app default"
|
||||
|
||||
-- No shortcut configured
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONSHORTCUT::T3099115336"] = "No shortcut configured"
|
||||
|
||||
-- Change shortcut
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONSHORTCUT::T4081853237"] = "Change shortcut"
|
||||
|
||||
-- Configure Keyboard Shortcut
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONSHORTCUT::T636303786"] = "Configure Keyboard Shortcut"
|
||||
|
||||
-- Yes, let the AI decide which data sources are needed.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCESELECTION::T1031370894"] = "Yes, let the AI decide which data sources are needed."
|
||||
|
||||
@@ -1765,21 +1804,27 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MOTIVATION::T1086130692"] = "Limitations
|
||||
-- Personal Needs and Limitations of Web Services
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MOTIVATION::T1839655973"] = "Personal Needs and Limitations of Web Services"
|
||||
|
||||
-- Democratization of AI
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MOTIVATION::T1986314327"] = "Democratization of AI"
|
||||
|
||||
-- While exploring available solutions, I found a desktop application called Anything LLM. Unfortunately, it fell short of meeting my specific requirements and lacked the user interface design I envisioned. For macOS, there were several apps similar to what I had in mind, but they were all commercial solutions shrouded in uncertainty. The developers' identities and the origins of these apps were unclear, raising significant security concerns. Reports from users about stolen API keys and unwanted charges only amplified my reservations.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MOTIVATION::T3552777197"] = "While exploring available solutions, I found a desktop application called Anything LLM. Unfortunately, it fell short of meeting my specific requirements and lacked the user interface design I envisioned. For macOS, there were several apps similar to what I had in mind, but they were all commercial solutions shrouded in uncertainty. The developers' identities and the origins of these apps were unclear, raising significant security concerns. Reports from users about stolen API keys and unwanted charges only amplified my reservations."
|
||||
|
||||
-- Hello, my name is Thorsten Sommer, and I am the initial creator of MindWork AI Studio. The motivation behind developing this app stems from several crucial needs and observations I made over time.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MOTIVATION::T3569462457"] = "Hello, my name is Thorsten Sommer, and I am the initial creator of MindWork AI Studio. The motivation behind developing this app stems from several crucial needs and observations I made over time."
|
||||
|
||||
-- Through MindWork AI Studio, I aim to provide a secure, flexible, and user-friendly tool that caters to a wider audience without compromising on functionality or design. This app is the culmination of my desire to meet personal requirements, address existing gaps in the market, and showcase innovative development practices.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MOTIVATION::T3622193740"] = "Through MindWork AI Studio, I aim to provide a secure, flexible, and user-friendly tool that caters to a wider audience without compromising on functionality or design. This app is the culmination of my desire to meet personal requirements, address existing gaps in the market, and showcase innovative development practices."
|
||||
-- We also want to contribute to the democratization of AI. MindWork AI Studio runs even on low-cost hardware, including computers around 100 EUR such as Raspberry Pi. This makes the app and its full feature set accessible to people and families with limited budgets. You can start with local LLMs for your first steps or use affordable cloud models. MindWork AI Studio itself is available free of charge.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MOTIVATION::T3672974243"] = "We also want to contribute to the democratization of AI. MindWork AI Studio runs even on low-cost hardware, including computers around 100 EUR such as Raspberry Pi. This makes the app and its full feature set accessible to people and families with limited budgets. You can start with local LLMs for your first steps or use affordable cloud models. MindWork AI Studio itself is available free of charge."
|
||||
|
||||
-- Relying on web services like ChatGPT was not a sustainable solution for me. I needed an AI that could also access files directly on my device, a functionality web services inherently lack due to security and privacy constraints. Although I could have scripted something in Python to meet my needs, this approach was too cumbersome for daily use. More importantly, I wanted to develop a solution that anyone could use without needing any programming knowledge.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MOTIVATION::T372007989"] = "Relying on web services like ChatGPT was not a sustainable solution for me. I needed an AI that could also access files directly on my device, a functionality web services inherently lack due to security and privacy constraints. Although I could have scripted something in Python to meet my needs, this approach was too cumbersome for daily use. More importantly, I wanted to develop a solution that anyone could use without needing any programming knowledge."
|
||||
|
||||
-- Hello, my name is Thorsten Sommer, and I am the initial creator of MindWork AI Studio. I started this project based on several crucial needs and observations I made over time. Today, we have a core team of developers and support from the open-source community.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MOTIVATION::T483341611"] = "Hello, my name is Thorsten Sommer, and I am the initial creator of MindWork AI Studio. I started this project based on several crucial needs and observations I made over time. Today, we have a core team of developers and support from the open-source community."
|
||||
|
||||
-- Cross-Platform and Modern Development
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MOTIVATION::T843057510"] = "Cross-Platform and Modern Development"
|
||||
|
||||
-- Today, our team aims to provide a secure, flexible, and user-friendly tool that serves a broad audience without compromising on functionality or design.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MOTIVATION::T904941692"] = "Today, our team aims to provide a secure, flexible, and user-friendly tool that serves a broad audience without compromising on functionality or design."
|
||||
|
||||
-- Copies the content to the clipboard
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MUDCOPYCLIPBOARDBUTTON::T12948066"] = "Copies the content to the clipboard"
|
||||
|
||||
@@ -2008,6 +2053,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1059411425"]
|
||||
-- Do you want to show preview features in the app?
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1118505044"] = "Do you want to show preview features in the app?"
|
||||
|
||||
-- Voice recording shortcut
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1278320412"] = "Voice recording shortcut"
|
||||
|
||||
-- How often should we check for app updates?
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1364944735"] = "How often should we check for app updates?"
|
||||
|
||||
@@ -2038,9 +2086,18 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1898060643"]
|
||||
-- Select the language for the app.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1907446663"] = "Select the language for the app."
|
||||
|
||||
-- When enabled, additional administration options become visible. These options are intended for IT staff to manage organization-wide configuration, e.g. configuring and exporting providers for an entire organization.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2013281167"] = "When enabled, additional administration options become visible. These options are intended for IT staff to manage organization-wide configuration, e.g. configuring and exporting providers for an entire organization."
|
||||
|
||||
-- The global keyboard shortcut for toggling voice recording. This shortcut works system-wide, even when the app is not focused.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2143741496"] = "The global keyboard shortcut for toggling voice recording. This shortcut works system-wide, even when the app is not focused."
|
||||
|
||||
-- Disable dictation and transcription
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T215381891"] = "Disable dictation and transcription"
|
||||
|
||||
-- Enterprise Administration
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2277116008"] = "Enterprise Administration"
|
||||
|
||||
-- Language behavior
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2341504363"] = "Language behavior"
|
||||
|
||||
@@ -2050,6 +2107,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T237706157"]
|
||||
-- Language
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2591284123"] = "Language"
|
||||
|
||||
-- Administration settings are visible
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2591866808"] = "Administration settings are visible"
|
||||
|
||||
-- Save energy?
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T3100928009"] = "Save energy?"
|
||||
|
||||
@@ -2059,9 +2119,18 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T3165555978"]
|
||||
-- App Options
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T3577148634"] = "App Options"
|
||||
|
||||
-- Generate a 256-bit encryption secret for encrypting API keys in configuration plugins. Deploy this secret to client machines via Group Policy (Windows Registry) or environment variables. Providers can then be exported with encrypted API keys using the export buttons in the provider settings.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T362833"] = "Generate a 256-bit encryption secret for encrypting API keys in configuration plugins. Deploy this secret to client machines via Group Policy (Windows Registry) or environment variables. Providers can then be exported with encrypted API keys using the export buttons in the provider settings."
|
||||
|
||||
-- When enabled, streamed content from the AI is updated once every third second. When disabled, streamed content will be updated as soon as it is available.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T3652888444"] = "When enabled, streamed content from the AI is updated once every third second. When disabled, streamed content will be updated as soon as it is available."
|
||||
|
||||
-- Show administration settings?
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T3694781396"] = "Show administration settings?"
|
||||
|
||||
-- Read the Enterprise IT documentation for details.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T3705451321"] = "Read the Enterprise IT documentation for details."
|
||||
|
||||
-- Enable spellchecking?
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T3914529369"] = "Enable spellchecking?"
|
||||
|
||||
@@ -2095,18 +2164,42 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T817101267"]
|
||||
-- Would you like to set one provider as the default for the entire app? When you configure a different provider for an assistant, it will always take precedence.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T844514734"] = "Would you like to set one provider as the default for the entire app? When you configure a different provider for an assistant, it will always take precedence."
|
||||
|
||||
-- Generate an encryption secret and copy it to the clipboard
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T922066419"] = "Generate an encryption secret and copy it to the clipboard"
|
||||
|
||||
-- Administration settings are not visible
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T929143445"] = "Administration settings are not visible"
|
||||
|
||||
-- Embedding Result
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1387042335"] = "Embedding Result"
|
||||
|
||||
-- Delete
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1469573738"] = "Delete"
|
||||
|
||||
-- Embed text
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1644934561"] = "Embed text"
|
||||
|
||||
-- Test Embedding Provider
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1655784761"] = "Test Embedding Provider"
|
||||
|
||||
-- Add Embedding
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1738753945"] = "Add Embedding"
|
||||
|
||||
-- Uses the provider-configured model
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1760715963"] = "Uses the provider-configured model"
|
||||
|
||||
-- Are you sure you want to delete the embedding provider '{0}'?
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1825371968"] = "Are you sure you want to delete the embedding provider '{0}'?"
|
||||
|
||||
-- Add Embedding Provider
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T190634634"] = "Add Embedding Provider"
|
||||
|
||||
-- Add text that should be embedded:
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1992646324"] = "Add text that should be embedded:"
|
||||
|
||||
-- Embedding Vector (one dimension per line)
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T2174876961"] = "Embedding Vector (one dimension per line)"
|
||||
|
||||
-- Model
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T2189814010"] = "Model"
|
||||
|
||||
@@ -2116,6 +2209,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T24199
|
||||
-- Name
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T266367750"] = "Name"
|
||||
|
||||
-- No embedding was returned.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T291969"] = "No embedding was returned."
|
||||
|
||||
-- Configured Embedding Providers
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T305753126"] = "Configured Embedding Providers"
|
||||
|
||||
@@ -2125,6 +2221,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T32512
|
||||
-- Edit
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T3267849393"] = "Edit"
|
||||
|
||||
-- Close
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T3448155331"] = "Close"
|
||||
|
||||
-- Actions
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T3865031940"] = "Actions"
|
||||
|
||||
@@ -2146,9 +2245,27 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T51130
|
||||
-- Open Dashboard
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T78223861"] = "Open Dashboard"
|
||||
|
||||
-- Test
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T805092869"] = "Test"
|
||||
|
||||
-- Example text to embed
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T816748904"] = "Example text to embed"
|
||||
|
||||
-- Provider
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T900237532"] = "Provider"
|
||||
|
||||
-- Export configuration
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T975426229"] = "Export configuration"
|
||||
|
||||
-- Cannot export the encrypted API key: No enterprise encryption secret is configured.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERBASE::T1832230847"] = "Cannot export the encrypted API key: No enterprise encryption secret is configured."
|
||||
|
||||
-- This provider has an API key configured. Do you want to include the encrypted API key in the export? Note: The recipient will need the same encryption secret to use the API key.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERBASE::T3368145670"] = "This provider has an API key configured. Do you want to include the encrypted API key in the export? Note: The recipient will need the same encryption secret to use the API key."
|
||||
|
||||
-- Export API Key?
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERBASE::T4010580285"] = "Export API Key?"
|
||||
|
||||
-- Show provider's confidence level?
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1052533048"] = "Show provider's confidence level?"
|
||||
|
||||
@@ -2164,6 +2281,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T162847
|
||||
-- Description
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1725856265"] = "Description"
|
||||
|
||||
-- Uses the provider-configured model
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1760715963"] = "Uses the provider-configured model"
|
||||
|
||||
-- Add Provider
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1806589097"] = "Add Provider"
|
||||
|
||||
@@ -2206,9 +2326,6 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T291173
|
||||
-- Configured LLM Providers
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3019870540"] = "Configured LLM Providers"
|
||||
|
||||
-- as selected by provider
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3082210376"] = "as selected by provider"
|
||||
|
||||
-- Edit
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3267849393"] = "Edit"
|
||||
|
||||
@@ -2254,6 +2371,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T853225
|
||||
-- Provider
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T900237532"] = "Provider"
|
||||
|
||||
-- Export configuration
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T975426229"] = "Export configuration"
|
||||
|
||||
-- No transcription provider configured yet.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T1079350363"] = "No transcription provider configured yet."
|
||||
|
||||
@@ -2266,6 +2386,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T14
|
||||
-- Add transcription provider
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T1645238629"] = "Add transcription provider"
|
||||
|
||||
-- Uses the provider-configured model
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T1760715963"] = "Uses the provider-configured model"
|
||||
|
||||
-- Add Transcription Provider
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T2066315685"] = "Add Transcription Provider"
|
||||
|
||||
@@ -2305,6 +2428,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T78
|
||||
-- Provider
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T900237532"] = "Provider"
|
||||
|
||||
-- Export configuration
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T975426229"] = "Export configuration"
|
||||
|
||||
-- Copy {0} to the clipboard
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TEXTINFOLINE::T2206391442"] = "Copy {0} to the clipboard"
|
||||
|
||||
@@ -2341,9 +2467,15 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T1648606751"] = "You'll be able t
|
||||
-- It will soon be possible to integrate data from the corporate network using a specified interface (External Retrieval Interface, ERI for short). This will likely require development work by the organization in question.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T1926587044"] = "It will soon be possible to integrate data from the corporate network using a specified interface (External Retrieval Interface, ERI for short). This will likely require development work by the organization in question."
|
||||
|
||||
-- Democratization of AI
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T1986314327"] = "Democratization of AI"
|
||||
|
||||
-- Whatever your job or task is, MindWork AI Studio aims to meet your needs: whether you're a project manager, scientist, artist, author, software developer, or game developer.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T2144737937"] = "Whatever your job or task is, MindWork AI Studio aims to meet your needs: whether you're a project manager, scientist, artist, author, software developer, or game developer."
|
||||
|
||||
-- We want to contribute to the democratization of AI. MindWork AI Studio runs even on low-cost hardware, including computers around 100 EUR such as Raspberry Pi. This makes the app and its full feature set accessible to people and families with limited budgets. You can start with local LLMs or use affordable cloud models. MindWork AI Studio itself is available free of charge.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T2201645589"] = "We want to contribute to the democratization of AI. MindWork AI Studio runs even on low-cost hardware, including computers around 100 EUR such as Raspberry Pi. This makes the app and its full feature set accessible to people and families with limited budgets. You can start with local LLMs or use affordable cloud models. MindWork AI Studio itself is available free of charge."
|
||||
|
||||
-- You can connect your email inboxes with AI Studio. The AI will read your emails and notify you of important events. You'll also be able to access knowledge from your emails in your chats.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T2289234741"] = "You can connect your email inboxes with AI Studio. The AI will read your emails and notify you of important events. You'll also be able to access knowledge from your emails in your chats."
|
||||
|
||||
@@ -2389,6 +2521,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T586430036"] = "Useful assistants
|
||||
-- Failed to create the transcription provider.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VOICERECORDER::T1689988905"] = "Failed to create the transcription provider."
|
||||
|
||||
-- Failed to start audio recording.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VOICERECORDER::T2144994226"] = "Failed to start audio recording."
|
||||
|
||||
-- Stop recording and start transcription
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VOICERECORDER::T224155287"] = "Stop recording and start transcription"
|
||||
|
||||
@@ -2401,6 +2536,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VOICERECORDER::T2851219233"] = "Transcrip
|
||||
-- The configured transcription provider was not found.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VOICERECORDER::T331613105"] = "The configured transcription provider was not found."
|
||||
|
||||
-- Failed to stop audio recording.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VOICERECORDER::T3462568264"] = "Failed to stop audio recording."
|
||||
|
||||
-- The configured transcription provider does not meet the minimum confidence level.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VOICERECORDER::T3834149033"] = "The configured transcription provider does not meet the minimum confidence level."
|
||||
|
||||
@@ -3205,6 +3343,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T290547799"] = "Cur
|
||||
-- Model selection
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T416738168"] = "Model selection"
|
||||
|
||||
-- We are currently unable to communicate with the provider to load models. Please try again later.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T504465522"] = "We are currently unable to communicate with the provider to load models. Please try again later."
|
||||
|
||||
-- Host
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T808120719"] = "Host"
|
||||
|
||||
@@ -3214,6 +3355,12 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T900237532"] = "Pro
|
||||
-- Cancel
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T900713019"] = "Cancel"
|
||||
|
||||
-- Embedding Vector
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGRESULTDIALOG::T1173984541"] = "Embedding Vector"
|
||||
|
||||
-- Close
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGRESULTDIALOG::T3448155331"] = "Close"
|
||||
|
||||
-- Unfortunately, Pandoc's GPL license isn't compatible with the AI Studios licenses. However, software under the GPL is free to use and free of charge. You'll need to accept the GPL license before we can download and install Pandoc for you automatically (recommended). Alternatively, you might download it yourself using the instructions below or install it otherwise, e.g., by using a package manager of your operating system.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PANDOCDIALOG::T1001483402"] = "Unfortunately, Pandoc's GPL license isn't compatible with the AI Studios licenses. However, software under the GPL is free to use and free of charge. You'll need to accept the GPL license before we can download and install Pandoc for you automatically (recommended). Alternatively, you might download it yourself using the instructions below or install it otherwise, e.g., by using a package manager of your operating system."
|
||||
|
||||
@@ -3412,12 +3559,18 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3361153305"] = "Show Expert
|
||||
-- Show available models
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3763891899"] = "Show available models"
|
||||
|
||||
-- This host uses the model configured at the provider level. No model selection is available.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3783329915"] = "This host uses the model configured at the provider level. No model selection is available."
|
||||
|
||||
-- Currently, we cannot query the models for the selected provider and/or host. Therefore, please enter the model name manually.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T4116737656"] = "Currently, we cannot query the models for the selected provider and/or host. Therefore, please enter the model name manually."
|
||||
|
||||
-- Model selection
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T416738168"] = "Model selection"
|
||||
|
||||
-- We are currently unable to communicate with the provider to load models. Please try again later.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T504465522"] = "We are currently unable to communicate with the provider to load models. Please try again later."
|
||||
|
||||
-- Host
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T808120719"] = "Host"
|
||||
|
||||
@@ -3979,33 +4132,6 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T774473
|
||||
-- Local Directory
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T926703547"] = "Local Directory"
|
||||
|
||||
-- Assistant: Document Analysis
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDOCUMENTANALYSIS::T1372406750"] = "Assistant: Document Analysis"
|
||||
|
||||
-- Most document analysis options can be customized and saved directly in the assistant. For this, the assistant has an auto-save function.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDOCUMENTANALYSIS::T1870328357"] = "Most document analysis options can be customized and saved directly in the assistant. For this, the assistant has an auto-save function."
|
||||
|
||||
-- Would you like to preselect one of your profiles?
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDOCUMENTANALYSIS::T2221665527"] = "Would you like to preselect one of your profiles?"
|
||||
|
||||
-- Preselect document analysis options?
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDOCUMENTANALYSIS::T2230062650"] = "Preselect document analysis options?"
|
||||
|
||||
-- When enabled, you can preselect some document analysis options.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDOCUMENTANALYSIS::T2301091111"] = "When enabled, you can preselect some document analysis options."
|
||||
|
||||
-- No document analysis options are preselected
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDOCUMENTANALYSIS::T3317802895"] = "No document analysis options are preselected"
|
||||
|
||||
-- Close
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDOCUMENTANALYSIS::T3448155331"] = "Close"
|
||||
|
||||
-- Document analysis options are preselected
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDOCUMENTANALYSIS::T3945756386"] = "Document analysis options are preselected"
|
||||
|
||||
-- Preselect one of your profiles?
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDOCUMENTANALYSIS::T4004501229"] = "Preselect one of your profiles?"
|
||||
|
||||
-- When enabled, you can preselect some ERI server options.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGERISERVER::T1280666275"] = "When enabled, you can preselect some ERI server options."
|
||||
|
||||
@@ -4582,6 +4708,42 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGWRITINGEMAILS::T3832
|
||||
-- Preselect one of your profiles?
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGWRITINGEMAILS::T4004501229"] = "Preselect one of your profiles?"
|
||||
|
||||
-- Save
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SHORTCUTDIALOG::T1294818664"] = "Save"
|
||||
|
||||
-- Press the desired key combination to set the shortcut. The shortcut will be registered globally and will work even when the app is not focused.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SHORTCUTDIALOG::T1464973299"] = "Press the desired key combination to set the shortcut. The shortcut will be registered globally and will work even when the app is not focused."
|
||||
|
||||
-- Press a key combination...
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SHORTCUTDIALOG::T1468443151"] = "Press a key combination..."
|
||||
|
||||
-- Clear Shortcut
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SHORTCUTDIALOG::T1807313248"] = "Clear Shortcut"
|
||||
|
||||
-- Invalid shortcut: {0}
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SHORTCUTDIALOG::T189893682"] = "Invalid shortcut: {0}"
|
||||
|
||||
-- This shortcut conflicts with: {0}
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SHORTCUTDIALOG::T2633102934"] = "This shortcut conflicts with: {0}"
|
||||
|
||||
-- Please include at least one modifier key (Ctrl, Shift, Alt, or Cmd).
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SHORTCUTDIALOG::T3060573513"] = "Please include at least one modifier key (Ctrl, Shift, Alt, or Cmd)."
|
||||
|
||||
-- Shortcut is valid and available.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SHORTCUTDIALOG::T3159532525"] = "Shortcut is valid and available."
|
||||
|
||||
-- Define a shortcut
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SHORTCUTDIALOG::T3734850493"] = "Define a shortcut"
|
||||
|
||||
-- This is the shortcut you previously used.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SHORTCUTDIALOG::T4167229652"] = "This is the shortcut you previously used."
|
||||
|
||||
-- Supported modifiers: Ctrl/Cmd, Shift, Alt.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SHORTCUTDIALOG::T889258890"] = "Supported modifiers: Ctrl/Cmd, Shift, Alt."
|
||||
|
||||
-- Cancel
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SHORTCUTDIALOG::T900713019"] = "Cancel"
|
||||
|
||||
-- Please enter a value.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SINGLEINPUTDIALOG::T3576780391"] = "Please enter a value."
|
||||
|
||||
@@ -4633,9 +4795,15 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T2842060373"] =
|
||||
-- Please enter a transcription model name.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T3703662664"] = "Please enter a transcription model name."
|
||||
|
||||
-- This host uses the model configured at the provider level. No model selection is available.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T3783329915"] = "This host uses the model configured at the provider level. No model selection is available."
|
||||
|
||||
-- Model selection
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T416738168"] = "Model selection"
|
||||
|
||||
-- We are currently unable to communicate with the provider to load models. Please try again later.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T504465522"] = "We are currently unable to communicate with the provider to load models. Please try again later."
|
||||
|
||||
-- Host
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T808120719"] = "Host"
|
||||
|
||||
@@ -4858,6 +5026,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T149711988"] = "You only pay for what yo
|
||||
-- Assistants
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T1614176092"] = "Assistants"
|
||||
|
||||
-- We want to contribute to the democratization of AI. MindWork AI Studio runs even on low-cost hardware, including computers around 100 EUR such as Raspberry Pi. This makes the app and its full feature set accessible to people and families with limited budgets. You can start with local LLMs or use affordable cloud models.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T1628689293"] = "We want to contribute to the democratization of AI. MindWork AI Studio runs even on low-cost hardware, including computers around 100 EUR such as Raspberry Pi. This makes the app and its full feature set accessible to people and families with limited budgets. You can start with local LLMs or use affordable cloud models."
|
||||
|
||||
-- Unrestricted usage
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T1686815996"] = "Unrestricted usage"
|
||||
|
||||
@@ -4867,6 +5038,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T1702902297"] = "Introduction"
|
||||
-- Vision
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T1892426825"] = "Vision"
|
||||
|
||||
-- Democratization of AI
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T1986314327"] = "Democratization of AI"
|
||||
|
||||
-- Let's get started
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T2331588413"] = "Let's get started"
|
||||
|
||||
@@ -4921,18 +5095,33 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1019424746"] = "Startup log file
|
||||
-- Browse AI Studio's source code on GitHub — we welcome your contributions.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1107156991"] = "Browse AI Studio's source code on GitHub — we welcome your contributions."
|
||||
|
||||
-- ID mismatch: the plugin ID differs from the enterprise configuration ID.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1137744461"] = "ID mismatch: the plugin ID differs from the enterprise configuration ID."
|
||||
|
||||
-- This is a private AI Studio installation. It runs without an enterprise configuration.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1209549230"] = "This is a private AI Studio installation. It runs without an enterprise configuration."
|
||||
|
||||
-- AI Studio runs with an enterprise configuration and a configuration server. The configuration plugin is not yet available.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1282228996"] = "AI Studio runs with an enterprise configuration and a configuration server. The configuration plugin is not yet available."
|
||||
|
||||
-- This library is used to read PDF files. This is necessary, e.g., for using PDFs as a data source for a chat.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1388816916"] = "This library is used to read PDF files. This is necessary, e.g., for using PDFs as a data source for a chat."
|
||||
|
||||
-- Database version
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1420062548"] = "Database version"
|
||||
|
||||
-- This library is used to extend the MudBlazor library. It provides additional components that are not part of the MudBlazor library.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1421513382"] = "This library is used to extend the MudBlazor library. It provides additional components that are not part of the MudBlazor library."
|
||||
|
||||
-- Waiting for the configuration plugin...
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1533382393"] = "Waiting for the configuration plugin..."
|
||||
|
||||
-- Encryption secret: is not configured
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1560776885"] = "Encryption secret: is not configured"
|
||||
|
||||
-- AI Studio runs with an enterprise configuration and configuration servers. The configuration plugins are active.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1596483935"] = "AI Studio runs with an enterprise configuration and configuration servers. The configuration plugins are active."
|
||||
|
||||
-- Qdrant is a vector database and vector similarity search engine. We use it to realize local RAG—retrieval-augmented generation—within AI Studio. Thanks for the effort and great work that has been and is being put into Qdrant.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1619832053"] = "Qdrant is a vector database and vector similarity search engine. We use it to realize local RAG—retrieval-augmented generation—within AI Studio. Thanks for the effort and great work that has been and is being put into Qdrant."
|
||||
|
||||
-- We use Lua as the language for plugins. Lua-CSharp lets Lua scripts communicate with AI Studio and vice versa. Thank you, Yusuke Nakada, for this great library.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T162898512"] = "We use Lua as the language for plugins. Lua-CSharp lets Lua scripts communicate with AI Studio and vice versa. Thank you, Yusuke Nakada, for this great library."
|
||||
|
||||
@@ -4966,9 +5155,15 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1915240766"] = "In order to use
|
||||
-- This library is used to convert HTML to Markdown. This is necessary, e.g., when you provide a URL as input for an assistant.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1924365263"] = "This library is used to convert HTML to Markdown. This is necessary, e.g., when you provide a URL as input for an assistant."
|
||||
|
||||
-- Encryption secret: is configured
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1931141322"] = "Encryption secret: is configured"
|
||||
|
||||
-- We use Rocket to implement the runtime API. This is necessary because the runtime must be able to communicate with the user interface (IPC). Rocket is a great framework for implementing web APIs in Rust.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T1943216839"] = "We use Rocket to implement the runtime API. This is necessary because the runtime must be able to communicate with the user interface (IPC). Rocket is a great framework for implementing web APIs in Rust."
|
||||
|
||||
-- Copies the following to the clipboard
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2029659664"] = "Copies the following to the clipboard"
|
||||
|
||||
-- Copies the server URL to the clipboard
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2037899437"] = "Copies the server URL to the clipboard"
|
||||
|
||||
@@ -4987,9 +5182,6 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2272122662"] = "Configuration se
|
||||
-- We must generate random numbers, e.g., for securing the interprocess communication between the user interface and the runtime. The rand library is great for this purpose.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2273492381"] = "We must generate random numbers, e.g., for securing the interprocess communication between the user interface and the runtime. The rand library is great for this purpose."
|
||||
|
||||
-- AI Studio runs with an enterprise configuration using a configuration plugin, without central configuration management.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2280402765"] = "AI Studio runs with an enterprise configuration using a configuration plugin, without central configuration management."
|
||||
|
||||
-- Configuration plugin ID:
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2301484629"] = "Configuration plugin ID:"
|
||||
|
||||
@@ -5014,6 +5206,12 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2557066213"] = "Used Open Source
|
||||
-- Build time
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T260228112"] = "Build time"
|
||||
|
||||
-- This library is used to create temporary folders for saving the certificate and private key for communication with Qdrant.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2619858133"] = "This library is used to create temporary folders for saving the certificate and private key for communication with Qdrant."
|
||||
|
||||
-- This crate provides derive macros for Rust enums, which we use to reduce boilerplate when implementing string conversions and metadata for runtime types. This is helpful for the communication between our Rust and .NET systems.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2635482790"] = "This crate provides derive macros for Rust enums, which we use to reduce boilerplate when implementing string conversions and metadata for runtime types. This is helpful for the communication between our Rust and .NET systems."
|
||||
|
||||
-- To be able to use the responses of the LLM in other apps, we often use the clipboard of the respective operating system. Unfortunately, in .NET there is no solution that works with all operating systems. Therefore, I have opted for this library in Rust. This way, data transfer to other apps works on every system.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2644379659"] = "To be able to use the responses of the LLM in other apps, we often use the clipboard of the respective operating system. Unfortunately, in .NET there is no solution that works with all operating systems. Therefore, I have opted for this library in Rust. This way, data transfer to other apps works on every system."
|
||||
|
||||
@@ -5047,6 +5245,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2840582448"] = "Explanation"
|
||||
-- The .NET backend cannot be started as a desktop app. Therefore, I use a second backend in Rust, which I call runtime. With Rust as the runtime, Tauri can be used to realize a typical desktop app. Thanks to Rust, this app can be offered for Windows, macOS, and Linux desktops. Rust is a great language for developing safe and high-performance software.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2868174483"] = "The .NET backend cannot be started as a desktop app. Therefore, I use a second backend in Rust, which I call runtime. With Rust as the runtime, Tauri can be used to realize a typical desktop app. Thanks to Rust, this app can be offered for Windows, macOS, and Linux desktops. Rust is a great language for developing safe and high-performance software."
|
||||
|
||||
-- AI Studio runs with an enterprise configuration and configuration servers. The configuration plugins are not yet available.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2924964415"] = "AI Studio runs with an enterprise configuration and configuration servers. The configuration plugins are not yet available."
|
||||
|
||||
-- Changelog
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3017574265"] = "Changelog"
|
||||
|
||||
@@ -5080,6 +5281,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3433065373"] = "Information abou
|
||||
-- Used Rust compiler
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3440211747"] = "Used Rust compiler"
|
||||
|
||||
-- AI Studio runs with an enterprise configuration using configuration plugins, without central configuration management.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3449345633"] = "AI Studio runs with an enterprise configuration using configuration plugins, without central configuration management."
|
||||
|
||||
-- Tauri is used to host the Blazor user interface. It is a great project that allows the creation of desktop applications using web technologies. I love Tauri!
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3494984593"] = "Tauri is used to host the Blazor user interface. It is a great project that allows the creation of desktop applications using web technologies. I love Tauri!"
|
||||
|
||||
@@ -5089,9 +5293,6 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3563271893"] = "Motivation"
|
||||
-- This library is used to read Excel and OpenDocument spreadsheet files. This is necessary, e.g., for using spreadsheets as a data source for a chat.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3722989559"] = "This library is used to read Excel and OpenDocument spreadsheet files. This is necessary, e.g., for using spreadsheets as a data source for a chat."
|
||||
|
||||
-- AI Studio runs with an enterprise configuration and a configuration server. The configuration plugin is active.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3741877842"] = "AI Studio runs with an enterprise configuration and a configuration server. The configuration plugin is active."
|
||||
|
||||
-- this version does not met the requirements
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T3813932670"] = "this version does not met the requirements"
|
||||
|
||||
@@ -5128,6 +5329,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T566998575"] = "This is a library
|
||||
-- Used .NET SDK
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T585329785"] = "Used .NET SDK"
|
||||
|
||||
-- This library is used to manage sidecar processes and to ensure that stale or zombie sidecars are detected and terminated.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T633932150"] = "This library is used to manage sidecar processes and to ensure that stale or zombie sidecars are detected and terminated."
|
||||
|
||||
-- Did you find a bug or are you experiencing issues? Report your concern here.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T639371534"] = "Did you find a bug or are you experiencing issues? Report your concern here."
|
||||
|
||||
@@ -5158,6 +5362,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T158493184"] = "Internal Plugins"
|
||||
-- Disabled Plugins
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T1724138133"] = "Disabled Plugins"
|
||||
|
||||
-- Send a mail
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T1999487139"] = "Send a mail"
|
||||
|
||||
-- Enable plugin
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T2057806005"] = "Enable plugin"
|
||||
|
||||
@@ -5170,6 +5377,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T2738444034"] = "Enabled Plugins"
|
||||
-- Actions
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T3865031940"] = "Actions"
|
||||
|
||||
-- Open website
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T4239378936"] = "Open website"
|
||||
|
||||
-- Settings
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::SETTINGS::T1258653480"] = "Settings"
|
||||
|
||||
@@ -5260,35 +5470,38 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::WRITER::T3948127789"] = "Suggestion"
|
||||
-- Your stage directions
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::WRITER::T779923726"] = "Your stage directions"
|
||||
|
||||
-- Tried to communicate with the LLM provider '{0}'. The API key might be invalid. The provider message is: '{1}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1073493061"] = "Tried to communicate with the LLM provider '{0}'. The API key might be invalid. The provider message is: '{1}'"
|
||||
-- We tried to communicate with the LLM provider '{0}' (type={1}). The server might be down or having issues. The provider message is: '{2}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1000247110"] = "We tried to communicate with the LLM provider '{0}' (type={1}). The server might be down or having issues. The provider message is: '{2}'"
|
||||
|
||||
-- Tried to stream the LLM provider '{0}' answer. There were some problems with the stream. The message is: '{1}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1487597412"] = "Tried to stream the LLM provider '{0}' answer. There were some problems with the stream. The message is: '{1}'"
|
||||
|
||||
-- Tried to communicate with the LLM provider '{0}'. The required message format might be changed. The provider message is: '{1}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1674355816"] = "Tried to communicate with the LLM provider '{0}'. The required message format might be changed. The provider message is: '{1}'"
|
||||
|
||||
-- Tried to stream the LLM provider '{0}' answer. Was not able to read the stream. The message is: '{1}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1856278860"] = "Tried to stream the LLM provider '{0}' answer. Was not able to read the stream. The message is: '{1}'"
|
||||
|
||||
-- Tried to communicate with the LLM provider '{0}'. Even after {1} retries, there were some problems with the request. The provider message is: '{2}'.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T2181034173"] = "Tried to communicate with the LLM provider '{0}'. Even after {1} retries, there were some problems with the request. The provider message is: '{2}'."
|
||||
-- We tried to communicate with the LLM provider '{0}' (type={1}). The API key might be invalid. The provider message is: '{2}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1924863735"] = "We tried to communicate with the LLM provider '{0}' (type={1}). The API key might be invalid. The provider message is: '{2}'"
|
||||
|
||||
-- Tried to communicate with the LLM provider '{0}'. Something was not found. The provider message is: '{1}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T2780552614"] = "Tried to communicate with the LLM provider '{0}'. Something was not found. The provider message is: '{1}'"
|
||||
-- We tried to communicate with the LLM provider '{0}' (type={1}). The provider is overloaded. The message is: '{2}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1999987800"] = "We tried to communicate with the LLM provider '{0}' (type={1}). The provider is overloaded. The message is: '{2}'"
|
||||
|
||||
-- We tried to communicate with the LLM provider '{0}' (type={1}). You might not be able to use this provider from your location. The provider message is: '{2}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T2107463087"] = "We tried to communicate with the LLM provider '{0}' (type={1}). You might not be able to use this provider from your location. The provider message is: '{2}'"
|
||||
|
||||
-- We tried to communicate with the LLM provider '{0}' (type={1}). Something was not found. The provider message is: '{2}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T3014737766"] = "We tried to communicate with the LLM provider '{0}' (type={1}). Something was not found. The provider message is: '{2}'"
|
||||
|
||||
-- We tried to communicate with the LLM provider '{0}' (type={1}). Even after {2} retries, there were some problems with the request. The provider message is: '{3}'.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T3049689432"] = "We tried to communicate with the LLM provider '{0}' (type={1}). Even after {2} retries, there were some problems with the request. The provider message is: '{3}'."
|
||||
|
||||
-- Tried to communicate with the LLM provider '{0}'. There were some problems with the request. The provider message is: '{1}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T3573577433"] = "Tried to communicate with the LLM provider '{0}'. There were some problems with the request. The provider message is: '{1}'"
|
||||
|
||||
-- Tried to communicate with the LLM provider '{0}'. The server might be down or having issues. The provider message is: '{1}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T3806716694"] = "Tried to communicate with the LLM provider '{0}'. The server might be down or having issues. The provider message is: '{1}'"
|
||||
-- We tried to communicate with the LLM provider '{0}' (type={1}). The required message format might be changed. The provider message is: '{2}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T3759732886"] = "We tried to communicate with the LLM provider '{0}' (type={1}). The required message format might be changed. The provider message is: '{2}'"
|
||||
|
||||
-- Tried to communicate with the LLM provider '{0}'. The provider is overloaded. The message is: '{1}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T4179546180"] = "Tried to communicate with the LLM provider '{0}'. The provider is overloaded. The message is: '{1}'"
|
||||
|
||||
-- Tried to communicate with the LLM provider '{0}'. You might not be able to use this provider from your location. The provider message is: '{1}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T862369179"] = "Tried to communicate with the LLM provider '{0}'. You might not be able to use this provider from your location. The provider message is: '{1}'"
|
||||
-- We tried to communicate with the LLM provider '{0}' (type={1}). The data of the chat, including all file attachments, is probably too large for the selected model and provider. The provider message is: '{2}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T4049517041"] = "We tried to communicate with the LLM provider '{0}' (type={1}). The data of the chat, including all file attachments, is probably too large for the selected model and provider. The provider message is: '{2}'"
|
||||
|
||||
-- The trust level of this provider **has not yet** been thoroughly **investigated and evaluated**. We do not know if your data is safe.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::CONFIDENCE::T1014558951"] = "The trust level of this provider **has not yet** been thoroughly **investigated and evaluated**. We do not know if your data is safe."
|
||||
@@ -5683,6 +5896,21 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::CONFIDENCESCHEMESEXTENSIONS::T3893997203"] = "
|
||||
-- Trust all LLM providers
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::CONFIDENCESCHEMESEXTENSIONS::T4107860491"] = "Trust all LLM providers"
|
||||
|
||||
-- Storage size
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::QDRANT::QDRANTCLIENTIMPLEMENTATION::T1230141403"] = "Storage size"
|
||||
|
||||
-- HTTP port
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::QDRANT::QDRANTCLIENTIMPLEMENTATION::T1717573768"] = "HTTP port"
|
||||
|
||||
-- Reported version
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::QDRANT::QDRANTCLIENTIMPLEMENTATION::T3556099842"] = "Reported version"
|
||||
|
||||
-- gRPC port
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::QDRANT::QDRANTCLIENTIMPLEMENTATION::T757840040"] = "gRPC port"
|
||||
|
||||
-- Number of collections
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::DATABASES::QDRANT::QDRANTCLIENTIMPLEMENTATION::T842647336"] = "Number of collections"
|
||||
|
||||
-- The related data is not allowed to be sent to any LLM provider. This means that this data source cannot be used at the moment.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::ERICLIENT::DATAMODEL::PROVIDERTYPEEXTENSIONS::T1555790630"] = "The related data is not allowed to be sent to any LLM provider. This means that this data source cannot be used at the moment."
|
||||
|
||||
@@ -5872,15 +6100,18 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINBASE::T2262604281"] = "The
|
||||
-- The field DESCRIPTION does not exist or is not a valid string.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINBASE::T229488255"] = "The field DESCRIPTION does not exist or is not a valid string."
|
||||
|
||||
-- The field SOURCE_URL is not a valid URL. The URL must start with 'http://' or 'https://'.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINBASE::T2320984047"] = "The field SOURCE_URL is not a valid URL. The URL must start with 'http://' or 'https://'."
|
||||
|
||||
-- The field VERSION is not a valid version number. The version number must be formatted as string in the major.minor.patch format (X.X.X).
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINBASE::T2538827536"] = "The field VERSION is not a valid version number. The version number must be formatted as string in the major.minor.patch format (X.X.X)."
|
||||
|
||||
-- The field SOURCE_URL is not a valid URL. The URL must start with 'http://', 'https://', or 'mailto:'.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINBASE::T2892057533"] = "The field SOURCE_URL is not a valid URL. The URL must start with 'http://', 'https://', or 'mailto:'."
|
||||
|
||||
-- The table AUTHORS is empty. At least one author must be specified.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINBASE::T2981832540"] = "The table AUTHORS is empty. At least one author must be specified."
|
||||
|
||||
-- The field SOURCE_URL is not a valid URL. When the URL starts with 'mailto:', it must contain a valid email address as recipient.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINBASE::T3165663073"] = "The field SOURCE_URL is not a valid URL. When the URL starts with 'mailto:', it must contain a valid email address as recipient."
|
||||
|
||||
-- The field SUPPORT_CONTACT is empty. The support contact must be a non-empty string.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::PLUGINSYSTEM::PLUGINBASE::T3524814526"] = "The field SUPPORT_CONTACT is empty. The support contact must be a non-empty string."
|
||||
|
||||
@@ -6118,23 +6349,23 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PANDOCAVAILABILITYSERVICE::T18544701
|
||||
-- Pandoc may be required for importing files.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PANDOCAVAILABILITYSERVICE::T2596465560"] = "Pandoc may be required for importing files."
|
||||
|
||||
-- Failed to delete the API key due to an API issue.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::APIKEYS::T3658273365"] = "Failed to delete the API key due to an API issue."
|
||||
|
||||
-- Failed to get the API key due to an API issue.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::APIKEYS::T3875720022"] = "Failed to get the API key due to an API issue."
|
||||
-- Failed to delete the secret data due to an API issue.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T2303057928"] = "Failed to delete the secret data due to an API issue."
|
||||
|
||||
-- Successfully copied the text to your clipboard
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::CLIPBOARD::T3351807428"] = "Successfully copied the text to your clipboard"
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3351807428"] = "Successfully copied the text to your clipboard"
|
||||
|
||||
-- Failed to delete the API key due to an API issue.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3658273365"] = "Failed to delete the API key due to an API issue."
|
||||
|
||||
-- Failed to copy the text to your clipboard.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::CLIPBOARD::T3724548108"] = "Failed to copy the text to your clipboard."
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3724548108"] = "Failed to copy the text to your clipboard."
|
||||
|
||||
-- Failed to delete the secret data due to an API issue.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::SECRETS::T2303057928"] = "Failed to delete the secret data due to an API issue."
|
||||
-- Failed to get the API key due to an API issue.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3875720022"] = "Failed to get the API key due to an API issue."
|
||||
|
||||
-- Failed to get the secret data due to an API issue.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::SECRETS::T4007657575"] = "Failed to get the secret data due to an API issue."
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T4007657575"] = "Failed to get the secret data due to an API issue."
|
||||
|
||||
-- No update found.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::UPDATESERVICE::T1015418291"] = "No update found."
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace AIStudio.Assistants.IconFinder;
|
||||
|
||||
public partial class AssistantIconFinder : AssistantBaseCore<SettingsDialogIconFinder>
|
||||
{
|
||||
public override Tools.Components Component => Tools.Components.ICON_FINDER_ASSISTANT;
|
||||
protected override Tools.Components Component => Tools.Components.ICON_FINDER_ASSISTANT;
|
||||
|
||||
protected override string Title => T("Icon Finder");
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace AIStudio.Assistants.JobPosting;
|
||||
|
||||
public partial class AssistantJobPostings : AssistantBaseCore<SettingsDialogJobPostings>
|
||||
{
|
||||
public override Tools.Components Component => Tools.Components.JOB_POSTING_ASSISTANT;
|
||||
protected override Tools.Components Component => Tools.Components.JOB_POSTING_ASSISTANT;
|
||||
|
||||
protected override string Title => T("Job Posting");
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace AIStudio.Assistants.LegalCheck;
|
||||
|
||||
public partial class AssistantLegalCheck : AssistantBaseCore<SettingsDialogLegalCheck>
|
||||
{
|
||||
public override Tools.Components Component => Tools.Components.LEGAL_CHECK_ASSISTANT;
|
||||
protected override Tools.Components Component => Tools.Components.LEGAL_CHECK_ASSISTANT;
|
||||
|
||||
protected override string Title => T("Legal Check");
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace AIStudio.Assistants.MyTasks;
|
||||
|
||||
public partial class AssistantMyTasks : AssistantBaseCore<SettingsDialogMyTasks>
|
||||
{
|
||||
public override Tools.Components Component => Tools.Components.MY_TASKS_ASSISTANT;
|
||||
protected override Tools.Components Component => Tools.Components.MY_TASKS_ASSISTANT;
|
||||
|
||||
protected override string Title => T("My Tasks");
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace AIStudio.Assistants.RewriteImprove;
|
||||
|
||||
public partial class AssistantRewriteImprove : AssistantBaseCore<SettingsDialogRewrite>
|
||||
{
|
||||
public override Tools.Components Component => Tools.Components.REWRITE_ASSISTANT;
|
||||
protected override Tools.Components Component => Tools.Components.REWRITE_ASSISTANT;
|
||||
|
||||
protected override string Title => T("Rewrite & Improve Text");
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace AIStudio.Assistants.Synonym;
|
||||
|
||||
public partial class AssistantSynonyms : AssistantBaseCore<SettingsDialogSynonyms>
|
||||
{
|
||||
public override Tools.Components Component => Tools.Components.SYNONYMS_ASSISTANT;
|
||||
protected override Tools.Components Component => Tools.Components.SYNONYMS_ASSISTANT;
|
||||
|
||||
protected override string Title => T("Synonyms");
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace AIStudio.Assistants.TextSummarizer;
|
||||
|
||||
public partial class AssistantTextSummarizer : AssistantBaseCore<SettingsDialogTextSummarizer>
|
||||
{
|
||||
public override Tools.Components Component => Tools.Components.TEXT_SUMMARIZER_ASSISTANT;
|
||||
protected override Tools.Components Component => Tools.Components.TEXT_SUMMARIZER_ASSISTANT;
|
||||
|
||||
protected override string Title => T("Text Summarizer");
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace AIStudio.Assistants.Translation;
|
||||
|
||||
public partial class AssistantTranslation : AssistantBaseCore<SettingsDialogTranslation>
|
||||
{
|
||||
public override Tools.Components Component => Tools.Components.TRANSLATION_ASSISTANT;
|
||||
protected override Tools.Components Component => Tools.Components.TRANSLATION_ASSISTANT;
|
||||
|
||||
protected override string Title => T("Translation");
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using System.Globalization;
|
||||
|
||||
using AIStudio.Components;
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Settings.DataModel;
|
||||
@@ -37,6 +39,12 @@ public sealed record ChatThread
|
||||
/// </summary>
|
||||
public string SelectedChatTemplate { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether to include the current date and time in the system prompt.
|
||||
/// False by default for backward compatibility.
|
||||
/// </summary>
|
||||
public bool IncludeDateTime { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// The data source options for this chat thread.
|
||||
/// </summary>
|
||||
@@ -65,7 +73,7 @@ public sealed record ChatThread
|
||||
/// <summary>
|
||||
/// The current system prompt for the chat thread.
|
||||
/// </summary>
|
||||
public string SystemPrompt { get; init; } = string.Empty;
|
||||
public string SystemPrompt { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The content blocks of the chat thread.
|
||||
@@ -83,33 +91,32 @@ public sealed record ChatThread
|
||||
/// is extended with the profile chosen.
|
||||
/// </remarks>
|
||||
/// <param name="settingsManager">The settings manager instance to use.</param>
|
||||
/// <param name="chatThread">The chat thread to prepare the system prompt for.</param>
|
||||
/// <returns>The prepared system prompt.</returns>
|
||||
public string PrepareSystemPrompt(SettingsManager settingsManager, ChatThread chatThread)
|
||||
public string PrepareSystemPrompt(SettingsManager settingsManager)
|
||||
{
|
||||
//
|
||||
// Use the information from the chat template, if provided. Otherwise, use the default system prompt
|
||||
//
|
||||
string systemPromptTextWithChatTemplate;
|
||||
var logMessage = $"Using no chat template for chat thread '{chatThread.Name}'.";
|
||||
if (string.IsNullOrWhiteSpace(chatThread.SelectedChatTemplate))
|
||||
systemPromptTextWithChatTemplate = chatThread.SystemPrompt;
|
||||
var logMessage = $"Using no chat template for chat thread '{this.Name}'.";
|
||||
if (string.IsNullOrWhiteSpace(this.SelectedChatTemplate))
|
||||
systemPromptTextWithChatTemplate = this.SystemPrompt;
|
||||
else
|
||||
{
|
||||
if(!Guid.TryParse(chatThread.SelectedChatTemplate, out var chatTemplateId))
|
||||
systemPromptTextWithChatTemplate = chatThread.SystemPrompt;
|
||||
if(!Guid.TryParse(this.SelectedChatTemplate, out var chatTemplateId))
|
||||
systemPromptTextWithChatTemplate = this.SystemPrompt;
|
||||
else
|
||||
{
|
||||
if(chatThread.SelectedChatTemplate == ChatTemplate.NO_CHAT_TEMPLATE.Id || chatTemplateId == Guid.Empty)
|
||||
systemPromptTextWithChatTemplate = chatThread.SystemPrompt;
|
||||
if(this.SelectedChatTemplate == ChatTemplate.NO_CHAT_TEMPLATE.Id || chatTemplateId == Guid.Empty)
|
||||
systemPromptTextWithChatTemplate = this.SystemPrompt;
|
||||
else
|
||||
{
|
||||
var chatTemplate = settingsManager.ConfigurationData.ChatTemplates.FirstOrDefault(x => x.Id == chatThread.SelectedChatTemplate);
|
||||
var chatTemplate = settingsManager.ConfigurationData.ChatTemplates.FirstOrDefault(x => x.Id == this.SelectedChatTemplate);
|
||||
if(chatTemplate == null)
|
||||
systemPromptTextWithChatTemplate = chatThread.SystemPrompt;
|
||||
systemPromptTextWithChatTemplate = this.SystemPrompt;
|
||||
else
|
||||
{
|
||||
logMessage = $"Using chat template '{chatTemplate.Name}' for chat thread '{chatThread.Name}'.";
|
||||
logMessage = $"Using chat template '{chatTemplate.Name}' for chat thread '{this.Name}'.";
|
||||
this.allowProfile = chatTemplate.AllowProfileUsage;
|
||||
systemPromptTextWithChatTemplate = chatTemplate.ToSystemPrompt();
|
||||
}
|
||||
@@ -120,20 +127,19 @@ public sealed record ChatThread
|
||||
// We need a way to save the changed system prompt in our chat thread.
|
||||
// Otherwise, the chat thread will always tell us that it is using the
|
||||
// default system prompt:
|
||||
chatThread = chatThread with { SystemPrompt = systemPromptTextWithChatTemplate };
|
||||
|
||||
this.SystemPrompt = systemPromptTextWithChatTemplate;
|
||||
LOGGER.LogInformation(logMessage);
|
||||
|
||||
|
||||
//
|
||||
// Add augmented data, if available:
|
||||
//
|
||||
var isAugmentedDataAvailable = !string.IsNullOrWhiteSpace(chatThread.AugmentedData);
|
||||
var isAugmentedDataAvailable = !string.IsNullOrWhiteSpace(this.AugmentedData);
|
||||
var systemPromptWithAugmentedData = isAugmentedDataAvailable switch
|
||||
{
|
||||
true => $"""
|
||||
{systemPromptTextWithChatTemplate}
|
||||
|
||||
{chatThread.AugmentedData}
|
||||
{this.AugmentedData}
|
||||
""",
|
||||
|
||||
false => systemPromptTextWithChatTemplate,
|
||||
@@ -149,25 +155,25 @@ public sealed record ChatThread
|
||||
// Add information from the profile if available and allowed:
|
||||
//
|
||||
string systemPromptText;
|
||||
logMessage = $"Using no profile for chat thread '{chatThread.Name}'.";
|
||||
if (string.IsNullOrWhiteSpace(chatThread.SelectedProfile) || this.allowProfile is false)
|
||||
logMessage = $"Using no profile for chat thread '{this.Name}'.";
|
||||
if (string.IsNullOrWhiteSpace(this.SelectedProfile) || !this.allowProfile)
|
||||
systemPromptText = systemPromptWithAugmentedData;
|
||||
else
|
||||
{
|
||||
if(!Guid.TryParse(chatThread.SelectedProfile, out var profileId))
|
||||
if(!Guid.TryParse(this.SelectedProfile, out var profileId))
|
||||
systemPromptText = systemPromptWithAugmentedData;
|
||||
else
|
||||
{
|
||||
if(chatThread.SelectedProfile == Profile.NO_PROFILE.Id || profileId == Guid.Empty)
|
||||
if(this.SelectedProfile == Profile.NO_PROFILE.Id || profileId == Guid.Empty)
|
||||
systemPromptText = systemPromptWithAugmentedData;
|
||||
else
|
||||
{
|
||||
var profile = settingsManager.ConfigurationData.Profiles.FirstOrDefault(x => x.Id == chatThread.SelectedProfile);
|
||||
if(profile == default)
|
||||
var profile = settingsManager.ConfigurationData.Profiles.FirstOrDefault(x => x.Id == this.SelectedProfile);
|
||||
if(profile is null)
|
||||
systemPromptText = systemPromptWithAugmentedData;
|
||||
else
|
||||
{
|
||||
logMessage = $"Using profile '{profile.Name}' for chat thread '{chatThread.Name}'.";
|
||||
logMessage = $"Using profile '{profile.Name}' for chat thread '{this.Name}'.";
|
||||
systemPromptText = $"""
|
||||
{systemPromptWithAugmentedData}
|
||||
|
||||
@@ -179,7 +185,24 @@ public sealed record ChatThread
|
||||
}
|
||||
|
||||
LOGGER.LogInformation(logMessage);
|
||||
return systemPromptText;
|
||||
if(!this.IncludeDateTime)
|
||||
return systemPromptText;
|
||||
|
||||
//
|
||||
// Prepend the current date and time to the system prompt:
|
||||
//
|
||||
var nowUtc = DateTime.UtcNow;
|
||||
var nowLocal = DateTime.Now;
|
||||
var currentDateTime = string.Create(
|
||||
new CultureInfo("en-US"),
|
||||
$"Today is {nowUtc:dddd, MMMM d, yyyy h:mm tt} (UTC) and {nowLocal:dddd, MMMM d, yyyy h:mm tt} (local time)."
|
||||
);
|
||||
|
||||
return $"""
|
||||
{currentDateTime}
|
||||
|
||||
{systemPromptText}
|
||||
""";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -2,5 +2,5 @@ namespace AIStudio.Chat;
|
||||
|
||||
public static class SystemPrompts
|
||||
{
|
||||
public const string DEFAULT = "You are a helpful assistant!";
|
||||
public const string DEFAULT = "You are a helpful assistant.";
|
||||
}
|
||||
@@ -1,30 +1,36 @@
|
||||
@inherits MSGComponentBase
|
||||
@typeparam TSettings
|
||||
|
||||
<MudCard Outlined="@true" Style="@this.BlockStyle">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudStack AlignItems="AlignItems.Center" Row="@true">
|
||||
<MudIcon Icon="@this.Icon" Size="Size.Large" Color="Color.Primary"/>
|
||||
<MudText Typo="Typo.h6">
|
||||
@this.Name
|
||||
@if (this.IsVisible)
|
||||
{
|
||||
<MudCard Outlined="@true" Style="@this.BlockStyle">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudStack AlignItems="AlignItems.Center" Row="@true">
|
||||
<MudIcon Icon="@this.Icon" Size="Size.Large" Color="Color.Primary"/>
|
||||
<MudText Typo="Typo.h6">
|
||||
@this.Name
|
||||
</MudText>
|
||||
</MudStack>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent>
|
||||
<MudStack>
|
||||
<MudText>
|
||||
@this.Description
|
||||
</MudText>
|
||||
</MudStack>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent>
|
||||
<MudStack>
|
||||
<MudText>
|
||||
@this.Description
|
||||
</MudText>
|
||||
</MudStack>
|
||||
</MudCardContent>
|
||||
<MudCardActions>
|
||||
<MudButtonGroup Variant="Variant.Outlined">
|
||||
<MudButton Size="Size.Large" Variant="Variant.Filled" StartIcon="@this.Icon" Color="Color.Default" Href="@this.Link">
|
||||
@this.ButtonText
|
||||
</MudButton>
|
||||
<MudIconButton Variant="Variant.Text" Icon="@Icons.Material.Filled.Settings" Color="Color.Default" OnClick="@this.OpenSettingsDialog"/>
|
||||
</MudButtonGroup>
|
||||
</MudCardActions>
|
||||
</MudCard>
|
||||
</MudCardContent>
|
||||
<MudCardActions>
|
||||
<MudButtonGroup Variant="Variant.Outlined">
|
||||
<MudButton Size="Size.Large" Variant="Variant.Filled" StartIcon="@this.Icon" Color="Color.Default" Href="@this.Link">
|
||||
@this.ButtonText
|
||||
</MudButton>
|
||||
@if (this.HasSettingsPanel)
|
||||
{
|
||||
<MudIconButton Variant="Variant.Text" Icon="@Icons.Material.Filled.Settings" Color="Color.Default" OnClick="@this.OpenSettingsDialog"/>
|
||||
}
|
||||
</MudButtonGroup>
|
||||
</MudCardActions>
|
||||
</MudCard>
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
using AIStudio.Settings.DataModel;
|
||||
using AIStudio.Dialogs.Settings;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
using DialogOptions = AIStudio.Dialogs.DialogOptions;
|
||||
@@ -8,32 +11,41 @@ public partial class AssistantBlock<TSettings> : MSGComponentBase where TSetting
|
||||
{
|
||||
[Parameter]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
|
||||
[Parameter]
|
||||
public string Description { get; set; } = string.Empty;
|
||||
|
||||
|
||||
[Parameter]
|
||||
public string Icon { get; set; } = Icons.Material.Filled.DisabledByDefault;
|
||||
|
||||
|
||||
[Parameter]
|
||||
public string ButtonText { get; set; } = "Start";
|
||||
|
||||
|
||||
[Parameter]
|
||||
public string Link { get; set; } = string.Empty;
|
||||
|
||||
|
||||
[Parameter]
|
||||
public Tools.Components Component { get; set; } = Tools.Components.NONE;
|
||||
|
||||
[Parameter]
|
||||
public PreviewFeatures RequiredPreviewFeature { get; set; } = PreviewFeatures.NONE;
|
||||
|
||||
[Inject]
|
||||
private MudTheme ColorTheme { get; init; } = null!;
|
||||
|
||||
|
||||
[Inject]
|
||||
private IDialogService DialogService { get; init; } = null!;
|
||||
|
||||
private async Task OpenSettingsDialog()
|
||||
{
|
||||
if (!this.HasSettingsPanel)
|
||||
return;
|
||||
|
||||
var dialogParameters = new DialogParameters();
|
||||
|
||||
|
||||
await this.DialogService.ShowAsync<TSettings>(T("Open Settings"), dialogParameters, DialogOptions.FULLSCREEN);
|
||||
}
|
||||
|
||||
|
||||
private string BorderColor => this.SettingsManager.IsDarkMode switch
|
||||
{
|
||||
true => this.ColorTheme.GetCurrentPalette(this.SettingsManager).GrayLight,
|
||||
@@ -41,4 +53,8 @@ public partial class AssistantBlock<TSettings> : MSGComponentBase where TSetting
|
||||
};
|
||||
|
||||
private string BlockStyle => $"border-width: 2px; border-color: {this.BorderColor}; border-radius: 12px; border-style: solid; max-width: 20em;";
|
||||
}
|
||||
|
||||
private bool IsVisible => this.SettingsManager.IsAssistantVisible(this.Component, assistantName: this.Name, requiredPreviewFeature: this.RequiredPreviewFeature);
|
||||
|
||||
private bool HasSettingsPanel => typeof(TSettings) != typeof(NoSettingsPanel);
|
||||
}
|
||||
@@ -13,6 +13,9 @@ public partial class Changelog
|
||||
|
||||
public static readonly Log[] LOGS =
|
||||
[
|
||||
new (234, "v26.2.2, build 234 (2026-02-22 14:16 UTC)", "v26.2.2.md"),
|
||||
new (233, "v26.2.1, build 233 (2026-02-01 19:16 UTC)", "v26.2.1.md"),
|
||||
new (232, "v26.1.2, build 232 (2026-01-25 14:05 UTC)", "v26.1.2.md"),
|
||||
new (231, "v26.1.1, build 231 (2026-01-11 15:53 UTC)", "v26.1.1.md"),
|
||||
new (230, "v0.10.0, build 230 (2025-12-31 14:04 UTC)", "v0.10.0.md"),
|
||||
new (229, "v0.9.54, build 229 (2025-11-24 18:28 UTC)", "v0.9.54.md"),
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
OnAdornmentClick="() => this.SendMessage()"
|
||||
Disabled="@this.IsInputForbidden()"
|
||||
Immediate="@true"
|
||||
OnKeyUp="this.InputKeyEvent"
|
||||
OnKeyUp="@this.InputKeyEvent"
|
||||
UserAttributes="@USER_INPUT_ATTRIBUTES"
|
||||
Class="@this.UserInputClass"
|
||||
Style="@this.UserInputStyle"/>
|
||||
@@ -66,7 +66,7 @@
|
||||
@if (this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is WorkspaceStorageBehavior.STORE_CHATS_MANUALLY)
|
||||
{
|
||||
<MudTooltip Text="@T("Save chat")" Placement="@TOOLBAR_TOOLTIP_PLACEMENT">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Save" OnClick="@(() => this.SaveThread())" Disabled="@(!this.CanThreadBeSaved)"/>
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Save" OnClick="@(() => this.SaveThread())" Disabled="@(!this.CanThreadBeSaved || this.isStreaming)"/>
|
||||
</MudTooltip>
|
||||
}
|
||||
|
||||
|
||||
@@ -97,7 +97,9 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
|
||||
// Use chat thread sent by the user:
|
||||
this.ChatThread = deferredContent;
|
||||
this.Logger.LogInformation($"The chat '{this.ChatThread.Name}' with {this.ChatThread.Blocks.Count} messages was deferred and will be rendered now.");
|
||||
this.ChatThread.IncludeDateTime = true;
|
||||
|
||||
this.Logger.LogInformation($"The chat '{this.ChatThread.ChatId}' with {this.ChatThread.Blocks.Count} messages was deferred and will be rendered now.");
|
||||
await this.ChatThreadChanged.InvokeAsync(this.ChatThread);
|
||||
|
||||
// We know already that the chat thread is not null,
|
||||
@@ -202,7 +204,6 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
|
||||
// Select the correct provider:
|
||||
await this.SelectProviderWhenLoadingChat();
|
||||
|
||||
await base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
@@ -436,6 +437,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
{
|
||||
this.ChatThread = new()
|
||||
{
|
||||
IncludeDateTime = true,
|
||||
SelectedProvider = this.Provider.Id,
|
||||
SelectedProfile = this.currentProfile.Id,
|
||||
SelectedChatTemplate = this.currentChatTemplate.Id,
|
||||
@@ -676,6 +678,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
//
|
||||
this.ChatThread = new()
|
||||
{
|
||||
IncludeDateTime = true,
|
||||
SelectedProvider = this.Provider.Id,
|
||||
SelectedProfile = this.currentProfile.Id,
|
||||
SelectedChatTemplate = this.currentChatTemplate.Id,
|
||||
@@ -909,6 +912,10 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
break;
|
||||
|
||||
case Event.CHAT_STREAMING_DONE:
|
||||
// Streaming mutates the last AI block over time.
|
||||
// In manual storage mode, a save during streaming must not
|
||||
// mark the final streamed state as already persisted.
|
||||
this.hasUnsavedChanges = true;
|
||||
if(this.autoSaveEnabled)
|
||||
await this.SaveThread();
|
||||
break;
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<div style="display: flex; align-items: center; gap: 8px; @this.Item.Style">
|
||||
<MudIcon Icon="@this.Item.Icon"/>
|
||||
<span>
|
||||
@this.Item.Text
|
||||
</span>
|
||||
@if (!string.IsNullOrWhiteSpace(this.Item.CopyValue))
|
||||
{
|
||||
<MudCopyClipboardButton TooltipMessage="@this.Item.CopyTooltip" StringContent="@this.Item.CopyValue"/>
|
||||
}
|
||||
</div>
|
||||
@@ -0,0 +1,9 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace AIStudio.Components;
|
||||
|
||||
public partial class ConfigInfoRow : ComponentBase
|
||||
{
|
||||
[Parameter]
|
||||
public ConfigInfoRowItem Item { get; set; } = new(Icons.Material.Filled.ArrowRightAlt, string.Empty, string.Empty, string.Empty, string.Empty);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace AIStudio.Components;
|
||||
|
||||
public sealed record ConfigInfoRowItem(
|
||||
string Icon,
|
||||
string Text,
|
||||
string CopyValue,
|
||||
string CopyTooltip,
|
||||
string Style = ""
|
||||
);
|
||||
@@ -0,0 +1,21 @@
|
||||
<MudPaper Outlined="true" Class="@this.Class">
|
||||
<div style="display: flex; align-items: center; gap: 8px; margin-bottom: 8px;">
|
||||
<MudIcon Icon="@this.HeaderIcon" Size="Size.Small"/>
|
||||
<MudText Typo="Typo.subtitle2">
|
||||
@this.HeaderText
|
||||
</MudText>
|
||||
</div>
|
||||
|
||||
@foreach (var item in this.Items)
|
||||
{
|
||||
<ConfigInfoRow Item="@item"/>
|
||||
}
|
||||
|
||||
@if (this.ShowWarning)
|
||||
{
|
||||
<div style="display: flex; align-items: center; gap: 8px; margin-top: 4px;">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Warning" Size="Size.Small" Color="Color.Warning"/>
|
||||
<MudText Typo="Typo.subtitle2">@this.WarningText</MudText>
|
||||
</div>
|
||||
}
|
||||
</MudPaper>
|
||||
@@ -0,0 +1,24 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace AIStudio.Components;
|
||||
|
||||
public partial class ConfigPluginInfoCard : ComponentBase
|
||||
{
|
||||
[Parameter]
|
||||
public string HeaderIcon { get; set; } = Icons.Material.Filled.Extension;
|
||||
|
||||
[Parameter]
|
||||
public string HeaderText { get; set; } = string.Empty;
|
||||
|
||||
[Parameter]
|
||||
public IEnumerable<ConfigInfoRowItem> Items { get; set; } = [];
|
||||
|
||||
[Parameter]
|
||||
public bool ShowWarning { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string WarningText { get; set; } = string.Empty;
|
||||
|
||||
[Parameter]
|
||||
public string Class { get; set; } = "pa-3 mt-2 mb-2";
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
@using AIStudio.Settings
|
||||
@inherits MSGComponentBase
|
||||
<ConfigurationSelect IsLocked="this.IsLocked" Disabled="this.Disabled" OptionDescription="@T("Select a minimum confidence level")" SelectedValue="@this.FilteredSelectedValue" Data="@ConfigurationSelectDataFactory.GetConfidenceLevelsData(this.SettingsManager, this.RestrictToGlobalMinimumConfidence)" SelectionUpdate="@this.SelectionUpdate" OptionHelp="@T("Choose the minimum confidence level that all LLM providers must meet. This way, you can ensure that only trustworthy providers are used. You cannot use any provider that falls below this level.")"/>
|
||||
<ConfigurationSelect IsLocked="this.IsLocked" Disabled="this.Disabled" OptionDescription="@T("Select a minimum confidence level")" SelectedValue="@this.FilteredSelectedValue" Data="@ConfigurationSelectDataFactory.GetConfidenceLevelsData(this.SettingsManager, this.RestrictToGlobalMinimumConfidence)" SelectionUpdate="@this.SelectionUpdate" SelectionUpdateAsync="@this.SelectionUpdateAsync" OptionHelp="@T("Choose the minimum confidence level that all LLM providers must meet. This way, you can ensure that only trustworthy providers are used. You cannot use any provider that falls below this level.")"/>
|
||||
@@ -17,6 +17,12 @@ public partial class ConfigurationMinConfidenceSelection : MSGComponentBase
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Action<ConfidenceLevel> SelectionUpdate { get; set; } = _ => { };
|
||||
|
||||
/// <summary>
|
||||
/// An asynchronous action that is called when the selection changes.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Func<ConfidenceLevel, Task> SelectionUpdateAsync { get; set; } = _ => Task.CompletedTask;
|
||||
|
||||
/// <summary>
|
||||
/// Boolean value indicating whether the selection is restricted to a global minimum confidence level.
|
||||
|
||||
@@ -14,8 +14,22 @@
|
||||
SelectedValuesChanged="@this.OptionChanged">
|
||||
@foreach (var data in this.Data)
|
||||
{
|
||||
<MudSelectItemExtended Value="@data.Value">
|
||||
@data.Name
|
||||
var isLockedValue = this.IsLockedValue(data.Value);
|
||||
<MudSelectItemExtended Value="@data.Value" Disabled="@isLockedValue" Style="@(isLockedValue ? "pointer-events:auto !important;" : null)">
|
||||
@if (isLockedValue)
|
||||
{
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Justify="Justify.FlexStart" Wrap="Wrap.NoWrap">
|
||||
@* MudTooltip.RootStyle is set as a workaround for issue -> https://github.com/MudBlazor/MudBlazor/issues/10882 *@
|
||||
<MudTooltip Text="@this.LockedTooltip()" Arrow="true" Placement="Placement.Right" RootStyle="display:inline-flex;">
|
||||
<MudIcon Icon="@Icons.Material.Filled.Lock" Color="Color.Error" Size="Size.Small" Class="mr-1"/>
|
||||
</MudTooltip>
|
||||
@data.Name
|
||||
</MudStack>
|
||||
}
|
||||
else
|
||||
{
|
||||
@data.Name
|
||||
}
|
||||
</MudSelectItemExtended>
|
||||
}
|
||||
</MudSelectExtended>
|
||||
@@ -27,6 +27,12 @@ public partial class ConfigurationMultiSelect<TData> : ConfigurationBaseCore
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Action<HashSet<TData>> SelectionUpdate { get; set; } = _ => { };
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether a specific item is locked by a configuration plugin.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Func<TData, bool> IsItemLocked { get; set; } = _ => false;
|
||||
|
||||
#region Overrides of ConfigurationBase
|
||||
|
||||
@@ -62,4 +68,12 @@ public partial class ConfigurationMultiSelect<TData> : ConfigurationBaseCore
|
||||
|
||||
return string.Format(T("You have selected {0} preview features."), selectedValues.Count);
|
||||
}
|
||||
|
||||
private bool IsLockedValue(TData value) => this.IsItemLocked(value);
|
||||
|
||||
private string LockedTooltip() =>
|
||||
this.T(
|
||||
"This feature is managed by your organization and has therefore been disabled.",
|
||||
typeof(ConfigurationBase).Namespace,
|
||||
nameof(ConfigurationBase));
|
||||
}
|
||||
@@ -25,6 +25,9 @@ public partial class ConfigurationProviderSelection : MSGComponentBase
|
||||
|
||||
[Parameter]
|
||||
public Tools.Components Component { get; set; } = Tools.Components.NONE;
|
||||
|
||||
[Parameter]
|
||||
public ConfidenceLevel ExplicitMinimumConfidence { get; set; } = ConfidenceLevel.UNKNOWN;
|
||||
|
||||
[Parameter]
|
||||
public Func<bool> Disabled { get; set; } = () => false;
|
||||
@@ -38,7 +41,14 @@ public partial class ConfigurationProviderSelection : MSGComponentBase
|
||||
if(this.Component is not Tools.Components.NONE and not Tools.Components.APP_SETTINGS)
|
||||
yield return new(T("Use app default"), string.Empty);
|
||||
|
||||
// Get the minimum confidence level for this component, and/or the enforced global minimum confidence level:
|
||||
var minimumLevel = this.SettingsManager.GetMinimumConfidenceLevel(this.Component);
|
||||
|
||||
// Apply the explicit minimum confidence level if set and higher than the current minimum level:
|
||||
if (this.ExplicitMinimumConfidence is not ConfidenceLevel.UNKNOWN && this.ExplicitMinimumConfidence > minimumLevel)
|
||||
minimumLevel = this.ExplicitMinimumConfidence;
|
||||
|
||||
// Filter the providers based on the minimum confidence level:
|
||||
foreach (var providerId in this.Data)
|
||||
{
|
||||
var provider = this.SettingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == providerId.Value);
|
||||
@@ -75,4 +85,4 @@ public partial class ConfigurationProviderSelection : MSGComponentBase
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,12 @@ public partial class ConfigurationSelect<TConfig> : ConfigurationBaseCore
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Action<TConfig> SelectionUpdate { get; set; } = _ => { };
|
||||
|
||||
/// <summary>
|
||||
/// An asynchronous action that is called when the selection changes.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Func<TConfig, Task> SelectionUpdateAsync { get; set; } = _ => Task.CompletedTask;
|
||||
|
||||
#region Overrides of ConfigurationBase
|
||||
|
||||
@@ -44,6 +50,7 @@ public partial class ConfigurationSelect<TConfig> : ConfigurationBaseCore
|
||||
private async Task OptionChanged(TConfig updatedValue)
|
||||
{
|
||||
this.SelectionUpdate(updatedValue);
|
||||
await this.SelectionUpdateAsync(updatedValue);
|
||||
await this.SettingsManager.StoreSettings();
|
||||
await this.InformAboutChange();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
@inherits ConfigurationBaseCore
|
||||
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
||||
<MudIcon Icon="@this.Icon" Color="@this.IconColor"/>
|
||||
<MudText Typo="Typo.body1" Class="flex-grow-1">
|
||||
@if (string.IsNullOrWhiteSpace(this.Shortcut()))
|
||||
{
|
||||
@T("No shortcut configured")
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudChip T="string" Color="Color.Primary" Size="Size.Small" Variant="Variant.Outlined">
|
||||
@this.GetDisplayShortcut()
|
||||
</MudChip>
|
||||
}
|
||||
</MudText>
|
||||
<MudButton Variant="Variant.Outlined"
|
||||
Color="Color.Primary"
|
||||
Size="Size.Small"
|
||||
StartIcon="@Icons.Material.Filled.Edit"
|
||||
OnClick="@this.OpenDialog"
|
||||
Disabled="@this.IsDisabled"
|
||||
Class="mb-1">
|
||||
@T("Change shortcut")
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
@@ -0,0 +1,109 @@
|
||||
using AIStudio.Dialogs;
|
||||
using AIStudio.Tools.Rust;
|
||||
using AIStudio.Tools.Services;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using DialogOptions = AIStudio.Dialogs.DialogOptions;
|
||||
|
||||
namespace AIStudio.Components;
|
||||
|
||||
/// <summary>
|
||||
/// A configuration component for capturing and displaying keyboard shortcuts.
|
||||
/// </summary>
|
||||
public partial class ConfigurationShortcut : ConfigurationBaseCore
|
||||
{
|
||||
[Inject]
|
||||
private IDialogService DialogService { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private RustService RustService { get; init; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// The current shortcut value.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Func<string> Shortcut { get; set; } = () => string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// An action which is called when the shortcut was changed.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Action<string> ShortcutUpdate { get; set; } = _ => { };
|
||||
|
||||
/// <summary>
|
||||
/// The name/identifier of the shortcut (used for conflict detection and registration).
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Shortcut ShortcutId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The icon to display.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string Icon { get; set; } = Icons.Material.Filled.Keyboard;
|
||||
|
||||
/// <summary>
|
||||
/// The color of the icon.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Color IconColor { get; set; } = Color.Default;
|
||||
|
||||
#region Overrides of ConfigurationBase
|
||||
|
||||
protected override bool Stretch => true;
|
||||
|
||||
protected override Variant Variant => Variant.Outlined;
|
||||
|
||||
protected override string Label => this.OptionDescription;
|
||||
|
||||
#endregion
|
||||
|
||||
private string GetDisplayShortcut()
|
||||
{
|
||||
var shortcut = this.Shortcut();
|
||||
if (string.IsNullOrWhiteSpace(shortcut))
|
||||
return string.Empty;
|
||||
|
||||
// Convert internal format to display format:
|
||||
return shortcut
|
||||
.Replace("CmdOrControl", OperatingSystem.IsMacOS() ? "Cmd" : "Ctrl")
|
||||
.Replace("CommandOrControl", OperatingSystem.IsMacOS() ? "Cmd" : "Ctrl");
|
||||
}
|
||||
|
||||
private async Task OpenDialog()
|
||||
{
|
||||
// Suspend shortcut processing while the dialog is open, so the user can
|
||||
// press the current shortcut to re-enter it without triggering the action:
|
||||
await this.RustService.SuspendShortcutProcessing();
|
||||
|
||||
try
|
||||
{
|
||||
var dialogParameters = new DialogParameters<ShortcutDialog>
|
||||
{
|
||||
{ x => x.InitialShortcut, this.Shortcut() },
|
||||
{ x => x.ShortcutId, this.ShortcutId },
|
||||
};
|
||||
|
||||
var dialogReference = await this.DialogService.ShowAsync<ShortcutDialog>(
|
||||
this.T("Configure Keyboard Shortcut"),
|
||||
dialogParameters,
|
||||
DialogOptions.FULLSCREEN);
|
||||
|
||||
var dialogResult = await dialogReference.Result;
|
||||
if (dialogResult is null || dialogResult.Canceled)
|
||||
return;
|
||||
|
||||
if (dialogResult.Data is string newShortcut)
|
||||
{
|
||||
this.ShortcutUpdate(newShortcut);
|
||||
await this.SettingsManager.StoreSettings();
|
||||
await this.InformAboutChange();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Resume the shortcut processing when the dialog is closed:
|
||||
await this.RustService.ResumeShortcutProcessing();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<MudText Typo="Typo.body1" Class="@this.Class">
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<MudIcon Icon="@Icons.Material.Filled.ArrowRightAlt"/>
|
||||
@if (this.IsConfigured)
|
||||
{
|
||||
<MudIcon Icon="@Icons.Material.Filled.CheckCircle" Color="Color.Success" Size="Size.Small"/>
|
||||
<span>@this.ConfiguredText</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudIcon Icon="@Icons.Material.Filled.Cancel" Color="Color.Error" Size="Size.Small"/>
|
||||
<span>@this.NotConfiguredText</span>
|
||||
}
|
||||
</div>
|
||||
</MudText>
|
||||
@@ -0,0 +1,18 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace AIStudio.Components;
|
||||
|
||||
public partial class EncryptionSecretInfo : ComponentBase
|
||||
{
|
||||
[Parameter]
|
||||
public bool IsConfigured { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string ConfiguredText { get; set; } = string.Empty;
|
||||
|
||||
[Parameter]
|
||||
public string NotConfiguredText { get; set; } = string.Empty;
|
||||
|
||||
[Parameter]
|
||||
public string Class { get; set; } = "mt-2 mb-2";
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
@inherits MSGComponentBase
|
||||
<MudText Typo="Typo.body1" Class="mb-3" Style="text-align: justify; hyphens: auto;">
|
||||
@T("Hello, my name is Thorsten Sommer, and I am the initial creator of MindWork AI Studio. The motivation behind developing this app stems from several crucial needs and observations I made over time.")
|
||||
@T("Hello, my name is Thorsten Sommer, and I am the initial creator of MindWork AI Studio. I started this project based on several crucial needs and observations I made over time. Today, we have a core team of developers and support from the open-source community.")
|
||||
</MudText>
|
||||
|
||||
<MudText Typo="Typo.h4">
|
||||
@@ -28,5 +28,13 @@
|
||||
</MudText>
|
||||
|
||||
<MudText Typo="Typo.body1" Class="mb-3" Style="text-align: justify; hyphens: auto;">
|
||||
@T("Through MindWork AI Studio, I aim to provide a secure, flexible, and user-friendly tool that caters to a wider audience without compromising on functionality or design. This app is the culmination of my desire to meet personal requirements, address existing gaps in the market, and showcase innovative development practices.")
|
||||
</MudText>
|
||||
@T("Today, our team aims to provide a secure, flexible, and user-friendly tool that serves a broad audience without compromising on functionality or design.")
|
||||
</MudText>
|
||||
|
||||
<MudText Typo="Typo.h4">
|
||||
@T("Democratization of AI")
|
||||
</MudText>
|
||||
|
||||
<MudText Typo="Typo.body1" Class="mb-3" Style="text-align: justify; hyphens: auto;">
|
||||
@T("We also want to contribute to the democratization of AI. MindWork AI Studio runs even on low-cost hardware, including computers around 100 EUR such as Raspberry Pi. This makes the app and its full feature set accessible to people and families with limited budgets. You can start with local LLMs for your first steps or use affordable cloud models. MindWork AI Studio itself is available free of charge.")
|
||||
</MudText>
|
||||
@@ -2,14 +2,14 @@
|
||||
@inherits MSGComponentBase
|
||||
|
||||
<MudStack Row="true" AlignItems="AlignItems.Baseline" StretchItems="StretchItems.Start" Class="mb-3" Wrap="Wrap.NoWrap">
|
||||
<MudSelect T="Profile" Strict="@true" Value="@this.Profile" ValueChanged="@this.SelectionChanged" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Person4" Margin="Margin.Dense" Label="@T("Select one of your profiles")" Variant="Variant.Outlined" Class="mb-3" Validation="@this.Validation">
|
||||
<MudSelect T="Profile" Strict="@true" Disabled="@this.Disabled" Value="@this.Profile" ValueChanged="@this.SelectionChanged" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Person4" Margin="Margin.Dense" Label="@T("Select one of your profiles")" Variant="Variant.Outlined" Class="mb-3" Validation="@this.Validation">
|
||||
@foreach (var profile in this.SettingsManager.ConfigurationData.Profiles.GetAllProfiles())
|
||||
{
|
||||
<MudSelectItem Value="profile">
|
||||
@profile.Name
|
||||
@profile.GetSafeName()
|
||||
</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Settings" OnClick="() => this.OpenSettingsDialog()"/>
|
||||
</MudStack>
|
||||
<MudIconButton Disabled="@this.Disabled" Icon="@Icons.Material.Filled.Settings" OnClick="() => this.OpenSettingsDialog()"/>
|
||||
</MudStack>
|
||||
@@ -17,6 +17,9 @@ public partial class ProfileFormSelection : MSGComponentBase
|
||||
|
||||
[Parameter]
|
||||
public Func<Profile, string?> Validation { get; set; } = _ => null;
|
||||
|
||||
[Parameter]
|
||||
public bool Disabled { get; set; }
|
||||
|
||||
[Inject]
|
||||
public IDialogService DialogService { get; init; } = null!;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
using AIStudio.Assistants;
|
||||
using AIStudio.Provider;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
@@ -10,7 +9,7 @@ namespace AIStudio.Components;
|
||||
public partial class ProviderSelection : MSGComponentBase
|
||||
{
|
||||
[CascadingParameter]
|
||||
public AssistantBase<NoComponent>? AssistantBase { get; set; }
|
||||
public Tools.Components? Component { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public AIStudio.Settings.Provider ProviderSettings { get; set; } = AIStudio.Settings.Provider.NONE;
|
||||
@@ -20,6 +19,12 @@ public partial class ProviderSelection : MSGComponentBase
|
||||
|
||||
[Parameter]
|
||||
public Func<AIStudio.Settings.Provider, string?> ValidateProvider { get; set; } = _ => null;
|
||||
|
||||
[Parameter]
|
||||
public ConfidenceLevel ExplicitMinimumConfidence { get; set; } = ConfidenceLevel.UNKNOWN;
|
||||
|
||||
[Inject]
|
||||
private ILogger<ProviderSelection> Logger { get; init; } = null!;
|
||||
|
||||
private async Task SelectionChanged(AIStudio.Settings.Provider provider)
|
||||
{
|
||||
@@ -30,10 +35,31 @@ public partial class ProviderSelection : MSGComponentBase
|
||||
[SuppressMessage("Usage", "MWAIS0001:Direct access to `Providers` is not allowed")]
|
||||
private IEnumerable<AIStudio.Settings.Provider> GetAvailableProviders()
|
||||
{
|
||||
var minimumLevel = this.SettingsManager.GetMinimumConfidenceLevel(this.AssistantBase?.Component ?? Tools.Components.NONE);
|
||||
foreach (var provider in this.SettingsManager.ConfigurationData.Providers)
|
||||
if (provider.UsedLLMProvider != LLMProviders.NONE)
|
||||
if (provider.UsedLLMProvider.GetConfidence(this.SettingsManager).Level >= minimumLevel)
|
||||
yield return provider;
|
||||
switch (this.Component)
|
||||
{
|
||||
case null:
|
||||
this.Logger.LogError("Component is null! Cannot filter providers based on component settings. Missed CascadingParameter?");
|
||||
yield break;
|
||||
|
||||
case Tools.Components.NONE:
|
||||
this.Logger.LogError("Component is NONE! Cannot filter providers based on component settings. Used wrong component?");
|
||||
yield break;
|
||||
|
||||
case { } component:
|
||||
|
||||
// Get the minimum confidence level for this component, and/or the global minimum if enforced:
|
||||
var minimumLevel = this.SettingsManager.GetMinimumConfidenceLevel(component);
|
||||
|
||||
// Override with the explicit minimum level if set and higher:
|
||||
if (this.ExplicitMinimumConfidence is not ConfidenceLevel.UNKNOWN && this.ExplicitMinimumConfidence > minimumLevel)
|
||||
minimumLevel = this.ExplicitMinimumConfidence;
|
||||
|
||||
// Filter providers based on the minimum confidence level:
|
||||
foreach (var provider in this.SettingsManager.ConfigurationData.Providers)
|
||||
if (provider.UsedLLMProvider != LLMProviders.NONE)
|
||||
if (provider.UsedLLMProvider.GetConfidence(this.SettingsManager).Level >= minimumLevel)
|
||||
yield return provider;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
@inherits MSGComponentBase
|
||||
<MudButton StartIcon="@Icons.Material.Filled.Description" OnClick="async () => await this.SelectFile()" Variant="Variant.Filled" Class="mb-3">
|
||||
<MudButton StartIcon="@Icons.Material.Filled.Description" OnClick="@(async () => await this.SelectFile())" Variant="Variant.Filled" Class="mb-3" Disabled="@this.Disabled">
|
||||
@if (string.IsNullOrWhiteSpace(this.Text))
|
||||
{
|
||||
@T("Use file content as input")
|
||||
@@ -8,4 +8,4 @@
|
||||
{
|
||||
@this.Text
|
||||
}
|
||||
</MudButton>
|
||||
</MudButton>
|
||||
@@ -15,6 +15,9 @@ public partial class ReadFileContent : MSGComponentBase
|
||||
|
||||
[Parameter]
|
||||
public EventCallback<string> FileContentChanged { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public bool Disabled { get; set; }
|
||||
|
||||
[Inject]
|
||||
private RustService RustService { get; init; } = null!;
|
||||
@@ -30,6 +33,9 @@ public partial class ReadFileContent : MSGComponentBase
|
||||
|
||||
private async Task SelectFile()
|
||||
{
|
||||
if (this.Disabled)
|
||||
return;
|
||||
|
||||
// Ensure that Pandoc is installed and ready:
|
||||
var pandocState = await this.PandocAvailabilityService.EnsureAvailabilityAsync(
|
||||
showSuccessMessage: false,
|
||||
@@ -73,4 +79,4 @@ public partial class ReadFileContent : MSGComponentBase
|
||||
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.Error, T("Failed to load file content")));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
@using AIStudio.Settings
|
||||
@using AIStudio.Settings.DataModel
|
||||
@using AIStudio.Tools.Rust
|
||||
@inherits SettingsPanelBase
|
||||
|
||||
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Apps" HeaderText="@T("App Options")">
|
||||
@@ -16,6 +17,7 @@
|
||||
<ConfigurationSelect OptionDescription="@T("Check for updates")" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.UpdateInterval)" Data="@ConfigurationSelectDataFactory.GetUpdateIntervalData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.UpdateInterval = selectedValue)" OptionHelp="@T("How often should we check for app updates?")" IsLocked="() => ManagedConfiguration.TryGet(x => x.App, x => x.UpdateInterval, out var meta) && meta.IsLocked"/>
|
||||
<ConfigurationSelect OptionDescription="@T("Update installation method")" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.UpdateInstallation)" Data="@ConfigurationSelectDataFactory.GetUpdateBehaviourData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.UpdateInstallation = selectedValue)" OptionHelp="@T("Should updates be installed automatically or manually?")" IsLocked="() => ManagedConfiguration.TryGet(x => x.App, x => x.UpdateInstallation, out var meta) && meta.IsLocked"/>
|
||||
<ConfigurationSelect OptionDescription="@T("Navigation bar behavior")" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.NavigationBehavior)" Data="@ConfigurationSelectDataFactory.GetNavBehaviorData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.NavigationBehavior = selectedValue)" OptionHelp="@T("Select the desired behavior for the navigation bar.")"/>
|
||||
<ConfigurationOption OptionDescription="@T("Show administration settings?")" LabelOn="@T("Administration settings are visible")" LabelOff="@T("Administration settings are not visible")" State="@(() => this.SettingsManager.ConfigurationData.App.ShowAdminSettings)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.App.ShowAdminSettings = updatedState)" OptionHelp="@T("When enabled, additional administration options become visible. These options are intended for IT staff to manage organization-wide configuration, e.g. configuring and exporting providers for an entire organization.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.App, x => x.ShowAdminSettings, out var meta) && meta.IsLocked"/>
|
||||
<ConfigurationSelect OptionDescription="@T("Preview feature visibility")" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.PreviewVisibility)" Data="@ConfigurationSelectDataFactory.GetPreviewVisibility()" SelectionUpdate="@this.UpdatePreviewFeatures" OptionHelp="@T("Do you want to show preview features in the app?")" IsLocked="() => ManagedConfiguration.TryGet(x => x.App, x => x.PreviewVisibility, out var meta) && meta.IsLocked"/>
|
||||
|
||||
@if (this.SettingsManager.ConfigurationData.App.PreviewVisibility > PreviewVisibility.NONE)
|
||||
@@ -23,15 +25,37 @@
|
||||
var availablePreviewFeatures = ConfigurationSelectDataFactory.GetPreviewFeaturesData(this.SettingsManager).ToList();
|
||||
if (availablePreviewFeatures.Count > 0)
|
||||
{
|
||||
<ConfigurationMultiSelect OptionDescription="@T("Select preview features")" SelectedValues="@(() => this.SettingsManager.ConfigurationData.App.EnabledPreviewFeatures.Where(x => !x.IsReleased()).ToHashSet())" Data="@availablePreviewFeatures" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.EnabledPreviewFeatures = selectedValue)" OptionHelp="@T("Which preview features would you like to enable?")" IsLocked="() => ManagedConfiguration.TryGet(x => x.App, x => x.EnabledPreviewFeatures, out var meta) && meta.IsLocked"/>
|
||||
<ConfigurationMultiSelect OptionDescription="@T("Select preview features")" SelectedValues="@this.GetSelectedPreviewFeatures" Data="@availablePreviewFeatures" SelectionUpdate="@this.UpdateEnabledPreviewFeatures" OptionHelp="@T("Which preview features would you like to enable?")" IsItemLocked="@this.IsPluginContributedPreviewFeature" IsLocked="() => ManagedConfiguration.TryGet(x => x.App, x => x.EnabledPreviewFeatures, out var meta) && meta.IsLocked"/>
|
||||
}
|
||||
}
|
||||
|
||||
<ConfigurationProviderSelection Component="Components.APP_SETTINGS" Data="@this.AvailableLLMProvidersFunc()" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.PreselectedProvider = selectedValue)" HelpText="@(() => T("Would you like to set one provider as the default for the entire app? When you configure a different provider for an assistant, it will always take precedence."))"/>
|
||||
<ConfigurationProviderSelection Component="Components.APP_SETTINGS" Data="@this.AvailableLLMProvidersFunc()" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.PreselectedProvider = selectedValue)" HelpText="@(() => T("Would you like to set one provider as the default for the entire app? When you configure a different provider for an assistant, it will always take precedence."))" IsLocked="() => ManagedConfiguration.TryGet(x => x.App, x => x.PreselectedProvider, out var meta) && meta.IsLocked"/>
|
||||
<ConfigurationSelect OptionDescription="@T("Preselect one of your profiles?")" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.PreselectedProfile)" Data="@ConfigurationSelectDataFactory.GetProfilesData(this.SettingsManager.ConfigurationData.Profiles)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.PreselectedProfile = selectedValue)" OptionHelp="@T("Would you like to set one of your profiles as the default for the entire app? When you configure a different profile for an assistant, it will always take precedence.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.App, x => x.PreselectedProfile, out var meta) && meta.IsLocked"/>
|
||||
|
||||
@if (PreviewFeatures.PRE_SPEECH_TO_TEXT_2026.IsEnabled(this.SettingsManager))
|
||||
{
|
||||
<ConfigurationSelect OptionDescription="@T("Select a transcription provider")" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.UseTranscriptionProvider)" Data="@this.GetFilteredTranscriptionProviders()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.UseTranscriptionProvider = selectedValue)" OptionHelp="@T("Select a transcription provider for transcribing your voice. Without a selected provider, dictation and transcription features will be disabled.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.App, x => x.UseTranscriptionProvider, out var meta) && meta.IsLocked"/>
|
||||
<ConfigurationShortcut ShortcutId="Shortcut.VOICE_RECORDING_TOGGLE" OptionDescription="@T("Voice recording shortcut")" Shortcut="@(() => this.SettingsManager.ConfigurationData.App.ShortcutVoiceRecording)" ShortcutUpdate="@(shortcut => this.SettingsManager.ConfigurationData.App.ShortcutVoiceRecording = shortcut)" OptionHelp="@T("The global keyboard shortcut for toggling voice recording. This shortcut works system-wide, even when the app is not focused.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.App, x => x.ShortcutVoiceRecording, out var meta) && meta.IsLocked"/>
|
||||
}
|
||||
</ExpansionPanel>
|
||||
|
||||
@if (this.SettingsManager.ConfigurationData.App.ShowAdminSettings)
|
||||
{
|
||||
<MudText Typo="Typo.h5" Class="mt-6 mb-3">
|
||||
@T("Enterprise Administration")
|
||||
</MudText>
|
||||
|
||||
<MudText Typo="Typo.body2" Class="mb-3">
|
||||
@T("Generate a 256-bit encryption secret for encrypting API keys in configuration plugins. Deploy this secret to client machines via Group Policy (Windows Registry) or environment variables. Providers can then be exported with encrypted API keys using the export buttons in the provider settings.")
|
||||
<MudLink Href="https://github.com/MindWorkAI/AI-Studio/blob/main/documentation/Enterprise%20IT.md" Target="_blank">
|
||||
@T("Read the Enterprise IT documentation for details.")
|
||||
</MudLink>
|
||||
</MudText>
|
||||
|
||||
<MudButton StartIcon="@Icons.Material.Filled.Key"
|
||||
Variant="Variant.Filled"
|
||||
Color="Color.Primary"
|
||||
OnClick="@this.GenerateEncryptionSecret">
|
||||
@T("Generate an encryption secret and copy it to the clipboard")
|
||||
</MudButton>
|
||||
}
|
||||
</ExpansionPanel>
|
||||
@@ -6,6 +6,12 @@ namespace AIStudio.Components.Settings;
|
||||
|
||||
public partial class SettingsPanelApp : SettingsPanelBase
|
||||
{
|
||||
private async Task GenerateEncryptionSecret()
|
||||
{
|
||||
var secret = EnterpriseEncryption.GenerateSecret();
|
||||
await this.RustService.CopyText2Clipboard(this.Snackbar, secret);
|
||||
}
|
||||
|
||||
private IEnumerable<ConfigurationSelectData<string>> GetFilteredTranscriptionProviders()
|
||||
{
|
||||
yield return new(T("Disable dictation and transcription"), string.Empty);
|
||||
@@ -21,7 +27,41 @@ public partial class SettingsPanelApp : SettingsPanelBase
|
||||
private void UpdatePreviewFeatures(PreviewVisibility previewVisibility)
|
||||
{
|
||||
this.SettingsManager.ConfigurationData.App.PreviewVisibility = previewVisibility;
|
||||
this.SettingsManager.ConfigurationData.App.EnabledPreviewFeatures = previewVisibility.FilterPreviewFeatures(this.SettingsManager.ConfigurationData.App.EnabledPreviewFeatures);
|
||||
var filtered = previewVisibility.FilterPreviewFeatures(this.SettingsManager.ConfigurationData.App.EnabledPreviewFeatures);
|
||||
filtered.UnionWith(this.GetPluginContributedPreviewFeatures());
|
||||
this.SettingsManager.ConfigurationData.App.EnabledPreviewFeatures = filtered;
|
||||
}
|
||||
|
||||
private HashSet<PreviewFeatures> GetPluginContributedPreviewFeatures()
|
||||
{
|
||||
if (ManagedConfiguration.TryGet(x => x.App, x => x.EnabledPreviewFeatures, out var meta) && meta.HasPluginContribution)
|
||||
return meta.PluginContribution.Where(x => !x.IsReleased()).ToHashSet();
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
private bool IsPluginContributedPreviewFeature(PreviewFeatures feature)
|
||||
{
|
||||
if (feature.IsReleased())
|
||||
return false;
|
||||
|
||||
if (!ManagedConfiguration.TryGet(x => x.App, x => x.EnabledPreviewFeatures, out var meta) || !meta.HasPluginContribution)
|
||||
return false;
|
||||
|
||||
return meta.PluginContribution.Contains(feature);
|
||||
}
|
||||
|
||||
private HashSet<PreviewFeatures> GetSelectedPreviewFeatures()
|
||||
{
|
||||
var enabled = this.SettingsManager.ConfigurationData.App.EnabledPreviewFeatures.Where(x => !x.IsReleased()).ToHashSet();
|
||||
enabled.UnionWith(this.GetPluginContributedPreviewFeatures());
|
||||
return enabled;
|
||||
}
|
||||
|
||||
private void UpdateEnabledPreviewFeatures(HashSet<PreviewFeatures> selectedFeatures)
|
||||
{
|
||||
selectedFeatures.UnionWith(this.GetPluginContributedPreviewFeatures());
|
||||
this.SettingsManager.ConfigurationData.App.EnabledPreviewFeatures = selectedFeatures;
|
||||
}
|
||||
|
||||
private async Task UpdateLangBehaviour(LangBehavior behavior)
|
||||
|
||||
@@ -15,4 +15,7 @@ public abstract class SettingsPanelBase : MSGComponentBase
|
||||
|
||||
[Inject]
|
||||
protected RustService RustService { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
protected ISnackbar Snackbar { get; init; } = null!;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
@using AIStudio.Provider
|
||||
@using AIStudio.Settings.DataModel
|
||||
@inherits SettingsPanelBase
|
||||
@inherits SettingsPanelProviderBase
|
||||
|
||||
@if (PreviewFeatures.PRE_RAG_2024.IsEnabled(this.SettingsManager))
|
||||
{
|
||||
@@ -22,7 +22,7 @@
|
||||
<col style="width: 12em;"/>
|
||||
<col style="width: 12em;"/>
|
||||
<col/>
|
||||
<col style="width: 16em;"/>
|
||||
<col style="width: 22em;"/>
|
||||
</ColGroup>
|
||||
<HeaderContent>
|
||||
<MudTh>#</MudTh>
|
||||
@@ -35,7 +35,7 @@
|
||||
<MudTd>@context.Num</MudTd>
|
||||
<MudTd>@context.Name</MudTd>
|
||||
<MudTd>@context.UsedLLMProvider.ToName()</MudTd>
|
||||
<MudTd>@GetEmbeddingProviderModelName(context)</MudTd>
|
||||
<MudTd>@this.GetEmbeddingProviderModelName(context)</MudTd>
|
||||
|
||||
<MudTd>
|
||||
<MudStack Row="true" Class="mb-2 mt-2" Spacing="1" Wrap="Wrap.Wrap">
|
||||
@@ -53,9 +53,18 @@
|
||||
<MudTooltip Text="@T("Edit")">
|
||||
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Edit" OnClick="@(() => this.EditEmbeddingProvider(context))"/>
|
||||
</MudTooltip>
|
||||
@if (this.SettingsManager.ConfigurationData.App.ShowAdminSettings)
|
||||
{
|
||||
<MudTooltip Text="@T("Export configuration")">
|
||||
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Dataset" OnClick="@(() => this.ExportEmbeddingProvider(context))"/>
|
||||
</MudTooltip>
|
||||
}
|
||||
<MudTooltip Text="@T("Delete")">
|
||||
<MudIconButton Color="Color.Error" Icon="@Icons.Material.Filled.Delete" OnClick="@(() => this.DeleteEmbeddingProvider(context))"/>
|
||||
</MudTooltip>
|
||||
<MudTooltip Text="@T("Test")">
|
||||
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Api" OnClick="@(() => this.TestEmbeddingProvider(context))"/>
|
||||
</MudTooltip>
|
||||
}
|
||||
</MudStack>
|
||||
</MudTd>
|
||||
@@ -73,4 +82,4 @@
|
||||
@T("Add Embedding")
|
||||
</MudButton>
|
||||
</ExpansionPanel>
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
using System.Globalization;
|
||||
using AIStudio.Dialogs;
|
||||
using AIStudio.Provider;
|
||||
using AIStudio.Settings;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
@@ -7,7 +9,7 @@ using DialogOptions = AIStudio.Dialogs.DialogOptions;
|
||||
|
||||
namespace AIStudio.Components.Settings;
|
||||
|
||||
public partial class SettingsPanelEmbeddings : SettingsPanelBase
|
||||
public partial class SettingsPanelEmbeddings : SettingsPanelProviderBase
|
||||
{
|
||||
[Parameter]
|
||||
public List<ConfigurationSelectData<string>> AvailableEmbeddingProviders { get; set; } = new();
|
||||
@@ -15,8 +17,12 @@ public partial class SettingsPanelEmbeddings : SettingsPanelBase
|
||||
[Parameter]
|
||||
public EventCallback<List<ConfigurationSelectData<string>>> AvailableEmbeddingProvidersChanged { get; set; }
|
||||
|
||||
private static string GetEmbeddingProviderModelName(EmbeddingProvider provider)
|
||||
private string GetEmbeddingProviderModelName(EmbeddingProvider provider)
|
||||
{
|
||||
// For system models, return localized text:
|
||||
if (provider.Model.IsSystemModel)
|
||||
return T("Uses the provider-configured model");
|
||||
|
||||
const int MAX_LENGTH = 36;
|
||||
var modelName = provider.Model.ToString();
|
||||
return modelName.Length > MAX_LENGTH ? "[...] " + modelName[^Math.Min(MAX_LENGTH, modelName.Length)..] : modelName;
|
||||
@@ -110,6 +116,17 @@ public partial class SettingsPanelEmbeddings : SettingsPanelBase
|
||||
await this.UpdateEmbeddingProviders();
|
||||
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
|
||||
}
|
||||
|
||||
private async Task ExportEmbeddingProvider(EmbeddingProvider provider)
|
||||
{
|
||||
if (!this.SettingsManager.ConfigurationData.App.ShowAdminSettings)
|
||||
return;
|
||||
|
||||
if (provider == EmbeddingProvider.NONE)
|
||||
return;
|
||||
|
||||
await this.ExportProvider(provider, SecretStoreType.EMBEDDING_PROVIDER, provider.ExportAsConfigurationSection);
|
||||
}
|
||||
|
||||
private async Task UpdateEmbeddingProviders()
|
||||
{
|
||||
@@ -119,4 +136,48 @@ public partial class SettingsPanelEmbeddings : SettingsPanelBase
|
||||
|
||||
await this.AvailableEmbeddingProvidersChanged.InvokeAsync(this.AvailableEmbeddingProviders);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task TestEmbeddingProvider(EmbeddingProvider provider)
|
||||
{
|
||||
var dialogParameters = new DialogParameters<SingleInputDialog>
|
||||
{
|
||||
{ x => x.ConfirmText, T("Embed text") },
|
||||
{ x => x.InputHeaderText, T("Add text that should be embedded:") },
|
||||
{ x => x.UserInput, T("Example text to embed") },
|
||||
};
|
||||
|
||||
var dialogReference = await this.DialogService.ShowAsync<SingleInputDialog>(T("Test Embedding Provider"), dialogParameters, DialogOptions.FULLSCREEN);
|
||||
var dialogResult = await dialogReference.Result;
|
||||
if (dialogResult is null || dialogResult.Canceled)
|
||||
return;
|
||||
|
||||
var inputText = dialogResult.Data as string;
|
||||
if (string.IsNullOrWhiteSpace(inputText))
|
||||
return;
|
||||
|
||||
var embeddingProvider = provider.CreateProvider();
|
||||
var embeddings = await embeddingProvider.EmbedTextAsync(provider.Model, this.SettingsManager, default, new List<string> { inputText });
|
||||
|
||||
if (embeddings.Count == 0)
|
||||
{
|
||||
await this.DialogService.ShowMessageBox(T("Embedding Result"), T("No embedding was returned."), T("Close"));
|
||||
return;
|
||||
}
|
||||
|
||||
var vector = embeddings.FirstOrDefault();
|
||||
if (vector is null || vector.Count == 0)
|
||||
{
|
||||
await this.DialogService.ShowMessageBox(T("Embedding Result"), T("No embedding was returned."), T("Close"));
|
||||
return;
|
||||
}
|
||||
|
||||
var resultText = string.Join(Environment.NewLine, vector.Select(value => value.ToString("G9", CultureInfo.InvariantCulture)));
|
||||
var resultParameters = new DialogParameters<EmbeddingResultDialog>
|
||||
{
|
||||
{ x => x.ResultText, resultText },
|
||||
{ x => x.ResultLabel, T("Embedding Vector (one dimension per line)") },
|
||||
};
|
||||
|
||||
await this.DialogService.ShowAsync<EmbeddingResultDialog>(T("Embedding Result"), resultParameters, DialogOptions.FULLSCREEN);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
using AIStudio.Dialogs;
|
||||
using AIStudio.Tools.PluginSystem;
|
||||
|
||||
using DialogOptions = AIStudio.Dialogs.DialogOptions;
|
||||
|
||||
namespace AIStudio.Components.Settings;
|
||||
|
||||
public abstract class SettingsPanelProviderBase : SettingsPanelBase
|
||||
{
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(SettingsPanelProviderBase).Namespace, nameof(SettingsPanelProviderBase));
|
||||
|
||||
/// <summary>
|
||||
/// Exports the provider configuration as Lua code, optionally including the encrypted API key if the provider has one
|
||||
/// configured and the user agrees to include it. The exportFunc should generate the Lua code based on the provided
|
||||
/// encrypted API key (which may be null if the user chose not to include it or if encryption is not available).
|
||||
/// The generated Lua code is then copied to the clipboard for easy sharing.
|
||||
/// </summary>
|
||||
/// <param name="secretId">The secret ID of the provider to check for an API key.</param>
|
||||
/// <param name="storeType">The type of secret store to check for the API key (e.g., LLM provider, transcription provider, etc.).</param>
|
||||
/// <param name="exportFunc">The function that generates the Lua code for the provider configuration, given the optional encrypted API key.</param>
|
||||
protected async Task ExportProvider(ISecretId secretId, SecretStoreType storeType, Func<string?, string> exportFunc)
|
||||
{
|
||||
string? encryptedApiKey = null;
|
||||
|
||||
// Check if the provider has an API key stored:
|
||||
var apiKeyResponse = await this.RustService.GetAPIKey(secretId, storeType, isTrying: true);
|
||||
if (apiKeyResponse.Success)
|
||||
{
|
||||
// Ask the user if they want to export the API key:
|
||||
var dialogParameters = new DialogParameters<ConfirmDialog>
|
||||
{
|
||||
{ x => x.Message, TB("This provider has an API key configured. Do you want to include the encrypted API key in the export? Note: The recipient will need the same encryption secret to use the API key.") },
|
||||
};
|
||||
|
||||
var dialogReference = await this.DialogService.ShowAsync<ConfirmDialog>(TB("Export API Key?"), dialogParameters, DialogOptions.FULLSCREEN);
|
||||
var dialogResult = await dialogReference.Result;
|
||||
if (dialogResult is { Canceled: false })
|
||||
{
|
||||
// User wants to export the API key - encrypt it:
|
||||
var encryption = PluginFactory.EnterpriseEncryption;
|
||||
if (encryption?.IsAvailable == true)
|
||||
{
|
||||
var decryptedApiKey = await apiKeyResponse.Secret.Decrypt(Program.ENCRYPTION);
|
||||
if (encryption.TryEncrypt(decryptedApiKey, out var encrypted))
|
||||
encryptedApiKey = encrypted;
|
||||
}
|
||||
else
|
||||
{
|
||||
// No encryption secret available - inform the user:
|
||||
this.Snackbar.Add(TB("Cannot export the encrypted API key: No enterprise encryption secret is configured."), Severity.Warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var luaCode = exportFunc(encryptedApiKey);
|
||||
if (string.IsNullOrWhiteSpace(luaCode))
|
||||
return;
|
||||
|
||||
await this.RustService.CopyText2Clipboard(this.Snackbar, luaCode);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
@using AIStudio.Provider
|
||||
@using AIStudio.Settings
|
||||
@using AIStudio.Provider.SelfHosted
|
||||
@inherits SettingsPanelBase
|
||||
@inherits SettingsPanelProviderBase
|
||||
|
||||
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Layers" HeaderText="@T("Configure LLM Providers")">
|
||||
<MudText Typo="Typo.h4" Class="mb-3">
|
||||
@@ -16,7 +15,7 @@
|
||||
<col style="width: 12em;"/>
|
||||
<col style="width: 12em;"/>
|
||||
<col/>
|
||||
<col style="width: 16em;"/>
|
||||
<col style="width: 22em;"/>
|
||||
</ColGroup>
|
||||
<HeaderContent>
|
||||
<MudTh>#</MudTh>
|
||||
@@ -29,20 +28,7 @@
|
||||
<MudTd>@context.Num</MudTd>
|
||||
<MudTd>@context.InstanceName</MudTd>
|
||||
<MudTd>@context.UsedLLMProvider.ToName()</MudTd>
|
||||
<MudTd>
|
||||
@if (context.UsedLLMProvider is not LLMProviders.SELF_HOSTED)
|
||||
{
|
||||
@GetLLMProviderModelName(context)
|
||||
}
|
||||
else if (context.UsedLLMProvider is LLMProviders.SELF_HOSTED && context.Host is not Host.LLAMA_CPP)
|
||||
{
|
||||
@GetLLMProviderModelName(context)
|
||||
}
|
||||
else
|
||||
{
|
||||
@T("as selected by provider")
|
||||
}
|
||||
</MudTd>
|
||||
<MudTd>@this.GetLLMProviderModelName(context)</MudTd>
|
||||
<MudTd>
|
||||
<MudStack Row="true" Class="mb-2 mt-2" Spacing="1" Wrap="Wrap.Wrap">
|
||||
@if (context.IsEnterpriseConfiguration)
|
||||
@@ -59,6 +45,12 @@
|
||||
<MudTooltip Text="@T("Edit")">
|
||||
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Edit" OnClick="@(() => this.EditLLMProvider(context))"/>
|
||||
</MudTooltip>
|
||||
@if (this.SettingsManager.ConfigurationData.App.ShowAdminSettings)
|
||||
{
|
||||
<MudTooltip Text="@T("Export configuration")">
|
||||
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Dataset" OnClick="@(() => this.ExportLLMProvider(context))"/>
|
||||
</MudTooltip>
|
||||
}
|
||||
<MudTooltip Text="@T("Delete")">
|
||||
<MudIconButton Color="Color.Error" Icon="@Icons.Material.Filled.Delete" OnClick="@(() => this.DeleteLLMProvider(context))"/>
|
||||
</MudTooltip>
|
||||
@@ -131,4 +123,4 @@
|
||||
</MudTable>
|
||||
}
|
||||
}
|
||||
</ExpansionPanel>
|
||||
</ExpansionPanel>
|
||||
@@ -10,7 +10,7 @@ using DialogOptions = AIStudio.Dialogs.DialogOptions;
|
||||
|
||||
namespace AIStudio.Components.Settings;
|
||||
|
||||
public partial class SettingsPanelProviders : SettingsPanelBase
|
||||
public partial class SettingsPanelProviders : SettingsPanelProviderBase
|
||||
{
|
||||
[Parameter]
|
||||
public List<ConfigurationSelectData<string>> AvailableLLMProviders { get; set; } = new();
|
||||
@@ -134,8 +134,23 @@ public partial class SettingsPanelProviders : SettingsPanelBase
|
||||
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
|
||||
}
|
||||
|
||||
private static string GetLLMProviderModelName(AIStudio.Settings.Provider provider)
|
||||
private async Task ExportLLMProvider(AIStudio.Settings.Provider provider)
|
||||
{
|
||||
if (!this.SettingsManager.ConfigurationData.App.ShowAdminSettings)
|
||||
return;
|
||||
|
||||
if (provider == AIStudio.Settings.Provider.NONE)
|
||||
return;
|
||||
|
||||
await this.ExportProvider(provider, SecretStoreType.LLM_PROVIDER, provider.ExportAsConfigurationSection);
|
||||
}
|
||||
|
||||
private string GetLLMProviderModelName(AIStudio.Settings.Provider provider)
|
||||
{
|
||||
// For system models, return localized text:
|
||||
if (provider.Model.IsSystemModel)
|
||||
return T("Uses the provider-configured model");
|
||||
|
||||
const int MAX_LENGTH = 36;
|
||||
var modelName = provider.Model.ToString();
|
||||
return modelName.Length > MAX_LENGTH ? "[...] " + modelName[^Math.Min(MAX_LENGTH, modelName.Length)..] : modelName;
|
||||
@@ -172,4 +187,4 @@ public partial class SettingsPanelProviders : SettingsPanelBase
|
||||
this.SettingsManager.ConfigurationData.LLMProviders.CustomConfidenceScheme[llmProvider] = level;
|
||||
await this.SettingsManager.StoreSettings();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
@using AIStudio.Provider
|
||||
@using AIStudio.Settings.DataModel
|
||||
@inherits SettingsPanelBase
|
||||
@inherits SettingsPanelProviderBase
|
||||
|
||||
@if (PreviewFeatures.PRE_SPEECH_TO_TEXT_2026.IsEnabled(this.SettingsManager))
|
||||
{
|
||||
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.VoiceChat" HeaderText="@T("Configure Transcription Providers")">
|
||||
<PreviewPrototype ApplyInnerScrollingFix="true"/>
|
||||
<PreviewBeta ApplyInnerScrollingFix="true"/>
|
||||
<MudText Typo="Typo.h4" Class="mb-3">
|
||||
@T("Configured Transcription Providers")
|
||||
</MudText>
|
||||
@@ -19,7 +19,7 @@
|
||||
<col style="width: 12em;"/>
|
||||
<col style="width: 12em;"/>
|
||||
<col/>
|
||||
<col style="width: 16em;"/>
|
||||
<col style="width: 22em;"/>
|
||||
</ColGroup>
|
||||
<HeaderContent>
|
||||
<MudTh>#</MudTh>
|
||||
@@ -32,7 +32,7 @@
|
||||
<MudTd>@context.Num</MudTd>
|
||||
<MudTd>@context.Name</MudTd>
|
||||
<MudTd>@context.UsedLLMProvider.ToName()</MudTd>
|
||||
<MudTd>@GetTranscriptionProviderModelName(context)</MudTd>
|
||||
<MudTd>@this.GetTranscriptionProviderModelName(context)</MudTd>
|
||||
|
||||
<MudTd>
|
||||
<MudStack Row="true" Class="mb-2 mt-2" Spacing="1" Wrap="Wrap.Wrap">
|
||||
@@ -50,6 +50,12 @@
|
||||
<MudTooltip Text="@T("Edit")">
|
||||
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Edit" OnClick="@(() => this.EditTranscriptionProvider(context))"/>
|
||||
</MudTooltip>
|
||||
@if (this.SettingsManager.ConfigurationData.App.ShowAdminSettings)
|
||||
{
|
||||
<MudTooltip Text="@T("Export configuration")">
|
||||
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Dataset" OnClick="@(() => this.ExportTranscriptionProvider(context))"/>
|
||||
</MudTooltip>
|
||||
}
|
||||
<MudTooltip Text="@T("Delete")">
|
||||
<MudIconButton Color="Color.Error" Icon="@Icons.Material.Filled.Delete" OnClick="@(() => this.DeleteTranscriptionProvider(context))"/>
|
||||
</MudTooltip>
|
||||
@@ -70,4 +76,4 @@
|
||||
@T("Add transcription provider")
|
||||
</MudButton>
|
||||
</ExpansionPanel>
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ using DialogOptions = AIStudio.Dialogs.DialogOptions;
|
||||
|
||||
namespace AIStudio.Components.Settings;
|
||||
|
||||
public partial class SettingsPanelTranscription : SettingsPanelBase
|
||||
public partial class SettingsPanelTranscription : SettingsPanelProviderBase
|
||||
{
|
||||
[Parameter]
|
||||
public List<ConfigurationSelectData<string>> AvailableTranscriptionProviders { get; set; } = new();
|
||||
@@ -15,8 +15,12 @@ public partial class SettingsPanelTranscription : SettingsPanelBase
|
||||
[Parameter]
|
||||
public EventCallback<List<ConfigurationSelectData<string>>> AvailableTranscriptionProvidersChanged { get; set; }
|
||||
|
||||
private static string GetTranscriptionProviderModelName(TranscriptionProvider provider)
|
||||
private string GetTranscriptionProviderModelName(TranscriptionProvider provider)
|
||||
{
|
||||
// For system models, return localized text:
|
||||
if (provider.Model.IsSystemModel)
|
||||
return T("Uses the provider-configured model");
|
||||
|
||||
const int MAX_LENGTH = 36;
|
||||
var modelName = provider.Model.ToString();
|
||||
return modelName.Length > MAX_LENGTH ? "[...] " + modelName[^Math.Min(MAX_LENGTH, modelName.Length)..] : modelName;
|
||||
@@ -110,6 +114,17 @@ public partial class SettingsPanelTranscription : SettingsPanelBase
|
||||
await this.UpdateTranscriptionProviders();
|
||||
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
|
||||
}
|
||||
|
||||
private async Task ExportTranscriptionProvider(TranscriptionProvider provider)
|
||||
{
|
||||
if (!this.SettingsManager.ConfigurationData.App.ShowAdminSettings)
|
||||
return;
|
||||
|
||||
if (provider == TranscriptionProvider.NONE)
|
||||
return;
|
||||
|
||||
await this.ExportProvider(provider, SecretStoreType.TRANSCRIPTION_PROVIDER, provider.ExportAsConfigurationSection);
|
||||
}
|
||||
|
||||
private async Task UpdateTranscriptionProviders()
|
||||
{
|
||||
@@ -119,4 +134,4 @@ public partial class SettingsPanelTranscription : SettingsPanelBase
|
||||
|
||||
await this.AvailableTranscriptionProvidersChanged.InvokeAsync(this.AvailableTranscriptionProviders);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,7 @@ public partial class Vision : MSGComponentBase
|
||||
this.itemsVision =
|
||||
[
|
||||
new(T("Meet your needs"), T("Whatever your job or task is, MindWork AI Studio aims to meet your needs: whether you're a project manager, scientist, artist, author, software developer, or game developer.")),
|
||||
new(T("Democratization of AI"), T("We want to contribute to the democratization of AI. MindWork AI Studio runs even on low-cost hardware, including computers around 100 EUR such as Raspberry Pi. This makes the app and its full feature set accessible to people and families with limited budgets. You can start with local LLMs or use affordable cloud models. MindWork AI Studio itself is available free of charge.")),
|
||||
new(T("Integrating your data"), T("You'll be able to integrate your data into AI Studio, like your PDF or Office files, or your Markdown notes.")),
|
||||
new(T("Integration of enterprise data"), T("It will soon be possible to integrate data from the corporate network using a specified interface (External Retrieval Interface, ERI for short). This will likely require development work by the organization in question.")),
|
||||
new(T("Useful assistants"), T("We'll develop more assistants for everyday tasks.")),
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
@if (PreviewFeatures.PRE_SPEECH_TO_TEXT_2026.IsEnabled(this.SettingsManager) && !string.IsNullOrWhiteSpace(this.SettingsManager.ConfigurationData.App.UseTranscriptionProvider))
|
||||
{
|
||||
<MudTooltip Text="@this.Tooltip">
|
||||
@if (this.isTranscribing)
|
||||
@if (this.isTranscribing || this.isPreparing)
|
||||
{
|
||||
<MudProgressCircular Size="Size.Small" Indeterminate="true" Color="Color.Primary"/>
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using AIStudio.Provider;
|
||||
using AIStudio.Tools.MIME;
|
||||
using AIStudio.Tools.Rust;
|
||||
using AIStudio.Tools.Services;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
@@ -20,8 +21,63 @@ public partial class VoiceRecorder : MSGComponentBase
|
||||
[Inject]
|
||||
private ISnackbar Snackbar { get; init; } = null!;
|
||||
|
||||
#region Overrides of MSGComponentBase
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
// Register for global shortcut events:
|
||||
this.ApplyFilters([], [Event.TAURI_EVENT_RECEIVED]);
|
||||
|
||||
await base.OnInitializedAsync();
|
||||
|
||||
try
|
||||
{
|
||||
// Initialize sound effects. This "warms up" the AudioContext and preloads all sounds for reliable playback:
|
||||
await this.JsRuntime.InvokeVoidAsync("initSoundEffects");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.Logger.LogError(ex, "Failed to initialize sound effects.");
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task ProcessIncomingMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default
|
||||
{
|
||||
switch (triggeredEvent)
|
||||
{
|
||||
case Event.TAURI_EVENT_RECEIVED when data is TauriEvent { EventType: TauriEventType.GLOBAL_SHORTCUT_PRESSED } tauriEvent:
|
||||
// Check if this is the voice recording toggle shortcut:
|
||||
if (tauriEvent.TryGetShortcut(out var shortcutId) && shortcutId == Shortcut.VOICE_RECORDING_TOGGLE)
|
||||
{
|
||||
this.Logger.LogInformation("Global shortcut triggered for voice recording toggle.");
|
||||
await this.ToggleRecordingFromShortcut();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toggles the recording state when triggered by a global shortcut.
|
||||
/// </summary>
|
||||
private async Task ToggleRecordingFromShortcut()
|
||||
{
|
||||
// Don't allow toggle if transcription is in progress or preparing:
|
||||
if (this.isTranscribing || this.isPreparing)
|
||||
{
|
||||
this.Logger.LogDebug("Ignoring shortcut: transcription or preparation is in progress.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Toggle the recording state:
|
||||
await this.OnRecordingToggled(!this.isRecording);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private uint numReceivedChunks;
|
||||
private bool isRecording;
|
||||
private bool isPreparing;
|
||||
private bool isTranscribing;
|
||||
private FileStream? currentRecordingStream;
|
||||
private string? currentRecordingPath;
|
||||
@@ -39,6 +95,19 @@ public partial class VoiceRecorder : MSGComponentBase
|
||||
{
|
||||
if (toggled)
|
||||
{
|
||||
this.isPreparing = true;
|
||||
this.StateHasChanged();
|
||||
|
||||
try
|
||||
{
|
||||
// Warm up sound effects:
|
||||
await this.JsRuntime.InvokeVoidAsync("initSoundEffects");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.Logger.LogError(ex, "Failed to initialize sound effects.");
|
||||
}
|
||||
|
||||
var mimeTypes = GetPreferredMimeTypes(
|
||||
Builder.Create().UseAudio().UseSubtype(AudioSubtype.OGG).Build(),
|
||||
Builder.Create().UseAudio().UseSubtype(AudioSubtype.AAC).Build(),
|
||||
@@ -56,20 +125,44 @@ public partial class VoiceRecorder : MSGComponentBase
|
||||
// Initialize the file stream for writing chunks:
|
||||
await this.InitializeRecordingStream();
|
||||
|
||||
var mimeTypeStrings = mimeTypes.ToStringArray();
|
||||
var actualMimeType = await this.JsRuntime.InvokeAsync<string>("audioRecorder.start", this.dotNetReference, mimeTypeStrings);
|
||||
try
|
||||
{
|
||||
var mimeTypeStrings = mimeTypes.ToStringArray();
|
||||
var actualMimeType = await this.JsRuntime.InvokeAsync<string>("audioRecorder.start", this.dotNetReference, mimeTypeStrings);
|
||||
|
||||
// Store the MIME type for later use:
|
||||
this.currentRecordingMimeType = actualMimeType;
|
||||
// Store the MIME type for later use:
|
||||
this.currentRecordingMimeType = actualMimeType;
|
||||
|
||||
this.Logger.LogInformation("Audio recording started with MIME type: '{ActualMimeType}'.", actualMimeType);
|
||||
this.isRecording = true;
|
||||
this.Logger.LogInformation("Audio recording started with MIME type: '{ActualMimeType}'.", actualMimeType);
|
||||
this.isPreparing = false;
|
||||
this.isRecording = true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
this.Logger.LogError(e, "Failed to start audio recording.");
|
||||
await this.MessageBus.SendError(new(Icons.Material.Filled.MicOff, this.T("Failed to start audio recording.")));
|
||||
|
||||
// Clean up the recording stream if starting failed:
|
||||
await this.FinalizeRecordingStream();
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.StateHasChanged();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var result = await this.JsRuntime.InvokeAsync<AudioRecordingResult>("audioRecorder.stop");
|
||||
if (result.ChangedMimeType)
|
||||
this.Logger.LogWarning("The recorded audio MIME type was changed to '{ResultMimeType}'.", result.MimeType);
|
||||
try
|
||||
{
|
||||
var result = await this.JsRuntime.InvokeAsync<AudioRecordingResult>("audioRecorder.stop");
|
||||
if (result.ChangedMimeType)
|
||||
this.Logger.LogWarning("The recorded audio MIME type was changed to '{ResultMimeType}'.", result.MimeType);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
this.Logger.LogError(e, "Failed to stop audio recording.");
|
||||
await this.MessageBus.SendError(new(Icons.Material.Filled.MicOff, this.T("Failed to stop audio recording.")));
|
||||
}
|
||||
|
||||
// Close and finalize the recording stream:
|
||||
await this.FinalizeRecordingStream();
|
||||
@@ -189,7 +282,11 @@ public partial class VoiceRecorder : MSGComponentBase
|
||||
private async Task TranscribeRecordingAsync()
|
||||
{
|
||||
if (this.finalRecordingPath is null)
|
||||
{
|
||||
// No recording to transcribe, but still release the microphone:
|
||||
await this.ReleaseMicrophoneAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
this.isTranscribing = true;
|
||||
this.StateHasChanged();
|
||||
@@ -223,7 +320,7 @@ public partial class VoiceRecorder : MSGComponentBase
|
||||
{
|
||||
this.Logger.LogWarning(
|
||||
"The configured transcription provider '{ProviderName}' has a confidence level of '{ProviderLevel}', which is below the minimum required level of '{MinimumLevel}'.",
|
||||
transcriptionProviderSettings.Name,
|
||||
transcriptionProviderSettings.UsedLLMProvider,
|
||||
providerConfidence.Level,
|
||||
minimumLevel);
|
||||
await this.MessageBus.SendError(new(Icons.Material.Filled.VoiceChat, this.T("The configured transcription provider does not meet the minimum confidence level.")));
|
||||
@@ -240,7 +337,7 @@ public partial class VoiceRecorder : MSGComponentBase
|
||||
}
|
||||
|
||||
// Call the transcription API:
|
||||
this.Logger.LogInformation("Starting transcription with provider '{ProviderName}' and model '{ModelName}'.", transcriptionProviderSettings.Name, transcriptionProviderSettings.Model.DisplayName);
|
||||
this.Logger.LogInformation("Starting transcription with provider '{ProviderName}' and model '{ModelName}'.", transcriptionProviderSettings.UsedLLMProvider, transcriptionProviderSettings.Model.ToString());
|
||||
var transcribedText = await provider.TranscribeAudioAsync(transcriptionProviderSettings.Model, this.finalRecordingPath, this.SettingsManager);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(transcribedText))
|
||||
@@ -261,8 +358,15 @@ public partial class VoiceRecorder : MSGComponentBase
|
||||
|
||||
this.Logger.LogInformation("Transcription completed successfully. Result length: {Length} characters.", transcribedText.Length);
|
||||
|
||||
// Play the transcription done sound effect:
|
||||
await this.JsRuntime.InvokeVoidAsync("playSound", "/sounds/transcription_done.ogg");
|
||||
try
|
||||
{
|
||||
// Play the transcription done sound effect:
|
||||
await this.JsRuntime.InvokeVoidAsync("playSound", "/sounds/transcription_done.ogg");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.Logger.LogError(ex, "Failed to play transcription done sound effect.");
|
||||
}
|
||||
|
||||
// Copy the transcribed text to the clipboard:
|
||||
await this.RustService.CopyText2Clipboard(this.Snackbar, transcribedText);
|
||||
@@ -288,12 +392,30 @@ public partial class VoiceRecorder : MSGComponentBase
|
||||
}
|
||||
finally
|
||||
{
|
||||
await this.ReleaseMicrophoneAsync();
|
||||
|
||||
this.finalRecordingPath = null;
|
||||
this.isTranscribing = false;
|
||||
this.StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ReleaseMicrophoneAsync()
|
||||
{
|
||||
// Wait a moment for any queued sounds to finish playing, then release the microphone.
|
||||
// This allows Bluetooth headsets to switch back to A2DP profile without interrupting audio:
|
||||
await Task.Delay(1_800);
|
||||
|
||||
try
|
||||
{
|
||||
await this.JsRuntime.InvokeVoidAsync("audioRecorder.releaseMicrophone");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
this.Logger.LogError(e, "Failed to release the microphone.");
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class AudioRecordingResult
|
||||
{
|
||||
public string MimeType { get; init; } = string.Empty;
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
</MudTooltip>
|
||||
|
||||
<MudTooltip Text="@T("Delete")" Placement="@WORKSPACE_ITEM_TOOLTIP_PLACEMENT">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Delete" Size="Size.Medium" Color="Color.Inherit" OnClick="@(() => this.DeleteChat(treeItem.Path))"/>
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Delete" Size="Size.Medium" Color="Color.Error" OnClick="@(() => this.DeleteChat(treeItem.Path))"/>
|
||||
</MudTooltip>
|
||||
</div>
|
||||
</div>
|
||||
@@ -57,7 +57,7 @@
|
||||
</MudTooltip>
|
||||
|
||||
<MudTooltip Text="@T("Delete")" Placement="@WORKSPACE_ITEM_TOOLTIP_PLACEMENT">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Delete" Size="Size.Medium" Color="Color.Inherit" OnClick="@(() => this.DeleteWorkspace(treeItem.Path))"/>
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Delete" Size="Size.Medium" Color="Color.Error" OnClick="@(() => this.DeleteWorkspace(treeItem.Path))"/>
|
||||
</MudTooltip>
|
||||
</div>
|
||||
</div>
|
||||
@@ -90,4 +90,4 @@
|
||||
break;
|
||||
}
|
||||
</ItemTemplate>
|
||||
</MudTreeView>
|
||||
</MudTreeView>
|
||||
@@ -44,7 +44,7 @@
|
||||
|
||||
@if (this.DataLLMProvider.IsHostNeeded())
|
||||
{
|
||||
<MudSelect @bind-Value="@this.DataHost" Label="@T("Host")" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.providerValidation.ValidatingHost">
|
||||
<MudSelect T="Host" Value="@this.DataHost" ValueChanged="@this.OnHostChanged" Label="@T("Host")" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.providerValidation.ValidatingHost">
|
||||
@foreach (Host host in Enum.GetValues(typeof(Host)))
|
||||
{
|
||||
if (host.IsEmbeddingSupported())
|
||||
@@ -101,6 +101,12 @@
|
||||
}
|
||||
}
|
||||
</MudStack>
|
||||
@if (!string.IsNullOrWhiteSpace(this.dataLoadingModelsIssue))
|
||||
{
|
||||
<MudAlert Severity="Severity.Error" Class="mt-3">
|
||||
@this.dataLoadingModelsIssue
|
||||
</MudAlert>
|
||||
}
|
||||
</MudField>
|
||||
|
||||
@* ReSharper disable once CSharpWarnings::CS8974 *@
|
||||
|
||||
@@ -71,7 +71,10 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
|
||||
|
||||
[Inject]
|
||||
private RustService RustService { get; init; } = null!;
|
||||
|
||||
|
||||
[Inject]
|
||||
private ILogger<EmbeddingProviderDialog> Logger { get; init; } = null!;
|
||||
|
||||
private static readonly Dictionary<string, object?> SPELLCHECK_ATTRIBUTES = new();
|
||||
|
||||
/// <summary>
|
||||
@@ -85,7 +88,8 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
|
||||
private string dataManuallyModel = string.Empty;
|
||||
private string dataAPIKeyStorageIssue = string.Empty;
|
||||
private string dataEditingPreviousInstanceName = string.Empty;
|
||||
|
||||
private string dataLoadingModelsIssue = string.Empty;
|
||||
|
||||
// We get the form reference from Blazor code to validate it manually:
|
||||
private MudForm form = null!;
|
||||
|
||||
@@ -102,6 +106,7 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
|
||||
GetPreviousInstanceName = () => this.dataEditingPreviousInstanceName,
|
||||
GetUsedInstanceNames = () => this.UsedInstanceNames,
|
||||
GetHost = () => this.DataHost,
|
||||
IsModelProvidedManually = () => this.DataLLMProvider is LLMProviders.SELF_HOSTED && this.DataHost is Host.OLLAMA,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -208,7 +213,16 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
|
||||
{
|
||||
await this.form.Validate();
|
||||
this.dataAPIKeyStorageIssue = string.Empty;
|
||||
|
||||
|
||||
// Manually validate the model selection (needed when no models are loaded
|
||||
// and the MudSelect is not rendered):
|
||||
var modelValidationError = this.providerValidation.ValidatingModel(this.DataModel);
|
||||
if (!string.IsNullOrWhiteSpace(modelValidationError))
|
||||
{
|
||||
this.dataIssues = [..this.dataIssues, modelValidationError];
|
||||
this.dataIsValid = false;
|
||||
}
|
||||
|
||||
// When the data is not valid, we don't store it:
|
||||
if (!this.dataIsValid)
|
||||
return;
|
||||
@@ -250,21 +264,40 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
|
||||
await this.form.Validate();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void OnHostChanged(Host selectedHost)
|
||||
{
|
||||
// When the host changes, reset the model selection state:
|
||||
this.DataHost = selectedHost;
|
||||
this.DataModel = default;
|
||||
this.dataManuallyModel = string.Empty;
|
||||
this.availableModels.Clear();
|
||||
this.dataLoadingModelsIssue = string.Empty;
|
||||
}
|
||||
|
||||
private async Task ReloadModels()
|
||||
{
|
||||
this.dataLoadingModelsIssue = string.Empty;
|
||||
var currentEmbeddingProviderSettings = this.CreateEmbeddingProviderSettings();
|
||||
var provider = currentEmbeddingProviderSettings.CreateProvider();
|
||||
if(provider is NoProvider)
|
||||
if (provider is NoProvider)
|
||||
return;
|
||||
|
||||
var models = await provider.GetEmbeddingModels(this.dataAPIKey);
|
||||
|
||||
// Order descending by ID means that the newest models probably come first:
|
||||
var orderedModels = models.OrderByDescending(n => n.Id);
|
||||
|
||||
this.availableModels.Clear();
|
||||
this.availableModels.AddRange(orderedModels);
|
||||
|
||||
try
|
||||
{
|
||||
var models = await provider.GetEmbeddingModels(this.dataAPIKey);
|
||||
|
||||
// Order descending by ID means that the newest models probably come first:
|
||||
var orderedModels = models.OrderByDescending(n => n.Id);
|
||||
|
||||
this.availableModels.Clear();
|
||||
this.availableModels.AddRange(orderedModels);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
this.Logger.LogError($"Failed to load models from provider '{this.DataLLMProvider}' (host={this.DataHost}, hostname='{this.DataHostname}'): {e.Message}");
|
||||
this.dataLoadingModelsIssue = T("We are currently unable to communicate with the provider to load models. Please try again later.");
|
||||
}
|
||||
}
|
||||
|
||||
private string APIKeyText => this.DataLLMProvider switch
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
@inherits MSGComponentBase
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
<MudTextField
|
||||
T="string"
|
||||
Text="@this.ResultText"
|
||||
Variant="Variant.Outlined"
|
||||
Lines="10"
|
||||
AutoGrow="@true"
|
||||
MaxLines="25"
|
||||
ReadOnly="true"
|
||||
Label="@this.ResultLabelText"
|
||||
AdornmentIcon="@Icons.Material.Filled.DataObject"
|
||||
Adornment="Adornment.Start"/>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="@this.Close" Variant="Variant.Filled" Color="Color.Primary">
|
||||
@T("Close")
|
||||
</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
@@ -0,0 +1,21 @@
|
||||
using AIStudio.Components;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace AIStudio.Dialogs;
|
||||
|
||||
public partial class EmbeddingResultDialog : MSGComponentBase
|
||||
{
|
||||
[CascadingParameter]
|
||||
private IMudDialogInstance MudDialog { get; set; } = null!;
|
||||
|
||||
[Parameter]
|
||||
public string ResultText { get; set; } = string.Empty;
|
||||
|
||||
[Parameter]
|
||||
public string ResultLabel { get; set; } = string.Empty;
|
||||
|
||||
private string ResultLabelText => string.IsNullOrWhiteSpace(this.ResultLabel) ? T("Embedding Vector") : this.ResultLabel;
|
||||
|
||||
private void Close() => this.MudDialog.Close();
|
||||
}
|
||||
@@ -1,7 +1,4 @@
|
||||
using System.Reflection;
|
||||
|
||||
using AIStudio.Components;
|
||||
using AIStudio.Tools.Metadata;
|
||||
using AIStudio.Components;
|
||||
using AIStudio.Tools.Services;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
@@ -11,9 +8,8 @@ namespace AIStudio.Dialogs;
|
||||
|
||||
public partial class PandocDialog : MSGComponentBase
|
||||
{
|
||||
private static readonly Assembly ASSEMBLY = Assembly.GetExecutingAssembly();
|
||||
private static readonly MetaDataArchitectureAttribute META_DATA_ARCH = ASSEMBLY.GetCustomAttribute<MetaDataArchitectureAttribute>()!;
|
||||
private static readonly RID CPU_ARCHITECTURE = META_DATA_ARCH.Architecture.ToRID();
|
||||
// Use runtime detection instead of metadata to ensure correct RID on dev machines:
|
||||
private static readonly RID CPU_ARCHITECTURE = RIDExtensions.GetCurrentRID();
|
||||
|
||||
[Parameter]
|
||||
public bool ShowInstallationPage { get; set; }
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
|
||||
@if (this.DataLLMProvider.IsHostNeeded())
|
||||
{
|
||||
<MudSelect @bind-Value="@this.DataHost" Label="@T("Host")" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.providerValidation.ValidatingHost">
|
||||
<MudSelect T="Host" Value="@this.DataHost" ValueChanged="@this.OnHostChanged" Label="@T("Host")" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.providerValidation.ValidatingHost">
|
||||
@foreach (Host host in Enum.GetValues(typeof(Host)))
|
||||
{
|
||||
@if (host.IsChatSupported())
|
||||
@@ -71,52 +71,69 @@
|
||||
@* ReSharper restore Asp.Entity *@
|
||||
}
|
||||
|
||||
<MudField FullWidth="true" Label="@T("Model selection")" Variant="Variant.Outlined" Class="mb-3">
|
||||
<MudStack Row="@true" AlignItems="AlignItems.Center" StretchItems="StretchItems.End">
|
||||
@if (this.DataLLMProvider.IsLLMModelProvidedManually())
|
||||
{
|
||||
<MudButton Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.OpenInBrowser" Href="@this.DataLLMProvider.GetModelsOverviewURL(this.HFInferenceProviderId)" Target="_blank">
|
||||
@T("Show available models")
|
||||
</MudButton>
|
||||
<MudTextField
|
||||
T="string"
|
||||
@bind-Text="@this.dataManuallyModel"
|
||||
Label="@T("Model")"
|
||||
Adornment="Adornment.Start"
|
||||
AdornmentIcon="@Icons.Material.Filled.FaceRetouchingNatural"
|
||||
AdornmentColor="Color.Info"
|
||||
Validation="@this.ValidateManuallyModel"
|
||||
UserAttributes="@SPELLCHECK_ATTRIBUTES"
|
||||
HelperText="@T("Currently, we cannot query the models for the selected provider and/or host. Therefore, please enter the model name manually.")"
|
||||
/>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudButton Disabled="@(!this.DataLLMProvider.CanLoadModels(this.DataHost, this.dataAPIKey))" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.Refresh" OnClick="@this.ReloadModels">
|
||||
@T("Load models")
|
||||
</MudButton>
|
||||
@if(this.availableModels.Count is 0)
|
||||
@if (!this.DataLLMProvider.IsLLMModelSelectionHidden(this.DataHost))
|
||||
{
|
||||
<MudField FullWidth="true" Label="@T("Model selection")" Variant="Variant.Outlined" Class="mb-3">
|
||||
<MudStack Row="@true" AlignItems="AlignItems.Center" StretchItems="StretchItems.End">
|
||||
@if (this.DataLLMProvider.IsLLMModelProvidedManually())
|
||||
{
|
||||
<MudText Typo="Typo.body1">
|
||||
@T("No models loaded or available.")
|
||||
</MudText>
|
||||
<MudButton Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.OpenInBrowser" Href="@this.DataLLMProvider.GetModelsOverviewURL(this.HFInferenceProviderId)" Target="_blank">
|
||||
@T("Show available models")
|
||||
</MudButton>
|
||||
<MudTextField
|
||||
T="string"
|
||||
@bind-Text="@this.dataManuallyModel"
|
||||
Label="@T("Model")"
|
||||
Adornment="Adornment.Start"
|
||||
AdornmentIcon="@Icons.Material.Filled.FaceRetouchingNatural"
|
||||
AdornmentColor="Color.Info"
|
||||
Validation="@this.ValidateManuallyModel"
|
||||
UserAttributes="@SPELLCHECK_ATTRIBUTES"
|
||||
HelperText="@T("Currently, we cannot query the models for the selected provider and/or host. Therefore, please enter the model name manually.")"
|
||||
/>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudSelect @bind-Value="@this.DataModel"
|
||||
OpenIcon="@Icons.Material.Filled.FaceRetouchingNatural" AdornmentColor="Color.Info"
|
||||
Adornment="Adornment.Start" Validation="@this.providerValidation.ValidatingModel">
|
||||
@foreach (var model in this.availableModels)
|
||||
{
|
||||
<MudSelectItem Value="@model">
|
||||
@model
|
||||
</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
<MudButton Disabled="@(!this.DataLLMProvider.CanLoadModels(this.DataHost, this.dataAPIKey))" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.Refresh" OnClick="@this.ReloadModels">
|
||||
@T("Load models")
|
||||
</MudButton>
|
||||
@if(this.availableModels.Count is 0)
|
||||
{
|
||||
<MudText Typo="Typo.body1">
|
||||
@T("No models loaded or available.")
|
||||
</MudText>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudSelect @bind-Value="@this.DataModel"
|
||||
OpenIcon="@Icons.Material.Filled.FaceRetouchingNatural" AdornmentColor="Color.Info"
|
||||
Adornment="Adornment.Start" Validation="@this.providerValidation.ValidatingModel">
|
||||
@foreach (var model in this.availableModels)
|
||||
{
|
||||
<MudSelectItem Value="@model">
|
||||
@model
|
||||
</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
}
|
||||
}
|
||||
</MudStack>
|
||||
@if (!string.IsNullOrWhiteSpace(this.dataLoadingModelsIssue))
|
||||
{
|
||||
<MudAlert Severity="Severity.Error" Class="mt-3">
|
||||
@this.dataLoadingModelsIssue
|
||||
</MudAlert>
|
||||
}
|
||||
</MudStack>
|
||||
</MudField>
|
||||
</MudField>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudField FullWidth="true" Label="@T("Model selection")" Variant="Variant.Outlined" Class="mb-3">
|
||||
<MudText Typo="Typo.body1">
|
||||
@T("This host uses the model configured at the provider level. No model selection is available.")
|
||||
</MudText>
|
||||
</MudField>
|
||||
}
|
||||
|
||||
@* ReSharper disable once CSharpWarnings::CS8974 *@
|
||||
<MudTextField
|
||||
|
||||
@@ -84,6 +84,9 @@ public partial class ProviderDialog : MSGComponentBase, ISecretId
|
||||
[Inject]
|
||||
private RustService RustService { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private ILogger<ProviderDialog> Logger { get; init; } = null!;
|
||||
|
||||
private static readonly Dictionary<string, object?> SPELLCHECK_ATTRIBUTES = new();
|
||||
|
||||
/// <summary>
|
||||
@@ -97,6 +100,7 @@ public partial class ProviderDialog : MSGComponentBase, ISecretId
|
||||
private string dataManuallyModel = string.Empty;
|
||||
private string dataAPIKeyStorageIssue = string.Empty;
|
||||
private string dataEditingPreviousInstanceName = string.Empty;
|
||||
private string dataLoadingModelsIssue = string.Empty;
|
||||
private bool showExpertSettings;
|
||||
|
||||
// We get the form reference from Blazor code to validate it manually:
|
||||
@@ -115,25 +119,36 @@ public partial class ProviderDialog : MSGComponentBase, ISecretId
|
||||
GetPreviousInstanceName = () => this.dataEditingPreviousInstanceName,
|
||||
GetUsedInstanceNames = () => this.UsedInstanceNames,
|
||||
GetHost = () => this.DataHost,
|
||||
IsModelProvidedManually = () => this.DataLLMProvider.IsLLMModelProvidedManually(),
|
||||
};
|
||||
}
|
||||
|
||||
private AIStudio.Settings.Provider CreateProviderSettings()
|
||||
{
|
||||
var cleanedHostname = this.DataHostname.Trim();
|
||||
|
||||
// Determine the model based on the provider and host configuration:
|
||||
Model model;
|
||||
if (this.DataLLMProvider.IsLLMModelSelectionHidden(this.DataHost))
|
||||
{
|
||||
// Use system model placeholder for hosts that don't support model selection (e.g., llama.cpp):
|
||||
model = Model.SYSTEM_MODEL;
|
||||
}
|
||||
else if (this.DataLLMProvider is LLMProviders.FIREWORKS or LLMProviders.HUGGINGFACE)
|
||||
{
|
||||
// These providers require manual model entry:
|
||||
model = new Model(this.dataManuallyModel, null);
|
||||
}
|
||||
else
|
||||
model = this.DataModel;
|
||||
|
||||
return new()
|
||||
{
|
||||
Num = this.DataNum,
|
||||
Id = this.DataId,
|
||||
InstanceName = this.DataInstanceName,
|
||||
UsedLLMProvider = this.DataLLMProvider,
|
||||
|
||||
Model = this.DataLLMProvider switch
|
||||
{
|
||||
LLMProviders.FIREWORKS or LLMProviders.HUGGINGFACE => new Model(this.dataManuallyModel, null),
|
||||
_ => this.DataModel
|
||||
},
|
||||
|
||||
Model = model,
|
||||
IsSelfHosted = this.DataLLMProvider is LLMProviders.SELF_HOSTED,
|
||||
IsEnterpriseConfiguration = false,
|
||||
Hostname = cleanedHostname.EndsWith('/') ? cleanedHostname[..^1] : cleanedHostname,
|
||||
@@ -222,7 +237,16 @@ public partial class ProviderDialog : MSGComponentBase, ISecretId
|
||||
await this.form.Validate();
|
||||
if (!string.IsNullOrWhiteSpace(this.dataAPIKeyStorageIssue))
|
||||
this.dataAPIKeyStorageIssue = string.Empty;
|
||||
|
||||
|
||||
// Manually validate the model selection (needed when no models are loaded
|
||||
// and the MudSelect is not rendered):
|
||||
var modelValidationError = this.providerValidation.ValidatingModel(this.DataModel);
|
||||
if (!string.IsNullOrWhiteSpace(modelValidationError))
|
||||
{
|
||||
this.dataIssues = [..this.dataIssues, modelValidationError];
|
||||
this.dataIsValid = false;
|
||||
}
|
||||
|
||||
// When the data is not valid, we don't store it:
|
||||
if (!this.dataIsValid)
|
||||
return;
|
||||
@@ -264,21 +288,40 @@ public partial class ProviderDialog : MSGComponentBase, ISecretId
|
||||
await this.form.Validate();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnHostChanged(Host selectedHost)
|
||||
{
|
||||
// When the host changes, reset the model selection state:
|
||||
this.DataHost = selectedHost;
|
||||
this.DataModel = default;
|
||||
this.dataManuallyModel = string.Empty;
|
||||
this.availableModels.Clear();
|
||||
this.dataLoadingModelsIssue = string.Empty;
|
||||
}
|
||||
|
||||
private async Task ReloadModels()
|
||||
{
|
||||
this.dataLoadingModelsIssue = string.Empty;
|
||||
var currentProviderSettings = this.CreateProviderSettings();
|
||||
var provider = currentProviderSettings.CreateProvider();
|
||||
if(provider is NoProvider)
|
||||
if (provider is NoProvider)
|
||||
return;
|
||||
|
||||
var models = await provider.GetTextModels(this.dataAPIKey);
|
||||
|
||||
// Order descending by ID means that the newest models probably come first:
|
||||
var orderedModels = models.OrderByDescending(n => n.Id);
|
||||
|
||||
this.availableModels.Clear();
|
||||
this.availableModels.AddRange(orderedModels);
|
||||
|
||||
try
|
||||
{
|
||||
var models = await provider.GetTextModels(this.dataAPIKey);
|
||||
|
||||
// Order descending by ID means that the newest models probably come first:
|
||||
var orderedModels = models.OrderByDescending(n => n.Id);
|
||||
|
||||
this.availableModels.Clear();
|
||||
this.availableModels.AddRange(orderedModels);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
this.Logger.LogError($"Failed to load models from provider '{this.DataLLMProvider}' (host={this.DataHost}, hostname='{this.DataHostname}'): {e.Message}");
|
||||
this.dataLoadingModelsIssue = T("We are currently unable to communicate with the provider to load models. Please try again later.");
|
||||
}
|
||||
}
|
||||
|
||||
private string APIKeyText => this.DataLLMProvider switch
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
@namespace AIStudio.Dialogs.Settings
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
@using AIStudio.Settings
|
||||
@inherits SettingsDialogBase
|
||||
|
||||
<MudDialog>
|
||||
<TitleContent>
|
||||
<PreviewPrototype/>
|
||||
<MudText Typo="Typo.h6" Class="d-flex align-center">
|
||||
<MudIcon Icon="@Icons.Material.Filled.DocumentScanner" Class="mr-2"/>
|
||||
@T("Assistant: Document Analysis")
|
||||
</MudText>
|
||||
</TitleContent>
|
||||
<DialogContent>
|
||||
<MudPaper Class="pa-3 mb-8 border-dashed border rounded-lg">
|
||||
<ConfigurationOption OptionDescription="@T("Preselect document analysis options?")" LabelOn="@T("Document analysis options are preselected")" LabelOff="@T("No document analysis options are preselected")" State="@(() => this.SettingsManager.ConfigurationData.ERI.PreselectOptions)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.ERI.PreselectOptions = updatedState)" OptionHelp="@T("When enabled, you can preselect some document analysis options.")"/>
|
||||
<ConfigurationMinConfidenceSelection Disabled="@(() => !this.SettingsManager.ConfigurationData.ERI.PreselectOptions)" RestrictToGlobalMinimumConfidence="@true" SelectedValue="@(() => this.SettingsManager.ConfigurationData.ERI.MinimumProviderConfidence)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.ERI.MinimumProviderConfidence = selectedValue)"/>
|
||||
<ConfigurationSelect OptionDescription="@T("Preselect one of your profiles?")" Disabled="@(() => !this.SettingsManager.ConfigurationData.ERI.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.ERI.PreselectedProfile)" Data="@ConfigurationSelectDataFactory.GetProfilesData(this.SettingsManager.ConfigurationData.Profiles)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.ERI.PreselectedProfile = selectedValue)" OptionHelp="@T("Would you like to preselect one of your profiles?")"/>
|
||||
|
||||
<MudText Typo="Typo.body1" Class="mb-3">
|
||||
@T("Most document analysis options can be customized and saved directly in the assistant. For this, the assistant has an auto-save function.")
|
||||
</MudText>
|
||||
</MudPaper>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="@this.Close" Variant="Variant.Filled">
|
||||
@T("Close")
|
||||
</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
@@ -1,3 +0,0 @@
|
||||
namespace AIStudio.Dialogs.Settings;
|
||||
|
||||
public partial class SettingsDialogDocumentAnalysis : SettingsDialogBase;
|
||||
@@ -0,0 +1,50 @@
|
||||
@inherits MSGComponentBase
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
<MudJustifiedText Typo="Typo.body1" Class="mb-3">
|
||||
@T("Press the desired key combination to set the shortcut. The shortcut will be registered globally and will work even when the app is not focused.")
|
||||
</MudJustifiedText>
|
||||
|
||||
<MudFocusTrap DefaultFocus="DefaultFocus.FirstChild">
|
||||
<MudTextField
|
||||
@ref="@this.inputField"
|
||||
T="string"
|
||||
Text="@this.ShowText"
|
||||
Variant="Variant.Outlined"
|
||||
Label="@T("Define a shortcut")"
|
||||
Placeholder="@T("Press a key combination...")"
|
||||
Adornment="Adornment.Start"
|
||||
AdornmentIcon="@Icons.Material.Filled.Keyboard"
|
||||
Immediate="@true"
|
||||
TextUpdateSuppression="false"
|
||||
OnKeyDown="@this.HandleKeyDown"
|
||||
OnBlur="@this.HandleBlur"
|
||||
UserAttributes="@USER_INPUT_ATTRIBUTES"
|
||||
AutoFocus="true"
|
||||
KeyDownPreventDefault="true"
|
||||
KeyUpPreventDefault="true"
|
||||
HelperText="@T("Supported modifiers: Ctrl/Cmd, Shift, Alt.")"
|
||||
Class="me-3"/>
|
||||
</MudFocusTrap>
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(this.validationMessage))
|
||||
{
|
||||
<MudAlert Severity="@this.validationSeverity" Variant="Variant.Filled" Class="mb-3">
|
||||
@this.validationMessage
|
||||
</MudAlert>
|
||||
}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="@this.ClearShortcut" Variant="Variant.Filled" Color="Color.Info" StartIcon="@Icons.Material.Filled.Clear">
|
||||
@T("Clear Shortcut")
|
||||
</MudButton>
|
||||
<MudSpacer/>
|
||||
<MudButton OnClick="@this.Cancel" Variant="Variant.Filled">
|
||||
@T("Cancel")
|
||||
</MudButton>
|
||||
<MudButton OnClick="@this.Confirm" Variant="Variant.Filled" Color="Color.Primary" Disabled="@this.hasValidationError">
|
||||
@T("Save")
|
||||
</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
@@ -0,0 +1,385 @@
|
||||
using AIStudio.Components;
|
||||
using AIStudio.Tools.Rust;
|
||||
using AIStudio.Tools.Services;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
|
||||
namespace AIStudio.Dialogs;
|
||||
|
||||
/// <summary>
|
||||
/// A dialog for capturing and configuring keyboard shortcuts.
|
||||
/// </summary>
|
||||
public partial class ShortcutDialog : MSGComponentBase
|
||||
{
|
||||
[CascadingParameter]
|
||||
private IMudDialogInstance MudDialog { get; set; } = null!;
|
||||
|
||||
[Inject]
|
||||
private RustService RustService { get; init; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// The initial shortcut value (in internal format, e.g., "CmdOrControl+1").
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string InitialShortcut { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The identifier of the shortcut for conflict detection.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Shortcut ShortcutId { get; set; }
|
||||
|
||||
private static readonly Dictionary<string, object?> USER_INPUT_ATTRIBUTES = new();
|
||||
|
||||
private string currentShortcut = string.Empty;
|
||||
private string originalShortcut = string.Empty;
|
||||
private string validationMessage = string.Empty;
|
||||
private Severity validationSeverity = Severity.Info;
|
||||
private bool hasValidationError;
|
||||
|
||||
//
|
||||
// Current key state:
|
||||
//
|
||||
private bool hasCtrl;
|
||||
private bool hasShift;
|
||||
private bool hasAlt;
|
||||
private bool hasMeta;
|
||||
private string? currentKey;
|
||||
private MudTextField<string>? inputField;
|
||||
|
||||
#region Overrides of ComponentBase
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await base.OnInitializedAsync();
|
||||
|
||||
// Configure the spellchecking for the user input:
|
||||
this.SettingsManager.InjectSpellchecking(USER_INPUT_ATTRIBUTES);
|
||||
|
||||
this.currentShortcut = this.InitialShortcut;
|
||||
this.originalShortcut = this.InitialShortcut;
|
||||
this.ParseExistingShortcut();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private string ShowText => string.IsNullOrWhiteSpace(this.currentShortcut)
|
||||
? T("Press a key combination...")
|
||||
: this.GetDisplayShortcut();
|
||||
|
||||
private void ParseExistingShortcut()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(this.currentShortcut))
|
||||
return;
|
||||
|
||||
// Parse the existing shortcut to set the state
|
||||
var parts = this.currentShortcut.Split('+');
|
||||
foreach (var part in parts)
|
||||
{
|
||||
switch (part.ToLowerInvariant())
|
||||
{
|
||||
case "cmdorcontrol":
|
||||
case "commandorcontrol":
|
||||
case "ctrl":
|
||||
case "control":
|
||||
case "cmd":
|
||||
case "command":
|
||||
this.hasCtrl = true;
|
||||
break;
|
||||
|
||||
case "shift":
|
||||
this.hasShift = true;
|
||||
break;
|
||||
|
||||
case "alt":
|
||||
this.hasAlt = true;
|
||||
break;
|
||||
|
||||
case "meta":
|
||||
case "super":
|
||||
this.hasMeta = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
this.currentKey = part;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleKeyDown(KeyboardEventArgs e)
|
||||
{
|
||||
// Ignore pure modifier key presses:
|
||||
if (IsModifierKey(e.Code))
|
||||
{
|
||||
this.UpdateModifiers(e);
|
||||
this.currentKey = null;
|
||||
this.UpdateShortcutString();
|
||||
return;
|
||||
}
|
||||
|
||||
this.UpdateModifiers(e);
|
||||
|
||||
// Get the key:
|
||||
this.currentKey = TranslateKeyCode(e.Code);
|
||||
|
||||
// Validate: must have at least one modifier + a key
|
||||
if (!this.hasCtrl && !this.hasShift && !this.hasAlt && !this.hasMeta)
|
||||
{
|
||||
this.validationMessage = T("Please include at least one modifier key (Ctrl, Shift, Alt, or Cmd).");
|
||||
this.validationSeverity = Severity.Warning;
|
||||
this.hasValidationError = true;
|
||||
this.StateHasChanged();
|
||||
return;
|
||||
}
|
||||
|
||||
this.UpdateShortcutString();
|
||||
await this.ValidateShortcut();
|
||||
|
||||
this.StateHasChanged();
|
||||
}
|
||||
|
||||
private void UpdateModifiers(KeyboardEventArgs e)
|
||||
{
|
||||
this.hasCtrl = e.CtrlKey || e.MetaKey; // Treat Meta (Cmd on Mac) same as Ctrl for cross-platform
|
||||
this.hasShift = e.ShiftKey;
|
||||
this.hasAlt = e.AltKey;
|
||||
this.hasMeta = e is { MetaKey: true, CtrlKey: false }; // Only set meta if not already using ctrl
|
||||
}
|
||||
|
||||
private void UpdateShortcutString()
|
||||
{
|
||||
var parts = new List<string>();
|
||||
|
||||
if (this.hasCtrl)
|
||||
parts.Add("CmdOrControl");
|
||||
|
||||
if (this.hasShift)
|
||||
parts.Add("Shift");
|
||||
|
||||
if (this.hasAlt)
|
||||
parts.Add("Alt");
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(this.currentKey))
|
||||
parts.Add(this.currentKey);
|
||||
|
||||
this.currentShortcut = parts.Count > 0 ? string.Join("+", parts) : string.Empty;
|
||||
this.StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task ValidateShortcut()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(this.currentShortcut) || string.IsNullOrWhiteSpace(this.currentKey))
|
||||
{
|
||||
this.validationMessage = string.Empty;
|
||||
this.hasValidationError = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the shortcut is valid by trying to register it with Rust
|
||||
var result = await this.RustService.ValidateShortcut(this.currentShortcut);
|
||||
if (result.IsValid)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(this.originalShortcut)
|
||||
&& this.currentShortcut.Equals(this.originalShortcut, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
this.validationMessage = T("This is the shortcut you previously used.");
|
||||
this.validationSeverity = Severity.Info;
|
||||
this.hasValidationError = false;
|
||||
this.StateHasChanged();
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.HasConflict)
|
||||
{
|
||||
this.validationMessage = string.Format(T("This shortcut conflicts with: {0}"), result.ConflictDescription);
|
||||
this.validationSeverity = Severity.Warning;
|
||||
this.hasValidationError = false; // Allow saving, but warn
|
||||
}
|
||||
else
|
||||
{
|
||||
this.validationMessage = T("Shortcut is valid and available.");
|
||||
this.validationSeverity = Severity.Success;
|
||||
this.hasValidationError = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this.validationMessage = string.Format(T("Invalid shortcut: {0}"), result.ErrorMessage);
|
||||
this.validationSeverity = Severity.Error;
|
||||
this.hasValidationError = true;
|
||||
}
|
||||
|
||||
this.StateHasChanged();
|
||||
}
|
||||
|
||||
private string GetDisplayShortcut()
|
||||
{
|
||||
// Convert internal format to display format:
|
||||
return this.currentShortcut
|
||||
.Replace("CmdOrControl", OperatingSystem.IsMacOS() ? "Cmd" : "Ctrl")
|
||||
.Replace("CommandOrControl", OperatingSystem.IsMacOS() ? "Cmd" : "Ctrl");
|
||||
}
|
||||
|
||||
private void ClearShortcut()
|
||||
{
|
||||
this.currentShortcut = string.Empty;
|
||||
this.currentKey = null;
|
||||
this.hasCtrl = false;
|
||||
this.hasShift = false;
|
||||
this.hasAlt = false;
|
||||
this.hasMeta = false;
|
||||
this.validationMessage = string.Empty;
|
||||
this.hasValidationError = false;
|
||||
this.StateHasChanged();
|
||||
}
|
||||
|
||||
private void Cancel() => this.MudDialog.Cancel();
|
||||
|
||||
private void Confirm() => this.MudDialog.Close(DialogResult.Ok(this.currentShortcut));
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the key code represents a modifier key.
|
||||
/// </summary>
|
||||
private static bool IsModifierKey(string code) => code switch
|
||||
{
|
||||
"ShiftLeft" or "ShiftRight" => true,
|
||||
"ControlLeft" or "ControlRight" => true,
|
||||
"AltLeft" or "AltRight" => true,
|
||||
"MetaLeft" or "MetaRight" => true,
|
||||
|
||||
_ => false,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Translates a JavaScript KeyboardEvent.code to Tauri shortcut format.
|
||||
/// </summary>
|
||||
private static string TranslateKeyCode(string code) => code switch
|
||||
{
|
||||
// Letters
|
||||
"KeyA" => "A",
|
||||
"KeyB" => "B",
|
||||
"KeyC" => "C",
|
||||
"KeyD" => "D",
|
||||
"KeyE" => "E",
|
||||
"KeyF" => "F",
|
||||
"KeyG" => "G",
|
||||
"KeyH" => "H",
|
||||
"KeyI" => "I",
|
||||
"KeyJ" => "J",
|
||||
"KeyK" => "K",
|
||||
"KeyL" => "L",
|
||||
"KeyM" => "M",
|
||||
"KeyN" => "N",
|
||||
"KeyO" => "O",
|
||||
"KeyP" => "P",
|
||||
"KeyQ" => "Q",
|
||||
"KeyR" => "R",
|
||||
"KeyS" => "S",
|
||||
"KeyT" => "T",
|
||||
"KeyU" => "U",
|
||||
"KeyV" => "V",
|
||||
"KeyW" => "W",
|
||||
"KeyX" => "X",
|
||||
"KeyY" => "Y",
|
||||
"KeyZ" => "Z",
|
||||
|
||||
// Numbers
|
||||
"Digit0" => "0",
|
||||
"Digit1" => "1",
|
||||
"Digit2" => "2",
|
||||
"Digit3" => "3",
|
||||
"Digit4" => "4",
|
||||
"Digit5" => "5",
|
||||
"Digit6" => "6",
|
||||
"Digit7" => "7",
|
||||
"Digit8" => "8",
|
||||
"Digit9" => "9",
|
||||
|
||||
// Function keys
|
||||
"F1" => "F1",
|
||||
"F2" => "F2",
|
||||
"F3" => "F3",
|
||||
"F4" => "F4",
|
||||
"F5" => "F5",
|
||||
"F6" => "F6",
|
||||
"F7" => "F7",
|
||||
"F8" => "F8",
|
||||
"F9" => "F9",
|
||||
"F10" => "F10",
|
||||
"F11" => "F11",
|
||||
"F12" => "F12",
|
||||
"F13" => "F13",
|
||||
"F14" => "F14",
|
||||
"F15" => "F15",
|
||||
"F16" => "F16",
|
||||
"F17" => "F17",
|
||||
"F18" => "F18",
|
||||
"F19" => "F19",
|
||||
"F20" => "F20",
|
||||
"F21" => "F21",
|
||||
"F22" => "F22",
|
||||
"F23" => "F23",
|
||||
"F24" => "F24",
|
||||
|
||||
// Special keys
|
||||
"Space" => "Space",
|
||||
"Enter" => "Enter",
|
||||
"Tab" => "Tab",
|
||||
"Escape" => "Escape",
|
||||
"Backspace" => "Backspace",
|
||||
"Delete" => "Delete",
|
||||
"Insert" => "Insert",
|
||||
"Home" => "Home",
|
||||
"End" => "End",
|
||||
"PageUp" => "PageUp",
|
||||
"PageDown" => "PageDown",
|
||||
|
||||
// Arrow keys
|
||||
"ArrowUp" => "Up",
|
||||
"ArrowDown" => "Down",
|
||||
"ArrowLeft" => "Left",
|
||||
"ArrowRight" => "Right",
|
||||
|
||||
// Numpad
|
||||
"Numpad0" => "Num0",
|
||||
"Numpad1" => "Num1",
|
||||
"Numpad2" => "Num2",
|
||||
"Numpad3" => "Num3",
|
||||
"Numpad4" => "Num4",
|
||||
"Numpad5" => "Num5",
|
||||
"Numpad6" => "Num6",
|
||||
"Numpad7" => "Num7",
|
||||
"Numpad8" => "Num8",
|
||||
"Numpad9" => "Num9",
|
||||
"NumpadAdd" => "NumAdd",
|
||||
"NumpadSubtract" => "NumSubtract",
|
||||
"NumpadMultiply" => "NumMultiply",
|
||||
"NumpadDivide" => "NumDivide",
|
||||
"NumpadDecimal" => "NumDecimal",
|
||||
"NumpadEnter" => "NumEnter",
|
||||
|
||||
// Punctuation
|
||||
"Minus" => "Minus",
|
||||
"Equal" => "Equal",
|
||||
"BracketLeft" => "BracketLeft",
|
||||
"BracketRight" => "BracketRight",
|
||||
"Backslash" => "Backslash",
|
||||
"Semicolon" => "Semicolon",
|
||||
"Quote" => "Quote",
|
||||
"Backquote" => "Backquote",
|
||||
"Comma" => "Comma",
|
||||
"Period" => "Period",
|
||||
"Slash" => "Slash",
|
||||
|
||||
// Default: return as-is
|
||||
_ => code,
|
||||
};
|
||||
|
||||
private void HandleBlur()
|
||||
{
|
||||
// Re-focus the input field to keep capturing keys:
|
||||
this.inputField?.FocusAsync();
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,21 @@
|
||||
@inherits MSGComponentBase
|
||||
<MudDialog>
|
||||
<MudDialog DefaultFocus="DefaultFocus.FirstChild">
|
||||
<DialogContent>
|
||||
<MudText Typo="Typo.body1">
|
||||
@this.Message
|
||||
</MudText>
|
||||
<MudForm @ref="this.form" Class="mt-4">
|
||||
<MudTextField T="string" @bind-Text="@this.UserInput" Variant="Variant.Outlined" AutoGrow="@false" Lines="1" Label="@this.GetInputHeaderText" AutoFocus="@true" UserAttributes="@USER_INPUT_ATTRIBUTES" Validation="@this.ValidateUserInput" />
|
||||
<MudTextField T="string"
|
||||
@bind-Text="@this.UserInput"
|
||||
Variant="Variant.Outlined"
|
||||
AutoGrow="@false"
|
||||
Lines="1"
|
||||
Label="@this.GetInputHeaderText"
|
||||
AutoFocus="@true"
|
||||
Immediate="@true"
|
||||
OnKeyDown="@this.HandleUserInputKeyDown"
|
||||
UserAttributes="@USER_INPUT_ATTRIBUTES"
|
||||
Validation="@this.ValidateUserInput" />
|
||||
</MudForm>
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
@@ -16,4 +26,4 @@
|
||||
@this.ConfirmText
|
||||
</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
</MudDialog>
|
||||
@@ -1,6 +1,7 @@
|
||||
using AIStudio.Components;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
|
||||
namespace AIStudio.Dialogs;
|
||||
|
||||
@@ -57,6 +58,19 @@ public partial class SingleInputDialog : MSGComponentBase
|
||||
|
||||
private void Cancel() => this.MudDialog.Cancel();
|
||||
|
||||
private async Task HandleUserInputKeyDown(KeyboardEventArgs keyEvent)
|
||||
{
|
||||
var key = keyEvent.Key.ToLowerInvariant();
|
||||
var code = keyEvent.Code.ToLowerInvariant();
|
||||
if (key is not "enter" && code is not "enter" and not "numpadenter")
|
||||
return;
|
||||
|
||||
if (keyEvent is { AltKey: true } or { CtrlKey: true } or { MetaKey: true })
|
||||
return;
|
||||
|
||||
await this.Confirm();
|
||||
}
|
||||
|
||||
private async Task Confirm()
|
||||
{
|
||||
await this.form.Validate();
|
||||
@@ -65,4 +79,4 @@ public partial class SingleInputDialog : MSGComponentBase
|
||||
|
||||
this.MudDialog.Close(DialogResult.Ok(this.UserInput));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,7 @@
|
||||
|
||||
@if (this.DataLLMProvider.IsHostNeeded())
|
||||
{
|
||||
<MudSelect @bind-Value="@this.DataHost" Label="@T("Host")" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.providerValidation.ValidatingHost">
|
||||
<MudSelect T="Host" Value="@this.DataHost" ValueChanged="@this.OnHostChanged" Label="@T("Host")" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.providerValidation.ValidatingHost">
|
||||
@foreach (Host host in Enum.GetValues(typeof(Host)))
|
||||
{
|
||||
if (host.IsTranscriptionSupported())
|
||||
@@ -57,51 +57,68 @@
|
||||
</MudSelect>
|
||||
}
|
||||
|
||||
<MudField FullWidth="true" Label="@T("Model selection")" Variant="Variant.Outlined" Class="mb-3">
|
||||
<MudStack Row="@true" AlignItems="AlignItems.Center" StretchItems="StretchItems.End">
|
||||
@if (this.DataLLMProvider.IsTranscriptionModelProvidedManually(this.DataHost))
|
||||
{
|
||||
<MudTextField
|
||||
T="string"
|
||||
@bind-Text="@this.dataManuallyModel"
|
||||
Label="@T("Model")"
|
||||
Class="mb-3"
|
||||
Adornment="Adornment.Start"
|
||||
AdornmentIcon="@Icons.Material.Filled.Dns"
|
||||
AdornmentColor="Color.Info"
|
||||
Validation="@this.ValidateManuallyModel"
|
||||
UserAttributes="@SPELLCHECK_ATTRIBUTES"
|
||||
HelperText="@T("Currently, we cannot query the transcription models for the selected provider and/or host. Therefore, please enter the model name manually.")"
|
||||
/>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudButton Disabled="@(!this.DataLLMProvider.CanLoadModels(this.DataHost, this.dataAPIKey))" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.Refresh" OnClick="@this.ReloadModels">
|
||||
@T("Load")
|
||||
</MudButton>
|
||||
@if(this.availableModels.Count is 0)
|
||||
@if (!this.DataLLMProvider.IsTranscriptionModelSelectionHidden(this.DataHost))
|
||||
{
|
||||
<MudField FullWidth="true" Label="@T("Model selection")" Variant="Variant.Outlined" Class="mb-3">
|
||||
<MudStack Row="@true" AlignItems="AlignItems.Center" StretchItems="StretchItems.End">
|
||||
@if (this.DataLLMProvider.IsTranscriptionModelProvidedManually(this.DataHost))
|
||||
{
|
||||
<MudText Typo="Typo.body1">
|
||||
@T("No models loaded or available.")
|
||||
</MudText>
|
||||
<MudTextField
|
||||
T="string"
|
||||
@bind-Text="@this.dataManuallyModel"
|
||||
Label="@T("Model")"
|
||||
Class="mb-3"
|
||||
Adornment="Adornment.Start"
|
||||
AdornmentIcon="@Icons.Material.Filled.Dns"
|
||||
AdornmentColor="Color.Info"
|
||||
Validation="@this.ValidateManuallyModel"
|
||||
UserAttributes="@SPELLCHECK_ATTRIBUTES"
|
||||
HelperText="@T("Currently, we cannot query the transcription models for the selected provider and/or host. Therefore, please enter the model name manually.")"
|
||||
/>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudSelect Disabled="@this.IsNoneProvider" @bind-Value="@this.DataModel" Label="@T("Model")"
|
||||
OpenIcon="@Icons.Material.Filled.FaceRetouchingNatural"
|
||||
AdornmentColor="Color.Info" Adornment="Adornment.Start"
|
||||
Validation="@this.providerValidation.ValidatingModel">
|
||||
@foreach (var model in this.availableModels)
|
||||
{
|
||||
<MudSelectItem Value="@model">
|
||||
@model
|
||||
</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
<MudButton Disabled="@(!this.DataLLMProvider.CanLoadModels(this.DataHost, this.dataAPIKey))" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.Refresh" OnClick="@this.ReloadModels">
|
||||
@T("Load")
|
||||
</MudButton>
|
||||
@if(this.availableModels.Count is 0)
|
||||
{
|
||||
<MudText Typo="Typo.body1">
|
||||
@T("No models loaded or available.")
|
||||
</MudText>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudSelect Disabled="@this.IsNoneProvider" @bind-Value="@this.DataModel" Label="@T("Model")"
|
||||
OpenIcon="@Icons.Material.Filled.FaceRetouchingNatural"
|
||||
AdornmentColor="Color.Info" Adornment="Adornment.Start"
|
||||
Validation="@this.providerValidation.ValidatingModel">
|
||||
@foreach (var model in this.availableModels)
|
||||
{
|
||||
<MudSelectItem Value="@model">
|
||||
@model
|
||||
</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
}
|
||||
}
|
||||
</MudStack>
|
||||
@if (!string.IsNullOrWhiteSpace(this.dataLoadingModelsIssue))
|
||||
{
|
||||
<MudAlert Severity="Severity.Error" Class="mt-3">
|
||||
@this.dataLoadingModelsIssue
|
||||
</MudAlert>
|
||||
}
|
||||
</MudStack>
|
||||
</MudField>
|
||||
</MudField>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudField FullWidth="true" Label="@T("Model selection")" Variant="Variant.Outlined" Class="mb-3">
|
||||
<MudText Typo="Typo.body1">
|
||||
@T("This host uses the model configured at the provider level. No model selection is available.")
|
||||
</MudText>
|
||||
</MudField>
|
||||
}
|
||||
|
||||
@* ReSharper disable once CSharpWarnings::CS8974 *@
|
||||
<MudTextField
|
||||
|
||||
@@ -71,7 +71,10 @@ public partial class TranscriptionProviderDialog : MSGComponentBase, ISecretId
|
||||
|
||||
[Inject]
|
||||
private RustService RustService { get; init; } = null!;
|
||||
|
||||
|
||||
[Inject]
|
||||
private ILogger<TranscriptionProviderDialog> Logger { get; init; } = null!;
|
||||
|
||||
private static readonly Dictionary<string, object?> SPELLCHECK_ATTRIBUTES = new();
|
||||
|
||||
/// <summary>
|
||||
@@ -85,7 +88,8 @@ public partial class TranscriptionProviderDialog : MSGComponentBase, ISecretId
|
||||
private string dataManuallyModel = string.Empty;
|
||||
private string dataAPIKeyStorageIssue = string.Empty;
|
||||
private string dataEditingPreviousInstanceName = string.Empty;
|
||||
|
||||
private string dataLoadingModelsIssue = string.Empty;
|
||||
|
||||
// We get the form reference from Blazor code to validate it manually:
|
||||
private MudForm form = null!;
|
||||
|
||||
@@ -102,31 +106,39 @@ public partial class TranscriptionProviderDialog : MSGComponentBase, ISecretId
|
||||
GetPreviousInstanceName = () => this.dataEditingPreviousInstanceName,
|
||||
GetUsedInstanceNames = () => this.UsedInstanceNames,
|
||||
GetHost = () => this.DataHost,
|
||||
IsModelProvidedManually = () => this.DataLLMProvider.IsTranscriptionModelProvidedManually(this.DataHost),
|
||||
};
|
||||
}
|
||||
|
||||
private TranscriptionProvider CreateTranscriptionProviderSettings()
|
||||
{
|
||||
var cleanedHostname = this.DataHostname.Trim();
|
||||
Model model = default;
|
||||
if(this.DataLLMProvider is LLMProviders.SELF_HOSTED)
|
||||
|
||||
// Determine the model based on the provider and host configuration:
|
||||
Model model;
|
||||
if (this.DataLLMProvider.IsTranscriptionModelSelectionHidden(this.DataHost))
|
||||
{
|
||||
// Use system model placeholder for hosts that don't support model selection (e.g., whisper.cpp):
|
||||
model = Model.SYSTEM_MODEL;
|
||||
}
|
||||
else if (this.DataLLMProvider is LLMProviders.SELF_HOSTED)
|
||||
{
|
||||
switch (this.DataHost)
|
||||
{
|
||||
case Host.OLLAMA:
|
||||
model = new Model(this.dataManuallyModel, null);
|
||||
break;
|
||||
|
||||
|
||||
case Host.VLLM:
|
||||
case Host.LM_STUDIO:
|
||||
case Host.WHISPER_CPP:
|
||||
default:
|
||||
model = this.DataModel;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
model = this.DataModel;
|
||||
|
||||
|
||||
return new()
|
||||
{
|
||||
Num = this.DataNum,
|
||||
@@ -216,7 +228,16 @@ public partial class TranscriptionProviderDialog : MSGComponentBase, ISecretId
|
||||
{
|
||||
await this.form.Validate();
|
||||
this.dataAPIKeyStorageIssue = string.Empty;
|
||||
|
||||
|
||||
// Manually validate the model selection (needed when no models are loaded
|
||||
// and the MudSelect is not rendered):
|
||||
var modelValidationError = this.providerValidation.ValidatingModel(this.DataModel);
|
||||
if (!string.IsNullOrWhiteSpace(modelValidationError))
|
||||
{
|
||||
this.dataIssues = [..this.dataIssues, modelValidationError];
|
||||
this.dataIsValid = false;
|
||||
}
|
||||
|
||||
// When the data is not valid, we don't store it:
|
||||
if (!this.dataIsValid)
|
||||
return;
|
||||
@@ -243,7 +264,7 @@ public partial class TranscriptionProviderDialog : MSGComponentBase, ISecretId
|
||||
{
|
||||
if (this.DataLLMProvider is LLMProviders.SELF_HOSTED && string.IsNullOrWhiteSpace(manuallyModel))
|
||||
return T("Please enter a transcription model name.");
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -258,21 +279,40 @@ public partial class TranscriptionProviderDialog : MSGComponentBase, ISecretId
|
||||
await this.form.Validate();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void OnHostChanged(Host selectedHost)
|
||||
{
|
||||
// When the host changes, reset the model selection state:
|
||||
this.DataHost = selectedHost;
|
||||
this.DataModel = default;
|
||||
this.dataManuallyModel = string.Empty;
|
||||
this.availableModels.Clear();
|
||||
this.dataLoadingModelsIssue = string.Empty;
|
||||
}
|
||||
|
||||
private async Task ReloadModels()
|
||||
{
|
||||
this.dataLoadingModelsIssue = string.Empty;
|
||||
var currentTranscriptionProviderSettings = this.CreateTranscriptionProviderSettings();
|
||||
var provider = currentTranscriptionProviderSettings.CreateProvider();
|
||||
if(provider is NoProvider)
|
||||
if (provider is NoProvider)
|
||||
return;
|
||||
|
||||
var models = await provider.GetTranscriptionModels(this.dataAPIKey);
|
||||
|
||||
// Order descending by ID means that the newest models probably come first:
|
||||
var orderedModels = models.OrderByDescending(n => n.Id);
|
||||
|
||||
this.availableModels.Clear();
|
||||
this.availableModels.AddRange(orderedModels);
|
||||
|
||||
try
|
||||
{
|
||||
var models = await provider.GetTranscriptionModels(this.dataAPIKey);
|
||||
|
||||
// Order descending by ID means that the newest models probably come first:
|
||||
var orderedModels = models.OrderByDescending(n => n.Id);
|
||||
|
||||
this.availableModels.Clear();
|
||||
this.availableModels.AddRange(orderedModels);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
this.Logger.LogError($"Failed to load models from provider '{this.DataLLMProvider}' (host={this.DataHost}, hostname='{this.DataHostname}'): {e.Message}");;
|
||||
this.dataLoadingModelsIssue = T("We are currently unable to communicate with the provider to load models. Please try again later.");
|
||||
}
|
||||
}
|
||||
|
||||
private string APIKeyText => this.DataLLMProvider switch
|
||||
|
||||
@@ -97,6 +97,7 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan
|
||||
|
||||
// Set the snackbar for the update service:
|
||||
UpdateService.SetBlazorDependencies(this.Snackbar);
|
||||
GlobalShortcutService.Initialize();
|
||||
TemporaryChatService.Initialize();
|
||||
|
||||
// Should the navigation bar be open by default?
|
||||
@@ -116,11 +117,6 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan
|
||||
await base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
private void LoadNavItems()
|
||||
{
|
||||
this.navItems = new List<NavBarItem>(this.GetNavItems());
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation of ILang
|
||||
@@ -215,9 +211,35 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan
|
||||
//
|
||||
// Check if there is an enterprise configuration plugin to download:
|
||||
//
|
||||
var enterpriseEnvironment = this.MessageBus.CheckDeferredMessages<EnterpriseEnvironment>(Event.STARTUP_ENTERPRISE_ENVIRONMENT).FirstOrDefault();
|
||||
if (enterpriseEnvironment != default)
|
||||
await PluginFactory.TryDownloadingConfigPluginAsync(enterpriseEnvironment.ConfigurationId, enterpriseEnvironment.ConfigurationServerUrl);
|
||||
var enterpriseEnvironments = this.MessageBus
|
||||
.CheckDeferredMessages<EnterpriseEnvironment>(Event.STARTUP_ENTERPRISE_ENVIRONMENT)
|
||||
.Where(env => env != default)
|
||||
.ToList();
|
||||
|
||||
var failedDeferredConfigIds = new HashSet<Guid>();
|
||||
foreach (var env in enterpriseEnvironments)
|
||||
{
|
||||
var wasDownloadSuccessful = await PluginFactory.TryDownloadingConfigPluginAsync(env.ConfigurationId, env.ConfigurationServerUrl);
|
||||
if (!wasDownloadSuccessful)
|
||||
{
|
||||
failedDeferredConfigIds.Add(env.ConfigurationId);
|
||||
this.Logger.LogWarning("Failed to download deferred enterprise configuration '{ConfigId}' during startup. Keeping managed plugins unchanged.", env.ConfigurationId);
|
||||
}
|
||||
}
|
||||
|
||||
if (EnterpriseEnvironmentService.HasValidEnterpriseSnapshot)
|
||||
{
|
||||
var activeConfigIds = EnterpriseEnvironmentService.CURRENT_ENVIRONMENTS
|
||||
.Select(env => env.ConfigurationId)
|
||||
.ToHashSet();
|
||||
|
||||
PluginFactory.RemoveUnreferencedManagedConfigurationPlugins(activeConfigIds);
|
||||
if (failedDeferredConfigIds.Count > 0)
|
||||
this.Logger.LogWarning("Deferred startup updates failed for {FailedCount} enterprise configuration(s). Those configurations were kept unchanged.", failedDeferredConfigIds.Count);
|
||||
}
|
||||
|
||||
// Initialize the enterprise encryption service for decrypting API keys:
|
||||
await PluginFactory.InitializeEnterpriseEncryption(this.RustService);
|
||||
|
||||
// Load (but not start) all plugins without waiting for them:
|
||||
#if DEBUG
|
||||
@@ -251,6 +273,11 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan
|
||||
|
||||
#endregion
|
||||
|
||||
private void LoadNavItems()
|
||||
{
|
||||
this.navItems = new List<NavBarItem>(this.GetNavItems());
|
||||
}
|
||||
|
||||
private IEnumerable<NavBarItem> GetNavItems()
|
||||
{
|
||||
var palette = this.ColorTheme.GetCurrentPalette(this.SettingsManager);
|
||||
|
||||
@@ -49,11 +49,12 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CodeBeam.MudBlazor.Extensions" Version="8.3.0" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.12.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="9.0.11" />
|
||||
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="9.0.12" />
|
||||
<PackageReference Include="MudBlazor" Version="8.15.0" />
|
||||
<PackageReference Include="MudBlazor.Markdown" Version="8.11.0" />
|
||||
<PackageReference Include="Qdrant.Client" Version="1.17.0" />
|
||||
<PackageReference Include="ReverseMarkdown" Version="5.0.0" />
|
||||
<PackageReference Include="LuaCSharp" Version="0.5.1" />
|
||||
<PackageReference Include="LuaCSharp" Version="0.5.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -81,6 +82,7 @@
|
||||
<MetaAppCommitHash>$([System.String]::Copy( $(Metadata) ).Split( ';' )[ 8 ])</MetaAppCommitHash>
|
||||
<MetaArchitecture>$([System.String]::Copy( $(Metadata) ).Split( ';' )[ 9 ])</MetaArchitecture>
|
||||
<MetaPdfiumVersion>$([System.String]::Copy( $(Metadata) ).Split( ';' )[ 10 ])</MetaPdfiumVersion>
|
||||
<MetaQdrantVersion>$([System.String]::Copy( $(Metadata) ).Split( ';' )[ 11 ])</MetaQdrantVersion>
|
||||
|
||||
<GenerateAssemblyInfo>true</GenerateAssemblyInfo>
|
||||
|
||||
@@ -108,6 +110,9 @@
|
||||
<AssemblyAttribute Include="AIStudio.Tools.Metadata.MetaDataLibraries">
|
||||
<_Parameter1>$(MetaPdfiumVersion)</_Parameter1>
|
||||
</AssemblyAttribute>
|
||||
<AssemblyAttribute Include="AIStudio.Tools.Metadata.MetaDataDatabases">
|
||||
<_Parameter1>$(MetaQdrantVersion)</_Parameter1>
|
||||
</AssemblyAttribute>
|
||||
</ItemGroup>
|
||||
|
||||
</Target>
|
||||
|
||||
@@ -9,60 +9,88 @@
|
||||
</MudText>
|
||||
|
||||
<InnerScrolling>
|
||||
|
||||
<MudText Typo="Typo.h4" Class="mb-2 mr-3">
|
||||
@T("General")
|
||||
</MudText>
|
||||
<MudStack Row="@true" Wrap="@Wrap.Wrap" Class="mb-3">
|
||||
<AssistantBlock TSettings="SettingsDialogTextSummarizer" Name="@T("Text Summarizer")" Description="@T("Use an LLM to summarize a given text.")" Icon="@Icons.Material.Filled.TextSnippet" Link="@Routes.ASSISTANT_SUMMARIZER"/>
|
||||
<AssistantBlock TSettings="SettingsDialogTranslation" Name="@T("Translation")" Description="@T("Translate text into another language.")" Icon="@Icons.Material.Filled.Translate" Link="@Routes.ASSISTANT_TRANSLATION"/>
|
||||
<AssistantBlock TSettings="SettingsDialogGrammarSpelling" Name="@T("Grammar & Spelling")" Description="@T("Check grammar and spelling of a given text.")" Icon="@Icons.Material.Filled.Edit" Link="@Routes.ASSISTANT_GRAMMAR_SPELLING"/>
|
||||
<AssistantBlock TSettings="SettingsDialogRewrite" Name="@T("Rewrite & Improve")" Description="@T("Rewrite and improve a given text for a chosen style.")" Icon="@Icons.Material.Filled.Edit" Link="@Routes.ASSISTANT_REWRITE"/>
|
||||
<AssistantBlock TSettings="SettingsDialogSynonyms" Name="@T("Synonyms")" Description="@T("Find synonyms for a given word or phrase.")" Icon="@Icons.Material.Filled.Spellcheck" Link="@Routes.ASSISTANT_SYNONYMS"/>
|
||||
</MudStack>
|
||||
|
||||
<MudText Typo="Typo.h4" Class="mb-2 mr-3 mt-6">
|
||||
@T("Business")
|
||||
</MudText>
|
||||
<MudStack Row="@true" Wrap="@Wrap.Wrap" Class="mb-3">
|
||||
<AssistantBlock TSettings="SettingsDialogWritingEMails" Name="@T("E-Mail")" Description="@T("Generate an e-mail for a given context.")" Icon="@Icons.Material.Filled.Email" Link="@Routes.ASSISTANT_EMAIL"/>
|
||||
|
||||
@if (PreviewFeatures.PRE_DOCUMENT_ANALYSIS_2025.IsEnabled(this.SettingsManager))
|
||||
{
|
||||
<AssistantBlock TSettings="SettingsDialogDocumentAnalysis" Name="@T("Document Analysis")" Description="@T("Analyze a document regarding defined rules and extract key information.")" Icon="@Icons.Material.Filled.DocumentScanner" Link="@Routes.ASSISTANT_DOCUMENT_ANALYSIS"/>
|
||||
}
|
||||
|
||||
<AssistantBlock TSettings="SettingsDialogMyTasks" Name="@T("My Tasks")" Description="@T("Analyze a text or an email for tasks you need to complete.")" Icon="@Icons.Material.Filled.Task" Link="@Routes.ASSISTANT_MY_TASKS"/>
|
||||
<AssistantBlock TSettings="SettingsDialogAgenda" Name="@T("Agenda Planner")" Description="@T("Generate an agenda for a given meeting, seminar, etc.")" Icon="@Icons.Material.Filled.CalendarToday" Link="@Routes.ASSISTANT_AGENDA"/>
|
||||
<AssistantBlock TSettings="SettingsDialogJobPostings" Name="@T("Job Posting")" Description="@T("Generate a job posting for a given job description.")" Icon="@Icons.Material.Filled.Work" Link="@Routes.ASSISTANT_JOB_POSTING"/>
|
||||
<AssistantBlock TSettings="SettingsDialogLegalCheck" Name="@T("Legal Check")" Description="@T("Ask a question about a legal document.")" Icon="@Icons.Material.Filled.Gavel" Link="@Routes.ASSISTANT_LEGAL_CHECK"/>
|
||||
<AssistantBlock TSettings="SettingsDialogIconFinder" Name="@T("Icon Finder")" Description="@T("Use an LLM to find an icon for a given context.")" Icon="@Icons.Material.Filled.FindInPage" Link="@Routes.ASSISTANT_ICON_FINDER"/>
|
||||
</MudStack>
|
||||
@if (this.SettingsManager.IsAnyCategoryAssistantVisible("General",
|
||||
(Components.TEXT_SUMMARIZER_ASSISTANT, PreviewFeatures.NONE),
|
||||
(Components.TRANSLATION_ASSISTANT, PreviewFeatures.NONE),
|
||||
(Components.GRAMMAR_SPELLING_ASSISTANT, PreviewFeatures.NONE),
|
||||
(Components.REWRITE_ASSISTANT, PreviewFeatures.NONE),
|
||||
(Components.SYNONYMS_ASSISTANT, PreviewFeatures.NONE)
|
||||
))
|
||||
{
|
||||
<MudText Typo="Typo.h4" Class="mb-2 mr-3">
|
||||
@T("General")
|
||||
</MudText>
|
||||
<MudStack Row="@true" Wrap="@Wrap.Wrap" Class="mb-3">
|
||||
<AssistantBlock TSettings="SettingsDialogTextSummarizer" Component="Components.TEXT_SUMMARIZER_ASSISTANT" Name="@T("Text Summarizer")" Description="@T("Use an LLM to summarize a given text.")" Icon="@Icons.Material.Filled.TextSnippet" Link="@Routes.ASSISTANT_SUMMARIZER"/>
|
||||
<AssistantBlock TSettings="SettingsDialogTranslation" Component="Components.TRANSLATION_ASSISTANT" Name="@T("Translation")" Description="@T("Translate text into another language.")" Icon="@Icons.Material.Filled.Translate" Link="@Routes.ASSISTANT_TRANSLATION"/>
|
||||
<AssistantBlock TSettings="SettingsDialogGrammarSpelling" Component="Components.GRAMMAR_SPELLING_ASSISTANT" Name="@T("Grammar & Spelling")" Description="@T("Check grammar and spelling of a given text.")" Icon="@Icons.Material.Filled.Edit" Link="@Routes.ASSISTANT_GRAMMAR_SPELLING"/>
|
||||
<AssistantBlock TSettings="SettingsDialogRewrite" Component="Components.REWRITE_ASSISTANT" Name="@T("Rewrite & Improve")" Description="@T("Rewrite and improve a given text for a chosen style.")" Icon="@Icons.Material.Filled.Edit" Link="@Routes.ASSISTANT_REWRITE"/>
|
||||
<AssistantBlock TSettings="SettingsDialogSynonyms" Component="Components.SYNONYMS_ASSISTANT" Name="@T("Synonyms")" Description="@T("Find synonyms for a given word or phrase.")" Icon="@Icons.Material.Filled.Spellcheck" Link="@Routes.ASSISTANT_SYNONYMS"/>
|
||||
</MudStack>
|
||||
}
|
||||
|
||||
<MudText Typo="Typo.h4" Class="mb-2 mr-3 mt-6">
|
||||
@T("Learning")
|
||||
</MudText>
|
||||
<MudStack Row="@true" Wrap="@Wrap.Wrap" Class="mb-3">
|
||||
<AssistantBlock TSettings="SettingsDialogAssistantBias" Name="@T("Bias of the Day")" Description="@T("Learn about one cognitive bias every day.")" Icon="@Icons.Material.Filled.Psychology" Link="@Routes.ASSISTANT_BIAS"/>
|
||||
</MudStack>
|
||||
|
||||
<MudText Typo="Typo.h4" Class="mb-2 mr-3 mt-6">
|
||||
@T("Software Engineering")
|
||||
</MudText>
|
||||
<MudStack Row="@true" Wrap="@Wrap.Wrap" Class="mb-3">
|
||||
<AssistantBlock TSettings="SettingsDialogCoding" Name="@T("Coding")" Description="@T("Get coding and debugging support from an LLM.")" Icon="@Icons.Material.Filled.Code" Link="@Routes.ASSISTANT_CODING"/>
|
||||
@if (PreviewFeatures.PRE_RAG_2024.IsEnabled(this.SettingsManager))
|
||||
{
|
||||
<AssistantBlock TSettings="SettingsDialogERIServer" Name="@T("ERI Server")" Description="@T("Generate an ERI server to integrate business systems.")" Icon="@Icons.Material.Filled.PrivateConnectivity" Link="@Routes.ASSISTANT_ERI"/>
|
||||
}
|
||||
</MudStack>
|
||||
|
||||
<MudText Typo="Typo.h4" Class="mb-2 mr-3 mt-6">
|
||||
@T("AI Studio Development")
|
||||
</MudText>
|
||||
<MudStack Row="@true" Wrap="@Wrap.Wrap" Class="mb-3">
|
||||
<AssistantBlock TSettings="SettingsDialogI18N" Name="@T("Localization")" Description="@T("Translate AI Studio text content into other languages")" Icon="@Icons.Material.Filled.Translate" Link="@Routes.ASSISTANT_AI_STUDIO_I18N"/>
|
||||
</MudStack>
|
||||
@if (this.SettingsManager.IsAnyCategoryAssistantVisible("Business",
|
||||
(Components.EMAIL_ASSISTANT, PreviewFeatures.NONE),
|
||||
(Components.DOCUMENT_ANALYSIS_ASSISTANT, PreviewFeatures.PRE_DOCUMENT_ANALYSIS_2025),
|
||||
(Components.MY_TASKS_ASSISTANT, PreviewFeatures.NONE),
|
||||
(Components.AGENDA_ASSISTANT, PreviewFeatures.NONE),
|
||||
(Components.JOB_POSTING_ASSISTANT, PreviewFeatures.NONE),
|
||||
(Components.LEGAL_CHECK_ASSISTANT, PreviewFeatures.NONE),
|
||||
(Components.ICON_FINDER_ASSISTANT, PreviewFeatures.NONE)
|
||||
))
|
||||
{
|
||||
<MudText Typo="Typo.h4" Class="mb-2 mr-3 mt-6">
|
||||
@T("Business")
|
||||
</MudText>
|
||||
<MudStack Row="@true" Wrap="@Wrap.Wrap" Class="mb-3">
|
||||
<AssistantBlock TSettings="SettingsDialogWritingEMails" Component="Components.EMAIL_ASSISTANT" Name="@T("E-Mail")" Description="@T("Generate an e-mail for a given context.")" Icon="@Icons.Material.Filled.Email" Link="@Routes.ASSISTANT_EMAIL"/>
|
||||
<AssistantBlock TSettings="NoSettingsPanel" Component="Components.DOCUMENT_ANALYSIS_ASSISTANT" RequiredPreviewFeature="PreviewFeatures.PRE_DOCUMENT_ANALYSIS_2025" Name="@T("Document Analysis")" Description="@T("Analyze a document regarding defined rules and extract key information.")" Icon="@Icons.Material.Filled.DocumentScanner" Link="@Routes.ASSISTANT_DOCUMENT_ANALYSIS"/>
|
||||
<AssistantBlock TSettings="SettingsDialogMyTasks" Component="Components.MY_TASKS_ASSISTANT" Name="@T("My Tasks")" Description="@T("Analyze a text or an email for tasks you need to complete.")" Icon="@Icons.Material.Filled.Task" Link="@Routes.ASSISTANT_MY_TASKS"/>
|
||||
<AssistantBlock TSettings="SettingsDialogAgenda" Component="Components.AGENDA_ASSISTANT" Name="@T("Agenda Planner")" Description="@T("Generate an agenda for a given meeting, seminar, etc.")" Icon="@Icons.Material.Filled.CalendarToday" Link="@Routes.ASSISTANT_AGENDA"/>
|
||||
<AssistantBlock TSettings="SettingsDialogJobPostings" Component="Components.JOB_POSTING_ASSISTANT" Name="@T("Job Posting")" Description="@T("Generate a job posting for a given job description.")" Icon="@Icons.Material.Filled.Work" Link="@Routes.ASSISTANT_JOB_POSTING"/>
|
||||
<AssistantBlock TSettings="SettingsDialogLegalCheck" Component="Components.LEGAL_CHECK_ASSISTANT" Name="@T("Legal Check")" Description="@T("Ask a question about a legal document.")" Icon="@Icons.Material.Filled.Gavel" Link="@Routes.ASSISTANT_LEGAL_CHECK"/>
|
||||
<AssistantBlock TSettings="SettingsDialogIconFinder" Component="Components.ICON_FINDER_ASSISTANT" Name="@T("Icon Finder")" Description="@T("Use an LLM to find an icon for a given context.")" Icon="@Icons.Material.Filled.FindInPage" Link="@Routes.ASSISTANT_ICON_FINDER"/>
|
||||
</MudStack>
|
||||
}
|
||||
|
||||
@if (this.SettingsManager.IsAnyCategoryAssistantVisible("Learning",
|
||||
(Components.BIAS_DAY_ASSISTANT, PreviewFeatures.NONE)
|
||||
))
|
||||
{
|
||||
<MudText Typo="Typo.h4" Class="mb-2 mr-3 mt-6">
|
||||
@T("Learning")
|
||||
</MudText>
|
||||
<MudStack Row="@true" Wrap="@Wrap.Wrap" Class="mb-3">
|
||||
<AssistantBlock TSettings="SettingsDialogAssistantBias" Component="Components.BIAS_DAY_ASSISTANT" Name="@T("Bias of the Day")" Description="@T("Learn about one cognitive bias every day.")" Icon="@Icons.Material.Filled.Psychology" Link="@Routes.ASSISTANT_BIAS"/>
|
||||
</MudStack>
|
||||
}
|
||||
|
||||
@if (this.SettingsManager.IsAnyCategoryAssistantVisible("Software Engineering",
|
||||
(Components.CODING_ASSISTANT, PreviewFeatures.NONE),
|
||||
(Components.ERI_ASSISTANT, PreviewFeatures.PRE_RAG_2024)
|
||||
))
|
||||
{
|
||||
<MudText Typo="Typo.h4" Class="mb-2 mr-3 mt-6">
|
||||
@T("Software Engineering")
|
||||
</MudText>
|
||||
<MudStack Row="@true" Wrap="@Wrap.Wrap" Class="mb-3">
|
||||
<AssistantBlock TSettings="SettingsDialogCoding" Component="Components.CODING_ASSISTANT" Name="@T("Coding")" Description="@T("Get coding and debugging support from an LLM.")" Icon="@Icons.Material.Filled.Code" Link="@Routes.ASSISTANT_CODING"/>
|
||||
<AssistantBlock TSettings="SettingsDialogERIServer" Component="Components.ERI_ASSISTANT" RequiredPreviewFeature="PreviewFeatures.PRE_RAG_2024" Name="@T("ERI Server")" Description="@T("Generate an ERI server to integrate business systems.")" Icon="@Icons.Material.Filled.PrivateConnectivity" Link="@Routes.ASSISTANT_ERI"/>
|
||||
</MudStack>
|
||||
}
|
||||
|
||||
@if (this.SettingsManager.IsAnyCategoryAssistantVisible("AI Studio Development",
|
||||
(Components.I18N_ASSISTANT, PreviewFeatures.NONE)
|
||||
))
|
||||
{
|
||||
<MudText Typo="Typo.h4" Class="mb-2 mr-3 mt-6">
|
||||
@T("AI Studio Development")
|
||||
</MudText>
|
||||
<MudStack Row="@true" Wrap="@Wrap.Wrap" Class="mb-3">
|
||||
<AssistantBlock TSettings="SettingsDialogI18N" Component="Components.I18N_ASSISTANT" Name="@T("Localization")" Description="@T("Translate AI Studio text content into other languages")" Icon="@Icons.Material.Filled.Translate" Link="@Routes.ASSISTANT_AI_STUDIO_I18N"/>
|
||||
</MudStack>
|
||||
}
|
||||
|
||||
</InnerScrolling>
|
||||
</div>
|
||||
</div>
|
||||
@@ -29,7 +29,9 @@
|
||||
</MudToolBar>
|
||||
</MudStack>
|
||||
|
||||
<ProviderSelection @bind-ProviderSettings="@this.providerSettings"/>
|
||||
<CascadingValue Value="Components.CHAT">
|
||||
<ProviderSelection @bind-ProviderSettings="@this.providerSettings"/>
|
||||
</CascadingValue>
|
||||
@if (this.AreWorkspacesVisible)
|
||||
{
|
||||
<MudSplitter Dimension="@this.ReadSplitterPosition" DimensionChanged="this.SplitterChanged" EnableSlide="@this.AreWorkspacesVisible" EnableMargin="@false" StartContentStyle="margin-right: 1em;" BarStyle="" EndContentStyle="margin-left: 1em;">
|
||||
@@ -143,4 +145,4 @@
|
||||
</MudDrawerContainer>
|
||||
</MudDrawer>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -31,6 +31,7 @@ public partial class Home : MSGComponentBase
|
||||
{
|
||||
this.itemsAdvantages = [
|
||||
new(this.T("Free of charge"), this.T("The app is free to use, both for personal and commercial purposes.")),
|
||||
new(this.T("Democratization of AI"), this.T("We want to contribute to the democratization of AI. MindWork AI Studio runs even on low-cost hardware, including computers around 100 EUR such as Raspberry Pi. This makes the app and its full feature set accessible to people and families with limited budgets. You can start with local LLMs or use affordable cloud models.")),
|
||||
new(this.T("Independence"), this.T("You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT5, o1, etc.), Perplexity, Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), OpenRouter, Hugging Face, and self-hosted models using vLLM, llama.cpp, ollama, LM Studio, Groq, or Fireworks. For scientists and employees of research institutions, we also support Helmholtz and GWDG AI services. These are available through federated logins like eduGAIN to all 18 Helmholtz Centers, the Max Planck Society, most German, and many international universities.")),
|
||||
new(this.T("Assistants"), this.T("You just want to quickly translate a text? AI Studio has so-called assistants for such and other tasks. No prompting is necessary when working with these assistants.")),
|
||||
new(this.T("Unrestricted usage"), this.T("Unlike services like ChatGPT, which impose limits after intensive use, MindWork AI Studio offers unlimited usage through the providers API.")),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
@attribute [Route(Routes.ABOUT)]
|
||||
@using AIStudio.Tools.PluginSystem
|
||||
@using AIStudio.Tools.Services
|
||||
@inherits MSGComponentBase
|
||||
|
||||
@@ -19,86 +20,158 @@
|
||||
<MudListItem T="string" Icon="@Icons.Material.Outlined.Build" Text="@this.VersionDotnetSdk"/>
|
||||
<MudListItem T="string" Icon="@Icons.Material.Outlined.Memory" Text="@this.VersionDotnetRuntime"/>
|
||||
<MudListItem T="string" Icon="@Icons.Material.Outlined.Build" Text="@this.VersionRust"/>
|
||||
<MudListItem T="string" Icon="@Icons.Material.Outlined.Storage">
|
||||
<MudText Typo="Typo.body1">
|
||||
@this.VersionDatabase
|
||||
</MudText>
|
||||
<MudCollapse Expanded="@this.showDatabaseDetails">
|
||||
<MudText Typo="Typo.body1" Class="mt-2 mb-2">
|
||||
@foreach (var item in this.databaseDisplayInfo)
|
||||
{
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<MudIcon Icon="@Icons.Material.Filled.ArrowRightAlt"/>
|
||||
<span>@item.Label: @item.Value</span>
|
||||
<MudCopyClipboardButton TooltipMessage="@(T("Copies the following to the clipboard")+": "+item.Value)" StringContent=@item.Value/>
|
||||
</div>
|
||||
}
|
||||
</MudText>
|
||||
</MudCollapse>
|
||||
<MudButton StartIcon="@(this.showDatabaseDetails ? Icons.Material.Filled.ExpandLess : Icons.Material.Filled.ExpandMore)"
|
||||
Size="Size.Small"
|
||||
Variant="Variant.Text"
|
||||
OnClick="@this.ToggleDatabaseDetails">
|
||||
@(this.showDatabaseDetails ? T("Hide Details") : T("Show Details"))
|
||||
</MudButton>
|
||||
</MudListItem>
|
||||
<MudListItem T="string" Icon="@Icons.Material.Outlined.DocumentScanner" Text="@this.VersionPdfium"/>
|
||||
<MudListItem T="string" Icon="@Icons.Material.Outlined.Article" Text="@this.versionPandoc"/>
|
||||
<MudListItem T="string" Icon="@Icons.Material.Outlined.Widgets" Text="@MudBlazorVersion"/>
|
||||
<MudListItem T="string" Icon="@Icons.Material.Outlined.Memory" Text="@TauriVersion"/>
|
||||
<MudListItem T="string" Icon="@Icons.Material.Outlined.Translate" Text="@this.OSLanguage"/>
|
||||
<MudListItem T="string" Icon="@Icons.Material.Outlined.Business">
|
||||
@switch (EnterpriseEnvironmentService.CURRENT_ENVIRONMENT.IsActive)
|
||||
@switch (HasAnyActiveEnvironment)
|
||||
{
|
||||
case false when this.configPlug is null:
|
||||
case false when this.configPlugins.Count == 0:
|
||||
<MudText Typo="Typo.body1">
|
||||
@T("This is a private AI Studio installation. It runs without an enterprise configuration.")
|
||||
</MudText>
|
||||
break;
|
||||
|
||||
|
||||
case false:
|
||||
<MudText Typo="Typo.body1">
|
||||
@T("AI Studio runs with an enterprise configuration using a configuration plugin, without central configuration management.")
|
||||
@T("AI Studio runs with an enterprise configuration using configuration plugins, without central configuration management.")
|
||||
</MudText>
|
||||
<MudCollapse Expanded="@this.showEnterpriseConfigDetails">
|
||||
<MudText Typo="Typo.body1" Class="mt-2 mb-2">
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<MudIcon Icon="@Icons.Material.Filled.ArrowRightAlt"/>
|
||||
<span>@T("Configuration plugin ID:") @this.configPlug!.Id</span>
|
||||
<MudCopyClipboardButton TooltipMessage="@T("Copies the configuration plugin ID to the clipboard")" StringContent=@this.configPlug!.Id.ToString()/>
|
||||
</div>
|
||||
</MudText>
|
||||
</MudCollapse>
|
||||
break;
|
||||
|
||||
case true when this.configPlug is null:
|
||||
<MudText Typo="Typo.body1">
|
||||
@T("AI Studio runs with an enterprise configuration and a configuration server. The configuration plugin is not yet available.")
|
||||
</MudText>
|
||||
<MudCollapse Expanded="@this.showEnterpriseConfigDetails">
|
||||
<MudText Typo="Typo.body1" Class="mt-2 mb-2">
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<MudIcon Icon="@Icons.Material.Filled.ArrowRightAlt"/>
|
||||
<span>@T("Enterprise configuration ID:") @EnterpriseEnvironmentService.CURRENT_ENVIRONMENT.ConfigurationId</span>
|
||||
<MudCopyClipboardButton TooltipMessage="@T("Copies the config ID to the clipboard")" StringContent=@EnterpriseEnvironmentService.CURRENT_ENVIRONMENT.ConfigurationId.ToString()/>
|
||||
</div>
|
||||
</MudText>
|
||||
|
||||
<MudText Typo="Typo.body1" Class="mt-2 mb-2">
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<MudIcon Icon="@Icons.Material.Filled.ArrowRightAlt"/>
|
||||
<span>@T("Configuration server:") @EnterpriseEnvironmentService.CURRENT_ENVIRONMENT.ConfigurationServerUrl</span>
|
||||
<MudCopyClipboardButton TooltipMessage="@T("Copies the server URL to the clipboard")" StringContent=@EnterpriseEnvironmentService.CURRENT_ENVIRONMENT.ConfigurationServerUrl/>
|
||||
</div>
|
||||
</MudText>
|
||||
</MudCollapse>
|
||||
break;
|
||||
|
||||
case true:
|
||||
<MudText Typo="Typo.body1">
|
||||
@T("AI Studio runs with an enterprise configuration and a configuration server. The configuration plugin is active.")
|
||||
</MudText>
|
||||
<MudCollapse Expanded="@this.showEnterpriseConfigDetails">
|
||||
<MudText Typo="Typo.body1" Class="mt-2 mb-2">
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<MudIcon Icon="@Icons.Material.Filled.ArrowRightAlt"/>
|
||||
<span>@T("Enterprise configuration ID:") @EnterpriseEnvironmentService.CURRENT_ENVIRONMENT.ConfigurationId</span>
|
||||
<MudCopyClipboardButton TooltipMessage="@T("Copies the config ID to the clipboard")" StringContent=@EnterpriseEnvironmentService.CURRENT_ENVIRONMENT.ConfigurationId.ToString()/>
|
||||
</div>
|
||||
</MudText>
|
||||
|
||||
<MudText Typo="Typo.body1" Class="mt-2 mb-2">
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<MudIcon Icon="@Icons.Material.Filled.ArrowRightAlt"/>
|
||||
<span>@T("Configuration server:") @EnterpriseEnvironmentService.CURRENT_ENVIRONMENT.ConfigurationServerUrl</span>
|
||||
<MudCopyClipboardButton TooltipMessage="@T("Copies the server URL to the clipboard")" StringContent=@EnterpriseEnvironmentService.CURRENT_ENVIRONMENT.ConfigurationServerUrl/>
|
||||
</div>
|
||||
</MudText>
|
||||
@foreach (var plug in this.configPlugins)
|
||||
{
|
||||
<ConfigPluginInfoCard HeaderIcon="@Icons.Material.Filled.Extension"
|
||||
HeaderText="@plug.Name"
|
||||
Items="@([
|
||||
new ConfigInfoRowItem(Icons.Material.Filled.ArrowRightAlt,
|
||||
$"{T("Configuration plugin ID:")} {plug.Id}",
|
||||
plug.Id.ToString(),
|
||||
T("Copies the configuration plugin ID to the clipboard"))
|
||||
])"/>
|
||||
}
|
||||
|
||||
<MudText Typo="Typo.body1" Class="mt-2 mb-2">
|
||||
<div style="display: flex; align-items: center; gap: 8px;">
|
||||
<MudIcon Icon="@Icons.Material.Filled.ArrowRightAlt"/>
|
||||
<span>@T("Configuration plugin ID:") @this.configPlug!.Id</span>
|
||||
<MudCopyClipboardButton TooltipMessage="@T("Copies the configuration plugin ID to the clipboard")" StringContent=@this.configPlug!.Id.ToString()/>
|
||||
</div>
|
||||
<EncryptionSecretInfo IsConfigured="@(PluginFactory.EnterpriseEncryption?.IsAvailable is true)"
|
||||
ConfiguredText="@T("Encryption secret: is configured")"
|
||||
NotConfiguredText="@T("Encryption secret: is not configured")"/>
|
||||
</MudCollapse>
|
||||
break;
|
||||
|
||||
case true when this.configPlugins.Count == 0:
|
||||
<MudText Typo="Typo.body1">
|
||||
@T("AI Studio runs with an enterprise configuration and configuration servers. The configuration plugins are not yet available.")
|
||||
</MudText>
|
||||
<MudCollapse Expanded="@this.showEnterpriseConfigDetails">
|
||||
@foreach (var env in EnterpriseEnvironmentService.CURRENT_ENVIRONMENTS.Where(e => e.IsActive))
|
||||
{
|
||||
<ConfigPluginInfoCard HeaderIcon="@Icons.Material.Filled.HourglassBottom"
|
||||
HeaderText="@T("Waiting for the configuration plugin...")"
|
||||
Items="@([
|
||||
new ConfigInfoRowItem(Icons.Material.Filled.ArrowRightAlt,
|
||||
$"{T("Enterprise configuration ID:")} {env.ConfigurationId}",
|
||||
env.ConfigurationId.ToString(),
|
||||
T("Copies the config ID to the clipboard")),
|
||||
|
||||
new ConfigInfoRowItem(Icons.Material.Filled.ArrowRightAlt,
|
||||
$"{T("Configuration server:")} {env.ConfigurationServerUrl}",
|
||||
env.ConfigurationServerUrl,
|
||||
T("Copies the server URL to the clipboard"),
|
||||
"margin-top: 4px;")
|
||||
])"/>
|
||||
}
|
||||
|
||||
<EncryptionSecretInfo IsConfigured="@(PluginFactory.EnterpriseEncryption?.IsAvailable is true)"
|
||||
ConfiguredText="@T("Encryption secret: is configured")"
|
||||
NotConfiguredText="@T("Encryption secret: is not configured")"/>
|
||||
</MudCollapse>
|
||||
break;
|
||||
|
||||
case true:
|
||||
@if (this.HasAnyLoadedEnterpriseConfigurationPlugin)
|
||||
{
|
||||
<MudText Typo="Typo.body1">
|
||||
@T("AI Studio runs with an enterprise configuration and configuration servers. The configuration plugins are active.")
|
||||
</MudText>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudText Typo="Typo.body1">
|
||||
@T("AI Studio runs with an enterprise configuration and configuration servers. The configuration plugins are not yet available.")
|
||||
</MudText>
|
||||
}
|
||||
<MudCollapse Expanded="@this.showEnterpriseConfigDetails">
|
||||
@foreach (var env in EnterpriseEnvironmentService.CURRENT_ENVIRONMENTS.Where(e => e.IsActive))
|
||||
{
|
||||
var matchingPlugin = this.FindManagedConfigurationPlugin(env.ConfigurationId);
|
||||
if (matchingPlugin is null)
|
||||
{
|
||||
<ConfigPluginInfoCard HeaderIcon="@Icons.Material.Filled.HourglassBottom"
|
||||
HeaderText="@T("Waiting for the configuration plugin...")"
|
||||
Items="@([
|
||||
new ConfigInfoRowItem(Icons.Material.Filled.ArrowRightAlt,
|
||||
$"{T("Enterprise configuration ID:")} {env.ConfigurationId}",
|
||||
env.ConfigurationId.ToString(),
|
||||
T("Copies the config ID to the clipboard")),
|
||||
|
||||
new ConfigInfoRowItem(Icons.Material.Filled.ArrowRightAlt,
|
||||
$"{T("Configuration server:")} {env.ConfigurationServerUrl}",
|
||||
env.ConfigurationServerUrl,
|
||||
T("Copies the server URL to the clipboard"),
|
||||
"margin-top: 4px;")
|
||||
])"/>
|
||||
continue;
|
||||
}
|
||||
|
||||
<ConfigPluginInfoCard HeaderIcon="@Icons.Material.Filled.Extension"
|
||||
HeaderText="@matchingPlugin.Name"
|
||||
Items="@([
|
||||
new ConfigInfoRowItem(Icons.Material.Filled.ArrowRightAlt,
|
||||
$"{T("Enterprise configuration ID:")} {env.ConfigurationId}",
|
||||
env.ConfigurationId.ToString(),
|
||||
T("Copies the config ID to the clipboard")),
|
||||
|
||||
new ConfigInfoRowItem(Icons.Material.Filled.ArrowRightAlt,
|
||||
$"{T("Configuration server:")} {env.ConfigurationServerUrl}",
|
||||
env.ConfigurationServerUrl,
|
||||
T("Copies the server URL to the clipboard"),
|
||||
"margin-top: 4px;"),
|
||||
|
||||
new ConfigInfoRowItem(Icons.Material.Filled.ArrowRightAlt,
|
||||
$"{T("Configuration plugin ID:")} {matchingPlugin.Id}",
|
||||
matchingPlugin.Id.ToString(),
|
||||
T("Copies the configuration plugin ID to the clipboard"),
|
||||
"margin-top: 4px;")
|
||||
])"
|
||||
ShowWarning="@this.IsManagedConfigurationIdMismatch(matchingPlugin, env.ConfigurationId)"
|
||||
WarningText="@T("ID mismatch: the plugin ID differs from the enterprise configuration ID.")"/>
|
||||
}
|
||||
|
||||
<EncryptionSecretInfo IsConfigured="@(PluginFactory.EnterpriseEncryption?.IsAvailable is true)"
|
||||
ConfiguredText="@T("Encryption secret: is configured")"
|
||||
NotConfiguredText="@T("Encryption secret: is not configured")"/>
|
||||
</MudCollapse>
|
||||
break;
|
||||
}
|
||||
@@ -115,10 +188,10 @@
|
||||
</MudListItem>
|
||||
</MudList>
|
||||
<MudStack Row="true">
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Info" StartIcon="@Icons.Material.Filled.Update" OnClick="() => this.CheckForUpdate()">
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Info" StartIcon="@Icons.Material.Filled.Update" OnClick="@(() => this.CheckForUpdate())">
|
||||
@T("Check for updates")
|
||||
</MudButton>
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Default" StartIcon="@Icons.Material.Filled.Download" OnClick="async () => await this.ShowPandocDialog()">
|
||||
<MudButton Variant="Variant.Filled" Color="Color.Default" StartIcon="@Icons.Material.Filled.Download" OnClick="@(async () => await this.ShowPandocDialog())">
|
||||
@this.PandocButtonText
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
@@ -126,7 +199,7 @@
|
||||
|
||||
<ExpansionPanel HeaderIcon="@Icons.Custom.Brands.GitHub" HeaderText="@T("Community & Code")">
|
||||
<MudList T="string" Class="mb-1">
|
||||
<MudListItem T="string" Icon="@Icons.Material.Outlined.Home" Target="_blank" Href="http://mindworkai.org/">
|
||||
<MudListItem T="string" Icon="@Icons.Material.Outlined.Home" Target="_blank" Href="https://mindworkai.org/">
|
||||
@T("Discover MindWork AI's mission and vision on our official homepage.")
|
||||
</MudListItem>
|
||||
<MudListItem T="string" Icon="@Icons.Custom.Brands.GitHub" Target="_blank" Href="https://github.com/MindWorkAI/AI-Studio">
|
||||
@@ -167,14 +240,14 @@
|
||||
@T("Startup log file")
|
||||
</MudText>
|
||||
<MudList T="string" Class="mb-3">
|
||||
<MudListItem T="string" Icon="@Icons.Material.Outlined.Folder" Text="@this.logPaths.LogStartupPath" OnClick="() => this.CopyStartupLogPath()"/>
|
||||
<MudListItem T="string" Icon="@Icons.Material.Outlined.Folder" Text="@this.logPaths.LogStartupPath" OnClick="@(() => this.CopyStartupLogPath())"/>
|
||||
</MudList>
|
||||
|
||||
<MudText Typo="Typo.h4">
|
||||
@T("Usage log file")
|
||||
</MudText>
|
||||
<MudList T="string" Class="mb-3">
|
||||
<MudListItem T="string" Icon="@Icons.Material.Outlined.Folder" Text="@this.logPaths.LogAppPath" OnClick="() => this.CopyAppLogPath()"/>
|
||||
<MudListItem T="string" Icon="@Icons.Material.Outlined.Folder" Text="@this.logPaths.LogAppPath" OnClick="@(() => this.CopyAppLogPath())"/>
|
||||
</MudList>
|
||||
</ExpansionPanel>
|
||||
|
||||
@@ -194,8 +267,10 @@
|
||||
<ThirdPartyComponent Name="CodeBeam.MudBlazor.Extensions" Developer="Mehmet Can Karagöz & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/CodeBeamOrg/CodeBeam.MudBlazor.Extensions/blob/dev/LICENSE" RepositoryUrl="https://github.com/CodeBeamOrg/CodeBeam.MudBlazor.Extensions" UseCase="@T("This library is used to extend the MudBlazor library. It provides additional components that are not part of the MudBlazor library.")"/>
|
||||
<ThirdPartyComponent Name="Rust" Developer="Graydon Hoare, Rust Foundation, Rust developers & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/rust-lang/rust/blob/master/LICENSE-MIT" RepositoryUrl="https://github.com/rust-lang/rust" UseCase="@T("The .NET backend cannot be started as a desktop app. Therefore, I use a second backend in Rust, which I call runtime. With Rust as the runtime, Tauri can be used to realize a typical desktop app. Thanks to Rust, this app can be offered for Windows, macOS, and Linux desktops. Rust is a great language for developing safe and high-performance software.")"/>
|
||||
<ThirdPartyComponent Name="Tauri" Developer="Daniel Thompson-Yvetot, Lucas Nogueira, Tensor, Boscop, Serge Zaitsev, George Burton & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/tauri-apps/tauri/blob/dev/LICENSE_MIT" RepositoryUrl="https://github.com/tauri-apps/tauri" UseCase="@T("Tauri is used to host the Blazor user interface. It is a great project that allows the creation of desktop applications using web technologies. I love Tauri!")"/>
|
||||
<ThirdPartyComponent Name="Qdrant" Developer="Andrey Vasnetsov, Tim Visée, Arnaud Gourlay, Luis Cossío, Ivan Pleshkov, Roman Titov, xzfc, JojiiOfficial & Open Source Community" LicenseName="Apache-2.0" LicenseUrl="https://github.com/qdrant/qdrant/blob/master/LICENSE" RepositoryUrl="https://github.com/qdrant/qdrant" UseCase="@T("Qdrant is a vector database and vector similarity search engine. We use it to realize local RAG—retrieval-augmented generation—within AI Studio. Thanks for the effort and great work that has been and is being put into Qdrant.")"/>
|
||||
<ThirdPartyComponent Name="Rocket" Developer="Sergio Benitez & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/rwf2/Rocket/blob/master/LICENSE-MIT" RepositoryUrl="https://github.com/rwf2/Rocket" UseCase="@T("We use Rocket to implement the runtime API. This is necessary because the runtime must be able to communicate with the user interface (IPC). Rocket is a great framework for implementing web APIs in Rust.")"/>
|
||||
<ThirdPartyComponent Name="serde" Developer="Erick Tryzelaar, David Tolnay & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/serde-rs/serde/blob/master/LICENSE-MIT" RepositoryUrl="https://github.com/serde-rs/serde" UseCase="@T("Now we have multiple systems, some developed in .NET and others in Rust. The data format JSON is responsible for translating data between both worlds (called data serialization and deserialization). Serde takes on this task in the Rust world. The counterpart in the .NET world is an integral part of .NET and is located in System.Text.Json.")"/>
|
||||
<ThirdPartyComponent Name="strum_macros" Developer="Peter Glotfelty & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/Peternator7/strum/blob/master/LICENSE" RepositoryUrl="https://github.com/Peternator7/strum" UseCase="@T("This crate provides derive macros for Rust enums, which we use to reduce boilerplate when implementing string conversions and metadata for runtime types. This is helpful for the communication between our Rust and .NET systems.")"/>
|
||||
<ThirdPartyComponent Name="keyring" Developer="Walther Chen, Daniel Brotsky & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/hwchen/keyring-rs/blob/master/LICENSE-MIT" RepositoryUrl="https://github.com/hwchen/keyring-rs" UseCase="@T("In order to use any LLM, each user must store their so-called API key for each LLM provider. This key must be kept secure, similar to a password. The safest way to do this is offered by operating systems like macOS, Windows, and Linux: They have mechanisms to store such data, if available, on special security hardware. Since this is currently not possible in .NET, we use this Rust library.")"/>
|
||||
<ThirdPartyComponent Name="arboard" Developer="Artur Kovacs, Avi Weinstock, 1Password & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/1Password/arboard/blob/master/LICENSE-MIT.txt" RepositoryUrl="https://github.com/1Password/arboard" UseCase="@T("To be able to use the responses of the LLM in other apps, we often use the clipboard of the respective operating system. Unfortunately, in .NET there is no solution that works with all operating systems. Therefore, I have opted for this library in Rust. This way, data transfer to other apps works on every system.")"/>
|
||||
<ThirdPartyComponent Name="tokio" Developer="Alex Crichton, Carl Lerche, Alice Ryhl, Taiki Endo, Ivan Petkov, Eliza Weisman, Lucio Franco & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/tokio-rs/tokio/blob/master/LICENSE" RepositoryUrl="https://github.com/tokio-rs/tokio" UseCase="@T("Code in the Rust language can be specified as synchronous or asynchronous. Unlike .NET and the C# language, Rust cannot execute asynchronous code by itself. Rust requires support in the form of an executor for this. Tokio is one such executor.")"/>
|
||||
@@ -213,6 +288,8 @@
|
||||
<ThirdPartyComponent Name="PDFium" Developer="Lei Zhang, Tom Sepez, Dan Sinclair, and Foxit, Google, Chromium, Collabora, Ada, DocsCorp, Dropbox, Microsoft, and PSPDFKit Teams & Open Source Community" LicenseName="Apache-2.0" LicenseUrl="https://pdfium.googlesource.com/pdfium/+/refs/heads/main/LICENSE" RepositoryUrl="https://pdfium.googlesource.com/pdfium" UseCase="@T("This library is used to read PDF files. This is necessary, e.g., for using PDFs as a data source for a chat.")"/>
|
||||
<ThirdPartyComponent Name="pdfium-render" Developer="Alastair Carey, Dorian Rudolph & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/ajrcarey/pdfium-render/blob/master/LICENSE.md" RepositoryUrl="https://github.com/ajrcarey/pdfium-render" UseCase="@T("This library is used to read PDF files. This is necessary, e.g., for using PDFs as a data source for a chat.")"/>
|
||||
<ThirdPartyComponent Name="sys-locale" Developer="1Password Team, ComplexSpaces & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/1Password/sys-locale/blob/main/LICENSE-MIT" RepositoryUrl="https://github.com/1Password/sys-locale" UseCase="@T("This library is used to determine the language of the operating system. This is necessary to set the language of the user interface.")"/>
|
||||
<ThirdPartyComponent Name="sysinfo" Developer="Guillaume Gomez & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/GuillaumeGomez/sysinfo/blob/main/LICENSE" RepositoryUrl="https://github.com/GuillaumeGomez/sysinfo" UseCase="@T("This library is used to manage sidecar processes and to ensure that stale or zombie sidecars are detected and terminated.")"/>
|
||||
<ThirdPartyComponent Name="tempfile" Developer="Steven Allen, Ashley Mannix & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/Stebalien/tempfile/blob/master/LICENSE-MIT" RepositoryUrl="https://github.com/Stebalien/tempfile" UseCase="@T("This library is used to create temporary folders for saving the certificate and private key for communication with Qdrant.")"/>
|
||||
<ThirdPartyComponent Name="Lua-CSharp" Developer="Yusuke Nakada & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/nuskey8/Lua-CSharp/blob/main/LICENSE" RepositoryUrl="https://github.com/nuskey8/Lua-CSharp" UseCase="@T("We use Lua as the language for plugins. Lua-CSharp lets Lua scripts communicate with AI Studio and vice versa. Thank you, Yusuke Nakada, for this great library.")" />
|
||||
<ThirdPartyComponent Name="HtmlAgilityPack" Developer="ZZZ Projects & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/zzzprojects/html-agility-pack/blob/master/LICENSE" RepositoryUrl="https://github.com/zzzprojects/html-agility-pack" UseCase="@T("We use the HtmlAgilityPack to extract content from the web. This is necessary, e.g., when you provide a URL as input for an assistant.")"/>
|
||||
<ThirdPartyComponent Name="ReverseMarkdown" Developer="Babu Annamalai & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/mysticmind/reversemarkdown-net/blob/master/LICENSE" RepositoryUrl="https://github.com/mysticmind/reversemarkdown-net" UseCase="@T("This library is used to convert HTML to Markdown. This is necessary, e.g., when you provide a URL as input for an assistant.")"/>
|
||||
@@ -224,4 +301,4 @@
|
||||
</ExpansionPanel>
|
||||
</MudExpansionPanels>
|
||||
</InnerScrolling>
|
||||
</div>
|
||||
</div>
|
||||
@@ -2,6 +2,7 @@ using System.Reflection;
|
||||
|
||||
using AIStudio.Components;
|
||||
using AIStudio.Dialogs;
|
||||
using AIStudio.Tools.Databases;
|
||||
using AIStudio.Tools.Metadata;
|
||||
using AIStudio.Tools.PluginSystem;
|
||||
using AIStudio.Tools.Rust;
|
||||
@@ -25,12 +26,16 @@ public partial class Information : MSGComponentBase
|
||||
|
||||
[Inject]
|
||||
private ISnackbar Snackbar { get; init; } = null!;
|
||||
|
||||
|
||||
[Inject]
|
||||
private DatabaseClient DatabaseClient { get; init; } = null!;
|
||||
|
||||
private static readonly Assembly ASSEMBLY = Assembly.GetExecutingAssembly();
|
||||
private static readonly MetaDataAttribute META_DATA = ASSEMBLY.GetCustomAttribute<MetaDataAttribute>()!;
|
||||
private static readonly MetaDataArchitectureAttribute META_DATA_ARCH = ASSEMBLY.GetCustomAttribute<MetaDataArchitectureAttribute>()!;
|
||||
private static readonly MetaDataLibrariesAttribute META_DATA_LIBRARIES = ASSEMBLY.GetCustomAttribute<MetaDataLibrariesAttribute>()!;
|
||||
|
||||
private static readonly MetaDataDatabasesAttribute META_DATA_DATABASES = ASSEMBLY.GetCustomAttribute<MetaDataDatabasesAttribute>()!;
|
||||
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(Information).Namespace, nameof(Information));
|
||||
|
||||
private string osLanguage = string.Empty;
|
||||
@@ -53,6 +58,8 @@ public partial class Information : MSGComponentBase
|
||||
|
||||
private string VersionPdfium => $"{T("Used PDFium version")}: v{META_DATA_LIBRARIES.PdfiumVersion}";
|
||||
|
||||
private string VersionDatabase => $"{T("Database version")}: {this.DatabaseClient.Name} v{META_DATA_DATABASES.DatabaseVersion}";
|
||||
|
||||
private string versionPandoc = TB("Determine Pandoc version, please wait...");
|
||||
private PandocInstallation pandocInstallation;
|
||||
|
||||
@@ -60,7 +67,22 @@ public partial class Information : MSGComponentBase
|
||||
|
||||
private bool showEnterpriseConfigDetails;
|
||||
|
||||
private IPluginMetadata? configPlug = PluginFactory.AvailablePlugins.FirstOrDefault(x => x.Type is PluginType.CONFIGURATION);
|
||||
private bool showDatabaseDetails;
|
||||
|
||||
private List<IAvailablePlugin> configPlugins = PluginFactory.AvailablePlugins
|
||||
.Where(x => x.Type is PluginType.CONFIGURATION)
|
||||
.OfType<IAvailablePlugin>()
|
||||
.ToList();
|
||||
|
||||
private sealed record DatabaseDisplayInfo(string Label, string Value);
|
||||
|
||||
private readonly List<DatabaseDisplayInfo> databaseDisplayInfo = new();
|
||||
|
||||
private static bool HasAnyActiveEnvironment => EnterpriseEnvironmentService.CURRENT_ENVIRONMENTS.Any(e => e.IsActive);
|
||||
|
||||
private bool HasAnyLoadedEnterpriseConfigurationPlugin => EnterpriseEnvironmentService.CURRENT_ENVIRONMENTS
|
||||
.Where(e => e.IsActive)
|
||||
.Any(env => this.FindManagedConfigurationPlugin(env.ConfigurationId) is not null);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the enterprise configuration has details that can be shown/hidden.
|
||||
@@ -70,16 +92,16 @@ public partial class Information : MSGComponentBase
|
||||
{
|
||||
get
|
||||
{
|
||||
return EnterpriseEnvironmentService.CURRENT_ENVIRONMENT.IsActive switch
|
||||
return HasAnyActiveEnvironment switch
|
||||
{
|
||||
// Case 1: No enterprise config and no plugin - no details available
|
||||
false when this.configPlug is null => false,
|
||||
false when this.configPlugins.Count == 0 => false,
|
||||
|
||||
// Case 2: Enterprise config with plugin but no central management - has details
|
||||
false => true,
|
||||
|
||||
// Case 3: Enterprise config active but no plugin - has details
|
||||
true when this.configPlug is null => true,
|
||||
true when this.configPlugins.Count == 0 => true,
|
||||
|
||||
// Case 4: Enterprise config active with plugin - has details
|
||||
true => true
|
||||
@@ -96,6 +118,11 @@ public partial class Information : MSGComponentBase
|
||||
this.osLanguage = await this.RustService.ReadUserLanguage();
|
||||
this.logPaths = await this.RustService.GetLogPaths();
|
||||
|
||||
await foreach (var (label, value) in this.DatabaseClient.GetDisplayInfo())
|
||||
{
|
||||
this.databaseDisplayInfo.Add(new DatabaseDisplayInfo(label, value));
|
||||
}
|
||||
|
||||
// Determine the Pandoc version may take some time, so we start it here
|
||||
// without waiting for the result:
|
||||
_ = this.DeterminePandocVersion();
|
||||
@@ -110,7 +137,10 @@ public partial class Information : MSGComponentBase
|
||||
switch (triggeredEvent)
|
||||
{
|
||||
case Event.PLUGINS_RELOADED:
|
||||
this.configPlug = PluginFactory.AvailablePlugins.FirstOrDefault(x => x.Type is PluginType.CONFIGURATION);
|
||||
this.configPlugins = PluginFactory.AvailablePlugins
|
||||
.Where(x => x.Type is PluginType.CONFIGURATION)
|
||||
.OfType<IAvailablePlugin>()
|
||||
.ToList();
|
||||
await this.InvokeAsync(this.StateHasChanged);
|
||||
break;
|
||||
}
|
||||
@@ -170,6 +200,23 @@ public partial class Information : MSGComponentBase
|
||||
{
|
||||
this.showEnterpriseConfigDetails = !this.showEnterpriseConfigDetails;
|
||||
}
|
||||
|
||||
private void ToggleDatabaseDetails()
|
||||
{
|
||||
this.showDatabaseDetails = !this.showDatabaseDetails;
|
||||
}
|
||||
|
||||
private IAvailablePlugin? FindManagedConfigurationPlugin(Guid configurationId)
|
||||
{
|
||||
return this.configPlugins.FirstOrDefault(plugin => plugin.ManagedConfigurationId == configurationId)
|
||||
// Backward compatibility for already downloaded plugins without ManagedConfigurationId.
|
||||
?? this.configPlugins.FirstOrDefault(plugin => plugin.ManagedConfigurationId is null && plugin.Id == configurationId);
|
||||
}
|
||||
|
||||
private bool IsManagedConfigurationIdMismatch(IAvailablePlugin plugin, Guid configurationId)
|
||||
{
|
||||
return plugin.ManagedConfigurationId == configurationId && plugin.Id != configurationId;
|
||||
}
|
||||
|
||||
private async Task CopyStartupLogPath()
|
||||
{
|
||||
|
||||
@@ -63,15 +63,35 @@
|
||||
</MudStack>
|
||||
</MudTd>
|
||||
<MudTd>
|
||||
@if (context is { IsInternal: false, Type: not PluginType.CONFIGURATION })
|
||||
{
|
||||
var isEnabled = this.SettingsManager.IsPluginEnabled(context);
|
||||
<MudTooltip Text="@(isEnabled ? T("Disable plugin") : T("Enable plugin"))">
|
||||
<MudSwitch T="bool" Value="@isEnabled" ValueChanged="@(_ => this.PluginActivationStateChanged(context))"/>
|
||||
</MudTooltip>
|
||||
}
|
||||
<MudStack Row="true" Spacing="1" AlignItems="AlignItems.Center">
|
||||
@if (context is { IsInternal: false, Type: not PluginType.CONFIGURATION })
|
||||
{
|
||||
var isEnabled = this.SettingsManager.IsPluginEnabled(context);
|
||||
<MudTooltip Text="@(isEnabled ? T("Disable plugin") : T("Enable plugin"))">
|
||||
<MudSwitch T="bool" Value="@isEnabled" ValueChanged="@(_ => this.PluginActivationStateChanged(context))"/>
|
||||
</MudTooltip>
|
||||
}
|
||||
|
||||
@if (context is { IsInternal: false } && !string.IsNullOrWhiteSpace(context.SourceURL))
|
||||
{
|
||||
var sourceUrl = context.SourceURL;
|
||||
var isSendingMail = IsSendingMail(sourceUrl);
|
||||
if(isSendingMail)
|
||||
{
|
||||
<MudTooltip Text="@T("Send a mail")">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.Email" Href="@sourceUrl" Target="_blank" Size="Size.Medium"/>
|
||||
</MudTooltip>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudTooltip Text="@T("Open website")">
|
||||
<MudIconButton Icon="@Icons.Material.Filled.OpenInBrowser" Href="@sourceUrl" Target="_blank" Size="Size.Medium"/>
|
||||
</MudTooltip>
|
||||
}
|
||||
}
|
||||
</MudStack>
|
||||
</MudTd>
|
||||
</RowTemplate>
|
||||
</MudTable>
|
||||
</InnerScrolling>
|
||||
</div>
|
||||
</div>
|
||||
@@ -49,6 +49,8 @@ public partial class Plugins : MSGComponentBase
|
||||
await this.SettingsManager.StoreSettings();
|
||||
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
|
||||
}
|
||||
|
||||
private static bool IsSendingMail(string sourceUrl) => sourceUrl.TrimStart().StartsWith("mailto:", StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
#region Overrides of MSGComponentBase
|
||||
|
||||
@@ -63,4 +65,4 @@ public partial class Plugins : MSGComponentBase
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,9 @@
|
||||
</MudText>
|
||||
<PreviewExperimental ApplyInnerScrollingFix="true"/>
|
||||
|
||||
<ProviderSelection @bind-ProviderSettings="@this.providerSettings"/>
|
||||
<CascadingValue Value="Components.WRITER">
|
||||
<ProviderSelection @bind-ProviderSettings="@this.providerSettings"/>
|
||||
</CascadingValue>
|
||||
<InnerScrolling>
|
||||
<ChildContent>
|
||||
<MudTextField
|
||||
@@ -55,4 +57,4 @@
|
||||
UserAttributes="@USER_INPUT_ATTRIBUTES"/>
|
||||
</FooterContent>
|
||||
</InnerScrolling>
|
||||
</div>
|
||||
</div>
|
||||
@@ -24,13 +24,21 @@ VERSION = "1.0.0"
|
||||
-- The type of the plugin:
|
||||
TYPE = "CONFIGURATION"
|
||||
|
||||
-- True when this plugin is deployed by an enterprise configuration server:
|
||||
DEPLOYED_USING_CONFIG_SERVER = false
|
||||
|
||||
-- The authors of the plugin:
|
||||
AUTHORS = {"<Company Name>"}
|
||||
|
||||
-- The support contact for the plugin:
|
||||
SUPPORT_CONTACT = "<IT Department of Company Name>"
|
||||
|
||||
-- The source URL for the plugin:
|
||||
-- The source URL for the plugin. Can be a HTTP(S) URL or an mailto link.
|
||||
-- You may link to an internal documentation page, a Git repository, or
|
||||
-- to a support or wiki page.
|
||||
--
|
||||
-- A mailto link could look like:
|
||||
-- SOURCE_URL = "mailto:helpdesk@company.org?subject=Help"
|
||||
SOURCE_URL = "<Any internal Git repository>"
|
||||
|
||||
-- The categories for the plugin:
|
||||
@@ -49,26 +57,40 @@ CONFIG = {}
|
||||
CONFIG["LLM_PROVIDERS"] = {}
|
||||
|
||||
-- An example of a configuration for a self-hosted server:
|
||||
CONFIG["LLM_PROVIDERS"][#CONFIG["LLM_PROVIDERS"]+1] = {
|
||||
["Id"] = "00000000-0000-0000-0000-000000000000",
|
||||
["InstanceName"] = "<user-friendly name for the combination of server and model>",
|
||||
["UsedLLMProvider"] = "SELF_HOSTED",
|
||||
|
||||
-- Allowed values for Host are: LM_STUDIO, LLAMACPP, OLLAMA, and VLLM
|
||||
["Host"] = "OLLAMA",
|
||||
["Hostname"] = "<https address of the server>",
|
||||
|
||||
-- Optional: Additional parameters for the API.
|
||||
-- Please refer to the documentation of the selected host for details.
|
||||
-- Might be something like ... \"temperature\": 0.5 ... for one parameter.
|
||||
-- Could be something like ... \"temperature\": 0.5, \"max_tokens\": 1000 ... for multiple parameters.
|
||||
-- Please do not add the enclosing curly braces {} here. Also, no trailing comma is allowed.
|
||||
["AdditionalJsonApiParameters"] = "",
|
||||
["Model"] = {
|
||||
["Id"] = "<the model ID>",
|
||||
["DisplayName"] = "<user-friendly name of the model>",
|
||||
}
|
||||
}
|
||||
-- CONFIG["LLM_PROVIDERS"][#CONFIG["LLM_PROVIDERS"]+1] = {
|
||||
-- ["Id"] = "00000000-0000-0000-0000-000000000000",
|
||||
-- ["InstanceName"] = "<user-friendly name for the combination of server and model>",
|
||||
-- ["UsedLLMProvider"] = "SELF_HOSTED",
|
||||
--
|
||||
-- -- Allowed values for Host are: LM_STUDIO, LLAMACPP, OLLAMA, and VLLM
|
||||
-- ["Host"] = "OLLAMA",
|
||||
-- ["Hostname"] = "<https address of the server>",
|
||||
--
|
||||
-- -- Optional: Additional parameters for the API.
|
||||
-- -- Please refer to the documentation of the selected host for details.
|
||||
-- -- Might be something like ... \"temperature\": 0.5 ... for one parameter.
|
||||
-- -- Could be something like ... \"temperature\": 0.5, \"max_tokens\": 1000 ... for multiple parameters.
|
||||
-- -- Please do not add the enclosing curly braces {} here. Also, no trailing comma is allowed.
|
||||
-- ["AdditionalJsonApiParameters"] = "",
|
||||
--
|
||||
-- -- Optional: Hugging Face inference provider. Only relevant for UsedLLMProvider = HUGGINGFACE.
|
||||
-- -- Allowed values are: CEREBRAS, NEBIUS_AI_STUDIO, SAMBANOVA, NOVITA, HYPERBOLIC, TOGETHER_AI, FIREWORKS, HF_INFERENCE_API
|
||||
-- -- ["HFInferenceProvider"] = "NOVITA",
|
||||
--
|
||||
-- -- Optional: Encrypted API key for cloud providers or secured on-premise models.
|
||||
-- -- The API key must be encrypted using the enterprise encryption secret.
|
||||
-- -- Format: "ENC:v1:<base64-encoded encrypted data>"
|
||||
-- -- The encryption secret must be configured via:
|
||||
-- -- Windows Registry: HKEY_CURRENT_USER\Software\github\MindWork AI Studio\Enterprise IT\config_encryption_secret
|
||||
-- -- Environment variable: MINDWORK_AI_STUDIO_ENTERPRISE_CONFIG_ENCRYPTION_SECRET
|
||||
-- -- You can export an encrypted API key from an existing provider using the export button in the settings.
|
||||
-- -- ["APIKey"] = "ENC:v1:<base64-encoded encrypted data>",
|
||||
--
|
||||
-- ["Model"] = {
|
||||
-- ["Id"] = "<the model ID>",
|
||||
-- ["DisplayName"] = "<user-friendly name of the model>",
|
||||
-- }
|
||||
-- }
|
||||
|
||||
-- Transcription providers for voice-to-text functionality:
|
||||
CONFIG["TRANSCRIPTION_PROVIDERS"] = {}
|
||||
@@ -82,6 +104,10 @@ CONFIG["TRANSCRIPTION_PROVIDERS"] = {}
|
||||
-- -- Allowed values for Host are: LM_STUDIO, LLAMACPP, OLLAMA, VLLM, and WHISPER_CPP
|
||||
-- ["Host"] = "WHISPER_CPP",
|
||||
-- ["Hostname"] = "<https address of the server>",
|
||||
--
|
||||
-- -- Optional: Encrypted API key (see LLM_PROVIDERS example for details)
|
||||
-- -- ["APIKey"] = "ENC:v1:<base64-encoded encrypted data>",
|
||||
--
|
||||
-- ["Model"] = {
|
||||
-- ["Id"] = "<the model ID>",
|
||||
-- ["DisplayName"] = "<user-friendly name of the model>",
|
||||
@@ -100,6 +126,10 @@ CONFIG["EMBEDDING_PROVIDERS"] = {}
|
||||
-- -- Allowed values for Host are: LM_STUDIO, LLAMACPP, OLLAMA, and VLLM
|
||||
-- ["Host"] = "OLLAMA",
|
||||
-- ["Hostname"] = "<https address of the server>",
|
||||
--
|
||||
-- -- Optional: Encrypted API key (see LLM_PROVIDERS example for details)
|
||||
-- -- ["APIKey"] = "ENC:v1:<base64-encoded encrypted data>",
|
||||
--
|
||||
-- ["Model"] = {
|
||||
-- ["Id"] = "<the model ID, e.g., nomic-embed-text>",
|
||||
-- ["DisplayName"] = "<user-friendly name of the model>",
|
||||
@@ -120,6 +150,10 @@ CONFIG["SETTINGS"] = {}
|
||||
-- Allowed values are: true, false
|
||||
-- CONFIG["SETTINGS"]["DataApp.AllowUserToAddProvider"] = false
|
||||
|
||||
-- Configure whether administration settings are visible in the UI:
|
||||
-- Allowed values are: true, false
|
||||
-- CONFIG["SETTINGS"]["DataApp.ShowAdminSettings"] = true
|
||||
|
||||
-- Configure the visibility of preview features:
|
||||
-- Allowed values are: NONE, RELEASE_CANDIDATE, BETA, ALPHA, PROTOTYPE, EXPERIMENTAL
|
||||
-- Please note:
|
||||
@@ -132,6 +166,11 @@ CONFIG["SETTINGS"] = {}
|
||||
-- Examples are PRE_WRITER_MODE_2024, PRE_RAG_2024, PRE_DOCUMENT_ANALYSIS_2025.
|
||||
-- CONFIG["SETTINGS"]["DataApp.EnabledPreviewFeatures"] = { "PRE_RAG_2024", "PRE_DOCUMENT_ANALYSIS_2025" }
|
||||
|
||||
-- Configure the preselected provider.
|
||||
-- It must be one of the provider IDs defined in CONFIG["LLM_PROVIDERS"].
|
||||
-- Please note: using an empty string ("") will lock the preselected provider selection, even though no valid preselected provider is found.
|
||||
-- CONFIG["SETTINGS"]["DataApp.PreselectedProvider"] = "00000000-0000-0000-0000-000000000000"
|
||||
|
||||
-- Configure the preselected profile.
|
||||
-- It must be one of the profile IDs defined in CONFIG["PROFILES"].
|
||||
-- Please note: using an empty string ("") will lock the preselected profile selection, even though no valid preselected profile is found.
|
||||
@@ -143,64 +182,121 @@ CONFIG["SETTINGS"] = {}
|
||||
-- Please note: using an empty string ("") will lock the selection and disable dictation/transcription.
|
||||
-- CONFIG["SETTINGS"]["DataApp.UseTranscriptionProvider"] = "00000000-0000-0000-0000-000000000000"
|
||||
|
||||
-- Configure which assistants should be hidden from the UI.
|
||||
-- Allowed values are:
|
||||
-- GRAMMAR_SPELLING_ASSISTANT, ICON_FINDER_ASSISTANT, REWRITE_ASSISTANT,
|
||||
-- TRANSLATION_ASSISTANT, AGENDA_ASSISTANT, CODING_ASSISTANT,
|
||||
-- TEXT_SUMMARIZER_ASSISTANT, EMAIL_ASSISTANT, LEGAL_CHECK_ASSISTANT,
|
||||
-- SYNONYMS_ASSISTANT, MY_TASKS_ASSISTANT, JOB_POSTING_ASSISTANT,
|
||||
-- BIAS_DAY_ASSISTANT, ERI_ASSISTANT, DOCUMENT_ANALYSIS_ASSISTANT,
|
||||
-- I18N_ASSISTANT
|
||||
-- CONFIG["SETTINGS"]["DataApp.HiddenAssistants"] = { "ERI_ASSISTANT", "I18N_ASSISTANT" }
|
||||
|
||||
-- Configure a global shortcut for starting and stopping dictation.
|
||||
--
|
||||
-- The format follows the Rust and Tauri conventions. Especially,
|
||||
-- when you want to use the CTRL key on Windows (or the CMD key on macOS),
|
||||
-- please use "CmdOrControl" as the key name. All parts of the shortcut
|
||||
-- must be separated by a plus sign (+).
|
||||
--
|
||||
-- Examples are: "CmdOrControl+Shift+D", "Alt+F9", "F8"
|
||||
-- CONFIG["SETTINGS"]["DataApp.ShortcutVoiceRecording"] = "CmdOrControl+1"
|
||||
|
||||
-- Example chat templates for this configuration:
|
||||
CONFIG["CHAT_TEMPLATES"] = {}
|
||||
|
||||
-- A simple example chat template:
|
||||
CONFIG["CHAT_TEMPLATES"][#CONFIG["CHAT_TEMPLATES"]+1] = {
|
||||
["Id"] = "00000000-0000-0000-0000-000000000000",
|
||||
["Name"] = "<user-friendly name of the chat template>",
|
||||
["SystemPrompt"] = "You are <Company Name>'s helpful AI assistant for <Department Name>. Your task is ...",
|
||||
["PredefinedUserPrompt"] = "Please help me with ...",
|
||||
["AllowProfileUsage"] = true,
|
||||
["ExampleConversation"] = {
|
||||
{
|
||||
-- Allowed values are: USER, AI, SYSTEM
|
||||
["Role"] = "USER",
|
||||
["Content"] = "Hello! Can you help me with a quick task?"
|
||||
},
|
||||
{
|
||||
-- Allowed values are: USER, AI, SYSTEM
|
||||
["Role"] = "AI",
|
||||
["Content"] = "Of course. What do you need?"
|
||||
}
|
||||
}
|
||||
}
|
||||
-- CONFIG["CHAT_TEMPLATES"][#CONFIG["CHAT_TEMPLATES"]+1] = {
|
||||
-- ["Id"] = "00000000-0000-0000-0000-000000000000",
|
||||
-- ["Name"] = "<user-friendly name of the chat template>",
|
||||
-- ["SystemPrompt"] = "You are <Company Name>'s helpful AI assistant for <Department Name>. Your task is ...",
|
||||
-- ["PredefinedUserPrompt"] = "Please help me with ...",
|
||||
-- ["AllowProfileUsage"] = true,
|
||||
-- ["ExampleConversation"] = {
|
||||
-- {
|
||||
-- -- Allowed values are: USER, AI, SYSTEM
|
||||
-- ["Role"] = "USER",
|
||||
-- ["Content"] = "Hello! Can you help me with a quick task?"
|
||||
-- },
|
||||
-- {
|
||||
-- -- Allowed values are: USER, AI, SYSTEM
|
||||
-- ["Role"] = "AI",
|
||||
-- ["Content"] = "Of course. What do you need?"
|
||||
-- }
|
||||
-- }
|
||||
-- }
|
||||
|
||||
-- An example chat template with file attachments:
|
||||
-- This template automatically attaches specified files when the user selects it.
|
||||
CONFIG["CHAT_TEMPLATES"][#CONFIG["CHAT_TEMPLATES"]+1] = {
|
||||
["Id"] = "00000000-0000-0000-0000-000000000001",
|
||||
["Name"] = "Document Analysis Template",
|
||||
["SystemPrompt"] = "You are an expert document analyst. Please analyze the attached documents and provide insights.",
|
||||
["PredefinedUserPrompt"] = "Please analyze the attached company guidelines and summarize the key points.",
|
||||
["AllowProfileUsage"] = true,
|
||||
-- Optional: Pre-attach files that will be automatically included when using this template.
|
||||
-- These files will be loaded when the user selects this chat template.
|
||||
-- Note: File paths must be absolute paths and accessible to all users.
|
||||
["FileAttachments"] = {
|
||||
"G:\\Company\\Documents\\Guidelines.pdf",
|
||||
"G:\\Company\\Documents\\CompanyPolicies.docx"
|
||||
},
|
||||
["ExampleConversation"] = {
|
||||
{
|
||||
["Role"] = "USER",
|
||||
["Content"] = "I have attached the company documents for analysis."
|
||||
},
|
||||
{
|
||||
["Role"] = "AI",
|
||||
["Content"] = "Thank you. I'll analyze the documents and provide a comprehensive summary."
|
||||
}
|
||||
}
|
||||
}
|
||||
-- CONFIG["CHAT_TEMPLATES"][#CONFIG["CHAT_TEMPLATES"]+1] = {
|
||||
-- ["Id"] = "00000000-0000-0000-0000-000000000001",
|
||||
-- ["Name"] = "Document Analysis Template",
|
||||
-- ["SystemPrompt"] = "You are an expert document analyst. Please analyze the attached documents and provide insights.",
|
||||
-- ["PredefinedUserPrompt"] = "Please analyze the attached company guidelines and summarize the key points.",
|
||||
-- ["AllowProfileUsage"] = true,
|
||||
-- -- Optional: Pre-attach files that will be automatically included when using this template.
|
||||
-- -- These files will be loaded when the user selects this chat template.
|
||||
-- -- Note: File paths must be absolute paths and accessible to all users.
|
||||
-- ["FileAttachments"] = {
|
||||
-- "G:\\Company\\Documents\\Guidelines.pdf",
|
||||
-- "G:\\Company\\Documents\\CompanyPolicies.docx"
|
||||
-- },
|
||||
-- ["ExampleConversation"] = {
|
||||
-- {
|
||||
-- ["Role"] = "USER",
|
||||
-- ["Content"] = "I have attached the company documents for analysis."
|
||||
-- },
|
||||
-- {
|
||||
-- ["Role"] = "AI",
|
||||
-- ["Content"] = "Thank you. I'll analyze the documents and provide a comprehensive summary."
|
||||
-- }
|
||||
-- }
|
||||
-- }
|
||||
|
||||
-- Document analysis policies for this configuration:
|
||||
CONFIG["DOCUMENT_ANALYSIS_POLICIES"] = {}
|
||||
|
||||
-- An example document analysis policy:
|
||||
-- CONFIG["DOCUMENT_ANALYSIS_POLICIES"][#CONFIG["DOCUMENT_ANALYSIS_POLICIES"]+1] = {
|
||||
-- ["Id"] = "00000000-0000-0000-0000-000000000000",
|
||||
-- ["PolicyName"] = "Compliance Summary Policy",
|
||||
-- ["PolicyDescription"] = "Summarizes compliance-relevant clauses, obligations, and deadlines found in provided documents.",
|
||||
--
|
||||
-- ["AnalysisRules"] = [===[
|
||||
-- Focus on compliance obligations, deadlines, and required actions.
|
||||
-- Ignore marketing content and high-level summaries.
|
||||
-- Flag any ambiguous or missing information.
|
||||
-- ]===],
|
||||
--
|
||||
-- ["OutputRules"] = [===[
|
||||
-- Provide a Markdown report with headings for Obligations, Deadlines,
|
||||
-- and Open Questions.
|
||||
-- ]===],
|
||||
--
|
||||
-- -- Optional: minimum provider confidence required for this policy.
|
||||
-- -- Allowed values are: NONE, VERY_LOW, LOW, MODERATE, MEDIUM, HIGH
|
||||
-- ["MinimumProviderConfidence"] = "MEDIUM",
|
||||
--
|
||||
-- -- Optional: preselect a provider or profile by ID.
|
||||
-- -- The IDs must exist in CONFIG["LLM_PROVIDERS"] or CONFIG["PROFILES"].
|
||||
-- ["PreselectedProvider"] = "00000000-0000-0000-0000-000000000000",
|
||||
-- ["PreselectedProfile"] = "00000000-0000-0000-0000-000000000000",
|
||||
--
|
||||
-- -- Optional: hide the policy definition section in the UI.
|
||||
-- -- When set to true, users will only see the document selection interface
|
||||
-- -- and cannot view or modify the policy settings.
|
||||
-- -- This is useful for enterprise configurations where policy details should remain hidden.
|
||||
-- -- Allowed values are: true, false (default: false)
|
||||
-- ["HidePolicyDefinition"] = false
|
||||
-- }
|
||||
|
||||
-- Profiles for this configuration:
|
||||
CONFIG["PROFILES"] = {}
|
||||
|
||||
-- A simple profile template:
|
||||
CONFIG["PROFILES"][#CONFIG["PROFILES"]+1] = {
|
||||
["Id"] = "00000000-0000-0000-0000-000000000000",
|
||||
["Name"] = "<user-friendly name of the profile>",
|
||||
["NeedToKnow"] = "I like to cook in my free time. My favorite meal is ...",
|
||||
["Actions"] = "Please always ensure the portion size is ..."
|
||||
}
|
||||
-- CONFIG["PROFILES"][#CONFIG["PROFILES"]+1] = {
|
||||
-- ["Id"] = "00000000-0000-0000-0000-000000000000",
|
||||
-- ["Name"] = "<user-friendly name of the profile>",
|
||||
-- ["NeedToKnow"] = "I like to cook in my free time. My favorite meal is ...",
|
||||
-- ["Actions"] = "Please always ensure the portion size is ..."
|
||||
-- }
|
||||
Loaded 100 of 235 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user