From 364941701b36e64bf3605c9b6598c9bc79f484ed Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Mon, 7 Apr 2025 18:37:23 +0200 Subject: [PATCH 01/93] Migrated linux-arm from QEMU to use ARM-based runner (#390) --- .github/workflows/build-and-release.yml | 219 ++---------------------- 1 file changed, 13 insertions(+), 206 deletions(-) diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index 6191915f..18d965e8 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -102,6 +102,12 @@ jobs: dotnet_runtime: 'linux-x64' dotnet_name_postfix: '-x86_64-unknown-linux-gnu' tauri_bundle: 'appimage deb updater' + + - platform: 'ubuntu-22.04-arm' # for ARM-based Linux + rust_target: 'aarch64-unknown-linux-gnu' + dotnet_runtime: 'linux-arm64' + dotnet_name_postfix: '-aarch64-unknown-linux-gnu' + tauri_bundle: 'deb' - platform: 'windows-latest' # for x86-based Windows rust_target: 'x86_64-pc-windows-msvc' @@ -277,6 +283,12 @@ jobs: sudo apt-get update sudo apt-get install -y libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf + - name: Setup dependencies (Ubuntu-specific, ARM) + if: matrix.platform == 'ubuntu-22.04-arm' && contains(matrix.rust_target, 'aarch64') + run: | + sudo apt-get update + sudo apt-get install -y libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf + - name: Setup Tauri (Unix) if: matrix.platform != 'windows-latest' run: | @@ -399,216 +411,11 @@ jobs: runtime/target/${{ matrix.rust_target }}/release/bundle/appimage/mind-work-ai-studio*AppImage.tar.gz* if-no-files-found: error retention-days: ${{ env.RETENTION_INTERMEDIATE_ASSETS }} - - build_linux_arm64: - name: Build app (linux-arm64) - runs-on: ubuntu-22.04 - needs: read_metadata - env: - SKIP: false # allows disabling this long-running job temporarily - - steps: - - name: Checkout repository - uses: actions/checkout@v4 - with: - lfs: false - - - name: Read and format metadata - if: ${{ env.SKIP != 'true' }} - id: metadata - run: | - # Read the lines of the metadata file: - app_version=$(sed -n '1p' metadata.txt) - build_time=$(sed -n '2p' metadata.txt) - build_number=$(sed -n '3p' metadata.txt) - - # Next line is the .NET SDK version. - # The format is '8.0.205 (commit 3e1383b780)'. - # We extract only the version number, though: - dotnet_sdk_version=$(sed -n '4p' metadata.txt | sed 's/[^0-9.]*\([0-9.]*\).*/\1/') - - # Next line is the .NET runtime version. - # The format is '8.0.5 (commit 087e15321b)'. - # We extract only the version number, though: - dotnet_runtime_version=$(sed -n '5p' metadata.txt | sed 's/[^0-9.]*\([0-9.]*\).*/\1/') - - # Next line is the Rust version. - # The format is '1.78.0 (commit 9b00956e5)'. - # We extract only the version number, though: - rust_version=$(sed -n '6p' metadata.txt | sed 's/[^0-9.]*\([0-9.]*\).*/\1/') - - # Next line is the MudBlazor version: - mud_blazor_version=$(sed -n '7p' metadata.txt) - - # Next line is the Tauri version: - tauri_version=$(sed -n '8p' metadata.txt) - - # Format the app version: - formatted_app_version="v${app_version}" - - # Write the metadata to the environment: - echo "APP_VERSION=${app_version}" >> $GITHUB_ENV - echo "FORMATTED_APP_VERSION=${formatted_app_version}" >> $GITHUB_ENV - echo "BUILD_TIME=${build_time}" >> $GITHUB_ENV - echo "BUILD_NUMBER=${build_number}" >> $GITHUB_ENV - echo "DOTNET_SDK_VERSION=${dotnet_sdk_version}" >> $GITHUB_ENV - echo "DOTNET_RUNTIME_VERSION=${dotnet_runtime_version}" >> $GITHUB_ENV - echo "RUST_VERSION=${rust_version}" >> $GITHUB_ENV - echo "MUD_BLAZOR_VERSION=${mud_blazor_version}" >> $GITHUB_ENV - echo "TAURI_VERSION=${tauri_version}" >> $GITHUB_ENV - - # Log the metadata: - echo "App version: '${formatted_app_version}'" - echo "Build time: '${build_time}'" - echo "Build number: '${build_number}'" - echo ".NET SDK version: '${dotnet_sdk_version}'" - echo ".NET runtime version: '${dotnet_runtime_version}'" - echo "Rust version: '${rust_version}'" - echo "MudBlazor version: '${mud_blazor_version}'" - echo "Tauri version: '${tauri_version}'" - - - name: Setup .NET - if: ${{ env.SKIP != 'true' }} - uses: actions/setup-dotnet@v4 - with: - dotnet-version: ${{ env.DOTNET_SDK_VERSI }} - cache: true - cache-dependency-path: 'app/MindWork AI Studio/packages.lock.json' - - - name: Build .NET project - if: ${{ env.SKIP != 'true' }} - run: | - cd "app/MindWork AI Studio" - dotnet publish --configuration release --runtime linux-arm64 --disable-build-servers --force --output ../../publish/dotnet - - - name: Move & rename the .NET artifact - if: ${{ env.SKIP != 'true' }} - run: | - mkdir -p "app/MindWork AI Studio/bin/dist" - cd publish/dotnet - mv mindworkAIStudio "../../app/MindWork AI Studio/bin/dist/mindworkAIStudioServer-aarch64-unknown-linux-gnu" - - - name: Cache linux arm64 runner image - if: ${{ env.SKIP != 'true' }} - uses: actions/cache@v4 - id: linux_arm_cache - with: - path: ${{ runner.temp }}/linux_arm_qemu_cache.img - key: target-linux-arm64-rust-${{ env.RUST_VERSION }} - - - name: Build linux arm runner image - uses: pguyot/arm-runner-action@v2 - id: build-linux-arm-runner - if: ${{ steps.linux_arm_cache.outputs.cache-hit != 'true' && env.SKIP != 'true' }} - env: - RUST_VERSION: ${{ env.RUST_VERSION }} - TAURI_VERSION: ${{ env.TAURI_VERSION }} - - with: - base_image: dietpi:rpi_armv8_bullseye - cpu: cortex-a53 - image_additional_mb: 8000 # ~ 8GB - optimize_image: false - shell: /bin/bash - commands: | - # Rust complains (rightly) that $HOME doesn't match eid home: - export HOME=/root - - # Workaround to CI worker being stuck on Updating crates.io index: - export CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse - - # Update and upgrade the system: - apt-get update --yes --allow-releaseinfo-change - apt-get upgrade --yes - apt-get autoremove --yes - apt-get install curl wget --yes - - # Install Rust: - curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- --default-toolchain none -y - source "$HOME/.cargo/env" - rustup toolchain install $RUST_VERSION - - # Install build tools and tauri-cli requirements: - apt-get install --yes libwebkit2gtk-4.0-dev build-essential libssl-dev libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev - - # Setup Tauri: - cargo install --version 1.6.2 tauri-cli - - - name: Add the built runner image to the cache - if: ${{ steps.linux_arm_cache.outputs.cache-hit != 'true' && env.SKIP != 'true' }} - run: | - mv ${{ steps.build-linux-arm-runner.outputs.image }} ${{ runner.temp }}/linux_arm_qemu_cache.img - - # - # This step does not work, because we start a VM with qemu to run the build. - # - #- name: Delete previous artifact, which may exist due to caching (Linux - Debian Package) - # if: ${{ env.SKIP != 'true' }} - # run: | - # rm -f result/target/aarch64-unknown-linux-gnu/release/bundle/deb/mind-work-ai-studio_*.deb - - - name: Build Tauri project - if: ${{ env.SKIP != 'true' }} - uses: pguyot/arm-runner-action@v2 - id: build-linux-arm - - with: - base_image: file://${{ runner.temp }}/linux_arm_qemu_cache.img - cpu: cortex-a53 - optimize_image: false - copy_artifact_path: runtime - copy_artifact_dest: result - bind_mount_repository: true - - # - # We do not need to set the PRIVATE_PUBLISH_KEY and PRIVATE_PUBLISH_KEY_PASSWORD here, - # because we cannot produce the AppImage on arm64. Only the AppImage supports the automatic - # update feature. The Debian package does not support this feature. - # - #PRIVATE_PUBLISH_KEY: ${{ secrets.PRIVATE_PUBLISH_KEY }} - #PRIVATE_PUBLISH_KEY_PASSWORD: ${{ secrets.PRIVATE_PUBLISH_KEY_PASSWORD }} - # - - shell: /bin/bash - commands: | - # Delete all previous artifacts, which may exist due to caching: - rm -f runtime/target/aarch64-unknown-linux-gnu/release/bundle/deb/mind-work-ai-studio_*.deb - - export HOME=/root - export CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse - source "$HOME/.cargo/env" - cd runtime - - # Try to restore the Rust cache from previous build: - mkdir -p /rust-cache/target - rm -fr target - cp -Rp /rust-cache/target target - - cargo tauri build --target aarch64-unknown-linux-gnu --bundles deb - - # Save the built libraries for the next job: - rm -fr /rust-cache/target - cp -Rp target /rust-cache - - - name: Update the runner image to cache the Rust runtime build - if: ${{ env.SKIP != 'true' }} - run: | - mv ${{ steps.build-linux-arm.outputs.image }} $RUNNER_TEMP/linux_arm_qemu_cache.img - - - name: Upload artifact (Linux - Debian Package) - if: ${{ env.SKIP != 'true' && startsWith(github.ref, 'refs/tags/v') }} - uses: actions/upload-artifact@v4 - with: - name: MindWork AI Studio (Linux - deb linux-arm64) - path: | - result/target/aarch64-unknown-linux-gnu/release/bundle/deb/mind-work-ai-studio_*.deb - if-no-files-found: warn - retention-days: ${{ env.RETENTION_INTERMEDIATE_ASSETS }} create_release: name: Prepare & create release runs-on: ubuntu-latest - needs: [build_main, read_metadata, build_linux_arm64] + needs: [build_main, read_metadata] if: startsWith(github.ref, 'refs/tags/v') steps: - name: Create artifact directory From 19935769035c35d42f77d61197d15332f70dd57e Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Mon, 7 Apr 2025 19:36:24 +0200 Subject: [PATCH 02/93] Initialize plugin hot reload in static constructor (#391) --- .../Tools/PluginSystem/PluginFactory.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs index 0cb87178..18d2023b 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs @@ -16,8 +16,8 @@ public static partial class PluginFactory private static readonly string PLUGINS_ROOT = Path.Join(DATA_DIR, "plugins"); private static readonly string INTERNAL_PLUGINS_ROOT = Path.Join(PLUGINS_ROOT, ".internal"); - - private static readonly FileSystemWatcher HOT_RELOAD_WATCHER = new(PLUGINS_ROOT); + + private static readonly FileSystemWatcher HOT_RELOAD_WATCHER; private static readonly List AVAILABLE_PLUGINS = []; @@ -25,6 +25,14 @@ public static partial class PluginFactory /// A list of all available plugins. /// public static IReadOnlyCollection AvailablePlugins => AVAILABLE_PLUGINS; + + static PluginFactory() + { + if (!Directory.Exists(PLUGINS_ROOT)) + Directory.CreateDirectory(PLUGINS_ROOT); + + HOT_RELOAD_WATCHER = new(PLUGINS_ROOT); + } /// /// Try to load all plugins from the plugins directory. From ac731f6f785bd7fbe623469fef1788ed39a4ca77 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Mon, 7 Apr 2025 19:43:42 +0200 Subject: [PATCH 03/93] Updated v0.9.39 metadata (#392) --- app/MindWork AI Studio/Components/Changelog.Logs.cs | 2 +- app/MindWork AI Studio/wwwroot/changelog/v0.9.39.md | 2 +- metadata.txt | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/MindWork AI Studio/Components/Changelog.Logs.cs b/app/MindWork AI Studio/Components/Changelog.Logs.cs index 5033852d..7079404f 100644 --- a/app/MindWork AI Studio/Components/Changelog.Logs.cs +++ b/app/MindWork AI Studio/Components/Changelog.Logs.cs @@ -13,7 +13,7 @@ public partial class Changelog public static readonly Log[] LOGS = [ - new (214, "v0.9.39, build 214 (2025-04-06 10:18 UTC)", "v0.9.39.md"), + new (214, "v0.9.39, build 214 (2025-04-07 17:39 UTC)", "v0.9.39.md"), new (213, "v0.9.38, build 213 (2025-03-17 18:18 UTC)", "v0.9.38.md"), new (212, "v0.9.37, build 212 (2025-03-16 20:32 UTC)", "v0.9.37.md"), new (211, "v0.9.36, build 211 (2025-03-15 10:42 UTC)", "v0.9.36.md"), diff --git a/app/MindWork AI Studio/wwwroot/changelog/v0.9.39.md b/app/MindWork AI Studio/wwwroot/changelog/v0.9.39.md index 4582c534..c6ed55d4 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v0.9.39.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v0.9.39.md @@ -1,4 +1,4 @@ -# v0.9.39, build 214 (2025-04-06 10:18 UTC) +# v0.9.39, build 214 (2025-04-07 17:39 UTC) - Added UI for error handling to display any LLM provider issues. - Added a feature flag for the plugin system. This flag is disabled by default and can be enabled inside the app settings. Please note that this feature is still in development; there are no plugins available yet. - Added the Lua library we use for the plugin system to the about page. diff --git a/metadata.txt b/metadata.txt index 29068a95..6e9cd50c 100644 --- a/metadata.txt +++ b/metadata.txt @@ -1,9 +1,9 @@ 0.9.39 -2025-04-06 10:18:02 UTC +2025-04-07 17:39:09 UTC 214 9.0.104 (commit 2750432faa) 9.0.3 (commit 831d23e561) 1.86.0 (commit 05f9846f8) 8.5.1 1.8.1 -a3ec2bbe7b7, release +19935769035, release From 693fe5e161615b18c8394b8a207f159376839d20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peer=20Sch=C3=BCtt?= Date: Thu, 10 Apr 2025 12:12:23 +0200 Subject: [PATCH 04/93] Create provider for the models from Alibaba Cloud (#394) --- .../AlibabaCloud/ProviderAlibabaCloud.cs | 172 ++++++++++++++++++ .../Provider/LLMProviders.cs | 1 + .../Provider/LLMProvidersExtensions.cs | 11 ++ 3 files changed, 184 insertions(+) create mode 100644 app/MindWork AI Studio/Provider/AlibabaCloud/ProviderAlibabaCloud.cs diff --git a/app/MindWork AI Studio/Provider/AlibabaCloud/ProviderAlibabaCloud.cs b/app/MindWork AI Studio/Provider/AlibabaCloud/ProviderAlibabaCloud.cs new file mode 100644 index 00000000..d8a633c2 --- /dev/null +++ b/app/MindWork AI Studio/Provider/AlibabaCloud/ProviderAlibabaCloud.cs @@ -0,0 +1,172 @@ +using System.Net.Http.Headers; +using System.Runtime.CompilerServices; +using System.Text; +using System.Text.Json; + +using AIStudio.Chat; +using AIStudio.Provider.OpenAI; +using AIStudio.Settings; + +namespace AIStudio.Provider.AlibabaCloud; + +public sealed class ProviderAlibabaCloud(ILogger logger) : BaseProvider("https://dashscope-intl.aliyuncs.com/compatible-mode/v1/", logger) +{ + + #region Implementation of IProvider + + /// + public override string Id => LLMProviders.ALIBABA_CLOUD.ToName(); + + /// + public override string InstanceName { get; set; } = "AlibabaCloud"; + + /// + public override async IAsyncEnumerable StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default) + { + // Get the API key: + var requestedSecret = await RUST_SERVICE.GetAPIKey(this); + if(!requestedSecret.Success) + yield break; + + // Prepare the system prompt: + var systemPrompt = new Message + { + Role = "system", + Content = chatThread.PrepareSystemPrompt(settingsManager, chatThread, this.logger), + }; + + // Prepare the AlibabaCloud HTTP chat request: + var alibabaCloudChatRequest = JsonSerializer.Serialize(new ChatRequest + { + Model = chatModel.Id, + + // Build the messages: + // - First of all the system prompt + // - Then none-empty user and AI messages + Messages = [systemPrompt, ..chatThread.Blocks.Where(n => n.ContentType is ContentType.TEXT && !string.IsNullOrWhiteSpace((n.Content as ContentText)?.Text)).Select(n => new Message + { + Role = n.Role switch + { + ChatRole.USER => "user", + ChatRole.AI => "assistant", + ChatRole.AGENT => "assistant", + ChatRole.SYSTEM => "system", + + _ => "user", + }, + + Content = n.Content switch + { + ContentText text => text.Text, + _ => string.Empty, + } + }).ToList()], + Stream = true, + }, JSON_SERIALIZER_OPTIONS); + + async Task RequestBuilder() + { + // Build the HTTP post request: + var request = new HttpRequestMessage(HttpMethod.Post, "chat/completions"); + + // Set the authorization header: + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await requestedSecret.Secret.Decrypt(ENCRYPTION)); + + // Set the content: + request.Content = new StringContent(alibabaCloudChatRequest, Encoding.UTF8, "application/json"); + return request; + } + + await foreach (var content in this.StreamChatCompletionInternal("AlibabaCloud", RequestBuilder, token)) + yield return content; + } + + #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously + /// + public override async IAsyncEnumerable StreamImageCompletion(Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default) + { + yield break; + } + #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously + + /// + public override Task> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) + { + var additionalModels = new[] + { + new Model("qwq-plus", "QwQ plus"), // reasoning model + new Model("qwen-max-latest", "Qwen-Max (Latest)"), + new Model("qwen-plus-latest", "Qwen-Plus (Latest)"), + new Model("qwen-turbo-latest", "Qwen-Turbo (Latest)"), + new Model("qvq-max", "QVQ Max"), // visual reasoning model + new Model("qvq-max-latest", "QVQ Max (Latest)"), // visual reasoning model + new Model("qwen-vl-max", "Qwen-VL Max"), // text generation model that can understand and process images + new Model("qwen-vl-plus", "Qwen-VL Plus"), // text generation model that can understand and process images + new Model("qwen-mt-plus", "Qwen-MT Plus"), // machine translation + new Model("qwen-mt-turbo", "Qwen-MT Turbo"), // machine translation + + //Open source + new Model("qwen2.5-14b-instruct-1m", "Qwen2.5 14b 1m context"), + new Model("qwen2.5-7b-instruct-1m", "Qwen2.5 7b 1m context"), + new Model("qwen2.5-72b-instruct", "Qwen2.5 72b"), + new Model("qwen2.5-32b-instruct", "Qwen2.5 32b"), + new Model("qwen2.5-14b-instruct", "Qwen2.5 14b"), + new Model("qwen2.5-7b-instruct", "Qwen2.5 7b"), + new Model("qwen2.5-omni-7b", "Qwen2.5-Omni 7b"), // omni-modal understanding and generation model + new Model("qwen2.5-vl-72b-instruct", "Qwen2.5-VL 72b"), + new Model("qwen2.5-vl-32b-instruct", "Qwen2.5-VL 32b"), + new Model("qwen2.5-vl-7b-instruct", "Qwen2.5-VL 7b"), + new Model("qwen2.5-vl-3b-instruct", "Qwen2.5-VL 3b"), + }; + + return this.LoadModels(["q"],token, apiKeyProvisional).ContinueWith(t => t.Result.Concat(additionalModels).OrderBy(x => x.Id).AsEnumerable(), token); + } + + /// + public override Task> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default) + { + return Task.FromResult(Enumerable.Empty()); + } + + /// + public override Task> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default) + { + + var additionalModels = new[] + { + new Model("text-embedding-v3", "text-embedding-v3"), + }; + + return this.LoadModels(["text-embedding-"], token, apiKeyProvisional).ContinueWith(t => t.Result.Concat(additionalModels).OrderBy(x => x.Id).AsEnumerable(), token); + } + + #endregion + + + private async Task> LoadModels(string[] prefixes, CancellationToken token, string? apiKeyProvisional = null) + { + var secretKey = apiKeyProvisional switch + { + not null => apiKeyProvisional, + _ => await RUST_SERVICE.GetAPIKey(this) switch + { + { Success: true } result => await result.Secret.Decrypt(ENCRYPTION), + _ => null, + } + }; + + if (secretKey is null) + return []; + + using var request = new HttpRequestMessage(HttpMethod.Get, "models"); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", secretKey); + + using var response = await this.httpClient.SendAsync(request, token); + if(!response.IsSuccessStatusCode) + return []; + + var modelResponse = await response.Content.ReadFromJsonAsync(token); + return modelResponse.Data.Where(model => prefixes.Any(prefix => model.Id.StartsWith(prefix, StringComparison.InvariantCulture))); + } + +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Provider/LLMProviders.cs b/app/MindWork AI Studio/Provider/LLMProviders.cs index 92c0873b..1c65835f 100644 --- a/app/MindWork AI Studio/Provider/LLMProviders.cs +++ b/app/MindWork AI Studio/Provider/LLMProviders.cs @@ -13,6 +13,7 @@ public enum LLMProviders GOOGLE = 7, X = 8, DEEP_SEEK = 11, + ALIBABA_CLOUD = 12, FIREWORKS = 5, GROQ = 6, diff --git a/app/MindWork AI Studio/Provider/LLMProvidersExtensions.cs b/app/MindWork AI Studio/Provider/LLMProvidersExtensions.cs index 7fb80e7f..c516ce7d 100644 --- a/app/MindWork AI Studio/Provider/LLMProvidersExtensions.cs +++ b/app/MindWork AI Studio/Provider/LLMProvidersExtensions.cs @@ -1,3 +1,4 @@ +using AIStudio.Provider.AlibabaCloud; using AIStudio.Provider.Anthropic; using AIStudio.Provider.DeepSeek; using AIStudio.Provider.Fireworks; @@ -32,6 +33,7 @@ public static class LLMProvidersExtensions LLMProviders.GOOGLE => "Google", LLMProviders.X => "xAI", LLMProviders.DEEP_SEEK => "DeepSeek", + LLMProviders.ALIBABA_CLOUD => "ALIBABA_CLOUD", LLMProviders.GROQ => "Groq", LLMProviders.FIREWORKS => "Fireworks.ai", @@ -75,6 +77,8 @@ public static class LLMProvidersExtensions LLMProviders.DEEP_SEEK => Confidence.CHINA_NO_TRAINING.WithRegion("Asia").WithSources("https://cdn.deepseek.com/policies/en-US/deepseek-open-platform-terms-of-service.html").WithLevel(settingsManager.GetConfiguredConfidenceLevel(llmProvider)), + LLMProviders.ALIBABA_CLOUD => Confidence.CHINA_NO_TRAINING.WithRegion("Asia").WithSources("https://www.alibabacloud.com/help/en/model-studio/support/faq-about-alibaba-cloud-model-studio").WithLevel(settingsManager.GetConfiguredConfidenceLevel(llmProvider)), + LLMProviders.SELF_HOSTED => Confidence.SELF_HOSTED.WithLevel(settingsManager.GetConfiguredConfidenceLevel(llmProvider)), LLMProviders.HELMHOLTZ => Confidence.GDPR_NO_TRAINING.WithRegion("Europe, Germany").WithSources("https://helmholtz.cloud/services/?serviceID=d7d5c597-a2f6-4bd1-b71e-4d6499d98570").WithLevel(settingsManager.GetConfiguredConfidenceLevel(llmProvider)), @@ -97,6 +101,7 @@ public static class LLMProvidersExtensions LLMProviders.MISTRAL => true, LLMProviders.GOOGLE => true, LLMProviders.HELMHOLTZ => true, + LLMProviders.ALIBABA_CLOUD => true, // // Providers that do not support embeddings: @@ -150,6 +155,7 @@ public static class LLMProvidersExtensions LLMProviders.GOOGLE => new ProviderGoogle(logger) { InstanceName = instanceName }, LLMProviders.X => new ProviderX(logger) { InstanceName = instanceName }, LLMProviders.DEEP_SEEK => new ProviderDeepSeek(logger) { InstanceName = instanceName }, + LLMProviders.ALIBABA_CLOUD => new ProviderAlibabaCloud(logger) { InstanceName = instanceName }, LLMProviders.GROQ => new ProviderGroq(logger) { InstanceName = instanceName }, LLMProviders.FIREWORKS => new ProviderFireworks(logger) { InstanceName = instanceName }, @@ -177,6 +183,7 @@ public static class LLMProvidersExtensions LLMProviders.GOOGLE => "https://console.cloud.google.com/", LLMProviders.X => "https://accounts.x.ai/sign-up", LLMProviders.DEEP_SEEK => "https://platform.deepseek.com/sign_up", + LLMProviders.ALIBABA_CLOUD => "https://account.alibabacloud.com/register/intl_register.htm", LLMProviders.GROQ => "https://console.groq.com/", LLMProviders.FIREWORKS => "https://fireworks.ai/login", @@ -197,6 +204,7 @@ public static class LLMProvidersExtensions LLMProviders.GOOGLE => "https://console.cloud.google.com/billing", LLMProviders.FIREWORKS => "https://fireworks.ai/account/billing", LLMProviders.DEEP_SEEK => "https://platform.deepseek.com/usage", + LLMProviders.ALIBABA_CLOUD => "https://usercenter2-intl.aliyun.com/billing", _ => string.Empty, }; @@ -211,6 +219,7 @@ public static class LLMProvidersExtensions LLMProviders.FIREWORKS => true, LLMProviders.GOOGLE => true, LLMProviders.DEEP_SEEK => true, + LLMProviders.ALIBABA_CLOUD => true, _ => false, }; @@ -253,6 +262,7 @@ public static class LLMProvidersExtensions LLMProviders.GOOGLE => true, LLMProviders.X => true, LLMProviders.DEEP_SEEK => true, + LLMProviders.ALIBABA_CLOUD => true, LLMProviders.GROQ => true, LLMProviders.FIREWORKS => true, @@ -272,6 +282,7 @@ public static class LLMProvidersExtensions LLMProviders.GOOGLE => true, LLMProviders.X => true, LLMProviders.DEEP_SEEK => true, + LLMProviders.ALIBABA_CLOUD => true, LLMProviders.GROQ => true, LLMProviders.FIREWORKS => true, From 56e99ffabaaec1d247e5b30d884bd19354d0f30d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peer=20Sch=C3=BCtt?= Date: Fri, 11 Apr 2025 08:14:20 +0200 Subject: [PATCH 05/93] Miscellaneous changes (#395) --- app/MindWork AI Studio/Dialogs/ProviderDialog.razor | 2 +- app/MindWork AI Studio/Provider/DeepSeek/ProviderDeepSeek.cs | 2 +- app/MindWork AI Studio/Provider/LLMProvidersExtensions.cs | 2 +- app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/MindWork AI Studio/Dialogs/ProviderDialog.razor b/app/MindWork AI Studio/Dialogs/ProviderDialog.razor index 108f7375..a7d5706e 100644 --- a/app/MindWork AI Studio/Dialogs/ProviderDialog.razor +++ b/app/MindWork AI Studio/Dialogs/ProviderDialog.razor @@ -9,7 +9,7 @@ @foreach (LLMProviders provider in Enum.GetValues(typeof(LLMProviders))) { - @provider + @provider.ToName() } Create account diff --git a/app/MindWork AI Studio/Provider/DeepSeek/ProviderDeepSeek.cs b/app/MindWork AI Studio/Provider/DeepSeek/ProviderDeepSeek.cs index b4ce57a0..a906ebad 100644 --- a/app/MindWork AI Studio/Provider/DeepSeek/ProviderDeepSeek.cs +++ b/app/MindWork AI Studio/Provider/DeepSeek/ProviderDeepSeek.cs @@ -76,7 +76,7 @@ public sealed class ProviderDeepSeek(ILogger logger) : BaseProvider("https://api return request; } - await foreach (var content in this.StreamChatCompletionInternal("Helmholtz", RequestBuilder, token)) + await foreach (var content in this.StreamChatCompletionInternal("DeepSeek", RequestBuilder, token)) yield return content; } diff --git a/app/MindWork AI Studio/Provider/LLMProvidersExtensions.cs b/app/MindWork AI Studio/Provider/LLMProvidersExtensions.cs index c516ce7d..72f06777 100644 --- a/app/MindWork AI Studio/Provider/LLMProvidersExtensions.cs +++ b/app/MindWork AI Studio/Provider/LLMProvidersExtensions.cs @@ -33,7 +33,7 @@ public static class LLMProvidersExtensions LLMProviders.GOOGLE => "Google", LLMProviders.X => "xAI", LLMProviders.DEEP_SEEK => "DeepSeek", - LLMProviders.ALIBABA_CLOUD => "ALIBABA_CLOUD", + LLMProviders.ALIBABA_CLOUD => "Alibaba Cloud", LLMProviders.GROQ => "Groq", LLMProviders.FIREWORKS => "Fireworks.ai", diff --git a/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs b/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs index ed092174..b45bb16a 100644 --- a/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs +++ b/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs @@ -122,7 +122,7 @@ public sealed class ProviderOpenAI(ILogger logger) : BaseProvider("https://api.o /// public override Task> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) { - return this.LoadModels(["gpt-", "o1-"], token, apiKeyProvisional); + return this.LoadModels(["gpt-", "o1-", "o3-", "o4-"], token, apiKeyProvisional); } /// From 712ed2938f77eb693fb392c429d247f6fec097a0 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Fri, 11 Apr 2025 08:30:11 +0200 Subject: [PATCH 06/93] Added the next changelog (#396) --- app/MindWork AI Studio/wwwroot/changelog/v0.9.40.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 app/MindWork AI Studio/wwwroot/changelog/v0.9.40.md diff --git a/app/MindWork AI Studio/wwwroot/changelog/v0.9.40.md b/app/MindWork AI Studio/wwwroot/changelog/v0.9.40.md new file mode 100644 index 00000000..c1bb8fc8 --- /dev/null +++ b/app/MindWork AI Studio/wwwroot/changelog/v0.9.40.md @@ -0,0 +1,5 @@ +# v0.9.40, build 215 (2025-04-xx xx:xx UTC) +- Added support for the announced OpenAI `o4` models. We hope that these `o4` models will be usable by the well-known chat completion API instead of the new responses API, though. AI Studio cannot use the new responses API right now. +- Added Alibaba Cloud as a new provider. +- Improved the provider selection by showing the name of the provider in the provider selection instead of its identifier. +- Fixed an issue where OpenAI `o3` models were not shown in the model selection. From 1ff27fe21fa04433f0ad5f56ede15b9116be3622 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peer=20Sch=C3=BCtt?= Date: Fri, 11 Apr 2025 14:31:10 +0200 Subject: [PATCH 07/93] Added Hugging Face inference provider (#397) --- app/MindWork AI Studio.sln.DotSettings | 3 + .../Settings/SettingsPanelProviders.razor | 2 +- .../Settings/SettingsPanelProviders.razor.cs | 1 + .../Dialogs/ProviderDialog.razor | 60 +++++---- .../Dialogs/ProviderDialog.razor.cs | 21 +++- app/MindWork AI Studio/Provider/Confidence.cs | 6 +- .../HuggingFace/HFInstanceProvider.cs | 18 +++ .../HFInstanceProviderExtensions.cs | 43 +++++++ .../HuggingFace/ProviderHuggingFace.cs | 115 ++++++++++++++++++ .../Provider/LLMProviders.cs | 1 + .../Provider/LLMProvidersExtensions.cs | 30 ++++- app/MindWork AI Studio/Settings/Provider.cs | 5 +- .../Tools/Validation/ProviderValidation.cs | 13 +- 13 files changed, 281 insertions(+), 37 deletions(-) create mode 100644 app/MindWork AI Studio/Provider/HuggingFace/HFInstanceProvider.cs create mode 100644 app/MindWork AI Studio/Provider/HuggingFace/HFInstanceProviderExtensions.cs create mode 100644 app/MindWork AI Studio/Provider/HuggingFace/ProviderHuggingFace.cs diff --git a/app/MindWork AI Studio.sln.DotSettings b/app/MindWork AI Studio.sln.DotSettings index 44079898..6cbaf40c 100644 --- a/app/MindWork AI Studio.sln.DotSettings +++ b/app/MindWork AI Studio.sln.DotSettings @@ -3,14 +3,17 @@ EDI ERI GWDG + HF LLM LM MSG RAG UI + URL True True True + True True True True \ No newline at end of file diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelProviders.razor b/app/MindWork AI Studio/Components/Settings/SettingsPanelProviders.razor index 51db26b4..9ab1a81c 100644 --- a/app/MindWork AI Studio/Components/Settings/SettingsPanelProviders.razor +++ b/app/MindWork AI Studio/Components/Settings/SettingsPanelProviders.razor @@ -29,7 +29,7 @@ @context.Num @context.InstanceName - @context.UsedLLMProvider + @context.UsedLLMProvider.ToName() @if (context.UsedLLMProvider is not LLMProviders.SELF_HOSTED) { diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelProviders.razor.cs b/app/MindWork AI Studio/Components/Settings/SettingsPanelProviders.razor.cs index 6ca2f6ca..5a71925b 100644 --- a/app/MindWork AI Studio/Components/Settings/SettingsPanelProviders.razor.cs +++ b/app/MindWork AI Studio/Components/Settings/SettingsPanelProviders.razor.cs @@ -65,6 +65,7 @@ public partial class SettingsPanelProviders : SettingsPanelBase { x => x.IsSelfHosted, provider.IsSelfHosted }, { x => x.IsEditing, true }, { x => x.DataHost, provider.Host }, + { x => x.HfInstanceProviderId, provider.HFInstanceProvider }, }; var dialogReference = await this.DialogService.ShowAsync("Edit LLM Provider", dialogParameters, DialogOptions.FULLSCREEN); diff --git a/app/MindWork AI Studio/Dialogs/ProviderDialog.razor b/app/MindWork AI Studio/Dialogs/ProviderDialog.razor index a7d5706e..5998f12f 100644 --- a/app/MindWork AI Studio/Dialogs/ProviderDialog.razor +++ b/app/MindWork AI Studio/Dialogs/ProviderDialog.razor @@ -1,4 +1,5 @@ @using AIStudio.Provider +@using AIStudio.Provider.HuggingFace @using AIStudio.Provider.SelfHosted @@ -28,38 +29,55 @@ InputType="InputType.Password" Validation="@this.providerValidation.ValidatingAPIKey" /> - - - - @foreach (Host host in Enum.GetValues(typeof(Host))) - { - @host.Name() - } - + @if (this.DataLLMProvider.IsHostnameNeeded()) + { + + } + + @if (this.DataLLMProvider.IsHostNeeded()) + { + + @foreach (Host host in Enum.GetValues(typeof(Host))) + { + @host.Name() + } + + } + + @if (this.DataLLMProvider.IsHFInstanceProviderNeeded()) + { + + @foreach (HFInstanceProvider instanceProvider in Enum.GetValues(typeof(HFInstanceProvider))) + { + @instanceProvider.ToName() + } + + + Please double-check if your model name matches the curl specifications provided by the instance provider. If it doesn't, you might get a Not Found error when trying to use the model. Here's a curl example. + } @if (this.DataLLMProvider.IsLLMModelProvidedManually()) { - Show available models + Show available models + /// The HFInstanceProvider to use, e.g., CEREBRAS. + /// + [Parameter] + public HFInstanceProvider HfInstanceProviderId { get; set; } = HFInstanceProvider.NONE; + /// /// Is this provider self-hosted? /// @@ -122,10 +129,16 @@ public partial class ProviderDialog : ComponentBase, ISecretId Id = this.DataId, InstanceName = this.DataInstanceName, UsedLLMProvider = this.DataLLMProvider, - Model = this.DataLLMProvider is LLMProviders.FIREWORKS ? new Model(this.dataManuallyModel, null) : this.DataModel, + Model = this.DataLLMProvider switch + { + LLMProviders.FIREWORKS => new Model(this.dataManuallyModel, null), + LLMProviders.HUGGINGFACE => new Model(this.dataManuallyModel, null), + _ => this.DataModel + }, IsSelfHosted = this.DataLLMProvider is LLMProviders.SELF_HOSTED, Hostname = cleanedHostname.EndsWith('/') ? cleanedHostname[..^1] : cleanedHostname, Host = this.DataHost, + HFInstanceProvider = this.HfInstanceProviderId, }; } @@ -146,8 +159,8 @@ public partial class ProviderDialog : ComponentBase, ISecretId { this.dataEditingPreviousInstanceName = this.DataInstanceName.ToLowerInvariant(); - // When using Fireworks, we must copy the model name: - if (this.DataLLMProvider is LLMProviders.FIREWORKS) + // When using Fireworks or Hugging Face, we must copy the model name: + if (this.DataLLMProvider is LLMProviders.FIREWORKS or LLMProviders.HUGGINGFACE) this.dataManuallyModel = this.DataModel.Id; // @@ -230,7 +243,7 @@ public partial class ProviderDialog : ComponentBase, ISecretId private string? ValidateManuallyModel(string manuallyModel) { - if (this.DataLLMProvider is LLMProviders.FIREWORKS && string.IsNullOrWhiteSpace(manuallyModel)) + if ((this.DataLLMProvider is LLMProviders.FIREWORKS or LLMProviders.HUGGINGFACE) && string.IsNullOrWhiteSpace(manuallyModel)) return "Please enter a model name."; return null; diff --git a/app/MindWork AI Studio/Provider/Confidence.cs b/app/MindWork AI Studio/Provider/Confidence.cs index 087ac4e0..a49c2781 100644 --- a/app/MindWork AI Studio/Provider/Confidence.cs +++ b/app/MindWork AI Studio/Provider/Confidence.cs @@ -35,10 +35,10 @@ public sealed record Confidence """, }; - public static readonly Confidence USA_NOT_TRUSTED = new() + public static readonly Confidence USA_HUB = new() { - Level = ConfidenceLevel.UNTRUSTED, - Description = "The provider operates its service from the USA and is subject to **U.S. jurisdiction**. In case of suspicion, authorities in the USA can access your data. The provider's terms of service state that **all your data can be used by the provider at will.**", + Level = ConfidenceLevel.UNKNOWN, + Description = "The provider operates its service from the USA and is subject to **U.S. jurisdiction**. In case of suspicion, authorities in the USA can access your data. Please inform yourself about the use of your data. We do not know if your data is safe.", }; public static readonly Confidence UNKNOWN = new() diff --git a/app/MindWork AI Studio/Provider/HuggingFace/HFInstanceProvider.cs b/app/MindWork AI Studio/Provider/HuggingFace/HFInstanceProvider.cs new file mode 100644 index 00000000..63221290 --- /dev/null +++ b/app/MindWork AI Studio/Provider/HuggingFace/HFInstanceProvider.cs @@ -0,0 +1,18 @@ +namespace AIStudio.Provider.HuggingFace; + +/// +/// Enum for instance providers that Hugging Face supports. +/// +public enum HFInstanceProvider +{ + NONE, + + CEREBRAS, + NEBIUS_AI_STUDIO, + SAMBANOVA, + NOVITA, + HYPERBOLIC, + TOGETHER_AI, + FIREWORKS, + HF_INFERENCE_API, +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Provider/HuggingFace/HFInstanceProviderExtensions.cs b/app/MindWork AI Studio/Provider/HuggingFace/HFInstanceProviderExtensions.cs new file mode 100644 index 00000000..b0d81fba --- /dev/null +++ b/app/MindWork AI Studio/Provider/HuggingFace/HFInstanceProviderExtensions.cs @@ -0,0 +1,43 @@ +namespace AIStudio.Provider.HuggingFace; + +public static class HFInstanceProviderExtensions +{ + public static string Endpoints(this HFInstanceProvider provider, Model model) => provider switch + { + HFInstanceProvider.CEREBRAS => "cerebras/v1/", + HFInstanceProvider.NEBIUS_AI_STUDIO => "nebius/v1/", + HFInstanceProvider.SAMBANOVA => "sambanova/v1/", + HFInstanceProvider.NOVITA => "novita/v3/openai/", + HFInstanceProvider.HYPERBOLIC => "hyperbolic/v1/", + HFInstanceProvider.TOGETHER_AI => "together/v1/", + HFInstanceProvider.FIREWORKS => "fireworks-ai/inference/v1/", + HFInstanceProvider.HF_INFERENCE_API => $"hf-inference/models/{model.ToString()}/v1/", + _ => string.Empty, + }; + + public static string EndpointsId(this HFInstanceProvider provider) => provider switch + { + HFInstanceProvider.CEREBRAS => "cerebras", + HFInstanceProvider.NEBIUS_AI_STUDIO => "nebius", + HFInstanceProvider.SAMBANOVA => "sambanova", + HFInstanceProvider.NOVITA => "novita", + HFInstanceProvider.HYPERBOLIC => "hyperbolic", + HFInstanceProvider.TOGETHER_AI => "together", + HFInstanceProvider.FIREWORKS => "fireworks", + HFInstanceProvider.HF_INFERENCE_API => "hf-inference", + _ => string.Empty, + }; + + public static string ToName(this HFInstanceProvider provider) => provider switch + { + HFInstanceProvider.CEREBRAS => "Cerebras", + HFInstanceProvider.NEBIUS_AI_STUDIO => "Nebius AI Studio", + HFInstanceProvider.SAMBANOVA => "Sambanova", + HFInstanceProvider.NOVITA => "Novita", + HFInstanceProvider.HYPERBOLIC => "Hyperbolic", + HFInstanceProvider.TOGETHER_AI => "Together AI", + HFInstanceProvider.FIREWORKS => "Fireworks AI", + HFInstanceProvider.HF_INFERENCE_API => "Hugging Face Inference API", + _ => string.Empty, + }; +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Provider/HuggingFace/ProviderHuggingFace.cs b/app/MindWork AI Studio/Provider/HuggingFace/ProviderHuggingFace.cs new file mode 100644 index 00000000..25f2baae --- /dev/null +++ b/app/MindWork AI Studio/Provider/HuggingFace/ProviderHuggingFace.cs @@ -0,0 +1,115 @@ +using System.Net.Http.Headers; +using System.Runtime.CompilerServices; +using System.Text; +using System.Text.Json; + +using AIStudio.Chat; +using AIStudio.Provider.OpenAI; +using AIStudio.Settings; + +namespace AIStudio.Provider.HuggingFace; + +public sealed class ProviderHuggingFace : BaseProvider +{ + public ProviderHuggingFace(ILogger logger, HFInstanceProvider hfProvider, Model model) : base($"https://router.huggingface.co/{hfProvider.Endpoints(model)}", logger) + { + logger.LogInformation($"We use the instance provider '{hfProvider}'. Thus we use the base URL 'https://router.huggingface.co/{hfProvider.Endpoints(model)}'."); + } + + #region Implementation of IProvider + + /// + public override string Id => LLMProviders.HUGGINGFACE.ToName(); + + /// + public override string InstanceName { get; set; } = "HuggingFace"; + + /// + public override async IAsyncEnumerable StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default) + { + // Get the API key: + var requestedSecret = await RUST_SERVICE.GetAPIKey(this); + if(!requestedSecret.Success) + yield break; + + // Prepare the system prompt: + var systemPrompt = new Message + { + Role = "system", + Content = chatThread.PrepareSystemPrompt(settingsManager, chatThread, this.logger), + }; + + // Prepare the HuggingFace HTTP chat request: + var huggingfaceChatRequest = JsonSerializer.Serialize(new ChatRequest + { + Model = chatModel.Id, + + // Build the messages: + // - First of all the system prompt + // - Then none-empty user and AI messages + Messages = [systemPrompt, ..chatThread.Blocks.Where(n => n.ContentType is ContentType.TEXT && !string.IsNullOrWhiteSpace((n.Content as ContentText)?.Text)).Select(n => new Message + { + Role = n.Role switch + { + ChatRole.USER => "user", + ChatRole.AI => "assistant", + ChatRole.AGENT => "assistant", + ChatRole.SYSTEM => "system", + + _ => "user", + }, + + Content = n.Content switch + { + ContentText text => text.Text, + _ => string.Empty, + } + }).ToList()], + Stream = true, + }, JSON_SERIALIZER_OPTIONS); + + async Task RequestBuilder() + { + // Build the HTTP post request: + var request = new HttpRequestMessage(HttpMethod.Post, "chat/completions"); + + // Set the authorization header: + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await requestedSecret.Secret.Decrypt(ENCRYPTION)); + + // Set the content: + request.Content = new StringContent(huggingfaceChatRequest, Encoding.UTF8, "application/json"); + return request; + } + + await foreach (var content in this.StreamChatCompletionInternal("HuggingFace", RequestBuilder, token)) + yield return content; + } + + #pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously + /// + public override async IAsyncEnumerable StreamImageCompletion(Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, [EnumeratorCancellation] CancellationToken token = default) + { + yield break; + } + #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously + + /// + public override Task> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) + { + return Task.FromResult(Enumerable.Empty()); + } + + /// + public override Task> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default) + { + return Task.FromResult(Enumerable.Empty()); + } + + /// + public override Task> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default) + { + return Task.FromResult(Enumerable.Empty()); + } + + #endregion +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Provider/LLMProviders.cs b/app/MindWork AI Studio/Provider/LLMProviders.cs index 1c65835f..118d68aa 100644 --- a/app/MindWork AI Studio/Provider/LLMProviders.cs +++ b/app/MindWork AI Studio/Provider/LLMProviders.cs @@ -17,6 +17,7 @@ public enum LLMProviders FIREWORKS = 5, GROQ = 6, + HUGGINGFACE = 13, SELF_HOSTED = 4, diff --git a/app/MindWork AI Studio/Provider/LLMProvidersExtensions.cs b/app/MindWork AI Studio/Provider/LLMProvidersExtensions.cs index 72f06777..8abb0bd4 100644 --- a/app/MindWork AI Studio/Provider/LLMProvidersExtensions.cs +++ b/app/MindWork AI Studio/Provider/LLMProvidersExtensions.cs @@ -6,6 +6,7 @@ using AIStudio.Provider.Google; using AIStudio.Provider.Groq; using AIStudio.Provider.GWDG; using AIStudio.Provider.Helmholtz; +using AIStudio.Provider.HuggingFace; using AIStudio.Provider.Mistral; using AIStudio.Provider.OpenAI; using AIStudio.Provider.SelfHosted; @@ -37,6 +38,7 @@ public static class LLMProvidersExtensions LLMProviders.GROQ => "Groq", LLMProviders.FIREWORKS => "Fireworks.ai", + LLMProviders.HUGGINGFACE => "Hugging Face", LLMProviders.SELF_HOSTED => "Self-hosted", @@ -56,7 +58,10 @@ public static class LLMProvidersExtensions { LLMProviders.NONE => Confidence.NONE, - LLMProviders.FIREWORKS => Confidence.USA_NOT_TRUSTED.WithRegion("America, U.S.").WithSources("https://fireworks.ai/terms-of-service").WithLevel(settingsManager.GetConfiguredConfidenceLevel(llmProvider)), + LLMProviders.FIREWORKS => Confidence.USA_HUB.WithRegion("America, U.S.").WithSources("https://fireworks.ai/terms-of-service").WithLevel(settingsManager.GetConfiguredConfidenceLevel(llmProvider)), + + // Not trusted, because huggingface only routes you to a third-party-provider and we can't make sure they do not use your data + LLMProviders.HUGGINGFACE => Confidence.USA_HUB.WithRegion("America, U.S.").WithSources("https://huggingface.co/terms-of-service").WithLevel(settingsManager.GetConfiguredConfidenceLevel(llmProvider)), LLMProviders.OPEN_AI => Confidence.USA_NO_TRAINING.WithRegion("America, U.S.").WithSources( "https://platform.openai.com/docs/models/default-usage-policies-by-endpoint", @@ -112,6 +117,7 @@ public static class LLMProvidersExtensions LLMProviders.X => false, LLMProviders.GWDG => false, LLMProviders.DEEP_SEEK => false, + LLMProviders.HUGGINGFACE => false, // // Self-hosted providers are treated as a special case anyway. @@ -129,7 +135,7 @@ public static class LLMProvidersExtensions /// The provider instance. public static IProvider CreateProvider(this AIStudio.Settings.Provider providerSettings, ILogger logger) { - return providerSettings.UsedLLMProvider.CreateProvider(providerSettings.InstanceName, providerSettings.Host, providerSettings.Hostname, logger); + return providerSettings.UsedLLMProvider.CreateProvider(providerSettings.InstanceName, providerSettings.Host, providerSettings.Hostname, providerSettings.Model, providerSettings.HFInstanceProvider ,logger); } /// @@ -140,10 +146,10 @@ public static class LLMProvidersExtensions /// The provider instance. public static IProvider CreateProvider(this EmbeddingProvider embeddingProviderSettings, ILogger logger) { - return embeddingProviderSettings.UsedLLMProvider.CreateProvider(embeddingProviderSettings.Name, embeddingProviderSettings.Host, embeddingProviderSettings.Hostname, logger); + return embeddingProviderSettings.UsedLLMProvider.CreateProvider(embeddingProviderSettings.Name, embeddingProviderSettings.Host, embeddingProviderSettings.Hostname, embeddingProviderSettings.Model, HFInstanceProvider.NONE,logger); } - private static IProvider CreateProvider(this LLMProviders provider, string instanceName, Host host, string hostname, ILogger logger) + private static IProvider CreateProvider(this LLMProviders provider, string instanceName, Host host, string hostname, Model model, HFInstanceProvider instanceProvider , ILogger logger) { try { @@ -159,6 +165,7 @@ public static class LLMProvidersExtensions LLMProviders.GROQ => new ProviderGroq(logger) { InstanceName = instanceName }, LLMProviders.FIREWORKS => new ProviderFireworks(logger) { InstanceName = instanceName }, + LLMProviders.HUGGINGFACE => new ProviderHuggingFace(logger, instanceProvider, model) { InstanceName = instanceName }, LLMProviders.SELF_HOSTED => new ProviderSelfHosted(logger, host, hostname) { InstanceName = instanceName }, @@ -187,6 +194,7 @@ public static class LLMProvidersExtensions LLMProviders.GROQ => "https://console.groq.com/", LLMProviders.FIREWORKS => "https://fireworks.ai/login", + LLMProviders.HUGGINGFACE => "https://huggingface.co/login", LLMProviders.HELMHOLTZ => "https://sdlaml.pages.jsc.fz-juelich.de/ai/guides/blablador_api_access/#step-1-register-on-gitlab", LLMProviders.GWDG => "https://docs.hpc.gwdg.de/services/saia/index.html#api-request", @@ -205,6 +213,7 @@ public static class LLMProvidersExtensions LLMProviders.FIREWORKS => "https://fireworks.ai/account/billing", LLMProviders.DEEP_SEEK => "https://platform.deepseek.com/usage", LLMProviders.ALIBABA_CLOUD => "https://usercenter2-intl.aliyun.com/billing", + LLMProviders.HUGGINGFACE => "https://huggingface.co/settings/billing", _ => string.Empty, }; @@ -220,19 +229,22 @@ public static class LLMProvidersExtensions LLMProviders.GOOGLE => true, LLMProviders.DEEP_SEEK => true, LLMProviders.ALIBABA_CLOUD => true, + LLMProviders.HUGGINGFACE => true, _ => false, }; - public static string GetModelsOverviewURL(this LLMProviders provider) => provider switch + public static string GetModelsOverviewURL(this LLMProviders provider, HFInstanceProvider instanceProvider) => provider switch { LLMProviders.FIREWORKS => "https://fireworks.ai/models?show=Serverless", + LLMProviders.HUGGINGFACE => $"https://huggingface.co/models?inference_provider={instanceProvider.EndpointsId()}", _ => string.Empty, }; public static bool IsLLMModelProvidedManually(this LLMProviders provider) => provider switch { LLMProviders.FIREWORKS => true, + LLMProviders.HUGGINGFACE => true, _ => false, }; @@ -268,6 +280,7 @@ public static class LLMProvidersExtensions LLMProviders.FIREWORKS => true, LLMProviders.HELMHOLTZ => true, LLMProviders.GWDG => true, + LLMProviders.HUGGINGFACE => true, LLMProviders.SELF_HOSTED => host is Host.OLLAMA, @@ -288,6 +301,7 @@ public static class LLMProvidersExtensions LLMProviders.FIREWORKS => true, LLMProviders.HELMHOLTZ => true, LLMProviders.GWDG => true, + LLMProviders.HUGGINGFACE => true, _ => false, }; @@ -317,4 +331,10 @@ public static class LLMProvidersExtensions return true; } + + public static bool IsHFInstanceProviderNeeded(this LLMProviders provider) => provider switch + { + LLMProviders.HUGGINGFACE => true, + _ => false, + }; } \ No newline at end of file diff --git a/app/MindWork AI Studio/Settings/Provider.cs b/app/MindWork AI Studio/Settings/Provider.cs index b349016d..6aefc5b5 100644 --- a/app/MindWork AI Studio/Settings/Provider.cs +++ b/app/MindWork AI Studio/Settings/Provider.cs @@ -1,7 +1,7 @@ using System.Text.Json.Serialization; using AIStudio.Provider; - +using AIStudio.Provider.HuggingFace; using Host = AIStudio.Provider.SelfHosted.Host; namespace AIStudio.Settings; @@ -24,7 +24,8 @@ public readonly record struct Provider( Model Model, bool IsSelfHosted = false, string Hostname = "http://localhost:1234", - Host Host = Host.NONE) : ISecretId + Host Host = Host.NONE, + HFInstanceProvider HFInstanceProvider = HFInstanceProvider.NONE) : ISecretId { #region Overrides of ValueType diff --git a/app/MindWork AI Studio/Tools/Validation/ProviderValidation.cs b/app/MindWork AI Studio/Tools/Validation/ProviderValidation.cs index be7b16be..12d27b43 100644 --- a/app/MindWork AI Studio/Tools/Validation/ProviderValidation.cs +++ b/app/MindWork AI Studio/Tools/Validation/ProviderValidation.cs @@ -1,5 +1,5 @@ using AIStudio.Provider; - +using AIStudio.Provider.HuggingFace; using Host = AIStudio.Provider.SelfHosted.Host; namespace AIStudio.Tools.Validation; @@ -93,4 +93,15 @@ public sealed class ProviderValidation return null; } + + public string? ValidatingHFInstanceProvider(HFInstanceProvider instanceProvider) + { + if(this.GetProvider() is not LLMProviders.HUGGINGFACE) + return null; + + if (instanceProvider is HFInstanceProvider.NONE) + return "Please select an Hugging Face instance provider."; + + return null; + } } \ No newline at end of file From 80c58ea7495864109a2d30b786c4196dee13fc94 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sat, 12 Apr 2025 10:08:36 +0200 Subject: [PATCH 08/93] Improved the developer experience by adding a tolerant enum converter (#398) --- .../Settings/SettingsManager.cs | 2 +- .../Settings/TolerantEnumConverter.cs | 54 +++++++++++++++++++ .../wwwroot/changelog/v0.9.40.md | 1 + 3 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 app/MindWork AI Studio/Settings/TolerantEnumConverter.cs diff --git a/app/MindWork AI Studio/Settings/SettingsManager.cs b/app/MindWork AI Studio/Settings/SettingsManager.cs index ec74cab8..dcbe48cb 100644 --- a/app/MindWork AI Studio/Settings/SettingsManager.cs +++ b/app/MindWork AI Studio/Settings/SettingsManager.cs @@ -20,7 +20,7 @@ public sealed class SettingsManager(ILogger logger) private static readonly JsonSerializerOptions JSON_OPTIONS = new() { WriteIndented = true, - Converters = { new JsonStringEnumConverter() }, + Converters = { new TolerantEnumConverter() }, }; private readonly ILogger logger = logger; diff --git a/app/MindWork AI Studio/Settings/TolerantEnumConverter.cs b/app/MindWork AI Studio/Settings/TolerantEnumConverter.cs new file mode 100644 index 00000000..c3f8fcd0 --- /dev/null +++ b/app/MindWork AI Studio/Settings/TolerantEnumConverter.cs @@ -0,0 +1,54 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace AIStudio.Settings; + +/// +/// Tries to convert a JSON string to an enum value. +/// +/// +/// When the target enum value does not exist, the value will be the default value. +/// This converter handles enum values as property names and values. +/// +public sealed class TolerantEnumConverter : JsonConverter +{ + private static readonly ILogger LOG = Program.LOGGER_FACTORY.CreateLogger(); + + public override bool CanConvert(Type typeToConvert) => typeToConvert.IsEnum; + + public override object? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + // Is this token a string? + if (reader.TokenType == JsonTokenType.String) + // Try to use that string as the name of the enum value: + if (Enum.TryParse(typeToConvert, reader.GetString(), out var result)) + return result; + + // In any other case, we will return the default enum value: + LOG.LogWarning($"Cannot read '{reader.GetString()}' as '{typeToConvert.Name}' enum; token type: {reader.TokenType}"); + return Activator.CreateInstance(typeToConvert); + } + + public override object ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + // Is this token a property name? + if (reader.TokenType == JsonTokenType.PropertyName) + // Try to use that property name as the name of the enum value: + if (Enum.TryParse(typeToConvert, reader.GetString(), out var result)) + return result; + + // In any other case, we will return the default enum value: + LOG.LogWarning($"Cannot read '{reader.GetString()}' as '{typeToConvert.Name}' enum; token type: {reader.TokenType}"); + return Activator.CreateInstance(typeToConvert)!; + } + + public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options) + { + writer.WriteStringValue(value.ToString()); + } + + public override void WriteAsPropertyName(Utf8JsonWriter writer, object value, JsonSerializerOptions options) + { + writer.WritePropertyName(value.ToString()!); + } +} \ No newline at end of file diff --git a/app/MindWork AI Studio/wwwroot/changelog/v0.9.40.md b/app/MindWork AI Studio/wwwroot/changelog/v0.9.40.md index c1bb8fc8..19cee1ed 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v0.9.40.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v0.9.40.md @@ -2,4 +2,5 @@ - Added support for the announced OpenAI `o4` models. We hope that these `o4` models will be usable by the well-known chat completion API instead of the new responses API, though. AI Studio cannot use the new responses API right now. - Added Alibaba Cloud as a new provider. - Improved the provider selection by showing the name of the provider in the provider selection instead of its identifier. +- Improved the developer experience by adding a tolerant enum converter for better configuration handling. - Fixed an issue where OpenAI `o3` models were not shown in the model selection. From 171ed37c272fb7669a8f5d74c50c2781dd3d8828 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sat, 12 Apr 2025 10:19:22 +0200 Subject: [PATCH 09/93] Updated changelog & acknowledgements for contributions (#399) --- app/MindWork AI Studio/Pages/Supporters.razor | 2 +- app/MindWork AI Studio/wwwroot/changelog/v0.9.40.md | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/MindWork AI Studio/Pages/Supporters.razor b/app/MindWork AI Studio/Pages/Supporters.razor index cffedf7d..c9350408 100644 --- a/app/MindWork AI Studio/Pages/Supporters.razor +++ b/app/MindWork AI Studio/Pages/Supporters.razor @@ -78,7 +78,7 @@ - + diff --git a/app/MindWork AI Studio/wwwroot/changelog/v0.9.40.md b/app/MindWork AI Studio/wwwroot/changelog/v0.9.40.md index 19cee1ed..e78ef4c6 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v0.9.40.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v0.9.40.md @@ -1,6 +1,7 @@ # v0.9.40, build 215 (2025-04-xx xx:xx UTC) - Added support for the announced OpenAI `o4` models. We hope that these `o4` models will be usable by the well-known chat completion API instead of the new responses API, though. AI Studio cannot use the new responses API right now. -- Added Alibaba Cloud as a new provider. +- Added Alibaba Cloud as a new provider. Thanks Peer `peerschuett` for the contribution. +- Added the Hugging Face inference provider as an LLM provider to AI Studio. Thanks Peer `peerschuett` for the contribution. - Improved the provider selection by showing the name of the provider in the provider selection instead of its identifier. - Improved the developer experience by adding a tolerant enum converter for better configuration handling. - Fixed an issue where OpenAI `o3` models were not shown in the model selection. From 3fc15d9789e26bf45d2eb97a030b812f77ce33ea Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sat, 12 Apr 2025 21:13:33 +0200 Subject: [PATCH 10/93] Configure language & start language plugins (#400) --- app/MindWork AI Studio.sln.DotSettings | 1 + .../Components/ChatComponent.razor.cs | 6 +- .../Components/InnerScrolling.razor.cs | 9 +- .../Components/MSGComponentBase.cs | 66 +++++++-- .../Settings/SettingsPanelApp.razor | 19 ++- .../Layout/MainLayout.razor.cs | 52 ++++--- app/MindWork AI Studio/Pages/Chat.razor | 4 +- app/MindWork AI Studio/Pages/Chat.razor.cs | 9 +- app/MindWork AI Studio/Pages/Home.razor | 5 +- app/MindWork AI Studio/Pages/Home.razor.cs | 12 +- app/MindWork AI Studio/Pages/Plugins.razor | 1 + app/MindWork AI Studio/Pages/Plugins.razor.cs | 43 ++---- app/MindWork AI Studio/Pages/Writer.razor.cs | 12 +- .../plugin.lua | 14 ++ .../plugin.lua | 14 ++ .../Settings/ConfigurationSelectData.cs | 16 ++ .../Settings/DataModel/DataApp.cs | 10 ++ .../Settings/DataModel/LangBehavior.cs | 7 + .../DataModel/LangBehaviourExtensions.cs | 12 ++ .../Settings/SettingsManager.cs | 49 +++++- app/MindWork AI Studio/Tools/Event.cs | 1 + app/MindWork AI Studio/Tools/FNVHash.cs | 62 ++++++++ .../Tools/PluginSystem/IAvailablePlugin.cs | 6 + .../Tools/PluginSystem/ILang.cs | 21 +++ .../Tools/PluginSystem/ILanguagePlugin.cs | 13 +- .../Tools/PluginSystem/NoPluginLanguage.cs | 26 ++++ .../PluginSystem/PluginFactory.HotReload.cs | 6 + .../PluginSystem/PluginFactory.Internal.cs | 18 ++- .../Tools/PluginSystem/PluginFactory.cs | 140 ++++++++++++++++-- .../Tools/PluginSystem/PluginLanguage.cs | 106 ++++++++----- .../Tools/PluginSystem/PluginMetadata.cs | 8 +- 31 files changed, 612 insertions(+), 156 deletions(-) create mode 100644 app/MindWork AI Studio/Settings/DataModel/LangBehavior.cs create mode 100644 app/MindWork AI Studio/Settings/DataModel/LangBehaviourExtensions.cs create mode 100644 app/MindWork AI Studio/Tools/FNVHash.cs create mode 100644 app/MindWork AI Studio/Tools/PluginSystem/IAvailablePlugin.cs create mode 100644 app/MindWork AI Studio/Tools/PluginSystem/ILang.cs create mode 100644 app/MindWork AI Studio/Tools/PluginSystem/NoPluginLanguage.cs diff --git a/app/MindWork AI Studio.sln.DotSettings b/app/MindWork AI Studio.sln.DotSettings index 6cbaf40c..4a94c5cc 100644 --- a/app/MindWork AI Studio.sln.DotSettings +++ b/app/MindWork AI Studio.sln.DotSettings @@ -2,6 +2,7 @@ AI EDI ERI + FNV GWDG HF LLM diff --git a/app/MindWork AI Studio/Components/ChatComponent.razor.cs b/app/MindWork AI Studio/Components/ChatComponent.razor.cs index 0759d5b4..f12be751 100644 --- a/app/MindWork AI Studio/Components/ChatComponent.razor.cs +++ b/app/MindWork AI Studio/Components/ChatComponent.razor.cs @@ -840,8 +840,8 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable #region Overrides of MSGComponentBase public override string ComponentName => nameof(ChatComponent); - - public override async Task ProcessIncomingMessage(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default + + protected override async Task ProcessIncomingMessage(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default { switch (triggeredEvent) { @@ -860,7 +860,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable } } - public override Task ProcessMessageWithResult(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data) where TResult : default where TPayload : default + protected override Task ProcessIncomingMessageWithResult(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data) where TResult : default where TPayload : default { switch (triggeredEvent) { diff --git a/app/MindWork AI Studio/Components/InnerScrolling.razor.cs b/app/MindWork AI Studio/Components/InnerScrolling.razor.cs index 6dd0143e..3238ffd9 100644 --- a/app/MindWork AI Studio/Components/InnerScrolling.razor.cs +++ b/app/MindWork AI Studio/Components/InnerScrolling.razor.cs @@ -46,8 +46,8 @@ public partial class InnerScrolling : MSGComponentBase #region Overrides of MSGComponentBase public override string ComponentName => nameof(InnerScrolling); - - public override Task ProcessIncomingMessage(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default + + protected override Task ProcessIncomingMessage(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default { switch (triggeredEvent) { @@ -59,11 +59,6 @@ public partial class InnerScrolling : MSGComponentBase return Task.CompletedTask; } - public override Task ProcessMessageWithResult(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data) where TResult : default where TPayload : default - { - return Task.FromResult(default(TResult)); - } - #endregion private string MinWidthStyle => string.IsNullOrWhiteSpace(this.MinWidth) ? string.Empty : $"min-width: {this.MinWidth}; "; diff --git a/app/MindWork AI Studio/Components/MSGComponentBase.cs b/app/MindWork AI Studio/Components/MSGComponentBase.cs index 4dddb57d..4e904c02 100644 --- a/app/MindWork AI Studio/Components/MSGComponentBase.cs +++ b/app/MindWork AI Studio/Components/MSGComponentBase.cs @@ -1,10 +1,11 @@ using AIStudio.Settings; +using AIStudio.Tools.PluginSystem; using Microsoft.AspNetCore.Components; namespace AIStudio.Components; -public abstract class MSGComponentBase : ComponentBase, IDisposable, IMessageBusReceiver +public abstract class MSGComponentBase : ComponentBase, IDisposable, IMessageBusReceiver, ILang { [Inject] protected SettingsManager SettingsManager { get; init; } = null!; @@ -12,12 +13,37 @@ public abstract class MSGComponentBase : ComponentBase, IDisposable, IMessageBus [Inject] protected MessageBus MessageBus { get; init; } = null!; + [Inject] + private ILogger Logger { get; init; } = null!; + + private ILanguagePlugin Lang { get; set; } = PluginFactory.BaseLanguage; + #region Overrides of ComponentBase - protected override void OnInitialized() + protected override async Task OnInitializedAsync() { + this.Lang = await this.SettingsManager.GetActiveLanguagePlugin(); + this.MessageBus.RegisterComponent(this); - base.OnInitialized(); + await base.OnInitializedAsync(); + } + + #endregion + + #region Implementation of ILang + + /// + public string T(string fallbackEN) + { + var type = this.GetType(); + var ns = $"{type.Namespace!}::{type.Name}".ToUpperInvariant().Replace(".", "::"); + var key = $"root::{ns}::T{fallbackEN.ToFNV32()}"; + + if(this.Lang.TryGetText(key, out var text, logWarning: false)) + return text; + + this.Logger.LogWarning($"Missing translation key '{key}' for content '{fallbackEN}'."); + return fallbackEN; } #endregion @@ -26,7 +52,7 @@ public abstract class MSGComponentBase : ComponentBase, IDisposable, IMessageBus public abstract string ComponentName { get; } - public Task ProcessMessage(ComponentBase? sendingComponent, Event triggeredEvent, T? data) + public async Task ProcessMessage(ComponentBase? sendingComponent, Event triggeredEvent, T? data) { switch (triggeredEvent) { @@ -34,19 +60,34 @@ public abstract class MSGComponentBase : ComponentBase, IDisposable, IMessageBus this.StateHasChanged(); break; + case Event.PLUGINS_RELOADED: + this.Lang = await this.SettingsManager.GetActiveLanguagePlugin(); + await this.InvokeAsync(this.StateHasChanged); + break; + default: - return this.ProcessIncomingMessage(sendingComponent, triggeredEvent, data); + await this.ProcessIncomingMessage(sendingComponent, triggeredEvent, data); + break; } - - return Task.CompletedTask; } - - public abstract Task ProcessIncomingMessage(ComponentBase? sendingComponent, Event triggeredEvent, T? data); - - public abstract Task ProcessMessageWithResult(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data); + + public async Task ProcessMessageWithResult(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data) + { + return await this.ProcessIncomingMessageWithResult(sendingComponent, triggeredEvent, data); + } #endregion + protected virtual Task ProcessIncomingMessage(ComponentBase? sendingComponent, Event triggeredEvent, T? data) + { + return Task.CompletedTask; + } + + protected virtual Task ProcessIncomingMessageWithResult(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data) + { + return Task.FromResult(default); + } + #region Implementation of IDisposable public void Dispose() @@ -71,7 +112,8 @@ public abstract class MSGComponentBase : ComponentBase, IDisposable, IMessageBus // Append the color theme changed event to the list of events: var eventsList = new List(events) { - Event.COLOR_THEME_CHANGED + Event.COLOR_THEME_CHANGED, + Event.PLUGINS_RELOADED, }; this.MessageBus.ApplyFilters(this, components, eventsList.ToArray()); diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelApp.razor b/app/MindWork AI Studio/Components/Settings/SettingsPanelApp.razor index e67aaf5f..da313dda 100644 --- a/app/MindWork AI Studio/Components/Settings/SettingsPanelApp.razor +++ b/app/MindWork AI Studio/Components/Settings/SettingsPanelApp.razor @@ -3,14 +3,25 @@ @inherits SettingsPanelBase + + @if (PreviewFeatures.PRE_PLUGINS_2025.IsEnabled(this.SettingsManager)) + { + + + @if (this.SettingsManager.ConfigurationData.App.LanguageBehavior is LangBehavior.MANUAL) + { + + } + } + - + - - @if(this.SettingsManager.ConfigurationData.App.PreviewVisibility > PreviewVisibility.NONE) + + @if (this.SettingsManager.ConfigurationData.App.PreviewVisibility > PreviewVisibility.NONE) { var availablePreviewFeatures = ConfigurationSelectDataFactory.GetPreviewFeaturesData(this.SettingsManager).ToList(); if (availablePreviewFeatures.Count > 0) @@ -18,7 +29,7 @@ } } - + \ No newline at end of file diff --git a/app/MindWork AI Studio/Layout/MainLayout.razor.cs b/app/MindWork AI Studio/Layout/MainLayout.razor.cs index 08e963cb..b80de4a5 100644 --- a/app/MindWork AI Studio/Layout/MainLayout.razor.cs +++ b/app/MindWork AI Studio/Layout/MainLayout.razor.cs @@ -83,26 +83,13 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, IDis // Ensure that all settings are loaded: await this.SettingsManager.LoadSettings(); - // - // We cannot process the plugins before the settings are loaded, - // and we know our data directory. - // - if(PreviewFeatures.PRE_PLUGINS_2025.IsEnabled(this.SettingsManager)) - { - // Ensure that all internal plugins are present: - await PluginFactory.EnsureInternalPlugins(); - - // Load (but not start) all plugins, without waiting for them: - var pluginLoadingTimeout = new CancellationTokenSource(TimeSpan.FromSeconds(5)); - _ = PluginFactory.LoadAll(pluginLoadingTimeout.Token); - - // Set up hot reloading for plugins: - PluginFactory.SetUpHotReloading(); - } - // Register this component with the message bus: this.MessageBus.RegisterComponent(this); - this.MessageBus.ApplyFilters(this, [], [ Event.UPDATE_AVAILABLE, Event.CONFIGURATION_CHANGED, Event.COLOR_THEME_CHANGED, Event.SHOW_ERROR ]); + this.MessageBus.ApplyFilters(this, [], + [ + Event.UPDATE_AVAILABLE, Event.CONFIGURATION_CHANGED, Event.COLOR_THEME_CHANGED, Event.SHOW_ERROR, + Event.STARTUP_PLUGIN_SYSTEM, Event.PLUGINS_RELOADED + ]); // Set the snackbar for the update service: UpdateService.SetBlazorDependencies(this.Snackbar); @@ -115,6 +102,9 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, IDis // Solve issue https://github.com/MudBlazor/MudBlazor/issues/11133: MudGlobal.TooltipDefaults.Duration = TimeSpan.Zero; + // Send a message to start the plugin system: + await this.MessageBus.SendMessage(this, Event.STARTUP_PLUGIN_SYSTEM); + await this.themeProvider.WatchSystemPreference(this.SystemeThemeChanged); await this.UpdateThemeConfiguration(); this.LoadNavItems(); @@ -179,6 +169,32 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, IDis error.Show(this.Snackbar); break; + + case Event.STARTUP_PLUGIN_SYSTEM: + if(PreviewFeatures.PRE_PLUGINS_2025.IsEnabled(this.SettingsManager)) + { + _ = Task.Run(async () => + { + // Set up the plugin system: + PluginFactory.Setup(); + + // Ensure that all internal plugins are present: + await PluginFactory.EnsureInternalPlugins(); + + // Load (but not start) all plugins, without waiting for them: + var pluginLoadingTimeout = new CancellationTokenSource(TimeSpan.FromSeconds(5)); + await PluginFactory.LoadAll(pluginLoadingTimeout.Token); + + // Set up hot reloading for plugins: + PluginFactory.SetUpHotReloading(); + }); + } + + break; + + case Event.PLUGINS_RELOADED: + await this.InvokeAsync(this.StateHasChanged); + break; } } diff --git a/app/MindWork AI Studio/Pages/Chat.razor b/app/MindWork AI Studio/Pages/Chat.razor index b77edf18..b95a9d62 100644 --- a/app/MindWork AI Studio/Pages/Chat.razor +++ b/app/MindWork AI Studio/Pages/Chat.razor @@ -7,11 +7,11 @@ @if (this.chatThread is not null && this.chatThread.WorkspaceId != Guid.Empty) { - @($"Chat in Workspace \"{this.currentWorkspaceName}\"") + @(T("Chat in Workspace") + $" \"{this.currentWorkspaceName}\"") } else { - @("Temporary Chat") + @(T("Short-Term Chat")) } diff --git a/app/MindWork AI Studio/Pages/Chat.razor.cs b/app/MindWork AI Studio/Pages/Chat.razor.cs index 3ee7ecc9..f3c5c8b5 100644 --- a/app/MindWork AI Studio/Pages/Chat.razor.cs +++ b/app/MindWork AI Studio/Pages/Chat.razor.cs @@ -83,8 +83,8 @@ public partial class Chat : MSGComponentBase #region Overrides of MSGComponentBase public override string ComponentName => nameof(Chat); - - public override Task ProcessIncomingMessage(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default + + protected override Task ProcessIncomingMessage(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default { switch (triggeredEvent) { @@ -96,10 +96,5 @@ public partial class Chat : MSGComponentBase return Task.CompletedTask; } - public override Task ProcessMessageWithResult(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data) where TResult : default where TPayload : default - { - return Task.FromResult(default(TResult)); - } - #endregion } \ No newline at end of file diff --git a/app/MindWork AI Studio/Pages/Home.razor b/app/MindWork AI Studio/Pages/Home.razor index de74a626..0b035995 100644 --- a/app/MindWork AI Studio/Pages/Home.razor +++ b/app/MindWork AI Studio/Pages/Home.razor @@ -1,8 +1,11 @@ @attribute [Route(Routes.HOME)] +@inherits MSGComponentBase
- Let's get started + + @T("Let's get started") + diff --git a/app/MindWork AI Studio/Pages/Home.razor.cs b/app/MindWork AI Studio/Pages/Home.razor.cs index 026fafe5..4468e651 100644 --- a/app/MindWork AI Studio/Pages/Home.razor.cs +++ b/app/MindWork AI Studio/Pages/Home.razor.cs @@ -6,10 +6,10 @@ using Changelog = AIStudio.Components.Changelog; namespace AIStudio.Pages; -public partial class Home : ComponentBase +public partial class Home : MSGComponentBase { [Inject] - private HttpClient HttpClient { get; set; } = null!; + private HttpClient HttpClient { get; init; } = null!; private string LastChangeContent { get; set; } = string.Empty; @@ -22,7 +22,13 @@ public partial class Home : ComponentBase } #endregion - + + #region Overrides of MSGComponentBase + + public override string ComponentName => nameof(Home); + + #endregion + private async Task ReadLastChangeAsync() { var latest = Changelog.LOGS.MaxBy(n => n.Build); diff --git a/app/MindWork AI Studio/Pages/Plugins.razor b/app/MindWork AI Studio/Pages/Plugins.razor index 6d5bf8ce..8306d6d1 100644 --- a/app/MindWork AI Studio/Pages/Plugins.razor +++ b/app/MindWork AI Studio/Pages/Plugins.razor @@ -1,4 +1,5 @@ @using AIStudio.Tools.PluginSystem +@inherits MSGComponentBase @attribute [Route(Routes.PLUGINS)]
diff --git a/app/MindWork AI Studio/Pages/Plugins.razor.cs b/app/MindWork AI Studio/Pages/Plugins.razor.cs index 9504c0ff..88c90433 100644 --- a/app/MindWork AI Studio/Pages/Plugins.razor.cs +++ b/app/MindWork AI Studio/Pages/Plugins.razor.cs @@ -1,22 +1,14 @@ -using AIStudio.Settings; +using AIStudio.Components; using AIStudio.Tools.PluginSystem; -using Microsoft.AspNetCore.Components; - namespace AIStudio.Pages; -public partial class Plugins : ComponentBase, IMessageBusReceiver +public partial class Plugins : MSGComponentBase { private const string GROUP_ENABLED = "Enabled"; private const string GROUP_DISABLED = "Disabled"; private const string GROUP_INTERNAL = "Internal"; - [Inject] - private MessageBus MessageBus { get; init; } = null!; - - [Inject] - private SettingsManager SettingsManager { get; init; } = null!; - private TableGroupDefinition groupConfig = null!; #region Overrides of ComponentBase @@ -45,7 +37,13 @@ public partial class Plugins : ComponentBase, IMessageBusReceiver } #endregion - + + #region Overrides of MSGComponentBase + + public override string ComponentName => nameof(Plugins); + + #endregion + private async Task PluginActivationStateChanged(IPluginMetadata pluginMeta) { if (this.SettingsManager.IsPluginEnabled(pluginMeta)) @@ -56,27 +54,4 @@ public partial class Plugins : ComponentBase, IMessageBusReceiver await this.SettingsManager.StoreSettings(); await this.MessageBus.SendMessage(this, Event.CONFIGURATION_CHANGED); } - - #region Implementation of IMessageBusReceiver - - public string ComponentName => nameof(Plugins); - - public Task ProcessMessage(ComponentBase? sendingComponent, Event triggeredEvent, T? data) - { - switch (triggeredEvent) - { - case Event.PLUGINS_RELOADED: - this.InvokeAsync(this.StateHasChanged); - break; - } - - return Task.CompletedTask; - } - - public Task ProcessMessageWithResult(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data) - { - return Task.FromResult(default); - } - - #endregion } \ No newline at end of file diff --git a/app/MindWork AI Studio/Pages/Writer.razor.cs b/app/MindWork AI Studio/Pages/Writer.razor.cs index 42b23abc..a36c0410 100644 --- a/app/MindWork AI Studio/Pages/Writer.razor.cs +++ b/app/MindWork AI Studio/Pages/Writer.razor.cs @@ -24,7 +24,7 @@ public partial class Writer : MSGComponentBase, IAsyncDisposable private string userInput = string.Empty; private string userDirection = string.Empty; private string suggestion = string.Empty; - + #region Overrides of ComponentBase protected override async Task OnInitializedAsync() @@ -43,16 +43,6 @@ public partial class Writer : MSGComponentBase, IAsyncDisposable public override string ComponentName => nameof(Writer); - public override Task ProcessIncomingMessage(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default - { - return Task.CompletedTask; - } - - public override Task ProcessMessageWithResult(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data) where TResult : default where TPayload : default - { - return Task.FromResult(default(TResult)); - } - #endregion private bool IsProviderSelected => this.providerSettings.UsedLLMProvider != LLMProviders.NONE; diff --git a/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua b/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua index 634c8e74..b301dbd7 100644 --- a/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua +++ b/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua @@ -44,6 +44,20 @@ DEPRECATION_MESSAGE = "" -- code followed by the ISO 3166-1 country code: IETF_TAG = "de-DE" +-- The language name in the user's language: +LANG_NAME = "Deutsch (Deutschland)" + UI_TEXT_CONTENT = { HOME = CONTENT_HOME, + AISTUDIO = { + PAGES = { + HOME = { + T2331588413 = "Lass uns anfangen", + }, + + CHAT = { + T3718856736 = "Vorläufiger Chat", + } + }, + } } diff --git a/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua b/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua index 78f66474..2687f722 100644 --- a/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua +++ b/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua @@ -44,6 +44,20 @@ DEPRECATION_MESSAGE = "" -- code followed by the ISO 3166-1 country code: IETF_TAG = "en-US" +-- The language name in the user's language: +LANG_NAME = "English (United States)" + UI_TEXT_CONTENT = { HOME = CONTENT_HOME, + AISTUDIO = { + PAGES = { + HOME = { + T2331588413 = "Let's get started", + }, + + CHAT = { + T3718856736 = "Short-Term Chat", + }, + }, + } } diff --git a/app/MindWork AI Studio/Settings/ConfigurationSelectData.cs b/app/MindWork AI Studio/Settings/ConfigurationSelectData.cs index 5a2b8f11..a47bde9d 100644 --- a/app/MindWork AI Studio/Settings/ConfigurationSelectData.cs +++ b/app/MindWork AI Studio/Settings/ConfigurationSelectData.cs @@ -6,6 +6,7 @@ using AIStudio.Assistants.TextSummarizer; using AIStudio.Assistants.EMail; using AIStudio.Provider; using AIStudio.Settings.DataModel; +using AIStudio.Tools.PluginSystem; using WritingStylesRewrite = AIStudio.Assistants.RewriteImprove.WritingStyles; using WritingStylesEMail = AIStudio.Assistants.EMail.WritingStyles; @@ -25,6 +26,21 @@ public readonly record struct ConfigurationSelectData(string Name, T Value); /// public static class ConfigurationSelectDataFactory { + public static IEnumerable> GetLangBehaviorData() + { + foreach (var behavior in Enum.GetValues()) + yield return new(behavior.Name(), behavior); + } + + public static IEnumerable> GetLanguagesData() + { + foreach (var runningPlugin in PluginFactory.RunningPlugins) + { + if(runningPlugin is ILanguagePlugin languagePlugin) + yield return new(languagePlugin.LangName, runningPlugin.Id); + } + } + public static IEnumerable> GetLoadingChatProviderBehavior() { yield return new("When possible, use the LLM provider which was used for each chat in the first place", LoadingChatProviderBehavior.USE_CHAT_PROVIDER_IF_AVAILABLE); diff --git a/app/MindWork AI Studio/Settings/DataModel/DataApp.cs b/app/MindWork AI Studio/Settings/DataModel/DataApp.cs index 7e89404a..af76fa26 100644 --- a/app/MindWork AI Studio/Settings/DataModel/DataApp.cs +++ b/app/MindWork AI Studio/Settings/DataModel/DataApp.cs @@ -2,6 +2,16 @@ namespace AIStudio.Settings.DataModel; public sealed class DataApp { + /// + /// The language behavior. + /// + public LangBehavior LanguageBehavior { get; set; } = LangBehavior.AUTO; + + /// + /// The language plugin ID to use. + /// + public Guid LanguagePluginId { get; set; } = Guid.Empty; + /// /// The preferred theme to use. /// diff --git a/app/MindWork AI Studio/Settings/DataModel/LangBehavior.cs b/app/MindWork AI Studio/Settings/DataModel/LangBehavior.cs new file mode 100644 index 00000000..492d6bca --- /dev/null +++ b/app/MindWork AI Studio/Settings/DataModel/LangBehavior.cs @@ -0,0 +1,7 @@ +namespace AIStudio.Settings.DataModel; + +public enum LangBehavior +{ + AUTO, + MANUAL, +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Settings/DataModel/LangBehaviourExtensions.cs b/app/MindWork AI Studio/Settings/DataModel/LangBehaviourExtensions.cs new file mode 100644 index 00000000..c71caf92 --- /dev/null +++ b/app/MindWork AI Studio/Settings/DataModel/LangBehaviourExtensions.cs @@ -0,0 +1,12 @@ +namespace AIStudio.Settings.DataModel; + +public static class LangBehaviorExtensions +{ + public static string Name(this LangBehavior langBehavior) => langBehavior switch + { + LangBehavior.AUTO => "Choose the language automatically, based on your system language.", + LangBehavior.MANUAL => "Choose the language manually.", + + _ => "Unknown option" + }; +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Settings/SettingsManager.cs b/app/MindWork AI Studio/Settings/SettingsManager.cs index dcbe48cb..24bd1ded 100644 --- a/app/MindWork AI Studio/Settings/SettingsManager.cs +++ b/app/MindWork AI Studio/Settings/SettingsManager.cs @@ -5,6 +5,7 @@ using System.Text.Json.Serialization; using AIStudio.Provider; using AIStudio.Settings.DataModel; using AIStudio.Tools.PluginSystem; +using AIStudio.Tools.Services; // ReSharper disable NotAccessedPositionalProperty.Local @@ -13,7 +14,7 @@ namespace AIStudio.Settings; /// /// The settings manager. /// -public sealed class SettingsManager(ILogger logger) +public sealed class SettingsManager(ILogger logger, RustService rustService) { private const string SETTINGS_FILENAME = "settings.json"; @@ -24,6 +25,7 @@ public sealed class SettingsManager(ILogger logger) }; private readonly ILogger logger = logger; + private readonly RustService rustService = rustService; /// /// The directory where the configuration files are stored. @@ -143,8 +145,53 @@ public sealed class SettingsManager(ILogger logger) return minimumLevel; } + /// + /// Checks if the given plugin is enabled. + /// + /// The plugin to check. + /// True, when the plugin is enabled, false otherwise. public bool IsPluginEnabled(IPluginMetadata plugin) => this.ConfigurationData.EnabledPlugins.Contains(plugin.Id); + /// + /// Returns the active language plugin. + /// + /// The active language plugin. + public async Task GetActiveLanguagePlugin() + { + switch (this.ConfigurationData.App.LanguageBehavior) + { + case LangBehavior.AUTO: + var languageCode = await this.rustService.ReadUserLanguage(); + var languagePlugin = PluginFactory.RunningPlugins.FirstOrDefault(x => x is ILanguagePlugin langPlug && langPlug.IETFTag == languageCode); + if (languagePlugin is null) + return PluginFactory.BaseLanguage; + + if (languagePlugin is ILanguagePlugin langPlugin) + return langPlugin; + + this.logger.LogError("The language plugin is not a language plugin."); + return PluginFactory.BaseLanguage; + + case LangBehavior.MANUAL: + var pluginId = this.ConfigurationData.App.LanguagePluginId; + var plugin = PluginFactory.RunningPlugins.FirstOrDefault(x => x.Id == pluginId); + if (plugin is null) + { + this.logger.LogWarning($"The chosen language plugin (id='{pluginId}') is not available."); + return PluginFactory.BaseLanguage; + } + + if (plugin is ILanguagePlugin chosenLangPlugin) + return chosenLangPlugin; + + this.logger.LogError("The chosen language plugin is not a language plugin."); + return PluginFactory.BaseLanguage; + } + + this.logger.LogError("The language behavior is unknown."); + return PluginFactory.BaseLanguage; + } + [SuppressMessage("Usage", "MWAIS0001:Direct access to `Providers` is not allowed")] public Provider GetPreselectedProvider(Tools.Components component, string? currentProviderId = null, bool usePreselectionBeforeCurrentProvider = false) { diff --git a/app/MindWork AI Studio/Tools/Event.cs b/app/MindWork AI Studio/Tools/Event.cs index 57758589..213adf29 100644 --- a/app/MindWork AI Studio/Tools/Event.cs +++ b/app/MindWork AI Studio/Tools/Event.cs @@ -8,6 +8,7 @@ public enum Event STATE_HAS_CHANGED, CONFIGURATION_CHANGED, COLOR_THEME_CHANGED, + STARTUP_PLUGIN_SYSTEM, PLUGINS_RELOADED, SHOW_ERROR, diff --git a/app/MindWork AI Studio/Tools/FNVHash.cs b/app/MindWork AI Studio/Tools/FNVHash.cs new file mode 100644 index 00000000..cc47645e --- /dev/null +++ b/app/MindWork AI Studio/Tools/FNVHash.cs @@ -0,0 +1,62 @@ +// ReSharper disable MemberCanBePrivate.Global +namespace AIStudio.Tools; + +/// +/// Implements the Fowler–Noll–Vo hash function for 32-bit and 64-bit hashes. +/// +public static class FNVHash +{ + private const uint FNV_OFFSET_BASIS_32_BIT = 2_166_136_261; + private const ulong FNV_OFFSET_BASIS_64_BIT = 14_695_981_039_346_656_037; + + private const uint FNV_PRIME_32_BIT = 16_777_619; + private const ulong FNV_PRIME_64_BIT = 1_099_511_628_211; + + /// + /// Computes the 32bit FNV-1a hash of a string. + /// + /// The string to hash. + /// The 32bit FNV-1a hash of the string. + public static uint ToFNV32(this string text) => ToFNV32(text.AsSpan()); + + /// + /// Computes the 32bit FNV-1a hash of a string. + /// + /// The string to hash. + /// The 32bit FNV-1a hash of the string. + public static uint ToFNV32(this ReadOnlySpan text) + { + var hash = FNV_OFFSET_BASIS_32_BIT; + foreach (var c in text) + { + hash ^= c; + hash *= FNV_PRIME_32_BIT; + } + + return hash; + } + + /// + /// Computes the 64bit FNV-1a hash of a string. + /// + /// The string to hash. + /// The 64bit FNV-1a hash of the string. + public static ulong ToFNV64(this string text) => ToFNV64(text.AsSpan()); + + /// + /// Computes the 64bit FNV-1a hash of a string. + /// + /// The string to hash. + /// The 64bit FNV-1a hash of the string. + public static ulong ToFNV64(this ReadOnlySpan text) + { + var hash = FNV_OFFSET_BASIS_64_BIT; + foreach (var c in text) + { + hash ^= c; + hash *= FNV_PRIME_64_BIT; + } + + return hash; + } +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/PluginSystem/IAvailablePlugin.cs b/app/MindWork AI Studio/Tools/PluginSystem/IAvailablePlugin.cs new file mode 100644 index 00000000..a992d303 --- /dev/null +++ b/app/MindWork AI Studio/Tools/PluginSystem/IAvailablePlugin.cs @@ -0,0 +1,6 @@ +namespace AIStudio.Tools.PluginSystem; + +public interface IAvailablePlugin : IPluginMetadata +{ + public string LocalPath { get; } +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/PluginSystem/ILang.cs b/app/MindWork AI Studio/Tools/PluginSystem/ILang.cs new file mode 100644 index 00000000..6c5277ea --- /dev/null +++ b/app/MindWork AI Studio/Tools/PluginSystem/ILang.cs @@ -0,0 +1,21 @@ +namespace AIStudio.Tools.PluginSystem; + +/// +/// Represents a contract to access text from a language plugin. +/// +public interface ILang +{ + /// + /// Tries to get a text from the language plugin. + /// + /// + /// The given fallback text is used to determine the key for + /// the language plugin. Base for the key is the namespace of + /// the using component and the fallback text in English (US). + /// The given text getting hashed. When the key does not exist, + /// the fallback text will be returned. + /// + /// The fallback text in English (US). + /// The text from the language plugin or the fallback text. + public string T(string fallbackEN); +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/PluginSystem/ILanguagePlugin.cs b/app/MindWork AI Studio/Tools/PluginSystem/ILanguagePlugin.cs index a33bf3f5..4f214d68 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/ILanguagePlugin.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/ILanguagePlugin.cs @@ -16,6 +16,17 @@ public interface ILanguagePlugin /// /// The key to use to get the text. /// The desired text. + /// When true, a warning will be logged if the key does not exist. /// True if the key exists, false otherwise. - public bool TryGetText(string key, out string value); + public bool TryGetText(string key, out string value, bool logWarning = false); + + /// + /// Gets the IETF tag of the language plugin. + /// + public string IETFTag { get; } + + /// + /// Gets the name of the language. + /// + public string LangName { get; } } \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/PluginSystem/NoPluginLanguage.cs b/app/MindWork AI Studio/Tools/PluginSystem/NoPluginLanguage.cs new file mode 100644 index 00000000..26d35849 --- /dev/null +++ b/app/MindWork AI Studio/Tools/PluginSystem/NoPluginLanguage.cs @@ -0,0 +1,26 @@ +using Lua; + +namespace AIStudio.Tools.PluginSystem; + +public sealed class NoPluginLanguage : PluginBase, ILanguagePlugin +{ + public static readonly NoPluginLanguage INSTANCE = new(); + + private NoPluginLanguage() : base(true, LuaState.Create(), PluginType.LANGUAGE, string.Empty) + { + } + + #region Implementation of ILanguagePlugin + + public bool TryGetText(string key, out string value, bool logWarning = false) + { + value = string.Empty; + return true; + } + + public string IETFTag => string.Empty; + + public string LangName => string.Empty; + + #endregion +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.HotReload.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.HotReload.cs index c2d75bf3..0a32c17d 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.HotReload.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.HotReload.cs @@ -4,6 +4,12 @@ public static partial class PluginFactory { public static void SetUpHotReloading() { + if (!IS_INITIALIZED) + { + LOG.LogError("PluginFactory is not initialized. Please call Setup() before using it."); + return; + } + LOG.LogInformation($"Start hot reloading plugins for path '{HOT_RELOAD_WATCHER.Path}'."); try { diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Internal.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Internal.cs index 2c14adb6..0a9e9dac 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Internal.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Internal.cs @@ -10,6 +10,12 @@ public static partial class PluginFactory { public static async Task EnsureInternalPlugins() { + if (!IS_INITIALIZED) + { + LOG.LogError("PluginFactory is not initialized. Please call Setup() before using it."); + return; + } + LOG.LogInformation("Start ensuring internal plugins."); foreach (var plugin in Enum.GetValues()) { @@ -40,15 +46,15 @@ public static partial class PluginFactory } // Ensure that the additional resources exist: - foreach (var content in resourceFileProvider.GetDirectoryContents(metaData.ResourcePath)) + foreach (var contentFilePath in resourceFileProvider.GetDirectoryContents(metaData.ResourcePath)) { - if(content.IsDirectory) + if(contentFilePath.IsDirectory) { LOG.LogError("The plugin contains a directory. This is not allowed."); continue; } - await CopyInternalPluginFile(content, metaData); + await CopyInternalPluginFile(contentFilePath, metaData); } } catch @@ -57,9 +63,9 @@ public static partial class PluginFactory } } - private static async Task CopyInternalPluginFile(IFileInfo resourceInfo, InternalPluginData metaData) + private static async Task CopyInternalPluginFile(IFileInfo resourceFilePath, InternalPluginData metaData) { - await using var inputStream = resourceInfo.CreateReadStream(); + await using var inputStream = resourceFilePath.CreateReadStream(); var pluginTypeBasePath = Path.Join(INTERNAL_PLUGINS_ROOT, metaData.Type.GetDirectory()); @@ -73,7 +79,7 @@ public static partial class PluginFactory if (!Directory.Exists(pluginPath)) Directory.CreateDirectory(pluginPath); - var pluginFilePath = Path.Join(pluginPath, resourceInfo.Name); + var pluginFilePath = Path.Join(pluginPath, resourceFilePath.Name); await using var outputStream = File.Create(pluginFilePath); await inputStream.CopyToAsync(outputStream); diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs index 18d2023b..57ac7ea1 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs @@ -9,29 +9,45 @@ namespace AIStudio.Tools.PluginSystem; public static partial class PluginFactory { - private static readonly ILogger LOG = Program.LOGGER_FACTORY.CreateLogger("PluginFactory"); - - private static readonly string DATA_DIR = SettingsManager.DataDirectory!; - - private static readonly string PLUGINS_ROOT = Path.Join(DATA_DIR, "plugins"); - - private static readonly string INTERNAL_PLUGINS_ROOT = Path.Join(PLUGINS_ROOT, ".internal"); + private static readonly ILogger LOG = Program.LOGGER_FACTORY.CreateLogger(nameof(PluginFactory)); + private static readonly SettingsManager SETTINGS_MANAGER = Program.SERVICE_PROVIDER.GetRequiredService(); + private static readonly List AVAILABLE_PLUGINS = []; + private static readonly List RUNNING_PLUGINS = []; - private static readonly FileSystemWatcher HOT_RELOAD_WATCHER; - - private static readonly List AVAILABLE_PLUGINS = []; + private static bool IS_INITIALIZED; + private static string DATA_DIR = string.Empty; + private static string PLUGINS_ROOT = string.Empty; + private static string INTERNAL_PLUGINS_ROOT = string.Empty; + private static FileSystemWatcher HOT_RELOAD_WATCHER = null!; + private static ILanguagePlugin BASE_LANGUAGE_PLUGIN = NoPluginLanguage.INSTANCE; /// /// A list of all available plugins. /// public static IReadOnlyCollection AvailablePlugins => AVAILABLE_PLUGINS; + + /// + /// A list of all running plugins. + /// + public static IReadOnlyCollection RunningPlugins => RUNNING_PLUGINS; - static PluginFactory() + public static ILanguagePlugin BaseLanguage => BASE_LANGUAGE_PLUGIN; + + /// + /// Set up the plugin factory. We will read the data directory from the settings manager. + /// Afterward, we will create the plugins directory and the internal plugin directory. + /// + public static void Setup() { + DATA_DIR = SettingsManager.DataDirectory!; + PLUGINS_ROOT = Path.Join(DATA_DIR, "plugins"); + INTERNAL_PLUGINS_ROOT = Path.Join(PLUGINS_ROOT, ".internal"); + if (!Directory.Exists(PLUGINS_ROOT)) Directory.CreateDirectory(PLUGINS_ROOT); HOT_RELOAD_WATCHER = new(PLUGINS_ROOT); + IS_INITIALIZED = true; } /// @@ -48,6 +64,12 @@ public static partial class PluginFactory /// public static async Task LoadAll(CancellationToken cancellationToken = default) { + if (!IS_INITIALIZED) + { + LOG.LogError("PluginFactory is not initialized. Please call Setup() before using it."); + return; + } + LOG.LogInformation("Start loading plugins."); if (!Directory.Exists(PLUGINS_ROOT)) { @@ -96,8 +118,11 @@ public static partial class PluginFactory } LOG.LogInformation($"Successfully loaded plugin: '{pluginMainFile}' (Id='{plugin.Id}', Type='{plugin.Type}', Name='{plugin.Name}', Version='{plugin.Version}', Authors='{string.Join(", ", plugin.Authors)}')"); - AVAILABLE_PLUGINS.Add(new PluginMetadata(plugin)); + AVAILABLE_PLUGINS.Add(new PluginMetadata(plugin, pluginPath)); } + + // Start or restart all plugins: + await RestartAllPlugins(cancellationToken); } private static async Task Load(string pluginPath, string code, CancellationToken cancellationToken = default) @@ -148,9 +173,100 @@ public static partial class PluginFactory _ => new NoPlugin("This plugin type is not supported yet. Please try again with a future version of AI Studio.") }; } + + private static async Task RestartAllPlugins(CancellationToken cancellationToken = default) + { + LOG.LogInformation("Try to start or restart all plugins."); + RUNNING_PLUGINS.Clear(); + + // + // Get the base language plugin. This is the plugin that will be used to fill in missing keys. + // + var baseLanguagePluginId = InternalPlugin.LANGUAGE_EN_US.MetaData().Id; + var baseLanguagePluginMetaData = AVAILABLE_PLUGINS.FirstOrDefault(p => p.Id == baseLanguagePluginId); + if (baseLanguagePluginMetaData is null) + { + LOG.LogError($"Was not able to find the base language plugin: Id='{baseLanguagePluginId}'. Please check your installation."); + return; + } + + var startedBasePlugin = await Start(baseLanguagePluginMetaData, cancellationToken); + if (startedBasePlugin is NoPlugin noPlugin) + { + LOG.LogError($"Was not able to start the base language plugin: Id='{baseLanguagePluginId}'. Reason: {noPlugin.Issues.First()}"); + return; + } + + if (startedBasePlugin is PluginLanguage languagePlugin) + { + BASE_LANGUAGE_PLUGIN = languagePlugin; + RUNNING_PLUGINS.Add(languagePlugin); + LOG.LogInformation($"Successfully started the base language plugin: Id='{languagePlugin.Id}', Type='{languagePlugin.Type}', Name='{languagePlugin.Name}', Version='{languagePlugin.Version}'"); + } + else + { + LOG.LogError($"Was not able to start the base language plugin: Id='{baseLanguagePluginId}'. Reason: {string.Join("; ", startedBasePlugin.Issues)}"); + return; + } + + // + // Iterate over all available plugins and try to start them. + // + foreach (var availablePlugin in AVAILABLE_PLUGINS) + { + if(cancellationToken.IsCancellationRequested) + break; + + if (availablePlugin.Id == baseLanguagePluginId) + continue; + + if (availablePlugin.IsInternal || SETTINGS_MANAGER.IsPluginEnabled(availablePlugin)) + if(await Start(availablePlugin, cancellationToken) is { IsValid: true } plugin) + RUNNING_PLUGINS.Add(plugin); + + // Inform all components that the plugins have been reloaded or started: + await MessageBus.INSTANCE.SendMessage(null, Event.PLUGINS_RELOADED); + } + } + + private static async Task Start(IAvailablePlugin meta, CancellationToken cancellationToken = default) + { + var pluginMainFile = Path.Join(meta.LocalPath, "plugin.lua"); + if(!File.Exists(pluginMainFile)) + { + LOG.LogError($"Was not able to start plugin: Id='{meta.Id}', Type='{meta.Type}', Name='{meta.Name}', Version='{meta.Version}'. Reason: The plugin file does not exist."); + return new NoPlugin($"The plugin file does not exist: {pluginMainFile}"); + } + + var code = await File.ReadAllTextAsync(pluginMainFile, Encoding.UTF8, cancellationToken); + var plugin = await Load(meta.LocalPath, code, cancellationToken); + if (plugin is NoPlugin noPlugin) + { + LOG.LogError($"Was not able to start plugin: Id='{meta.Id}', Type='{meta.Type}', Name='{meta.Name}', Version='{meta.Version}'. Reason: {noPlugin.Issues.First()}"); + return noPlugin; + } + + if (plugin.IsValid) + { + // + // When this is a language plugin, we need to set the base language plugin. + // + if (plugin is PluginLanguage languagePlugin && BASE_LANGUAGE_PLUGIN != NoPluginLanguage.INSTANCE) + languagePlugin.SetBaseLanguage(BASE_LANGUAGE_PLUGIN); + + LOG.LogInformation($"Successfully started plugin: Id='{plugin.Id}', Type='{plugin.Type}', Name='{plugin.Name}', Version='{plugin.Version}'"); + return plugin; + } + + LOG.LogError($"Was not able to start plugin: Id='{meta.Id}', Type='{meta.Type}', Name='{meta.Name}', Version='{meta.Version}'. Reasons: {string.Join("; ", plugin.Issues)}"); + return new NoPlugin($"Was not able to start plugin: Id='{meta.Id}', Type='{meta.Type}', Name='{meta.Name}', Version='{meta.Version}'. Reasons: {string.Join("; ", plugin.Issues)}"); + } public static void Dispose() { + if(!IS_INITIALIZED) + return; + HOT_RELOAD_WATCHER.Dispose(); } } \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginLanguage.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginLanguage.cs index 384d81eb..1c201a11 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginLanguage.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginLanguage.cs @@ -4,9 +4,12 @@ namespace AIStudio.Tools.PluginSystem; public sealed class PluginLanguage : PluginBase, ILanguagePlugin { + private static readonly ILogger LOGGER = Program.LOGGER_FACTORY.CreateLogger(); + private readonly Dictionary content = []; private readonly List otherLanguagePlugins = []; private readonly string langCultureTag; + private readonly string langName; private ILanguagePlugin? baseLanguage; @@ -15,6 +18,9 @@ public sealed class PluginLanguage : PluginBase, ILanguagePlugin if(!this.TryInitIETFTag(out var issue, out this.langCultureTag)) this.pluginIssues.Add(issue); + if(!this.TryInitLangName(out issue, out this.langName)) + this.pluginIssues.Add(issue); + if (this.TryInitUITextContent(out issue, out var readContent)) this.content = readContent; else @@ -36,39 +42,6 @@ public sealed class PluginLanguage : PluginBase, ILanguagePlugin /// /// The language plugin to add. public void AddOtherLanguagePlugin(ILanguagePlugin languagePlugin) => this.otherLanguagePlugins.Add(languagePlugin); - - /// - /// Tries to get a text from the language plugin. - /// - /// - /// When the key neither in the base language nor in this language exist, - /// the value will be an empty string. Please note that the key is case-sensitive. - /// Furthermore, the keys are in the format "root::key". That means that - /// the keys are hierarchical and separated by "::". - /// - /// The key to use to get the text. - /// The desired text. - /// True if the key exists, false otherwise. - public bool TryGetText(string key, out string value) - { - // First, we check if the key is part of the main language pack: - if (this.content.TryGetValue(key, out value!)) - return true; - - // Second, we check if the key is part of the other language packs, such as the assistant plugins: - foreach (var otherLanguagePlugin in this.otherLanguagePlugins) - if(otherLanguagePlugin.TryGetText(key, out value)) - return true; - - // Finally, we check if the key is part of the base language pack. This is the case, - // when a language plugin does not cover all keys. In this case, the base language plugin - // will be used to fill in the missing keys: - if(this.baseLanguage is not null && this.baseLanguage.TryGetText(key, out value)) - return true; - - value = string.Empty; - return false; - } /// /// Tries to initialize the IETF tag. @@ -127,4 +100,71 @@ public sealed class PluginLanguage : PluginBase, ILanguagePlugin message = string.Empty; return true; } + + private bool TryInitLangName(out string message, out string readLangName) + { + if (!this.state.Environment["LANG_NAME"].TryRead(out readLangName)) + { + message = "The field LANG_NAME does not exist or is not a valid string."; + readLangName = string.Empty; + return false; + } + + if (string.IsNullOrWhiteSpace(readLangName)) + { + message = "The field LANG_NAME is empty. Use a valid language name."; + readLangName = string.Empty; + return false; + } + + message = string.Empty; + return true; + } + + #region Implementation of ILanguagePlugin + + /// + /// Tries to get a text from the language plugin. + /// + /// + /// When the key neither in the base language nor in this language exist, + /// the value will be an empty string. Please note that the key is case-sensitive. + /// Furthermore, the keys are in the format "root::key". That means that + /// the keys are hierarchical and separated by "::". + /// + /// The key to use to get the text. + /// The desired text. + /// When true, a warning will be logged if the key does not exist. + /// True if the key exists, false otherwise. + public bool TryGetText(string key, out string value, bool logWarning = false) + { + // First, we check if the key is part of the main language pack: + if (this.content.TryGetValue(key, out value!)) + return true; + + // Second, we check if the key is part of the other language packs, such as the assistant plugins: + foreach (var otherLanguagePlugin in this.otherLanguagePlugins) + if(otherLanguagePlugin.TryGetText(key, out value)) + return true; + + // Finally, we check if the key is part of the base language pack. This is the case, + // when a language plugin does not cover all keys. In this case, the base language plugin + // will be used to fill in the missing keys: + if(this.baseLanguage is not null && this.baseLanguage.TryGetText(key, out value)) + return true; + + if(logWarning) + LOGGER.LogWarning($"Missing translation key '{key}'."); + + value = string.Empty; + return false; + } + + /// + public string IETFTag => this.langCultureTag; + + /// + public string LangName => this.langName; + + #endregion } \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginMetadata.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginMetadata.cs index 2bfdab1e..e98644cb 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginMetadata.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginMetadata.cs @@ -1,6 +1,6 @@ namespace AIStudio.Tools.PluginSystem; -public sealed class PluginMetadata(PluginBase plugin) : IPluginMetadata +public sealed class PluginMetadata(PluginBase plugin, string localPath) : IAvailablePlugin { #region Implementation of IPluginMetadata @@ -47,4 +47,10 @@ public sealed class PluginMetadata(PluginBase plugin) : IPluginMetadata public bool IsInternal { get; } = plugin.IsInternal; #endregion + + #region Implementation of IAvailablePlugin + + public string LocalPath { get; } = localPath; + + #endregion } \ No newline at end of file From c715b38f41c4ddbc742abe5533161e11341cfaa8 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Mon, 14 Apr 2025 19:29:56 +0200 Subject: [PATCH 11/93] Migrated build script to C# (#401) --- app/Build/Build Script.csproj | 21 + app/Build/Commands/CheckRidsCommand.cs | 21 + app/Build/Commands/CollectI18NKeysCommand.cs | 167 ++++++ app/Build/Commands/PrepareAction.cs | 10 + app/Build/Commands/UpdateMetadataCommands.cs | 557 ++++++++++++++++++ app/Build/Commands/UpdateWebAssetsCommand.cs | 49 ++ app/Build/GlobalUsings.cs | 7 + app/Build/Program.cs | 9 + app/Build/Tools/Environment.cs | 77 +++ app/Build/Tools/RID.cs | 15 + app/Build/Tools/RIDExtensions.cs | 18 + app/MindWork AI Studio.sln | 12 + app/MindWork AI Studio.sln.DotSettings | 2 + .../Components/MSGComponentBase.cs | 2 + .../MindWork AI Studio.csproj | 1 + app/MindWork AI Studio/build.nu | 388 ------------ app/MindWork AI Studio/packages.lock.json | 3 + .../Tools => SharedTools}/FNVHash.cs | 2 +- app/SharedTools/SharedTools.csproj | 10 + documentation/Build.md | 47 +- 20 files changed, 1007 insertions(+), 411 deletions(-) create mode 100644 app/Build/Build Script.csproj create mode 100644 app/Build/Commands/CheckRidsCommand.cs create mode 100644 app/Build/Commands/CollectI18NKeysCommand.cs create mode 100644 app/Build/Commands/PrepareAction.cs create mode 100644 app/Build/Commands/UpdateMetadataCommands.cs create mode 100644 app/Build/Commands/UpdateWebAssetsCommand.cs create mode 100644 app/Build/GlobalUsings.cs create mode 100644 app/Build/Program.cs create mode 100644 app/Build/Tools/Environment.cs create mode 100644 app/Build/Tools/RID.cs create mode 100644 app/Build/Tools/RIDExtensions.cs delete mode 100755 app/MindWork AI Studio/build.nu rename app/{MindWork AI Studio/Tools => SharedTools}/FNVHash.cs (98%) create mode 100644 app/SharedTools/SharedTools.csproj diff --git a/app/Build/Build Script.csproj b/app/Build/Build Script.csproj new file mode 100644 index 00000000..5694b509 --- /dev/null +++ b/app/Build/Build Script.csproj @@ -0,0 +1,21 @@ + + + + Exe + net9.0 + Build + latest + enable + enable + build + + + + + + + + + + + diff --git a/app/Build/Commands/CheckRidsCommand.cs b/app/Build/Commands/CheckRidsCommand.cs new file mode 100644 index 00000000..62bf3662 --- /dev/null +++ b/app/Build/Commands/CheckRidsCommand.cs @@ -0,0 +1,21 @@ +// ReSharper disable ClassNeverInstantiated.Global +// ReSharper disable UnusedType.Global +// ReSharper disable UnusedMember.Global +namespace Build.Commands; + +public sealed class CheckRidsCommand +{ + [Command("check-rids", Description = "Check the RIDs for the current OS")] + public void GetRids() + { + if(!Environment.IsWorkingDirectoryValid()) + return; + + var rids = Environment.GetRidsForCurrentOS(); + Console.WriteLine("The following RIDs are available for the current OS:"); + foreach (var rid in rids) + { + Console.WriteLine($"- {rid}"); + } + } +} \ No newline at end of file diff --git a/app/Build/Commands/CollectI18NKeysCommand.cs b/app/Build/Commands/CollectI18NKeysCommand.cs new file mode 100644 index 00000000..8b2554bf --- /dev/null +++ b/app/Build/Commands/CollectI18NKeysCommand.cs @@ -0,0 +1,167 @@ +using System.Text.RegularExpressions; + +using SharedTools; + +// ReSharper disable ClassNeverInstantiated.Global +// ReSharper disable UnusedType.Global +// ReSharper disable UnusedMember.Global + +namespace Build.Commands; + +public sealed partial class CollectI18NKeysCommand +{ + [Command("collect-i18n", Description = "Collect I18N keys")] + public async Task CollectI18NKeys() + { + if(!Environment.IsWorkingDirectoryValid()) + return; + + Console.WriteLine("========================="); + Console.Write("- Collecting I18N keys ..."); + + var cwd = Environment.GetAIStudioDirectory(); + var binPath = Path.Join(cwd, "bin"); + var objPath = Path.Join(cwd, "obj"); + var wwwrootPath = Path.Join(cwd, "wwwroot"); + var allFiles = Directory.EnumerateFiles(cwd, "*", SearchOption.AllDirectories); + var counter = 0; + var sb = new StringBuilder(); + + foreach (var filePath in allFiles) + { + counter++; + if(filePath.StartsWith(binPath, StringComparison.OrdinalIgnoreCase)) + continue; + + if(filePath.StartsWith(objPath, StringComparison.OrdinalIgnoreCase)) + continue; + + if(filePath.StartsWith(wwwrootPath, StringComparison.OrdinalIgnoreCase)) + continue; + + var content = await File.ReadAllTextAsync(filePath, Encoding.UTF8); + var matches = this.FindAllTextTags(content); + if (matches.Count == 0) + continue; + + var ns = this.DetermineNamespace(filePath); + var fileInfo = new FileInfo(filePath); + var name = fileInfo.Name.Replace(fileInfo.Extension, string.Empty); + var langNamespace = $"{ns}::{name}".ToUpperInvariant().Replace(".", "::"); + foreach (var match in matches) + { + var key = $"root::{langNamespace}::T{match.ToFNV32()}"; + + } + } + + Console.WriteLine($" {counter:###,###} files processed."); + Console.WriteLine(); + } + + private List FindAllTextTags(ReadOnlySpan fileContent) + { + const string START_TAG = """ + T(" + """; + + const string END_TAG = """ + ") + """; + + var matches = new List(); + var startIdx = fileContent.IndexOf(START_TAG); + var content = fileContent; + while (startIdx > -1) + { + content = content[(startIdx + START_TAG.Length)..]; + var endIdx = content.IndexOf(END_TAG); + if (endIdx == -1) + break; + + var match = content[..endIdx]; + matches.Add(match.ToString()); + + startIdx = content.IndexOf(START_TAG); + } + + return matches; + } + + private string? DetermineNamespace(string filePath) + { + // Is it a C# file? Then we can read the namespace from it: + if (filePath.EndsWith(".cs", StringComparison.OrdinalIgnoreCase)) + return this.ReadNamespaceFromCSharp(filePath); + + // Is it a Razor file? Then, it depends: + if (filePath.EndsWith(".razor", StringComparison.OrdinalIgnoreCase)) + { + // Check if the file contains a namespace declaration: + var blazorNamespace = this.ReadNamespaceFromRazor(filePath); + if (blazorNamespace != null) + return blazorNamespace; + + // Alright, no namespace declaration. Let's check the corresponding C# file: + var csFilePath = $"{filePath}.cs"; + if (File.Exists(csFilePath)) + { + var csNamespace = this.ReadNamespaceFromCSharp(csFilePath); + if (csNamespace != null) + return csNamespace; + + Console.WriteLine($"- Error: Neither the blazor file '{filePath}' nor the corresponding C# file '{csFilePath}' contain a namespace declaration."); + return null; + } + + Console.WriteLine($"- Error: The blazor file '{filePath}' does not contain a namespace declaration and the corresponding C# file '{csFilePath}' does not exist."); + return null; + } + + // Not a C# or Razor file. We can't determine the namespace: + Console.WriteLine($"- Error: The file '{filePath}' is neither a C# nor a Razor file. We can't determine the namespace."); + return null; + } + + private string? ReadNamespaceFromCSharp(string filePath) + { + var content = File.ReadAllText(filePath, Encoding.UTF8); + var matches = CSharpNamespaceRegex().Matches(content); + + if (matches.Count == 0) + return null; + + if (matches.Count > 1) + { + Console.WriteLine($"The file '{filePath}' contains multiple namespaces. This scenario is not supported."); + return null; + } + + var match = matches[0]; + return match.Groups[1].Value; + } + + private string? ReadNamespaceFromRazor(string filePath) + { + var content = File.ReadAllText(filePath, Encoding.UTF8); + var matches = BlazorNamespaceRegex().Matches(content); + + if (matches.Count == 0) + return null; + + if (matches.Count > 1) + { + Console.WriteLine($"The file '{filePath}' contains multiple namespaces. This scenario is not supported."); + return null; + } + + var match = matches[0]; + return match.Groups[1].Value; + } + + [GeneratedRegex("""@namespace\s+([a-zA-Z0-9_.]+)""")] + private static partial Regex BlazorNamespaceRegex(); + + [GeneratedRegex("""namespace\s+([a-zA-Z0-9_.]+)""")] + private static partial Regex CSharpNamespaceRegex(); +} \ No newline at end of file diff --git a/app/Build/Commands/PrepareAction.cs b/app/Build/Commands/PrepareAction.cs new file mode 100644 index 00000000..2f2ffcb2 --- /dev/null +++ b/app/Build/Commands/PrepareAction.cs @@ -0,0 +1,10 @@ +namespace Build.Commands; + +public enum PrepareAction +{ + NONE, + + PATCH, + MINOR, + MAJOR, +} \ No newline at end of file diff --git a/app/Build/Commands/UpdateMetadataCommands.cs b/app/Build/Commands/UpdateMetadataCommands.cs new file mode 100644 index 00000000..d7055d4a --- /dev/null +++ b/app/Build/Commands/UpdateMetadataCommands.cs @@ -0,0 +1,557 @@ +using System.Diagnostics; +using System.Text.RegularExpressions; + +using Build.Tools; + +namespace Build.Commands; + +// ReSharper disable MemberCanBePrivate.Global +// ReSharper disable ClassNeverInstantiated.Global +// ReSharper disable UnusedType.Global +// ReSharper disable UnusedMember.Global + +public sealed partial class UpdateMetadataCommands +{ + [Command("release", Description = "Prepare & build the next release")] + public async Task Release(PrepareAction action) + { + if(!Environment.IsWorkingDirectoryValid()) + return; + + // Prepare the metadata for the next release: + await this.Prepare(action); + + // Build once to allow the Rust compiler to read the changed metadata + // and to update all .NET artifacts: + await this.Build(); + + // Now, we update the web assets (which may were updated by the first build): + new UpdateWebAssetsCommand().UpdateWebAssets(); + + // Collect the I18N keys from the source code. This step yields a I18N file + // that must be part of the final release: + await new CollectI18NKeysCommand().CollectI18NKeys(); + + // Build the final release, where Rust knows the updated metadata, the .NET + // artifacts are already in place, and .NET knows the updated web assets, etc.: + await this.Build(); + } + + [Command("prepare", Description = "Prepare the metadata for the next release")] + public async Task Prepare(PrepareAction action) + { + if(!Environment.IsWorkingDirectoryValid()) + return; + + Console.WriteLine("=============================="); + Console.WriteLine("- Prepare the metadata for the next release ..."); + + var appVersion = await this.UpdateAppVersion(action); + if (!string.IsNullOrWhiteSpace(appVersion)) + { + var buildNumber = await this.IncreaseBuildNumber(); + var buildTime = await this.UpdateBuildTime(); + await this.UpdateChangelog(buildNumber, appVersion, buildTime); + await this.UpdateDotnetVersion(); + await this.UpdateRustVersion(); + await this.UpdateMudBlazorVersion(); + await this.UpdateTauriVersion(); + await this.UpdateProjectCommitHash(); + await this.UpdateLicenceYear(Path.GetFullPath(Path.Combine(Environment.GetAIStudioDirectory(), "..", "..", "LICENSE.md"))); + await this.UpdateLicenceYear(Path.GetFullPath(Path.Combine(Environment.GetAIStudioDirectory(), "Pages", "About.razor.cs"))); + Console.WriteLine(); + } + } + + [Command("build", Description = "Build MindWork AI Studio")] + public async Task Build() + { + if(!Environment.IsWorkingDirectoryValid()) + return; + + // + // Build the .NET project: + // + var pathApp = Environment.GetAIStudioDirectory(); + var rids = Environment.GetRidsForCurrentOS(); + foreach (var rid in rids) + { + Console.WriteLine("=============================="); + Console.Write($"- Start .NET build for '{rid.ToName()}' ..."); + await this.ReadCommandOutput(pathApp, "dotnet", $"clean --configuration release --runtime {rid.ToName()}"); + var dotnetBuildOutput = await this.ReadCommandOutput(pathApp, "dotnet", $"publish --configuration release --runtime {rid.ToName()} --disable-build-servers --force"); + var dotnetBuildOutputLines = dotnetBuildOutput.Split([global::System.Environment.NewLine], StringSplitOptions.RemoveEmptyEntries); + var foundIssue = false; + foreach (var buildOutputLine in dotnetBuildOutputLines) + { + if(buildOutputLine.Contains(" error ") || buildOutputLine.Contains("#warning")) + { + if(!foundIssue) + { + foundIssue = true; + Console.WriteLine(); + Console.WriteLine("- Build has issues:"); + } + + Console.Write(" - "); + Console.WriteLine(buildOutputLine); + } + } + + if(foundIssue) + Console.WriteLine(); + else + { + Console.WriteLine(" completed successfully."); + } + + // + // Prepare the .NET artifact to be used by Tauri as sidecar: + // + var os = Environment.GetOS(); + var tauriSidecarArtifactName = rid switch + { + RID.WIN_X64 => "mindworkAIStudioServer-x86_64-pc-windows-msvc.exe", + RID.WIN_ARM64 => "mindworkAIStudioServer-aarch64-pc-windows-msvc.exe", + + RID.LINUX_X64 => "mindworkAIStudioServer-x86_64-unknown-linux-gnu", + RID.LINUX_ARM64 => "mindworkAIStudioServer-aarch64-unknown-linux-gnu", + + RID.OSX_ARM64 => "mindworkAIStudioServer-aarch64-apple-darwin", + RID.OSX_X64 => "mindworkAIStudioServer-x86_64-apple-darwin", + + _ => string.Empty, + }; + + if (string.IsNullOrWhiteSpace(tauriSidecarArtifactName)) + { + Console.WriteLine($"- Error: Unsupported rid '{rid.ToName()}'."); + return; + } + + var dotnetArtifactPath = Path.Combine(pathApp, "bin", "dist"); + if(!Directory.Exists(dotnetArtifactPath)) + Directory.CreateDirectory(dotnetArtifactPath); + + var dotnetArtifactFilename = os switch + { + "windows" => "mindworkAIStudio.exe", + _ => "mindworkAIStudio", + }; + + var dotnetPublishedPath = Path.Combine(pathApp, "bin", "release", Environment.DOTNET_VERSION, rid.ToName(), "publish", dotnetArtifactFilename); + var finalDestination = Path.Combine(dotnetArtifactPath, tauriSidecarArtifactName); + + if(File.Exists(dotnetPublishedPath)) + Console.WriteLine("- Published .NET artifact found."); + else + { + Console.WriteLine($"- Error: Published .NET artifact not found: '{dotnetPublishedPath}'."); + return; + } + + Console.Write($"- Move the .NET artifact to the Tauri sidecar destination ..."); + try + { + File.Move(dotnetPublishedPath, finalDestination, true); + Console.WriteLine(" done."); + } + catch (Exception e) + { + Console.WriteLine(" failed."); + Console.WriteLine($" - Error: {e.Message}"); + } + + Console.WriteLine(); + } + + // + // Build the Rust project / runtime: + // + + Console.WriteLine("=============================="); + Console.WriteLine("- Start building the Rust runtime ..."); + + var pathRuntime = Environment.GetRustRuntimeDirectory(); + var rustBuildOutput = await this.ReadCommandOutput(pathRuntime, "cargo", "tauri build --bundles none", true); + var rustBuildOutputLines = rustBuildOutput.Split([global::System.Environment.NewLine], StringSplitOptions.RemoveEmptyEntries); + var foundRustIssue = false; + foreach (var buildOutputLine in rustBuildOutputLines) + { + if(buildOutputLine.Contains("error", StringComparison.OrdinalIgnoreCase) || buildOutputLine.Contains("warning")) + { + if(!foundRustIssue) + { + foundRustIssue = true; + Console.WriteLine(); + Console.WriteLine("- Build has issues:"); + } + + Console.Write(" - "); + Console.WriteLine(buildOutputLine); + } + } + + if(foundRustIssue) + Console.WriteLine(); + else + { + Console.WriteLine(); + Console.WriteLine("- Compilation completed successfully."); + Console.WriteLine(); + } + } + + private async Task UpdateChangelog(int buildNumber, string appVersion, string buildTime) + { + var pathChangelogs = Path.Combine(Environment.GetAIStudioDirectory(), "wwwroot", "changelog"); + var expectedLogFilename = $"v{appVersion}.md"; + var expectedLogFilePath = Path.Combine(pathChangelogs, expectedLogFilename); + + if(!File.Exists(expectedLogFilePath)) + { + Console.WriteLine($"- Error: The changelog file '{expectedLogFilename}' does not exist."); + return; + } + + // Right now, the build time is formatted as "yyyy-MM-dd HH:mm:ss UTC", but must remove the seconds: + buildTime = buildTime[..^7] + " UTC"; + + const string CODE_START = + """ + LOGS = + [ + """; + + var changelogCodePath = Path.Join(Environment.GetAIStudioDirectory(), "Components", "Changelog.Logs.cs"); + var changelogCode = await File.ReadAllTextAsync(changelogCodePath, Encoding.UTF8); + var updatedCode = + $""" + {CODE_START} + new ({buildNumber}, "v{appVersion}, build {buildNumber} ({buildTime})", "{expectedLogFilename}"), + """; + + changelogCode = changelogCode.Replace(CODE_START, updatedCode); + await File.WriteAllTextAsync(changelogCodePath, changelogCode, Environment.UTF8_NO_BOM); + } + + private async Task UpdateProjectCommitHash() + { + const int COMMIT_HASH_INDEX = 8; + + var pathMetadata = Environment.GetMetadataPath(); + var lines = await File.ReadAllLinesAsync(pathMetadata, Encoding.UTF8); + var currentCommitHash = lines[COMMIT_HASH_INDEX].Trim(); + var headCommitHash = await this.ReadCommandOutput(Environment.GetAIStudioDirectory(), "git", "rev-parse HEAD"); + var first10Chars = headCommitHash[..11]; + var updatedCommitHash = $"{first10Chars}, release"; + + Console.WriteLine($"- Updating commit hash from '{currentCommitHash}' to '{updatedCommitHash}'."); + lines[COMMIT_HASH_INDEX] = updatedCommitHash; + + await File.WriteAllLinesAsync(pathMetadata, lines, Environment.UTF8_NO_BOM); + } + + private async Task UpdateAppVersion(PrepareAction action) + { + const int APP_VERSION_INDEX = 0; + + if (action == PrepareAction.NONE) + { + Console.WriteLine("- No action specified. Skipping app version update."); + return string.Empty; + } + + var pathMetadata = Environment.GetMetadataPath(); + var lines = await File.ReadAllLinesAsync(pathMetadata, Encoding.UTF8); + var currentAppVersionLine = lines[APP_VERSION_INDEX].Trim(); + var currentAppVersion = AppVersionRegex().Match(currentAppVersionLine); + var currentPatch = int.Parse(currentAppVersion.Groups["patch"].Value); + var currentMinor = int.Parse(currentAppVersion.Groups["minor"].Value); + var currentMajor = int.Parse(currentAppVersion.Groups["major"].Value); + + switch (action) + { + case PrepareAction.PATCH: + currentPatch++; + break; + + case PrepareAction.MINOR: + currentPatch = 0; + currentMinor++; + break; + + case PrepareAction.MAJOR: + currentPatch = 0; + currentMinor = 0; + currentMajor++; + break; + } + + var updatedAppVersion = $"{currentMajor}.{currentMinor}.{currentPatch}"; + Console.WriteLine($"- Updating app version from '{currentAppVersionLine}' to '{updatedAppVersion}'."); + + lines[APP_VERSION_INDEX] = updatedAppVersion; + await File.WriteAllLinesAsync(pathMetadata, lines, Environment.UTF8_NO_BOM); + return updatedAppVersion; + } + + private async Task UpdateLicenceYear(string licenceFilePath) + { + var currentYear = DateTime.UtcNow.Year.ToString(); + var lines = await File.ReadAllLinesAsync(licenceFilePath, Encoding.UTF8); + + var found = false; + var copyrightYear = string.Empty; + var updatedLines = new List(lines.Length); + foreach (var line in lines) + { + var match = FindCopyrightRegex().Match(line); + if (match.Success) + { + copyrightYear = match.Groups["year"].Value; + + if(!found && copyrightYear != currentYear) + Console.WriteLine($"- Updating the licence's year in '{Path.GetFileName(licenceFilePath)}' from '{copyrightYear}' to '{currentYear}'."); + + updatedLines.Add(ReplaceCopyrightYearRegex().Replace(line, currentYear)); + found = true; + } + else + updatedLines.Add(line); + } + + await File.WriteAllLinesAsync(licenceFilePath, updatedLines, Environment.UTF8_NO_BOM); + if (!found) + Console.WriteLine($"- Error: No copyright year found in '{Path.GetFileName(licenceFilePath)}'."); + else if (copyrightYear == currentYear) + Console.WriteLine($"- The copyright year in '{Path.GetFileName(licenceFilePath)}' is already up to date."); + } + + private async Task UpdateTauriVersion() + { + const int TAURI_VERSION_INDEX = 7; + + var pathMetadata = Environment.GetMetadataPath(); + var lines = await File.ReadAllLinesAsync(pathMetadata, Encoding.UTF8); + var currentTauriVersion = lines[TAURI_VERSION_INDEX].Trim(); + + var matches = await this.DetermineVersion("Tauri", Environment.GetRustRuntimeDirectory(), TauriVersionRegex(), "cargo", "tree --depth 1"); + if (matches.Count == 0) + return; + + var updatedTauriVersion = matches[0].Groups["version"].Value; + if(currentTauriVersion == updatedTauriVersion) + { + Console.WriteLine("- The Tauri version is already up to date."); + return; + } + + Console.WriteLine($"- Updated Tauri version from {currentTauriVersion} to {updatedTauriVersion}."); + lines[TAURI_VERSION_INDEX] = updatedTauriVersion; + + await File.WriteAllLinesAsync(pathMetadata, lines, Environment.UTF8_NO_BOM); + } + + private async Task UpdateMudBlazorVersion() + { + const int MUD_BLAZOR_VERSION_INDEX = 6; + + var pathMetadata = Environment.GetMetadataPath(); + var lines = await File.ReadAllLinesAsync(pathMetadata, Encoding.UTF8); + var currentMudBlazorVersion = lines[MUD_BLAZOR_VERSION_INDEX].Trim(); + + var matches = await this.DetermineVersion("MudBlazor", Environment.GetAIStudioDirectory(), MudBlazorVersionRegex(), "dotnet", "list package"); + if (matches.Count == 0) + return; + + var updatedMudBlazorVersion = matches[0].Groups["version"].Value; + if(currentMudBlazorVersion == updatedMudBlazorVersion) + { + Console.WriteLine("- The MudBlazor version is already up to date."); + return; + } + + Console.WriteLine($"- Updated MudBlazor version from {currentMudBlazorVersion} to {updatedMudBlazorVersion}."); + lines[MUD_BLAZOR_VERSION_INDEX] = updatedMudBlazorVersion; + + await File.WriteAllLinesAsync(pathMetadata, lines, Environment.UTF8_NO_BOM); + } + + private async Task UpdateRustVersion() + { + const int RUST_VERSION_INDEX = 5; + + var pathMetadata = Environment.GetMetadataPath(); + var lines = await File.ReadAllLinesAsync(pathMetadata, Encoding.UTF8); + var currentRustVersion = lines[RUST_VERSION_INDEX].Trim(); + var matches = await this.DetermineVersion("Rust", Environment.GetRustRuntimeDirectory(), RustVersionRegex(), "rustc", "-Vv"); + if (matches.Count == 0) + return; + + var updatedRustVersion = matches[0].Groups["version"].Value + " (commit " + matches[0].Groups["commit"].Value + ")"; + if(currentRustVersion == updatedRustVersion) + { + Console.WriteLine("- Rust version is already up to date."); + return; + } + + Console.WriteLine($"- Updated Rust version from {currentRustVersion} to {updatedRustVersion}."); + lines[RUST_VERSION_INDEX] = updatedRustVersion; + + await File.WriteAllLinesAsync(pathMetadata, lines, Environment.UTF8_NO_BOM); + } + + private async Task UpdateDotnetVersion() + { + const int DOTNET_VERSION_INDEX = 4; + const int DOTNET_SDK_VERSION_INDEX = 3; + + var pathMetadata = Environment.GetMetadataPath(); + var lines = await File.ReadAllLinesAsync(pathMetadata, Encoding.UTF8); + var currentDotnetVersion = lines[DOTNET_VERSION_INDEX].Trim(); + var currentDotnetSdkVersion = lines[DOTNET_SDK_VERSION_INDEX].Trim(); + + var matches = await this.DetermineVersion(".NET", Environment.GetAIStudioDirectory(), DotnetVersionRegex(), "dotnet", "--info"); + if (matches.Count == 0) + return; + + var updatedDotnetVersion = matches[0].Groups["hostVersion"].Value + " (commit " + matches[0].Groups["hostCommit"].Value + ")"; + var updatedDotnetSdkVersion = matches[0].Groups["sdkVersion"].Value + " (commit " + matches[0].Groups["sdkCommit"].Value + ")"; + if(currentDotnetVersion == updatedDotnetVersion && currentDotnetSdkVersion == updatedDotnetSdkVersion) + { + Console.WriteLine("- .NET version is already up to date."); + return; + } + + Console.WriteLine($"- Updated .NET SDK version from {currentDotnetSdkVersion} to {updatedDotnetSdkVersion}."); + Console.WriteLine($"- Updated .NET version from {currentDotnetVersion} to {updatedDotnetVersion}."); + + lines[DOTNET_VERSION_INDEX] = updatedDotnetVersion; + lines[DOTNET_SDK_VERSION_INDEX] = updatedDotnetSdkVersion; + + await File.WriteAllLinesAsync(pathMetadata, lines, Environment.UTF8_NO_BOM); + } + + private async Task> DetermineVersion(string name, string workingDirectory, Regex regex, string program, string command) + { + var processInfo = new ProcessStartInfo + { + WorkingDirectory = workingDirectory, + FileName = program, + Arguments = command, + RedirectStandardOutput = true, + UseShellExecute = false, + CreateNoWindow = true + }; + + using var process = new Process(); + process.StartInfo = processInfo; + process.Start(); + + var output = await process.StandardOutput.ReadToEndAsync(); + await process.WaitForExitAsync(); + + var matches = regex.Matches(output); + if (matches.Count == 0) + { + Console.WriteLine($"- Error: Was not able to determine the {name} version."); + return []; + } + + return matches; + } + + private async Task ReadCommandOutput(string workingDirectory, string program, string command, bool showLiveOutput = false) + { + var processInfo = new ProcessStartInfo + { + WorkingDirectory = workingDirectory, + FileName = program, + Arguments = command, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true, + }; + + var sb = new StringBuilder(); + using var process = new Process(); + process.StartInfo = processInfo; + process.Start(); + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + + process.OutputDataReceived += (_, args) => + { + if(!string.IsNullOrWhiteSpace(args.Data)) + { + if(showLiveOutput) + Console.WriteLine(args.Data); + sb.AppendLine(args.Data); + } + }; + + process.ErrorDataReceived += (_, args) => + { + if(!string.IsNullOrWhiteSpace(args.Data)) + { + if(showLiveOutput) + Console.WriteLine(args.Data); + sb.AppendLine(args.Data); + } + }; + + await process.WaitForExitAsync(); + return sb.ToString(); + } + + private async Task IncreaseBuildNumber() + { + const int BUILD_NUMBER_INDEX = 2; + var pathMetadata = Environment.GetMetadataPath(); + var lines = await File.ReadAllLinesAsync(pathMetadata, Encoding.UTF8); + var buildNumber = int.Parse(lines[BUILD_NUMBER_INDEX]) + 1; + + Console.WriteLine($"- Updating build number from '{lines[BUILD_NUMBER_INDEX]}' to '{buildNumber}'."); + + lines[BUILD_NUMBER_INDEX] = buildNumber.ToString(); + await File.WriteAllLinesAsync(pathMetadata, lines, Environment.UTF8_NO_BOM); + return buildNumber; + } + + private async Task UpdateBuildTime() + { + const int BUILD_TIME_INDEX = 1; + var pathMetadata = Environment.GetMetadataPath(); + var lines = await File.ReadAllLinesAsync(pathMetadata, Encoding.UTF8); + var buildTime = DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss") + " UTC"; + + Console.WriteLine($"- Updating build time from '{lines[BUILD_TIME_INDEX]}' to '{buildTime}'."); + + lines[BUILD_TIME_INDEX] = buildTime; + await File.WriteAllLinesAsync(pathMetadata, lines, Environment.UTF8_NO_BOM); + return buildTime; + } + + [GeneratedRegex("""(?ms).?(NET\s+SDK|SDK\s+\.NET)\s*:\s+Version:\s+(?[0-9.]+).+Commit:\s+(?[a-zA-Z0-9]+).+Host:\s+Version:\s+(?[0-9.]+).+Commit:\s+(?[a-zA-Z0-9]+)""")] + private static partial Regex DotnetVersionRegex(); + + [GeneratedRegex("""rustc (?[0-9.]+)(?:-nightly)? \((?[a-zA-Z0-9]+)""")] + private static partial Regex RustVersionRegex(); + + [GeneratedRegex("""MudBlazor\s+(?[0-9.]+)""")] + private static partial Regex MudBlazorVersionRegex(); + + [GeneratedRegex("""tauri\s+v(?[0-9.]+)""")] + private static partial Regex TauriVersionRegex(); + + [GeneratedRegex("""^\s*Copyright\s+(?[0-9]{4})""")] + private static partial Regex FindCopyrightRegex(); + + [GeneratedRegex("""([0-9]{4})""")] + private static partial Regex ReplaceCopyrightYearRegex(); + + [GeneratedRegex("""(?[0-9]+)\.(?[0-9]+)\.(?[0-9]+)""")] + private static partial Regex AppVersionRegex(); +} \ No newline at end of file diff --git a/app/Build/Commands/UpdateWebAssetsCommand.cs b/app/Build/Commands/UpdateWebAssetsCommand.cs new file mode 100644 index 00000000..33dc9b04 --- /dev/null +++ b/app/Build/Commands/UpdateWebAssetsCommand.cs @@ -0,0 +1,49 @@ +// ReSharper disable ClassNeverInstantiated.Global +// ReSharper disable UnusedType.Global +// ReSharper disable UnusedMember.Global + +using Build.Tools; + +namespace Build.Commands; + +public sealed class UpdateWebAssetsCommand +{ + [Command("update-web", Description = "Update web assets")] + public void UpdateWebAssets() + { + if(!Environment.IsWorkingDirectoryValid()) + return; + + Console.WriteLine("========================="); + Console.Write("- Updating web assets ..."); + + var rid = Environment.GetRidsForCurrentOS().First(); + var cwd = Environment.GetAIStudioDirectory(); + var contentPath = Path.Join(cwd, "bin", "release", Environment.DOTNET_VERSION, rid.ToName(), "publish", "wwwroot", "_content"); + var isMudBlazorDirectoryPresent = Directory.Exists(Path.Join(contentPath, "MudBlazor")); + if (!isMudBlazorDirectoryPresent) + { + Console.WriteLine(); + Console.WriteLine($"- Error: No web assets found for RID '{rid}'. Please publish the project first."); + return; + } + + Directory.CreateDirectory(Path.Join(cwd, "wwwroot", "system")); + var sourcePaths = Directory.EnumerateFiles(contentPath, "*", SearchOption.AllDirectories); + var counter = 0; + foreach(var sourcePath in sourcePaths) + { + counter++; + var relativePath = Path.GetRelativePath(cwd, sourcePath); + var targetPath = Path.Join(cwd, "wwwroot", relativePath); + var targetDirectory = Path.GetDirectoryName(targetPath); + if (targetDirectory != null) + Directory.CreateDirectory(targetDirectory); + + File.Copy(sourcePath, targetPath, true); + } + + Console.WriteLine($" {counter:###,###} web assets updated successfully."); + Console.WriteLine(); + } +} \ No newline at end of file diff --git a/app/Build/GlobalUsings.cs b/app/Build/GlobalUsings.cs new file mode 100644 index 00000000..e15bc8b9 --- /dev/null +++ b/app/Build/GlobalUsings.cs @@ -0,0 +1,7 @@ +// Global using directives + +global using System.Text; + +global using Cocona; + +global using Environment = Build.Tools.Environment; \ No newline at end of file diff --git a/app/Build/Program.cs b/app/Build/Program.cs new file mode 100644 index 00000000..e7744b36 --- /dev/null +++ b/app/Build/Program.cs @@ -0,0 +1,9 @@ +using Build.Commands; + +var builder = CoconaApp.CreateBuilder(); +var app = builder.Build(); +app.AddCommands(); +app.AddCommands(); +app.AddCommands(); +app.AddCommands(); +app.Run(); \ No newline at end of file diff --git a/app/Build/Tools/Environment.cs b/app/Build/Tools/Environment.cs new file mode 100644 index 00000000..9776b633 --- /dev/null +++ b/app/Build/Tools/Environment.cs @@ -0,0 +1,77 @@ +using System.Runtime.InteropServices; + +namespace Build.Tools; + +public static class Environment +{ + public const string DOTNET_VERSION = "net9.0"; + public static readonly Encoding UTF8_NO_BOM = new UTF8Encoding(false); + + private static readonly Dictionary ALL_RIDS = Enum.GetValues().Select(rid => new KeyValuePair(rid, rid.ToName())).ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + + public static bool IsWorkingDirectoryValid() + { + var currentDirectory = Directory.GetCurrentDirectory(); + var mainFile = Path.Combine(currentDirectory, "Program.cs"); + var projectFile = Path.Combine(currentDirectory, "Build Script.csproj"); + + if (!currentDirectory.EndsWith("Build", StringComparison.Ordinal) || !File.Exists(mainFile) || !File.Exists(projectFile)) + { + Console.WriteLine("The current directory is not a valid working directory for the build script. Go to the /app/Build directory within the git repository."); + return false; + } + + return true; + } + + public static string GetAIStudioDirectory() + { + var currentDirectory = Directory.GetCurrentDirectory(); + var directory = Path.Combine(currentDirectory, "..", "MindWork AI Studio"); + return Path.GetFullPath(directory); + } + + public static string GetRustRuntimeDirectory() + { + var currentDirectory = Directory.GetCurrentDirectory(); + var directory = Path.Combine(currentDirectory, "..", "..", "runtime"); + return Path.GetFullPath(directory); + } + + public static string GetMetadataPath() + { + var currentDirectory = Directory.GetCurrentDirectory(); + var directory = Path.Combine(currentDirectory, "..", "..", "metadata.txt"); + return Path.GetFullPath(directory); + } + + public static string? GetOS() + { + if(RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + return "windows"; + + if(RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + return "linux"; + + if(RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + return "darwin"; + + Console.WriteLine($"Error: Unsupported OS '{RuntimeInformation.OSDescription}'"); + return null; + } + + public static IEnumerable GetRidsForCurrentOS() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + return ALL_RIDS.Where(rid => rid.Value.StartsWith("win-", StringComparison.Ordinal)).Select(n => n.Key); + + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + return ALL_RIDS.Where(rid => rid.Value.StartsWith("osx-", StringComparison.Ordinal)).Select(n => n.Key); + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + return ALL_RIDS.Where(rid => rid.Value.StartsWith("linux-", StringComparison.Ordinal)).Select(n => n.Key); + + Console.WriteLine($"Error: Unsupported OS '{RuntimeInformation.OSDescription}'"); + return []; + } +} \ No newline at end of file diff --git a/app/Build/Tools/RID.cs b/app/Build/Tools/RID.cs new file mode 100644 index 00000000..73ca86ee --- /dev/null +++ b/app/Build/Tools/RID.cs @@ -0,0 +1,15 @@ +namespace Build.Tools; + +public enum RID +{ + NONE, + + WIN_X64, + WIN_ARM64, + + LINUX_X64, + LINUX_ARM64, + + OSX_X64, + OSX_ARM64, +} \ No newline at end of file diff --git a/app/Build/Tools/RIDExtensions.cs b/app/Build/Tools/RIDExtensions.cs new file mode 100644 index 00000000..60fe4711 --- /dev/null +++ b/app/Build/Tools/RIDExtensions.cs @@ -0,0 +1,18 @@ +namespace Build.Tools; + +public static class RIDExtensions +{ + public static string ToName(this RID rid) => rid switch + { + RID.WIN_X64 => "win-x64", + RID.WIN_ARM64 => "win-arm64", + + RID.LINUX_X64 => "linux-x64", + RID.LINUX_ARM64 => "linux-arm64", + + RID.OSX_X64 => "osx-x64", + RID.OSX_ARM64 => "osx-arm64", + + _ => string.Empty, + }; +} \ No newline at end of file diff --git a/app/MindWork AI Studio.sln b/app/MindWork AI Studio.sln index 37871ac7..0bb1ab52 100644 --- a/app/MindWork AI Studio.sln +++ b/app/MindWork AI Studio.sln @@ -4,6 +4,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MindWork AI Studio", "MindW EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SourceCodeRules", "SourceCodeRules\SourceCodeRules\SourceCodeRules.csproj", "{0976C1CB-D499-4C86-8ADA-B7A7A4DE0BF8}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Build Script", "Build\Build Script.csproj", "{447A5590-68E1-4EF8-9451-A41AF5FBE571}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharedTools", "SharedTools\SharedTools.csproj", "{969C74DF-7678-4CD5-B269-D03E1ECA3D2A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -18,6 +22,14 @@ Global {0976C1CB-D499-4C86-8ADA-B7A7A4DE0BF8}.Debug|Any CPU.Build.0 = Debug|Any CPU {0976C1CB-D499-4C86-8ADA-B7A7A4DE0BF8}.Release|Any CPU.ActiveCfg = Release|Any CPU {0976C1CB-D499-4C86-8ADA-B7A7A4DE0BF8}.Release|Any CPU.Build.0 = Release|Any CPU + {447A5590-68E1-4EF8-9451-A41AF5FBE571}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {447A5590-68E1-4EF8-9451-A41AF5FBE571}.Debug|Any CPU.Build.0 = Debug|Any CPU + {447A5590-68E1-4EF8-9451-A41AF5FBE571}.Release|Any CPU.ActiveCfg = Release|Any CPU + {447A5590-68E1-4EF8-9451-A41AF5FBE571}.Release|Any CPU.Build.0 = Release|Any CPU + {969C74DF-7678-4CD5-B269-D03E1ECA3D2A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {969C74DF-7678-4CD5-B269-D03E1ECA3D2A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {969C74DF-7678-4CD5-B269-D03E1ECA3D2A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {969C74DF-7678-4CD5-B269-D03E1ECA3D2A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution EndGlobalSection diff --git a/app/MindWork AI Studio.sln.DotSettings b/app/MindWork AI Studio.sln.DotSettings index 4a94c5cc..a626186b 100644 --- a/app/MindWork AI Studio.sln.DotSettings +++ b/app/MindWork AI Studio.sln.DotSettings @@ -8,7 +8,9 @@ LLM LM MSG + OS RAG + RID UI URL True diff --git a/app/MindWork AI Studio/Components/MSGComponentBase.cs b/app/MindWork AI Studio/Components/MSGComponentBase.cs index 4e904c02..a70a5257 100644 --- a/app/MindWork AI Studio/Components/MSGComponentBase.cs +++ b/app/MindWork AI Studio/Components/MSGComponentBase.cs @@ -3,6 +3,8 @@ using AIStudio.Tools.PluginSystem; using Microsoft.AspNetCore.Components; +using SharedTools; + namespace AIStudio.Components; public abstract class MSGComponentBase : ComponentBase, IDisposable, IMessageBusReceiver, ILang diff --git a/app/MindWork AI Studio/MindWork AI Studio.csproj b/app/MindWork AI Studio/MindWork AI Studio.csproj index a4fae3cb..f70535ce 100644 --- a/app/MindWork AI Studio/MindWork AI Studio.csproj +++ b/app/MindWork AI Studio/MindWork AI Studio.csproj @@ -56,6 +56,7 @@ + diff --git a/app/MindWork AI Studio/build.nu b/app/MindWork AI Studio/build.nu deleted file mode 100755 index 3c9c2e4e..00000000 --- a/app/MindWork AI Studio/build.nu +++ /dev/null @@ -1,388 +0,0 @@ -#!/usr/bin/env nu - -def main [] {} - -def are_assets_exist [rid: string] { - $"bin/release/net9.0/($rid)/publish/wwwroot/_content/MudBlazor/MudBlazor.min.css" | path exists -} - -def "main help" [] { - print "Usage: nu build.nu [action]" - print "" - print "Optional Actions:" - print "-----------------" - print " fix_web_assets Prepare the web assets; run this once for each release on one platform; changes will be committed." - print "" - print " metadata Update the metadata file; run this on every platform right before the release; changes will be" - print " committed once; there should be no differences between the platforms." - print "" - print "Actions:" - print "---------" - print " prepare [action] Prepare the project for a release; increases the version & build numbers, updates the build time," - print " and runs fix_web_assets; run this once for each release on one platform; changes will be committed." - print " The action can be 'major', 'minor', or 'patch'. The version will be updated accordingly." - print "" - print " publish Publish the project for all supported RIDs; run this on every platform." - print "" -} - -def "main prepare" [action: string] { - if (update_app_version $action) { - main fix_web_assets - inc_build_number - update_build_time - update_changelog - main metadata - } -} - -def "main metadata" [] { - update_dotnet_version - update_rust_version - update_mudblazor_version - update_tauri_version - update_project_commit_hash - update_license_year "../../LICENSE.md" - update_license_year "Pages/About.razor.cs" -} - -def "main fix_web_assets" [] { - - # Get the matching RIDs for the current OS: - let rids = get_rids - - # We chose the first RID to copy the assets from: - let rid = $rids.0 - - if (are_assets_exist $rid) == false { - print $"Web assets do not exist for ($rid). Please build the project first." - return - } - - # Ensure, that the dist directory exists: - mkdir wwwroot/system - - # Copy the web assets from the first RID to the source project: - let source_paths = glob --depth 99 bin/release/net9.0/($rid)/publish/wwwroot/_content/* - - for source_path in $source_paths { - cp --recursive --force --update $source_path wwwroot/system/ - } -} - -def "main publish" [] { - - main metadata - - # Ensure, that the dist directory exists: - mkdir bin/dist - - # Get the matching RIDs for the current OS: - let rids = get_rids - - if ($rids | length) == 0 { - print "No RIDs to build for." - return - } - - let current_os = get_os - let published_filename_dotnet = match $current_os { - "windows" => "mindworkAIStudio.exe", - _ => "mindworkAIStudio" - } - - # Build for each RID: - for rid in $rids { - print "==============================" - print $"Start building for ($rid)..." - - ^dotnet publish --configuration release --runtime $rid --disable-build-servers --force - - let final_filename = match $rid { - "win-x64" => "mindworkAIStudioServer-x86_64-pc-windows-msvc.exe", - "win-arm64" => "mindworkAIStudioServer-aarch64-pc-windows-msvc.exe", - "linux-x64" => "mindworkAIStudioServer-x86_64-unknown-linux-gnu", - "linux-arm64" => "mindworkAIStudioServer-aarch64-unknown-linux-gnu", - "osx-arm64" => "mindworkAIStudioServer-aarch64-apple-darwin", - "osx-x64" => "mindworkAIStudioServer-x86_64-apple-darwin", - - _ => { - print $"Unsupported RID for final filename: ($rid)" - return - } - } - - let published_path = $"bin/release/net9.0/($rid)/publish/($published_filename_dotnet)" - let final_path = $"bin/dist/($final_filename)" - - if ($published_path | path exists) { - print $"Published file ($published_path) exists." - } else { - print $"Published file ($published_path) does not exist. Compiling might failed?" - return - } - - print $"Moving ($published_path) to ($final_path)..." - mv --force $published_path $final_path - } - - print "==============================" - print "Start building runtime..." - - cd ../../runtime - try { - cargo tauri build --bundles none - }; - - cd "../app/MindWork AI Studio" - print "==============================" - print "Building done." -} - -def get_rids [] { - # Define the list of RIDs to build for, cf. https://learn.microsoft.com/en-us/dotnet/core/rid-catalog: - let rids = ["win-x64", "win-arm64", "linux-x64", "linux-arm64", "osx-arm64", "osx-x64"] - - # Get the current OS: - let current_os = get_os - let current_os_dotnet = match $current_os { - "windows" => "win-", - "linux" => "linux-", - "darwin" => "osx-", - - _ => { - print $"Unsupported OS: ($current_os)" - return - } - } - - # Filter the RIDs to build for the current OS: - let rids = $rids | where $it =~ $current_os_dotnet - - # Return the list of RIDs to build for: - $rids -} - -def get_os [] { - let os = (sys host).name | str downcase - if $os =~ "linux" { - return "linux" - } - $os -} - -def update_build_time [] { - mut meta_lines = open --raw ../../metadata.txt | lines - mut build_time = $meta_lines.1 - - let updated_build_time = (date now | date to-timezone UTC | format date "%Y-%m-%d %H:%M:%S") - print $"Updated build time from ($build_time) to ($updated_build_time) UTC." - - $build_time = $"($updated_build_time) UTC" - $meta_lines.1 = $build_time - $meta_lines | save --raw --force ../../metadata.txt -} - -def inc_build_number [] { - mut meta_lines = open --raw ../../metadata.txt | lines - mut build_number = $meta_lines.2 | into int - - let updated_build_number = ([$build_number, 1] | math sum) - print $"Incremented build number from ($build_number) to ($updated_build_number)." - - $build_number = $updated_build_number - $meta_lines.2 = ($build_number | into string) - $meta_lines | save --raw --force ../../metadata.txt -} - -def update_dotnet_version [] { - mut meta_lines = open --raw ../../metadata.txt | lines - mut dotnet_sdk_version = $meta_lines.3 - mut dotnet_version = $meta_lines.4 - - let dotnet_data = (^dotnet --info) | collect | parse --regex '(?ms).?(NET\s+SDK|SDK\s+\.NET)\s*:\s+Version:\s+(?P[0-9.]+).+Commit:\s+(?P[a-zA-Z0-9]+).+Host:\s+Version:\s+(?P[0-9.]+).+Commit:\s+(?P[a-zA-Z0-9]+)' - let sdk_version = $dotnet_data.sdkVersion.0 - let host_version = $dotnet_data.hostVersion.0 - let sdkCommit = $dotnet_data.sdkCommit.0 - let hostCommit = $dotnet_data.hostCommit.0 - - print $"Updated .NET SDK version from ($dotnet_sdk_version) to ($sdk_version) \(commit ($sdkCommit)\)." - $meta_lines.3 = $"($sdk_version) \(commit ($sdkCommit)\)" - - print $"Updated .NET version from ($dotnet_version) to ($host_version) \(commit ($hostCommit)\)." - $meta_lines.4 = $"($host_version) \(commit ($hostCommit)\)" - - $meta_lines | save --raw --force ../../metadata.txt -} - -def update_rust_version [] { - mut meta_lines = open --raw ../../metadata.txt | lines - mut rust_version = $meta_lines.5 - - let rust_data = (^rustc -Vv) | parse --regex 'rustc (?[0-9.]+)(?:-nightly)? \((?[a-zA-Z0-9]+)' - let version = $rust_data.version.0 - let commit = $rust_data.commit.0 - - print $"Updated Rust version from ($rust_version) to ($version) \(commit ($commit)\)." - $meta_lines.5 = $"($version) \(commit ($commit)\)" - - $meta_lines | save --raw --force ../../metadata.txt -} - -def update_mudblazor_version [] { - mut meta_lines = open --raw ../../metadata.txt | lines - mut mudblazor_version = $meta_lines.6 - - let mudblazor_data = (^dotnet list package) | parse --regex 'MudBlazor\s+(?[0-9.]+)' - let version = $mudblazor_data.version.0 - - print $"Updated MudBlazor version from ($mudblazor_version) to ($version)." - $meta_lines.6 = $version - - $meta_lines | save --raw --force ../../metadata.txt -} - -def update_tauri_version [] { - mut meta_lines = open --raw ../../metadata.txt | lines - mut tauri_version = $meta_lines.7 - - cd ../../runtime - let tauri_data = (^cargo tree --depth 1) | parse --regex 'tauri\s+v(?[0-9.]+)' - let version = $tauri_data.version.0 - cd "../app/MindWork AI Studio" - - print $"Updated Tauri version from ($tauri_version) to ($version)." - $meta_lines.7 = $version - - $meta_lines | save --raw --force ../../metadata.txt -} - -def update_license_year [licence_file: string] { - let current_year = (date now | date to-timezone UTC | format date "%Y") - let license_text = open --raw $licence_file | lines - print $"Updating the license's year in ($licence_file) to ($current_year)." - - # Target line looks like `Copyright 2024 Thorsten Sommer`. - # Perhaps, there are whitespaces at the beginning. Using - # a regex to match the year. - let updated_license_text = $license_text | each { |it| - if $it =~ '^\s*Copyright\s+[0-9]{4}' { - $it | str replace --regex '([0-9]{4})' $"($current_year)" - } else { - $it - } - } - - $updated_license_text | save --raw --force $licence_file -} - -def update_app_version [action: string] { - mut meta_lines = open --raw ../../metadata.txt | lines - mut app_version = $meta_lines.0 - - let version_data = $app_version | parse --regex '(?P[0-9]+)\.(?P[0-9]+)\.(?P[0-9]+)' - - if $action == "major" { - - mut major = $version_data.major | into int - $major = ([$major.0, 1] | math sum) - - let updated_version = [$major, 0, 0] | str join "." - print $"Updated app version from ($app_version) to ($updated_version)." - $meta_lines.0 = $updated_version - - } else if $action == "minor" { - - let major = $version_data.major | into int - mut minor = $version_data.minor | into int - $minor = ([$minor.0, 1] | math sum) - - let updated_version = [$major.0, $minor, 0] | str join "." - print $"Updated app version from ($app_version) to ($updated_version)." - $meta_lines.0 = $updated_version - - } else if $action == "patch" { - - let major = $version_data.major | into int - let minor = $version_data.minor | into int - mut patch = $version_data.patch | into int - $patch = ([$patch.0, 1] | math sum) - - let updated_version = [$major.0, $minor.0, $patch] | str join "." - print $"Updated app version from ($app_version) to ($updated_version)." - $meta_lines.0 = $updated_version - - } else { - print $"Invalid action '($action)'. Please use 'major', 'minor', or 'patch'." - return false - } - - $meta_lines | save --raw --force ../../metadata.txt - return true -} - -def update_project_commit_hash [] { - mut meta_lines = open --raw ../../metadata.txt | lines - mut commit_hash = $meta_lines.8 - - # Check, if the work directory is clean. We allow, that the metadata file is dirty: - let git_status = (^git status --porcelain) | lines - let dirty_files = $git_status | length - let first_is_metadata = ($dirty_files > 0 and $git_status.0 =~ '^\sM\s+metadata.txt$') - let git_tag_response = ^git describe --tags --exact-match | complete - let state = { - num_dirty: $dirty_files, - first_is_metadata: $first_is_metadata, - git_tag_present: ($git_tag_response.exit_code == 0), - git_tag: $git_tag_response.stdout - } - - let commit_postfix = match $state { - { num_dirty: $num_dirty, first_is_metadata: _, git_tag_present: _, git_tag: _ } if $num_dirty > 1 => ", dev debug", - { num_dirty: $num_dirty, first_is_metadata: false, git_tag_present: _, git_tag: _ } if $num_dirty == 1 => ", dev debug", - { num_dirty: $num_dirty, first_is_metadata: true, git_tag_present: false, git_tag: _ } if $num_dirty == 1 => ", dev testing", - { num_dirty: $num_dirty, first_is_metadata: false, git_tag_present: false, git_tag: _ } if $num_dirty == 0 => ", dev testing", - { num_dirty: $num_dirty, first_is_metadata: true, git_tag_present: true, git_tag: $tag } if $num_dirty == 1 => $", release $tag", - { num_dirty: $num_dirty, first_is_metadata: false, git_tag_present: true, git_tag: $tag } if $num_dirty == 0 => $", release $tag", - - _ => "-dev unknown" - } - - # Use the first ten characters of the commit hash: - let updated_commit_hash = (^git rev-parse HEAD) | str substring 0..10 | append $commit_postfix | str join - print $"Updated commit hash from ($commit_hash) to ($updated_commit_hash)." - - $meta_lines.8 = $updated_commit_hash - $meta_lines | save --raw --force ../../metadata.txt -} - -def update_changelog [] { - # Get all changelog files: - let all_changelog_files = glob wwwroot/changelog/*.md - - # Create a table with the build numbers and the corresponding file names: - let table = $all_changelog_files | reduce --fold [] { |it, acc| - let header_line = open --raw $it | lines | first - let file_name = $it | path basename - let header_data = $header_line | parse --regex '#\s+(?P
(?Pv[0-9.]+)[,\s]+build\s+(?P[0-9]+)[,\s]+\((?P[0-9-?\s:UTC]+)\))' - $acc ++ [[build_num, file_name, header]; [$header_data.build_num.0, $file_name, $header_data.header.0]] - } | sort-by build_num --natural --reverse - - # Now, we build the necessary C# code: - const tab = " "; # using 4 spaces as one tab - let code_rows = $table | reduce --fold "" { |it, acc| - $acc ++ $"($tab)($tab)new \(($it.build_num), \"($it.header)\", \"($it.file_name)\"\),\n" - } - - let code = ($"LOGS = \n($tab)[\n($code_rows)\n($tab)];") - - # Next, update the Changelog.Logs.cs file: - let changelog_logs_source_file = open --raw "Components/Changelog.Logs.cs" - let result = $changelog_logs_source_file | str replace --regex '(?ms)LOGS =\s+\[[\w\s".,-:()?]+\];' $code - - # Save the updated file: - $result | save --raw --force "Components/Changelog.Logs.cs" - - let number_change_logs = $table | length - print $"Updated Changelog.Logs.cs with ($number_change_logs) change logs." -} \ No newline at end of file diff --git a/app/MindWork AI Studio/packages.lock.json b/app/MindWork AI Studio/packages.lock.json index 7c4e67da..b35b2e3d 100644 --- a/app/MindWork AI Studio/packages.lock.json +++ b/app/MindWork AI Studio/packages.lock.json @@ -205,6 +205,9 @@ "type": "Transitive", "resolved": "0.16.9", "contentHash": "7WaVMHklpT3Ye2ragqRIwlFRsb6kOk63BOGADV0fan3ulVfGLUYkDi5yNUsZS/7FVNkWbtHAlDLmu4WnHGfqvQ==" + }, + "sharedtools": { + "type": "Project" } }, "net9.0/osx-arm64": {} diff --git a/app/MindWork AI Studio/Tools/FNVHash.cs b/app/SharedTools/FNVHash.cs similarity index 98% rename from app/MindWork AI Studio/Tools/FNVHash.cs rename to app/SharedTools/FNVHash.cs index cc47645e..a60beeff 100644 --- a/app/MindWork AI Studio/Tools/FNVHash.cs +++ b/app/SharedTools/FNVHash.cs @@ -1,5 +1,5 @@ // ReSharper disable MemberCanBePrivate.Global -namespace AIStudio.Tools; +namespace SharedTools; /// /// Implements the Fowler–Noll–Vo hash function for 32-bit and 64-bit hashes. diff --git a/app/SharedTools/SharedTools.csproj b/app/SharedTools/SharedTools.csproj new file mode 100644 index 00000000..e0439ac8 --- /dev/null +++ b/app/SharedTools/SharedTools.csproj @@ -0,0 +1,10 @@ + + + + net9.0 + latest + enable + enable + + + diff --git a/documentation/Build.md b/documentation/Build.md index b25a27ef..41b874d9 100644 --- a/documentation/Build.md +++ b/documentation/Build.md @@ -5,43 +5,46 @@ You just want to use the app? Then simply [download the appropriate setup for yo 1. Install the [.NET 9 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/9.0). 2. [Install the Rust compiler](https://www.rust-lang.org/tools/install) in the latest version. 3. Met the prerequisites for building [Tauri](https://tauri.app/v1/guides/getting-started/prerequisites/). Node.js is **not** required, though. -4. Install the Tauri CLI by running `cargo install --version 1.6.2 tauri-cli`. -5. [Install NuShell](https://www.nushell.sh/). NuShell works on all operating systems and is required because the build script is written in NuShell. -6. Clone the repository. +4. Clone the repository. ## One-time mandatory steps Regardless of whether you want to build the app locally for yourself (not trusting the pre-built binaries) or test your changes before creating a PR, you have to run the following commands at least once: -1. Open a terminal using NuShell. -2. Navigate to the `/app/MindWork AI Studio` directory within the repository. -3. Run `dotnet restore` to bring up the .NET dependencies. -4. Run `nu build.nu publish` to build the entire app. +1. Open a terminal. +2. Install the Tauri CLI by running `cargo install --version 1.6.2 tauri-cli`. +3. Navigate to the `/app/Build` directory within the repository. +4. Run `dotnet run build` to build the entire app. This is necessary because the build script and the Tauri framework assume that the .NET app is available as a so-called "sidecar." Although the sidecar is only necessary for the final release and shipping, Tauri requires it to be present during development as well. ## Build AI Studio from source In order to build MindWork AI Studio from source instead of using the pre-built binaries, follow these steps: 1. Ensure you have met all the prerequisites. -2. Open a terminal with NuShell. -3. Navigate to the `/app/MindWork AI Studio` directory within the repository. -4. To build the current version, run `nu build.nu publish` to build the entire app. +2. Open a terminal. +3. Navigate to the `/app/Build` directory within the repository. +4. To build the current version, run `dotnet run build` to build the entire app. - This will build the app for the current operating system, for both x64 (Intel, AMD) and ARM64 (e.g., Apple Silicon, Raspberry Pi). - - The final setup program will be located in `runtime/target/release/bundle` afterward. -5. In order to create a new release: - 1. Before finishing the PR, make sure to create a changelog file in the `/app/MindWork AI Studio/wwwroot/changelog` directory. The file should be named `vX.Y.Z.md` and contain the changes made in the release (your changes and any other changes that are part of the release). - 2. To prepare a new release, run `nu build.nu prepare `, where `` is either `patch`, `minor`, or `major`. - 3. The actual release will be built by our GitHub Workflow. For this to work, you need to create a PR with your changes. - 4. Your proposed changes will be reviewed and merged. - 5. Once the PR is merged, a member of the maintainers team will create & push an appropriate git tag in the format `vX.Y.Z`. - 6. The GitHub Workflow will then build the release and upload it to the [release page](https://github.com/MindWorkAI/AI-Studio/releases/latest). - 7. Building the release including virus scanning takes some time. Please be patient. + - The final setup program will be located in `runtime/target/release` afterward. ## Run the app locally with all your changes Do you want to test your changes before creating a PR? Follow these steps: 1. Ensure you have met all the prerequisites. -2. At least once, you have to run the `nu build.nu publish` command (see above, "Build instructions"). This is necessary because the Tauri framework checks whether the .NET app as so-called "sidecar" is available. Although the sidecar is only necessary for the final release and shipping, Tauri requires it to be present during development. -3. Open a terminal (in this case, it doesn't have to be NuShell). +2. At least once, you have to run the `dotnet run build` command (see above, "Build instructions"). This is necessary because the Tauri framework checks whether the .NET app as so-called "sidecar" is available. Although the sidecar is only necessary for the final release and shipping, Tauri requires it to be present during development. +3. Open a terminal. 4. Navigate to the `runtime` directory within the repository, e.g. `cd repos/mindwork-ai-studio/runtime`. 5. Run `cargo tauri dev --no-watch`. -Cargo will compile the Rust code and start the runtime. The runtime will then start the .NET compiler. When the .NET source code is compiled, the app will start. You can now test your changes. \ No newline at end of file +Cargo will compile the Rust code and start the runtime. The runtime will then start the .NET compiler. When the .NET source code is compiled, the app will start. You can now test your changes. + +## Create a release +In order to create a release: +1. To create a new release, you need to be a maintainer of the repository—see step 8. +2. Make sure there's a changelog file for the version you want to create in the `/app/MindWork AI Studio/wwwroot/changelog` directory. Name the file `vX.Y.Z.md` and include all release changes—your updates and any others included in this version. +3. After you have created the changelog file, you must commit the changes to the repository. +4. To prepare a new release, open a terminal, go to `/app/Build` and run `dotnet run release --action `, where `` is either `patch` (creating a patch version), `minor` (creating a minor version), or `major` (creating a major version). +5. Now wait until all process steps have been completed. Among other things, the version number will be incremented, the new changelog registered, and the version numbers of central dependencies updated, etc. +6. The actual release will be built by our GitHub Workflow. For this to work, you need to create a PR with your changes. +7. Your proposed changes will be reviewed and merged. +8. Once the PR is merged, a member of the maintainers team will create & push an appropriate git tag in the format `vX.Y.Z`. +9. The GitHub Workflow will then build the release and upload it to the [release page](https://github.com/MindWorkAI/AI-Studio/releases/latest). +10. Building the release including virus scanning takes some time. Please be patient. \ No newline at end of file From b153b9c4e991ccfa48b47538a526a235bb339506 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Tue, 15 Apr 2025 08:08:39 +0200 Subject: [PATCH 12/93] Added the current CPU architecture to the metadata (#402) --- .github/workflows/build-and-release.yml | 9 ++++ app/Build/Commands/UpdateMetadataCommands.cs | 27 ++++++++--- app/Build/Commands/UpdateWebAssetsCommand.cs | 4 +- app/Build/Tools/Environment.cs | 4 +- app/Build/Tools/RIDExtensions.cs | 18 -------- .../Dialogs/UpdateDialog.razor.cs | 1 + app/MindWork AI Studio/MetaDataAttribute.cs | 33 ------------- .../MindWork AI Studio.csproj | 6 ++- app/MindWork AI Studio/Pages/About.razor.cs | 6 ++- .../Tools/Metadata/MetaDataArchitecture.cs | 8 ++++ .../Tools/Metadata/MetaDataAttribute.cs | 34 ++++++++++++++ app/{Build/Tools => SharedTools}/RID.cs | 2 +- app/SharedTools/RIDExtensions.cs | 46 +++++++++++++++++++ metadata.txt | 1 + runtime/build.rs | 45 +++++++++++++++--- runtime/src/main.rs | 4 +- 16 files changed, 177 insertions(+), 71 deletions(-) delete mode 100644 app/Build/Tools/RIDExtensions.cs delete mode 100644 app/MindWork AI Studio/MetaDataAttribute.cs create mode 100644 app/MindWork AI Studio/Tools/Metadata/MetaDataArchitecture.cs create mode 100644 app/MindWork AI Studio/Tools/Metadata/MetaDataAttribute.cs rename app/{Build/Tools => SharedTools}/RID.cs (84%) create mode 100644 app/SharedTools/RIDExtensions.cs diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index 18d965e8..6bd4841c 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -160,6 +160,9 @@ jobs: # Format the app version: formatted_app_version="v${app_version}" + # Set the architecture: + sed -i '' "10s/.*/${{ matrix.dotnet_runtime }}/" metadata.txt + # Write the metadata to the environment: echo "APP_VERSION=${app_version}" >> $GITHUB_ENV echo "FORMATTED_APP_VERSION=${formatted_app_version}" >> $GITHUB_ENV @@ -212,6 +215,12 @@ jobs: # Format the app version: $formatted_app_version = "v${app_version}" + # Set the architecture: + $metadata[9] = "${{ matrix.dotnet_runtime }}" + + # Write the changed metadata back to the file: + Set-Content -Path metadata.txt -Value $metadata + # 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 diff --git a/app/Build/Commands/UpdateMetadataCommands.cs b/app/Build/Commands/UpdateMetadataCommands.cs index d7055d4a..e2bf1589 100644 --- a/app/Build/Commands/UpdateMetadataCommands.cs +++ b/app/Build/Commands/UpdateMetadataCommands.cs @@ -1,7 +1,7 @@ using System.Diagnostics; using System.Text.RegularExpressions; -using Build.Tools; +using SharedTools; namespace Build.Commands; @@ -77,9 +77,11 @@ public sealed partial class UpdateMetadataCommands foreach (var rid in rids) { Console.WriteLine("=============================="); - Console.Write($"- Start .NET build for '{rid.ToName()}' ..."); - await this.ReadCommandOutput(pathApp, "dotnet", $"clean --configuration release --runtime {rid.ToName()}"); - var dotnetBuildOutput = await this.ReadCommandOutput(pathApp, "dotnet", $"publish --configuration release --runtime {rid.ToName()} --disable-build-servers --force"); + await this.UpdateArchitecture(rid); + + Console.Write($"- Start .NET build for '{rid.AsMicrosoftRid()}' ..."); + await this.ReadCommandOutput(pathApp, "dotnet", $"clean --configuration release --runtime {rid.AsMicrosoftRid()}"); + var dotnetBuildOutput = await this.ReadCommandOutput(pathApp, "dotnet", $"publish --configuration release --runtime {rid.AsMicrosoftRid()} --disable-build-servers --force"); var dotnetBuildOutputLines = dotnetBuildOutput.Split([global::System.Environment.NewLine], StringSplitOptions.RemoveEmptyEntries); var foundIssue = false; foreach (var buildOutputLine in dotnetBuildOutputLines) @@ -125,7 +127,7 @@ public sealed partial class UpdateMetadataCommands if (string.IsNullOrWhiteSpace(tauriSidecarArtifactName)) { - Console.WriteLine($"- Error: Unsupported rid '{rid.ToName()}'."); + Console.WriteLine($"- Error: Unsupported rid '{rid.AsMicrosoftRid()}'."); return; } @@ -139,7 +141,7 @@ public sealed partial class UpdateMetadataCommands _ => "mindworkAIStudio", }; - var dotnetPublishedPath = Path.Combine(pathApp, "bin", "release", Environment.DOTNET_VERSION, rid.ToName(), "publish", dotnetArtifactFilename); + var dotnetPublishedPath = Path.Combine(pathApp, "bin", "release", Environment.DOTNET_VERSION, rid.AsMicrosoftRid(), "publish", dotnetArtifactFilename); var finalDestination = Path.Combine(dotnetArtifactPath, tauriSidecarArtifactName); if(File.Exists(dotnetPublishedPath)) @@ -235,6 +237,19 @@ public sealed partial class UpdateMetadataCommands await File.WriteAllTextAsync(changelogCodePath, changelogCode, Environment.UTF8_NO_BOM); } + private async Task UpdateArchitecture(RID rid) + { + const int ARCHITECTURE_INDEX = 9; + + var pathMetadata = Environment.GetMetadataPath(); + var lines = await File.ReadAllLinesAsync(pathMetadata, Encoding.UTF8); + Console.Write("- Updating architecture ..."); + lines[ARCHITECTURE_INDEX] = rid.AsMicrosoftRid(); + + await File.WriteAllLinesAsync(pathMetadata, lines, Environment.UTF8_NO_BOM); + Console.WriteLine(" done."); + } + private async Task UpdateProjectCommitHash() { const int COMMIT_HASH_INDEX = 8; diff --git a/app/Build/Commands/UpdateWebAssetsCommand.cs b/app/Build/Commands/UpdateWebAssetsCommand.cs index 33dc9b04..3e0ca633 100644 --- a/app/Build/Commands/UpdateWebAssetsCommand.cs +++ b/app/Build/Commands/UpdateWebAssetsCommand.cs @@ -4,6 +4,8 @@ using Build.Tools; +using SharedTools; + namespace Build.Commands; public sealed class UpdateWebAssetsCommand @@ -19,7 +21,7 @@ public sealed class UpdateWebAssetsCommand var rid = Environment.GetRidsForCurrentOS().First(); var cwd = Environment.GetAIStudioDirectory(); - var contentPath = Path.Join(cwd, "bin", "release", Environment.DOTNET_VERSION, rid.ToName(), "publish", "wwwroot", "_content"); + var contentPath = Path.Join(cwd, "bin", "release", Environment.DOTNET_VERSION, rid.AsMicrosoftRid(), "publish", "wwwroot", "_content"); var isMudBlazorDirectoryPresent = Directory.Exists(Path.Join(contentPath, "MudBlazor")); if (!isMudBlazorDirectoryPresent) { diff --git a/app/Build/Tools/Environment.cs b/app/Build/Tools/Environment.cs index 9776b633..a86000f4 100644 --- a/app/Build/Tools/Environment.cs +++ b/app/Build/Tools/Environment.cs @@ -1,5 +1,7 @@ using System.Runtime.InteropServices; +using SharedTools; + namespace Build.Tools; public static class Environment @@ -7,7 +9,7 @@ public static class Environment public const string DOTNET_VERSION = "net9.0"; public static readonly Encoding UTF8_NO_BOM = new UTF8Encoding(false); - private static readonly Dictionary ALL_RIDS = Enum.GetValues().Select(rid => new KeyValuePair(rid, rid.ToName())).ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + private static readonly Dictionary ALL_RIDS = Enum.GetValues().Select(rid => new KeyValuePair(rid, rid.AsMicrosoftRid())).ToDictionary(kvp => kvp.Key, kvp => kvp.Value); public static bool IsWorkingDirectoryValid() { diff --git a/app/Build/Tools/RIDExtensions.cs b/app/Build/Tools/RIDExtensions.cs deleted file mode 100644 index 60fe4711..00000000 --- a/app/Build/Tools/RIDExtensions.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Build.Tools; - -public static class RIDExtensions -{ - public static string ToName(this RID rid) => rid switch - { - RID.WIN_X64 => "win-x64", - RID.WIN_ARM64 => "win-arm64", - - RID.LINUX_X64 => "linux-x64", - RID.LINUX_ARM64 => "linux-arm64", - - RID.OSX_X64 => "osx-x64", - RID.OSX_ARM64 => "osx-arm64", - - _ => string.Empty, - }; -} \ No newline at end of file diff --git a/app/MindWork AI Studio/Dialogs/UpdateDialog.razor.cs b/app/MindWork AI Studio/Dialogs/UpdateDialog.razor.cs index 555cb182..0623b164 100644 --- a/app/MindWork AI Studio/Dialogs/UpdateDialog.razor.cs +++ b/app/MindWork AI Studio/Dialogs/UpdateDialog.razor.cs @@ -1,5 +1,6 @@ using System.Reflection; +using AIStudio.Tools.Metadata; using AIStudio.Tools.Rust; using Microsoft.AspNetCore.Components; diff --git a/app/MindWork AI Studio/MetaDataAttribute.cs b/app/MindWork AI Studio/MetaDataAttribute.cs deleted file mode 100644 index 4b610eb2..00000000 --- a/app/MindWork AI Studio/MetaDataAttribute.cs +++ /dev/null @@ -1,33 +0,0 @@ -namespace AIStudio; - -[AttributeUsage(AttributeTargets.Assembly)] -public class MetaDataAttribute( - string version, - string buildTime, - uint buildNum, - string dotnetSdkVersion, - string dotnetVersion, - string rustVersion, - string mudBlazorVersion, - string tauriVersion, - string appCommitHash - ) : Attribute -{ - public string BuildTime { get; } = buildTime; - - public string Version { get; } = version; - - public uint BuildNum { get; } = buildNum; - - public string DotnetVersion { get; } = dotnetVersion; - - public string DotnetSdkVersion { get; } = dotnetSdkVersion; - - public string RustVersion { get; } = rustVersion; - - public string MudBlazorVersion { get; } = mudBlazorVersion; - - public string TauriVersion { get; } = tauriVersion; - - public string AppCommitHash { get; } = appCommitHash; -} \ No newline at end of file diff --git a/app/MindWork AI Studio/MindWork AI Studio.csproj b/app/MindWork AI Studio/MindWork AI Studio.csproj index f70535ce..d9e8e7f3 100644 --- a/app/MindWork AI Studio/MindWork AI Studio.csproj +++ b/app/MindWork AI Studio/MindWork AI Studio.csproj @@ -78,6 +78,7 @@ $([System.String]::Copy( $(Metadata) ).Split( ';' )[ 6 ]) $([System.String]::Copy( $(Metadata) ).Split( ';' )[ 7 ]) $([System.String]::Copy( $(Metadata) ).Split( ';' )[ 8 ]) + $([System.String]::Copy( $(Metadata) ).Split( ';' )[ 9 ]) true @@ -87,7 +88,7 @@ - + <_Parameter1>$(MetaVersion) <_Parameter2>$(MetaBuildTime) <_Parameter3>$(MetaBuild) @@ -99,6 +100,9 @@ <_Parameter8>$(MetaTauriVersion) <_Parameter9>$(MetaAppCommitHash) + + <_Parameter1>$(MetaArchitecture) + diff --git a/app/MindWork AI Studio/Pages/About.razor.cs b/app/MindWork AI Studio/Pages/About.razor.cs index 51727506..0be178f7 100644 --- a/app/MindWork AI Studio/Pages/About.razor.cs +++ b/app/MindWork AI Studio/Pages/About.razor.cs @@ -1,10 +1,13 @@ using System.Reflection; +using AIStudio.Tools.Metadata; using AIStudio.Tools.Rust; using AIStudio.Tools.Services; using Microsoft.AspNetCore.Components; +using SharedTools; + namespace AIStudio.Pages; public partial class About : ComponentBase @@ -20,6 +23,7 @@ public partial class About : ComponentBase private static readonly Assembly ASSEMBLY = Assembly.GetExecutingAssembly(); private static readonly MetaDataAttribute META_DATA = ASSEMBLY.GetCustomAttribute()!; + private static readonly MetaDataArchitecture META_DATA_ARCH = ASSEMBLY.GetCustomAttribute()!; private static string VersionDotnetRuntime => $"Used .NET runtime: v{META_DATA.DotnetVersion}"; @@ -27,7 +31,7 @@ public partial class About : ComponentBase private static string VersionRust => $"Used Rust compiler: v{META_DATA.RustVersion}"; - private static string VersionApp => $"MindWork AI Studio: v{META_DATA.Version} (commit {META_DATA.AppCommitHash}, build {META_DATA.BuildNum})"; + private static string VersionApp => $"MindWork AI Studio: v{META_DATA.Version} (commit {META_DATA.AppCommitHash}, build {META_DATA.BuildNum}, {META_DATA_ARCH.Architecture.ToRID().ToUserFriendlyName()})"; private static string BuildTime => $"Build time: {META_DATA.BuildTime}"; diff --git a/app/MindWork AI Studio/Tools/Metadata/MetaDataArchitecture.cs b/app/MindWork AI Studio/Tools/Metadata/MetaDataArchitecture.cs new file mode 100644 index 00000000..c5341c54 --- /dev/null +++ b/app/MindWork AI Studio/Tools/Metadata/MetaDataArchitecture.cs @@ -0,0 +1,8 @@ +// ReSharper disable ClassNeverInstantiated.Global +namespace AIStudio.Tools.Metadata; + +[AttributeUsage(AttributeTargets.Assembly)] +public class MetaDataArchitecture(string architecture) : Attribute +{ + public string Architecture => architecture; +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/Metadata/MetaDataAttribute.cs b/app/MindWork AI Studio/Tools/Metadata/MetaDataAttribute.cs new file mode 100644 index 00000000..36668829 --- /dev/null +++ b/app/MindWork AI Studio/Tools/Metadata/MetaDataAttribute.cs @@ -0,0 +1,34 @@ +// ReSharper disable ClassNeverInstantiated.Global +namespace AIStudio.Tools.Metadata; + +[AttributeUsage(AttributeTargets.Assembly)] +public class MetaDataAttribute( + string version, + string buildTime, + uint buildNum, + string dotnetSdkVersion, + string dotnetVersion, + string rustVersion, + string mudBlazorVersion, + string tauriVersion, + string appCommitHash + ) : Attribute +{ + public string BuildTime => buildTime; + + public string Version => version; + + public uint BuildNum => buildNum; + + public string DotnetVersion => dotnetVersion; + + public string DotnetSdkVersion => dotnetSdkVersion; + + public string RustVersion => rustVersion; + + public string MudBlazorVersion => mudBlazorVersion; + + public string TauriVersion => tauriVersion; + + public string AppCommitHash => appCommitHash; +} \ No newline at end of file diff --git a/app/Build/Tools/RID.cs b/app/SharedTools/RID.cs similarity index 84% rename from app/Build/Tools/RID.cs rename to app/SharedTools/RID.cs index 73ca86ee..95733713 100644 --- a/app/Build/Tools/RID.cs +++ b/app/SharedTools/RID.cs @@ -1,4 +1,4 @@ -namespace Build.Tools; +namespace SharedTools; public enum RID { diff --git a/app/SharedTools/RIDExtensions.cs b/app/SharedTools/RIDExtensions.cs new file mode 100644 index 00000000..015357f9 --- /dev/null +++ b/app/SharedTools/RIDExtensions.cs @@ -0,0 +1,46 @@ +namespace SharedTools; + +public static class RIDExtensions +{ + public static string AsMicrosoftRid(this RID rid) => rid switch + { + RID.WIN_X64 => "win-x64", + RID.WIN_ARM64 => "win-arm64", + + RID.LINUX_X64 => "linux-x64", + RID.LINUX_ARM64 => "linux-arm64", + + RID.OSX_X64 => "osx-x64", + RID.OSX_ARM64 => "osx-arm64", + + _ => string.Empty, + }; + + public static string ToUserFriendlyName(this RID rid) => rid switch + { + RID.WIN_X64 => "Windows x64", + RID.WIN_ARM64 => "Windows ARM64", + + RID.LINUX_X64 => "Linux x64", + RID.LINUX_ARM64 => "Linux ARM64", + + RID.OSX_X64 => "macOS x64", + RID.OSX_ARM64 => "macOS ARM64", + + _ => "unknown", + }; + + public static RID ToRID(this string rid) => rid switch + { + "win-x64" => RID.WIN_X64, + "win-arm64" => RID.WIN_ARM64, + + "linux-x64" => RID.LINUX_X64, + "linux-arm64" => RID.LINUX_ARM64, + + "osx-x64" => RID.OSX_X64, + "osx-arm64" => RID.OSX_ARM64, + + _ => RID.NONE, + }; +} \ No newline at end of file diff --git a/metadata.txt b/metadata.txt index 6e9cd50c..4c7cb905 100644 --- a/metadata.txt +++ b/metadata.txt @@ -7,3 +7,4 @@ 8.5.1 1.8.1 19935769035, release +osx-arm64 \ No newline at end of file diff --git a/runtime/build.rs b/runtime/build.rs index bdd09222..93871d1a 100644 --- a/runtime/build.rs +++ b/runtime/build.rs @@ -2,17 +2,48 @@ use std::path::PathBuf; fn main() { tauri_build::build(); - - // Tells Cargo to re-run this script only, when the version.txt file was changed: - println!("cargo:rerun-if-changed=../metadata.txt"); - + let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap()); println!("cargo:rustc-env=OUT_DIR={}", out_dir.to_str().unwrap()); + // + // When we are in debug mode, we want to set the current RID + // to the current architecture. This is necessary, so that + // the developers get the right behavior. + // + // We read the current OS and architecture and overwrite the + // current RID in the metadata file (line #10). + // + // We have to take care of different naming conventions. The + // following RIDs are supported: win-x64, win-arm64, linux-x64, + // linux-arm64, osx-x64, osx-arm64. + // + let current_os = std::env::consts::OS; + let current_arch = std::env::consts::ARCH; + let rid = match (current_os, current_arch) { + ("windows", "x86_64") => "win-x64", + ("windows", "aarch64") => "win-arm64", + + ("linux", "x86_64") => "linux-x64", + ("linux", "aarch64") => "linux-arm64", + + ("macos", "x86_64") => "osx-x64", + ("macos", "aarch64") => "osx-arm64", + + _ => panic!("Unsupported OS or architecture: {current_os} {current_arch}"), + }; + let metadata = include_str!("../metadata.txt"); - let mut metadata_lines = metadata.lines(); - let version = metadata_lines.next().unwrap(); - + let mut metadata_lines = metadata.lines().collect::>(); + metadata_lines[9] = rid; + let new_metadata = metadata_lines.join("\n"); + std::fs::write("../metadata.txt", new_metadata).unwrap(); + + // + // Read the current version and update the + // Rust and Tauri configuration files: + // + let version = metadata_lines[0]; update_cargo_toml("Cargo.toml", version); update_tauri_conf("tauri.conf.json", version); } diff --git a/runtime/src/main.rs b/runtime/src/main.rs index 3b0026d4..be7f5ea1 100644 --- a/runtime/src/main.rs +++ b/runtime/src/main.rs @@ -14,7 +14,6 @@ use mindwork_ai_studio::runtime_api::start_runtime_api; #[tokio::main] async fn main() { - let metadata = include_str!("../../metadata.txt"); let mut metadata_lines = metadata.lines(); let app_version = metadata_lines.next().unwrap(); @@ -26,6 +25,7 @@ async fn main() { let mud_blazor_version = metadata_lines.next().unwrap(); let tauri_version = metadata_lines.next().unwrap(); let app_commit_hash = metadata_lines.next().unwrap(); + let architecture = metadata_lines.next().unwrap(); init_logging(); info!("Starting MindWork AI Studio:"); @@ -33,7 +33,7 @@ async fn main() { let working_directory = std::env::current_dir().unwrap(); info!(".. The working directory is: '{working_directory:?}'"); - info!(".. Version: v{app_version} (commit {app_commit_hash}, build {build_number})"); + info!(".. Version: v{app_version} (commit {app_commit_hash}, build {build_number}, {architecture})"); info!(".. Build time: {build_time}"); info!(".. .NET SDK: v{dotnet_sdk_version}"); info!(".. .NET: v{dotnet_version}"); From 09020415dbace8cb1dc3af2bb7d91c1aefa87ef2 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Tue, 15 Apr 2025 08:18:15 +0200 Subject: [PATCH 13/93] Fixed sed handling for macOS and Linux (#403) --- .github/workflows/build-and-release.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index 6bd4841c..a7015fc5 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -161,7 +161,11 @@ jobs: formatted_app_version="v${app_version}" # Set the architecture: - sed -i '' "10s/.*/${{ matrix.dotnet_runtime }}/" metadata.txt + if sed --version 2>/dev/null | grep -q GNU; then + sed -i "10s/.*/${{ matrix.dotnet_runtime }}/" metadata.txt + else + sed -i '' "10s/.*/${{ matrix.dotnet_runtime }}/" metadata.txt + fi # Write the metadata to the environment: echo "APP_VERSION=${app_version}" >> $GITHUB_ENV @@ -173,6 +177,7 @@ jobs: echo "RUST_VERSION=${rust_version}" >> $GITHUB_ENV echo "MUD_BLAZOR_VERSION=${mud_blazor_version}" >> $GITHUB_ENV echo "TAURI_VERSION=${tauri_version}" >> $GITHUB_ENV + echo "ARCHITECTURE=${{ matrix.dotnet_runtime }}" >> $GITHUB_ENV # Log the metadata: echo "App version: '${formatted_app_version}'" @@ -183,6 +188,7 @@ jobs: echo "Rust version: '${rust_version}'" echo "MudBlazor version: '${mud_blazor_version}'" echo "Tauri version: '${tauri_version}'" + echo "Architecture: '${{ matrix.dotnet_runtime }}'" - name: Read and format metadata (Windows) if: matrix.platform == 'windows-latest' @@ -230,6 +236,7 @@ jobs: Write-Output "DOTNET_RUNTIME_VERSION=${dotnet_runtime_version}" >> $env:GITHUB_ENV Write-Output "RUST_VERSION=${rust_version}" >> $env:GITHUB_ENV Write-Output "MUD_BLAZOR_VERSION=${mud_blazor_version}" >> $env:GITHUB_ENV + Write-Output "ARCHITECTURE=${{ matrix.dotnet_runtime }}" >> $env:GITHUB_ENV # Log the metadata: Write-Output "App version: '${formatted_app_version}'" @@ -240,6 +247,7 @@ jobs: Write-Output "Rust version: '${rust_version}'" Write-Output "MudBlazor version: '${mud_blazor_version}'" Write-Output "Tauri version: '${tauri_version}'" + Write-Output "Architecture: '${{ matrix.dotnet_runtime }}'" - name: Setup .NET uses: actions/setup-dotnet@v4 From c6e45c26d6f7c13d1d9c067943ac7df30f90627f Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sun, 20 Apr 2025 12:10:07 +0200 Subject: [PATCH 14/93] Improved changelog (#406) --- app/MindWork AI Studio/wwwroot/changelog/v0.9.40.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/MindWork AI Studio/wwwroot/changelog/v0.9.40.md b/app/MindWork AI Studio/wwwroot/changelog/v0.9.40.md index e78ef4c6..7e7d7b4c 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v0.9.40.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v0.9.40.md @@ -1,5 +1,5 @@ # v0.9.40, build 215 (2025-04-xx xx:xx UTC) -- Added support for the announced OpenAI `o4` models. We hope that these `o4` models will be usable by the well-known chat completion API instead of the new responses API, though. AI Studio cannot use the new responses API right now. +- Added support for the announced OpenAI `o4` models. - Added Alibaba Cloud as a new provider. Thanks Peer `peerschuett` for the contribution. - Added the Hugging Face inference provider as an LLM provider to AI Studio. Thanks Peer `peerschuett` for the contribution. - Improved the provider selection by showing the name of the provider in the provider selection instead of its identifier. From f8c60d87ae84e3faac4f8ba32f1107c0d80d2e4d Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sun, 20 Apr 2025 12:42:42 +0200 Subject: [PATCH 15/93] Improved provider dialogs (#407) --- .../Settings/SettingsPanelEmbeddings.razor | 2 +- .../Dialogs/EmbeddingProviderDialog.razor | 77 +++++++++++-------- .../Dialogs/ProviderDialog.razor | 38 +++++---- .../wwwroot/changelog/v0.9.40.md | 1 + 4 files changed, 66 insertions(+), 52 deletions(-) diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelEmbeddings.razor b/app/MindWork AI Studio/Components/Settings/SettingsPanelEmbeddings.razor index 7203ff78..1e200175 100644 --- a/app/MindWork AI Studio/Components/Settings/SettingsPanelEmbeddings.razor +++ b/app/MindWork AI Studio/Components/Settings/SettingsPanelEmbeddings.razor @@ -41,7 +41,7 @@ @context.Num @context.Name - @context.UsedLLMProvider + @context.UsedLLMProvider.ToName() @this.GetEmbeddingProviderModelName(context) diff --git a/app/MindWork AI Studio/Dialogs/EmbeddingProviderDialog.razor b/app/MindWork AI Studio/Dialogs/EmbeddingProviderDialog.razor index 5305e433..2aea5f63 100644 --- a/app/MindWork AI Studio/Dialogs/EmbeddingProviderDialog.razor +++ b/app/MindWork AI Studio/Dialogs/EmbeddingProviderDialog.razor @@ -11,49 +11,58 @@ { if (provider.ProvideEmbeddings()) { - @provider + + @provider.ToName() + } } Create account - @* ReSharper disable once CSharpWarnings::CS8974 *@ - - - + @if (this.DataLLMProvider.IsAPIKeyNeeded(this.DataHost)) + { + @* ReSharper disable once CSharpWarnings::CS8974 *@ + + } - - @foreach (Host host in Enum.GetValues(typeof(Host))) - { - if (host.AreEmbeddingsSupported()) + @if (this.DataLLMProvider.IsHostnameNeeded()) + { + + } + + @if (this.DataLLMProvider.IsHostNeeded()) + { + + @foreach (Host host in Enum.GetValues(typeof(Host))) { - @host.Name() + if (host.AreEmbeddingsSupported()) + { + + @host.Name() + + } } - } - + + } @if (this.DataLLMProvider.IsEmbeddingModelProvidedManually(this.DataHost)) diff --git a/app/MindWork AI Studio/Dialogs/ProviderDialog.razor b/app/MindWork AI Studio/Dialogs/ProviderDialog.razor index 5998f12f..1a01faba 100644 --- a/app/MindWork AI Studio/Dialogs/ProviderDialog.razor +++ b/app/MindWork AI Studio/Dialogs/ProviderDialog.razor @@ -10,25 +10,28 @@ @foreach (LLMProviders provider in Enum.GetValues(typeof(LLMProviders))) { - @provider.ToName() + + @provider.ToName() + } Create account - @* ReSharper disable once CSharpWarnings::CS8974 *@ - + @if (this.DataLLMProvider.IsAPIKeyNeeded(this.DataHost)) + { + @* ReSharper disable once CSharpWarnings::CS8974 *@ + + } @if (this.DataLLMProvider.IsHostnameNeeded()) { @@ -36,7 +39,6 @@ T="string" @bind-Text="@this.DataHostname" Label="Hostname" - Disabled="@(!this.DataLLMProvider.IsHostnameNeeded())" Class="mb-3" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Dns" @@ -47,10 +49,12 @@ @if (this.DataLLMProvider.IsHostNeeded()) { - + @foreach (Host host in Enum.GetValues(typeof(Host))) { - @host.Name() + + @host.Name() + } } diff --git a/app/MindWork AI Studio/wwwroot/changelog/v0.9.40.md b/app/MindWork AI Studio/wwwroot/changelog/v0.9.40.md index 7e7d7b4c..f7edfe98 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v0.9.40.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v0.9.40.md @@ -2,6 +2,7 @@ - Added support for the announced OpenAI `o4` models. - Added Alibaba Cloud as a new provider. Thanks Peer `peerschuett` for the contribution. - Added the Hugging Face inference provider as an LLM provider to AI Studio. Thanks Peer `peerschuett` for the contribution. +- Improved the LLM & embedding provider dialogs by hiding not relevant options. - Improved the provider selection by showing the name of the provider in the provider selection instead of its identifier. - Improved the developer experience by adding a tolerant enum converter for better configuration handling. - Fixed an issue where OpenAI `o3` models were not shown in the model selection. From c10c084da426c2cdd695befcecf087e620e86369 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sun, 20 Apr 2025 13:33:03 +0200 Subject: [PATCH 16/93] Upgraded dependencies (#408) --- app/Build/Commands/UpdateMetadataCommands.cs | 15 ++++ .../MindWork AI Studio.csproj | 6 +- app/MindWork AI Studio/packages.lock.json | 83 ++++++++++++------- .../wwwroot/changelog/v0.9.40.md | 2 + metadata.txt | 6 +- runtime/Cargo.lock | 16 ++-- runtime/Cargo.toml | 5 +- 7 files changed, 86 insertions(+), 47 deletions(-) diff --git a/app/Build/Commands/UpdateMetadataCommands.cs b/app/Build/Commands/UpdateMetadataCommands.cs index e2bf1589..464f8008 100644 --- a/app/Build/Commands/UpdateMetadataCommands.cs +++ b/app/Build/Commands/UpdateMetadataCommands.cs @@ -36,6 +36,21 @@ public sealed partial class UpdateMetadataCommands // artifacts are already in place, and .NET knows the updated web assets, etc.: await this.Build(); } + + [Command("update-versions", Description = "The command will update the package versions in the metadata file")] + public async Task UpdateVersions() + { + if(!Environment.IsWorkingDirectoryValid()) + return; + + Console.WriteLine("=============================="); + Console.WriteLine("- Update the main package versions ..."); + + await this.UpdateDotnetVersion(); + await this.UpdateRustVersion(); + await this.UpdateMudBlazorVersion(); + await this.UpdateTauriVersion(); + } [Command("prepare", Description = "Prepare the metadata for the next release")] public async Task Prepare(PrepareAction action) diff --git a/app/MindWork AI Studio/MindWork AI Studio.csproj b/app/MindWork AI Studio/MindWork AI Studio.csproj index d9e8e7f3..8a579b26 100644 --- a/app/MindWork AI Studio/MindWork AI Studio.csproj +++ b/app/MindWork AI Studio/MindWork AI Studio.csproj @@ -47,10 +47,10 @@ - - + + - + diff --git a/app/MindWork AI Studio/packages.lock.json b/app/MindWork AI Studio/packages.lock.json index b35b2e3d..a87a2412 100644 --- a/app/MindWork AI Studio/packages.lock.json +++ b/app/MindWork AI Studio/packages.lock.json @@ -18,9 +18,9 @@ }, "HtmlAgilityPack": { "type": "Direct", - "requested": "[1.12.0, )", - "resolved": "1.12.0", - "contentHash": "VHtVZmfoYhQyA/POvZRLuTpCz1zhzIDrdYRJIRV73e9wKAzjW71biYNOHOWx8MxEX3TE4TWVfx1QDRoZcj2AWw==" + "requested": "[1.12.1, )", + "resolved": "1.12.1", + "contentHash": "SP6/2Y26CXtxjXn0Wwsom9Ek35SNWKHEu/IWhNEFejBSSVWWXPRSlpqpBSYWv1SQhYFnwMO01xVbEdK3iRR4hg==" }, "LuaCSharp": { "type": "Direct", @@ -30,18 +30,18 @@ }, "Microsoft.Extensions.FileProviders.Embedded": { "type": "Direct", - "requested": "[9.0.3, )", - "resolved": "9.0.3", - "contentHash": "UKfKGlZ7jKfe6v4rLsjnH/mGbD3e4YD9EK+Uobu+KIxwfhZuLLCtXm4CWTOf2s1t+ItmMs0QqbSJAXaMXCxLOw==", + "requested": "[9.0.4, )", + "resolved": "9.0.4", + "contentHash": "fictUnSF95D+M9iH4X6TYBjud2gbB2r6bcIi0sQknXFc2bHbNucoaK+SzfLCzb47tHSR9a5pm0F1Ioj0PgmFeQ==", "dependencies": { - "Microsoft.Extensions.FileProviders.Abstractions": "9.0.3" + "Microsoft.Extensions.FileProviders.Abstractions": "9.0.4" } }, "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[9.0.3, )", - "resolved": "9.0.3", - "contentHash": "1rqGTfubVg0qj2PsK6esyq3PIxtYJYrN3LsYUV9RrvH3anmt3fT3ozYdAZZH4U8JU/pt5pPIUk8NBSu26wtekA==" + "requested": "[9.0.4, )", + "resolved": "9.0.4", + "contentHash": "xUdlUxiFwXhTYhB4VxKg/IA0+jlZXJPo70LYuMryWbJHdonIpZjw+7DO2B0pWwpXIOs6MlH5WVXPEtfrGEcVZA==" }, "MudBlazor": { "type": "Direct", @@ -56,12 +56,13 @@ }, "MudBlazor.Markdown": { "type": "Direct", - "requested": "[8.0.0, )", - "resolved": "8.0.0", - "contentHash": "0DcXQFEIgKJsaMCDva0Ck3gempoctyc7s8GLK5VagozlZdXql6W4SKX/imM/NfyfV7SxLrUTRJyLJX0Te+02sQ==", + "requested": "[8.5.1, )", + "resolved": "8.5.1", + "contentHash": "UrNr948Nn70CuDoTWaN/HgtaIKO4oKfj7W6Mw8Ei9OG7MCzCwkhUYJLe/CRTKCkt3wINquB/2AAN7ezgEfmbww==", "dependencies": { - "Markdig": "0.40.0", - "MudBlazor": "8.0.0" + "Markdig": "0.41.0", + "Microsoft.Extensions.Caching.Memory": "9.0.4", + "MudBlazor": "8.5.1" } }, "ReverseMarkdown": { @@ -85,8 +86,8 @@ }, "Markdig": { "type": "Transitive", - "resolved": "0.40.0", - "contentHash": "4ve14zs+gt1irldTQE3y5FLAHuzmhW7T99lAAvVipe/q2LWT/nUCO0iICb9TXGvMX6n7Z1OZroFXkdSy91rO8w==" + "resolved": "0.41.0", + "contentHash": "nEGSjfQ2i+MzJjvCZqoIBqW2x0iBALhhVogud48oPA/39a0n0jOhghdTYdm4xaDFBXmc4MxsVJAP5gtdvADvWQ==" }, "Microsoft.AspNetCore.Authorization": { "type": "Transitive", @@ -137,6 +138,26 @@ "resolved": "9.0.1", "contentHash": "EZnHifamF7IFEIyjAKMtJM3I/94OIe72i3P09v5oL0twmsmfQwal6Ni3m8lbB5mge3jWFhMozeW+rUdRSqnXRQ==" }, + "Microsoft.Extensions.Caching.Abstractions": { + "type": "Transitive", + "resolved": "9.0.4", + "contentHash": "imcZ5BGhBw5mNsWLepBbqqumWaFe0GtvyCvne2/2wsDIBRa2+Lhx4cU/pKt/4BwOizzUEOls2k1eOJQXHGMalg==", + "dependencies": { + "Microsoft.Extensions.Primitives": "9.0.4" + } + }, + "Microsoft.Extensions.Caching.Memory": { + "type": "Transitive", + "resolved": "9.0.4", + "contentHash": "G5rEq1Qez5VJDTEyRsRUnewAspKjaY57VGsdZ8g8Ja6sXXzoiI3PpTd1t43HjHqNWD5A06MQveb2lscn+2CU+w==", + "dependencies": { + "Microsoft.Extensions.Caching.Abstractions": "9.0.4", + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4", + "Microsoft.Extensions.Logging.Abstractions": "9.0.4", + "Microsoft.Extensions.Options": "9.0.4", + "Microsoft.Extensions.Primitives": "9.0.4" + } + }, "Microsoft.Extensions.DependencyInjection": { "type": "Transitive", "resolved": "9.0.1", @@ -147,15 +168,15 @@ }, "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "Transitive", - "resolved": "9.0.1", - "contentHash": "Tr74eP0oQ3AyC24ch17N8PuEkrPbD0JqIfENCYqmgKYNOmL8wQKzLJu3ObxTUDrjnn4rHoR1qKa37/eQyHmCDA==" + "resolved": "9.0.4", + "contentHash": "UI0TQPVkS78bFdjkTodmkH0Fe8lXv9LnhGFKgKrsgUJ5a5FVdFRcgjIkBVLbGgdRhxWirxH/8IXUtEyYJx6GQg==" }, "Microsoft.Extensions.FileProviders.Abstractions": { "type": "Transitive", - "resolved": "9.0.3", - "contentHash": "umczZ3+QPpzlrW/lkvy+IB0p52+qZ5w++aqx2lTCMOaPKzwcbVdrJgiQ3ajw5QWBp7gChLUiCYkSlWUpfjv24g==", + "resolved": "9.0.4", + "contentHash": "gQN2o/KnBfVk6Bd71E2YsvO5lsqrqHmaepDGk+FB/C4aiQY9B0XKKNKfl5/TqcNOs9OEithm4opiMHAErMFyEw==", "dependencies": { - "Microsoft.Extensions.Primitives": "9.0.3" + "Microsoft.Extensions.Primitives": "9.0.4" } }, "Microsoft.Extensions.Localization": { @@ -176,25 +197,25 @@ }, "Microsoft.Extensions.Logging.Abstractions": { "type": "Transitive", - "resolved": "9.0.1", - "contentHash": "w2gUqXN/jNIuvqYwX3lbXagsizVNXYyt6LlF57+tMve4JYCEgCMMAjRce6uKcDASJgpMbErRT1PfHy2OhbkqEA==", + "resolved": "9.0.4", + "contentHash": "0MXlimU4Dud6t+iNi5NEz3dO2w1HXdhoOLaYFuLPCjAsvlPQGwOT6V2KZRMLEhCAm/stSZt1AUv0XmDdkjvtbw==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1" + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4" } }, "Microsoft.Extensions.Options": { "type": "Transitive", - "resolved": "9.0.1", - "contentHash": "nggoNKnWcsBIAaOWHA+53XZWrslC7aGeok+aR+epDPRy7HI7GwMnGZE8yEsL2Onw7kMOHVHwKcsDls1INkNUJQ==", + "resolved": "9.0.4", + "contentHash": "fiFI2+58kicqVZyt/6obqoFwHiab7LC4FkQ3mmiBJ28Yy4fAvy2+v9MRnSvvlOO8chTOjKsdafFl/K9veCPo5g==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.1", - "Microsoft.Extensions.Primitives": "9.0.1" + "Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4", + "Microsoft.Extensions.Primitives": "9.0.4" } }, "Microsoft.Extensions.Primitives": { "type": "Transitive", - "resolved": "9.0.3", - "contentHash": "yCCJHvBcRyqapMSNzP+kTc57Eaavq2cr5Tmuil6/XVnipQf5xmskxakSQ1enU6S4+fNg3sJ27WcInV64q24JsA==" + "resolved": "9.0.4", + "contentHash": "SPFyMjyku1nqTFFJ928JAMd0QnRe4xjE7KeKnZMWXf3xk+6e0WiOZAluYtLdbJUXtsl2cCRSi8cBquJ408k8RA==" }, "Microsoft.JSInterop": { "type": "Transitive", diff --git a/app/MindWork AI Studio/wwwroot/changelog/v0.9.40.md b/app/MindWork AI Studio/wwwroot/changelog/v0.9.40.md index f7edfe98..29426981 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v0.9.40.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v0.9.40.md @@ -6,3 +6,5 @@ - Improved the provider selection by showing the name of the provider in the provider selection instead of its identifier. - Improved the developer experience by adding a tolerant enum converter for better configuration handling. - Fixed an issue where OpenAI `o3` models were not shown in the model selection. +- Upgraded to .NET 9.0.4. +- Upgraded .NET & Rust dependencies. diff --git a/metadata.txt b/metadata.txt index 4c7cb905..c1dcbe32 100644 --- a/metadata.txt +++ b/metadata.txt @@ -1,10 +1,10 @@ 0.9.39 2025-04-07 17:39:09 UTC 214 -9.0.104 (commit 2750432faa) -9.0.3 (commit 831d23e561) +9.0.105 (commit 35890ecb87) +9.0.4 (commit f57e6dc747) 1.86.0 (commit 05f9846f8) 8.5.1 1.8.1 19935769035, release -osx-arm64 \ No newline at end of file +osx-arm64 diff --git a/runtime/Cargo.lock b/runtime/Cargo.lock index 0f05dcd1..14dd651e 100644 --- a/runtime/Cargo.lock +++ b/runtime/Cargo.lock @@ -711,9 +711,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.13" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" dependencies = [ "crossbeam-utils", ] @@ -2619,6 +2619,7 @@ dependencies = [ "calamine", "cbc", "cipher", + "crossbeam-channel", "file-format", "flexi_logger", "futures", @@ -2629,7 +2630,7 @@ dependencies = [ "openssl", "pbkdf2", "pdfium-render", - "rand 0.9.0", + "rand 0.9.1", "rand_chacha 0.9.0", "rcgen", "reqwest 0.12.15", @@ -3199,9 +3200,9 @@ dependencies = [ [[package]] name = "pdfium-render" -version = "0.8.29" +version = "0.8.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5cbb29c282cfbd0a8142ccd3bb0ce8da53e59141ce02a023b980bc72b6c0eec" +checksum = "2773a939ec2c736640f5f2e62a325c2e1a997d694961c50f17cadfb4c8682e84" dependencies = [ "bitflags 2.6.0", "bytemuck", @@ -3615,13 +3616,12 @@ dependencies = [ [[package]] name = "rand" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" dependencies = [ "rand_chacha 0.9.0", "rand_core 0.9.0", - "zerocopy", ] [[package]] diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 6d5e8e05..685d5001 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -23,7 +23,7 @@ flexi_logger = "0.30.1" log = { version = "0.4.27", features = ["kv"] } once_cell = "1.21.3" rocket = { version = "0.5.1", features = ["json", "tls"] } -rand = "0.9" +rand = "0.9.1" rand_chacha = "0.9" base64 = "0.22.1" cipher = { version = "0.4.4", features = ["std"] } @@ -35,12 +35,13 @@ sha2 = "0.10.8" rcgen = { version = "0.13.2", features = ["pem"] } file-format = "0.26.0" calamine = "0.26.1" -pdfium-render = "0.8.29" +pdfium-render = "0.8.30" sys-locale = "0.3.2" # Fixes security vulnerability downstream, where the upstream is not fixed yet: url = "2.5" ring = "0.17.14" +crossbeam-channel = "0.5.15" [target.'cfg(target_os = "linux")'.dependencies] # See issue https://github.com/tauri-apps/tauri/issues/4470 From 2144cfe059078cd1ed076be098fbf5e7b0da85f3 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sun, 20 Apr 2025 15:24:43 +0200 Subject: [PATCH 17/93] Updated documentation (#409) --- README.md | 40 +++++++++++++---- .../Settings/SettingsPanelProviders.razor.cs | 2 +- .../Dialogs/ProviderDialog.razor | 12 +++--- .../Dialogs/ProviderDialog.razor.cs | 4 +- app/MindWork AI Studio/Pages/Home.razor.cs | 2 +- ...anceProvider.cs => HFInferenceProvider.cs} | 4 +- .../HFInferenceProviderExtensions.cs | 43 +++++++++++++++++++ .../HFInstanceProviderExtensions.cs | 43 ------------------- .../HuggingFace/ProviderHuggingFace.cs | 4 +- .../Provider/LLMProvidersExtensions.cs | 12 +++--- app/MindWork AI Studio/Settings/Provider.cs | 2 +- .../Tools/Validation/ProviderValidation.cs | 6 +-- .../wwwroot/changelog/v0.9.40.md | 1 + documentation/Setup.md | 4 +- metadata.txt | 2 +- 15 files changed, 104 insertions(+), 77 deletions(-) rename app/MindWork AI Studio/Provider/HuggingFace/{HFInstanceProvider.cs => HFInferenceProvider.cs} (70%) create mode 100644 app/MindWork AI Studio/Provider/HuggingFace/HFInferenceProviderExtensions.cs delete mode 100644 app/MindWork AI Studio/Provider/HuggingFace/HFInstanceProviderExtensions.cs diff --git a/README.md b/README.md index 9c1b3a37..16b57720 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ Things we are currently working on: - [x] ~~App: Management of data sources (local & external data via [ERI](https://github.com/MindWorkAI/ERI)) (PR [#259](https://github.com/MindWorkAI/AI-Studio/pull/259), [#273](https://github.com/MindWorkAI/AI-Studio/pull/273))~~ - [x] ~~Runtime: Extract data from txt / md / pdf / docx / xlsx files (PR [#374](https://github.com/MindWorkAI/AI-Studio/pull/374))~~ - [ ] (*Optional*) Runtime: Implement internal embedding provider through [fastembed-rs](https://github.com/Anush008/fastembed-rs) + - [ ] App: Implement dialog for checking & handling [pandoc](https://pandoc.org/) installation ([PR #393](https://github.com/MindWorkAI/AI-Studio/pull/393)) - [ ] App: Implement external embedding providers - [ ] App: Implement the process to vectorize one local file using embeddings - [ ] Runtime: Integration of the vector database [LanceDB](https://github.com/lancedb/lancedb) @@ -24,26 +25,37 @@ Things we are currently working on: - [x] ~~App: Integrate data sources in chats (PR [#282](https://github.com/MindWorkAI/AI-Studio/pull/282))~~ -- Since September 2024: Experiments have been started on how we can work on long texts with AI Studio. Let's say you want to write a fantasy novel or create a complex project proposal and use LLM for support. The initial experiments were promising, but not yet satisfactory. We are testing further approaches until a satisfactory solution is found. The current state of our experiment is available as an experimental preview feature through your app configuration. Related PR: ~~[#167](https://github.com/MindWorkAI/AI-Studio/pull/167), [#226](https://github.com/MindWorkAI/AI-Studio/pull/226)~~. +- Since September 2024: Experiments have been started on how we can work on long texts with AI Studio. Let's say you want to write a fantasy novel or create a complex project proposal and use LLM for support. The initial experiments were promising, but not yet satisfactory. We are testing further approaches until a satisfactory solution is found. The current state of our experiment is available as an experimental preview feature through your app configuration. Related PR: ~~[PR #167](https://github.com/MindWorkAI/AI-Studio/pull/167), [PR #226](https://github.com/MindWorkAI/AI-Studio/pull/226)~~, [PR #376](https://github.com/MindWorkAI/AI-Studio/pull/376). +- Since March 2025: We have started developing the plugin system. There will be language plugins to offer AI Studio in other languages, configuration plugins to centrally manage certain providers and rules within an organization, and assistant plugins that allow anyone to develop their own assistants. We are using Lua as the plugin language: + - [x] ~~Plan & implement the base plugin system ([PR #322](https://github.com/MindWorkAI/AI-Studio/pull/322))~~ + - [x] ~~Start the plugin system ([PR #372](https://github.com/MindWorkAI/AI-Studio/pull/372))~~ + - [x] ~~Added hot-reload support for plugins ([PR #377](https://github.com/MindWorkAI/AI-Studio/pull/377), [PR #391](https://github.com/MindWorkAI/AI-Studio/pull/391))~~ + - [ ] Add support for other languages (I18N) to AI Studio (~~[PR #381](https://github.com/MindWorkAI/AI-Studio/pull/381), [PR #400](https://github.com/MindWorkAI/AI-Studio/pull/400)~~, [PR #404](https://github.com/MindWorkAI/AI-Studio/pull/404)) + - [ ] Add an I18N assistant to translate all AI Studio texts to a certain language & culture + - [ ] Provide MindWork AI Studio in German ([#31](https://github.com/MindWorkAI/Planning/issues/31)) + - [ ] Add configuration plugins, which allow pre-defining some LLM providers in organizations + - [ ] Add an app store for plugins, showcasing community-contributed plugins from public GitHub and GitLab repositories. This will enable AI Studio users to discover, install, and update plugins directly within the platform. + - [ ] Add assistant plugins Other News: -- October 2024: We've found the first two financial supporters. Huge thanks to `richard-stanton` and `peerschuett` for backing the project. Thanks for having the courage to be the first to support us. +- April 2025: We have two active financial supporters: Peer `peerschuett` and Dominic `donework`. Thank you very much for your support. MindWork AI reinvests these donations by passing them on to our AI Studio dependencies ([see here](https://github.com/orgs/MindWorkAI/sponsoring)). In the event that we receive large donations, we will first sign the app ([#56](https://github.com/MindWorkAI/Planning/issues/56)). In case we receive more donations, we will look for and pay staff to develop features for AI Studio. -- October 2024: The [German Aerospace Center (DLR)](https://en.wikipedia.org/wiki/German_Aerospace_Center) ([Website](https://www.dlr.de/en)) will use AI Studio at least within the scope of one project and will also contribute to its further development. This is great news. +- April 2025: The [German Aerospace Center (DLR)](https://en.wikipedia.org/wiki/German_Aerospace_Center) ([Website](https://www.dlr.de/en)) will use AI Studio at least within the scope of three projects and will also contribute to its further development. This is great news. Features we have recently released: +- v0.9.40: Added support for the `o4` models from OpenAI. Also, we added Alibaba Cloud & Hugging Face as LLM providers. +- v0.9.39: Added the plugin system as a preview feature. +- v0.9.31: Added Helmholtz & GWDG as LLM providers. This is a huge improvement for many researchers out there who can use these providers for free. We added DeepSeek as a provider as well. +- v0.9.29: Added agents to support the RAG process (selecting the best data sources & validating retrieved data as part of the augmentation process) +- v0.9.26+: Added RAG for external data sources using our [ERI interface](https://mindworkai.org/#eri---external-retrieval-interface) as a preview feature. - v0.9.25: Added [xAI](https://x.ai/) as a new provider. xAI provides their Grok models for generating content. - v0.9.23: Added support for OpenAI `o` models (`o1`, `o1-mini`, `o3`, etc.); added also an [ERI](https://github.com/MindWorkAI/ERI) server coding assistant as a preview feature behind the RAG feature flag. Your own ERI server can be used to gain access to, e.g., your enterprise data from within AI Studio. - v0.9.22: Added options for preview features; added embedding provider configuration for RAG (preview) and writer mode (experimental preview). - v0.9.18: Added the new Anthropic Heiku model; added Groq and Google Gemini as provider options. -- v0.9.17: Added the new Anthropic model `claude-3-5-sonnet-20241022`. -- v0.9.16: Added workspace display options & improved the layout of the app window. -- v0.9.15: Added the bias-of-the-day assistant. Tells you about a cognitive bias every day. -- v0.9.13: You can use `ollama` providers secured with API keys. ## What is AI Studio? @@ -56,7 +68,19 @@ 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. -- **Independence**: You are not tied to any single provider. Instead, you can choose the provider that best suits their needs. Right now, we support OpenAI (GPT4o, o1, etc.), Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, and self-hosted models using [llama.cpp](https://github.com/ggerganov/llama.cpp), [ollama](https://github.com/ollama/ollama), [LM Studio](https://lmstudio.ai/), [Groq](https://groq.com/), or [Fireworks](https://fireworks.ai/). For scientists and employees of research institutions, we also support [Helmholtz](https://helmholtz.cloud/services/?serviceID=d7d5c597-a2f6-4bd1-b71e-4d6499d98570) and [GWDG](https://gwdg.de/services/application-services/ai-services/) 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. +- **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/) (GPT4o, GPT4.1, o1, o3, o4, etc.) + - [Mistral](https://mistral.ai/) + - [Anthropic](https://www.anthropic.com/) (Claude) + - [Google Gemini](https://gemini.google.com) + - [xAI](https://x.ai/) (Grok) + - [DeepSeek](https://www.deepseek.com/en) + - [Alibaba Cloud](https://www.alibabacloud.com) (Qwen) + - [Hugging Face](https://huggingface.co/) using their [inference providers](https://huggingface.co/docs/inference-providers/index) such as Cerebras, Nebius, Sambanova, Novita, Hyperbolic, Together AI, Fireworks, Hugging Face + - Self-hosted models using [llama.cpp](https://github.com/ggerganov/llama.cpp), [ollama](https://github.com/ollama/ollama), [LM Studio](https://lmstudio.ai/) + - [Groq](https://groq.com/) + - [Fireworks](https://fireworks.ai/) + - For scientists and employees of research institutions, we also support [Helmholtz](https://helmholtz.cloud/services/?serviceID=d7d5c597-a2f6-4bd1-b71e-4d6499d98570) and [GWDG](https://gwdg.de/services/application-services/ai-services/) 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. - **Assistants**: 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. - **Unrestricted usage**: Unlike services like ChatGPT, which impose limits after intensive use, MindWork AI Studio offers unlimited usage through the providers API. - **Cost-effective**: You only pay for what you use, which can be cheaper than monthly subscription services like ChatGPT Plus, especially if used infrequently. But beware, here be dragons: For extremely intensive usage, the API costs can be significantly higher. Unfortunately, providers currently do not offer a way to display current costs in the app. Therefore, check your account with the respective provider to see how your costs are developing. When available, use prepaid and set a cost limit. diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelProviders.razor.cs b/app/MindWork AI Studio/Components/Settings/SettingsPanelProviders.razor.cs index 5a71925b..ce03a430 100644 --- a/app/MindWork AI Studio/Components/Settings/SettingsPanelProviders.razor.cs +++ b/app/MindWork AI Studio/Components/Settings/SettingsPanelProviders.razor.cs @@ -65,7 +65,7 @@ public partial class SettingsPanelProviders : SettingsPanelBase { x => x.IsSelfHosted, provider.IsSelfHosted }, { x => x.IsEditing, true }, { x => x.DataHost, provider.Host }, - { x => x.HfInstanceProviderId, provider.HFInstanceProvider }, + { x => x.HFInferenceProviderId, provider.HFInferenceProvider }, }; var dialogReference = await this.DialogService.ShowAsync("Edit LLM Provider", dialogParameters, DialogOptions.FULLSCREEN); diff --git a/app/MindWork AI Studio/Dialogs/ProviderDialog.razor b/app/MindWork AI Studio/Dialogs/ProviderDialog.razor index 1a01faba..3664062a 100644 --- a/app/MindWork AI Studio/Dialogs/ProviderDialog.razor +++ b/app/MindWork AI Studio/Dialogs/ProviderDialog.razor @@ -61,20 +61,22 @@ @if (this.DataLLMProvider.IsHFInstanceProviderNeeded()) { - - @foreach (HFInstanceProvider instanceProvider in Enum.GetValues(typeof(HFInstanceProvider))) + + @foreach (HFInferenceProvider inferenceProvider in Enum.GetValues(typeof(HFInferenceProvider))) { - @instanceProvider.ToName() + + @inferenceProvider.ToName() + } - Please double-check if your model name matches the curl specifications provided by the instance provider. If it doesn't, you might get a Not Found error when trying to use the model. Here's a curl example. + Please double-check if your model name matches the curl specifications provided by the inference provider. If it doesn't, you might get a Not Found error when trying to use the model. Here's a curl example. } @if (this.DataLLMProvider.IsLLMModelProvidedManually()) { - Show available models + Show available models [Parameter] - public HFInstanceProvider HfInstanceProviderId { get; set; } = HFInstanceProvider.NONE; + public HFInferenceProvider HFInferenceProviderId { get; set; } = HFInferenceProvider.NONE; /// /// Is this provider self-hosted? @@ -138,7 +138,7 @@ public partial class ProviderDialog : ComponentBase, ISecretId IsSelfHosted = this.DataLLMProvider is LLMProviders.SELF_HOSTED, Hostname = cleanedHostname.EndsWith('/') ? cleanedHostname[..^1] : cleanedHostname, Host = this.DataHost, - HFInstanceProvider = this.HfInstanceProviderId, + HFInferenceProvider = this.HFInferenceProviderId, }; } diff --git a/app/MindWork AI Studio/Pages/Home.razor.cs b/app/MindWork AI Studio/Pages/Home.razor.cs index 4468e651..6a5dfff0 100644 --- a/app/MindWork AI Studio/Pages/Home.razor.cs +++ b/app/MindWork AI Studio/Pages/Home.razor.cs @@ -39,7 +39,7 @@ public partial class Home : MSGComponentBase private static readonly TextItem[] ITEMS_ADVANTAGES = [ new("Free of charge", "The app is free to use, both for personal and commercial purposes."), - new("Independence", "You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT4o, o1, etc.), Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, and self-hosted models using 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("Independence", "You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT4o, o1, etc.), Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), Hugging Face, and self-hosted models using 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("Assistants", "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("Unrestricted usage", "Unlike services like ChatGPT, which impose limits after intensive use, MindWork AI Studio offers unlimited usage through the providers API."), new("Cost-effective", "You only pay for what you use, which can be cheaper than monthly subscription services like ChatGPT Plus, especially if used infrequently. But beware, here be dragons: For extremely intensive usage, the API costs can be significantly higher. Unfortunately, providers currently do not offer a way to display current costs in the app. Therefore, check your account with the respective provider to see how your costs are developing. When available, use prepaid and set a cost limit."), diff --git a/app/MindWork AI Studio/Provider/HuggingFace/HFInstanceProvider.cs b/app/MindWork AI Studio/Provider/HuggingFace/HFInferenceProvider.cs similarity index 70% rename from app/MindWork AI Studio/Provider/HuggingFace/HFInstanceProvider.cs rename to app/MindWork AI Studio/Provider/HuggingFace/HFInferenceProvider.cs index 63221290..01b722eb 100644 --- a/app/MindWork AI Studio/Provider/HuggingFace/HFInstanceProvider.cs +++ b/app/MindWork AI Studio/Provider/HuggingFace/HFInferenceProvider.cs @@ -1,9 +1,9 @@ namespace AIStudio.Provider.HuggingFace; /// -/// Enum for instance providers that Hugging Face supports. +/// Enum for inference providers that Hugging Face supports. /// -public enum HFInstanceProvider +public enum HFInferenceProvider { NONE, diff --git a/app/MindWork AI Studio/Provider/HuggingFace/HFInferenceProviderExtensions.cs b/app/MindWork AI Studio/Provider/HuggingFace/HFInferenceProviderExtensions.cs new file mode 100644 index 00000000..0e103938 --- /dev/null +++ b/app/MindWork AI Studio/Provider/HuggingFace/HFInferenceProviderExtensions.cs @@ -0,0 +1,43 @@ +namespace AIStudio.Provider.HuggingFace; + +public static class HFInferenceProviderExtensions +{ + public static string Endpoints(this HFInferenceProvider provider, Model model) => provider switch + { + HFInferenceProvider.CEREBRAS => "cerebras/v1/", + HFInferenceProvider.NEBIUS_AI_STUDIO => "nebius/v1/", + HFInferenceProvider.SAMBANOVA => "sambanova/v1/", + HFInferenceProvider.NOVITA => "novita/v3/openai/", + HFInferenceProvider.HYPERBOLIC => "hyperbolic/v1/", + HFInferenceProvider.TOGETHER_AI => "together/v1/", + HFInferenceProvider.FIREWORKS => "fireworks-ai/inference/v1/", + HFInferenceProvider.HF_INFERENCE_API => $"hf-inference/models/{model.ToString()}/v1/", + _ => string.Empty, + }; + + public static string EndpointsId(this HFInferenceProvider provider) => provider switch + { + HFInferenceProvider.CEREBRAS => "cerebras", + HFInferenceProvider.NEBIUS_AI_STUDIO => "nebius", + HFInferenceProvider.SAMBANOVA => "sambanova", + HFInferenceProvider.NOVITA => "novita", + HFInferenceProvider.HYPERBOLIC => "hyperbolic", + HFInferenceProvider.TOGETHER_AI => "together", + HFInferenceProvider.FIREWORKS => "fireworks", + HFInferenceProvider.HF_INFERENCE_API => "hf-inference", + _ => string.Empty, + }; + + public static string ToName(this HFInferenceProvider provider) => provider switch + { + HFInferenceProvider.CEREBRAS => "Cerebras", + HFInferenceProvider.NEBIUS_AI_STUDIO => "Nebius AI Studio", + HFInferenceProvider.SAMBANOVA => "Sambanova", + HFInferenceProvider.NOVITA => "Novita", + HFInferenceProvider.HYPERBOLIC => "Hyperbolic", + HFInferenceProvider.TOGETHER_AI => "Together AI", + HFInferenceProvider.FIREWORKS => "Fireworks AI", + HFInferenceProvider.HF_INFERENCE_API => "Hugging Face Inference API", + _ => string.Empty, + }; +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Provider/HuggingFace/HFInstanceProviderExtensions.cs b/app/MindWork AI Studio/Provider/HuggingFace/HFInstanceProviderExtensions.cs deleted file mode 100644 index b0d81fba..00000000 --- a/app/MindWork AI Studio/Provider/HuggingFace/HFInstanceProviderExtensions.cs +++ /dev/null @@ -1,43 +0,0 @@ -namespace AIStudio.Provider.HuggingFace; - -public static class HFInstanceProviderExtensions -{ - public static string Endpoints(this HFInstanceProvider provider, Model model) => provider switch - { - HFInstanceProvider.CEREBRAS => "cerebras/v1/", - HFInstanceProvider.NEBIUS_AI_STUDIO => "nebius/v1/", - HFInstanceProvider.SAMBANOVA => "sambanova/v1/", - HFInstanceProvider.NOVITA => "novita/v3/openai/", - HFInstanceProvider.HYPERBOLIC => "hyperbolic/v1/", - HFInstanceProvider.TOGETHER_AI => "together/v1/", - HFInstanceProvider.FIREWORKS => "fireworks-ai/inference/v1/", - HFInstanceProvider.HF_INFERENCE_API => $"hf-inference/models/{model.ToString()}/v1/", - _ => string.Empty, - }; - - public static string EndpointsId(this HFInstanceProvider provider) => provider switch - { - HFInstanceProvider.CEREBRAS => "cerebras", - HFInstanceProvider.NEBIUS_AI_STUDIO => "nebius", - HFInstanceProvider.SAMBANOVA => "sambanova", - HFInstanceProvider.NOVITA => "novita", - HFInstanceProvider.HYPERBOLIC => "hyperbolic", - HFInstanceProvider.TOGETHER_AI => "together", - HFInstanceProvider.FIREWORKS => "fireworks", - HFInstanceProvider.HF_INFERENCE_API => "hf-inference", - _ => string.Empty, - }; - - public static string ToName(this HFInstanceProvider provider) => provider switch - { - HFInstanceProvider.CEREBRAS => "Cerebras", - HFInstanceProvider.NEBIUS_AI_STUDIO => "Nebius AI Studio", - HFInstanceProvider.SAMBANOVA => "Sambanova", - HFInstanceProvider.NOVITA => "Novita", - HFInstanceProvider.HYPERBOLIC => "Hyperbolic", - HFInstanceProvider.TOGETHER_AI => "Together AI", - HFInstanceProvider.FIREWORKS => "Fireworks AI", - HFInstanceProvider.HF_INFERENCE_API => "Hugging Face Inference API", - _ => string.Empty, - }; -} \ No newline at end of file diff --git a/app/MindWork AI Studio/Provider/HuggingFace/ProviderHuggingFace.cs b/app/MindWork AI Studio/Provider/HuggingFace/ProviderHuggingFace.cs index 25f2baae..e98de1f9 100644 --- a/app/MindWork AI Studio/Provider/HuggingFace/ProviderHuggingFace.cs +++ b/app/MindWork AI Studio/Provider/HuggingFace/ProviderHuggingFace.cs @@ -11,9 +11,9 @@ namespace AIStudio.Provider.HuggingFace; public sealed class ProviderHuggingFace : BaseProvider { - public ProviderHuggingFace(ILogger logger, HFInstanceProvider hfProvider, Model model) : base($"https://router.huggingface.co/{hfProvider.Endpoints(model)}", logger) + public ProviderHuggingFace(ILogger logger, HFInferenceProvider hfProvider, Model model) : base($"https://router.huggingface.co/{hfProvider.Endpoints(model)}", logger) { - logger.LogInformation($"We use the instance provider '{hfProvider}'. Thus we use the base URL 'https://router.huggingface.co/{hfProvider.Endpoints(model)}'."); + logger.LogInformation($"We use the inferende provider '{hfProvider}'. Thus we use the base URL 'https://router.huggingface.co/{hfProvider.Endpoints(model)}'."); } #region Implementation of IProvider diff --git a/app/MindWork AI Studio/Provider/LLMProvidersExtensions.cs b/app/MindWork AI Studio/Provider/LLMProvidersExtensions.cs index 8abb0bd4..68a62297 100644 --- a/app/MindWork AI Studio/Provider/LLMProvidersExtensions.cs +++ b/app/MindWork AI Studio/Provider/LLMProvidersExtensions.cs @@ -135,7 +135,7 @@ public static class LLMProvidersExtensions /// The provider instance. public static IProvider CreateProvider(this AIStudio.Settings.Provider providerSettings, ILogger logger) { - return providerSettings.UsedLLMProvider.CreateProvider(providerSettings.InstanceName, providerSettings.Host, providerSettings.Hostname, providerSettings.Model, providerSettings.HFInstanceProvider ,logger); + return providerSettings.UsedLLMProvider.CreateProvider(providerSettings.InstanceName, providerSettings.Host, providerSettings.Hostname, providerSettings.Model, providerSettings.HFInferenceProvider ,logger); } /// @@ -146,10 +146,10 @@ public static class LLMProvidersExtensions /// The provider instance. public static IProvider CreateProvider(this EmbeddingProvider embeddingProviderSettings, ILogger logger) { - return embeddingProviderSettings.UsedLLMProvider.CreateProvider(embeddingProviderSettings.Name, embeddingProviderSettings.Host, embeddingProviderSettings.Hostname, embeddingProviderSettings.Model, HFInstanceProvider.NONE,logger); + return embeddingProviderSettings.UsedLLMProvider.CreateProvider(embeddingProviderSettings.Name, embeddingProviderSettings.Host, embeddingProviderSettings.Hostname, embeddingProviderSettings.Model, HFInferenceProvider.NONE,logger); } - private static IProvider CreateProvider(this LLMProviders provider, string instanceName, Host host, string hostname, Model model, HFInstanceProvider instanceProvider , ILogger logger) + private static IProvider CreateProvider(this LLMProviders provider, string instanceName, Host host, string hostname, Model model, HFInferenceProvider inferenceProvider , ILogger logger) { try { @@ -165,7 +165,7 @@ public static class LLMProvidersExtensions LLMProviders.GROQ => new ProviderGroq(logger) { InstanceName = instanceName }, LLMProviders.FIREWORKS => new ProviderFireworks(logger) { InstanceName = instanceName }, - LLMProviders.HUGGINGFACE => new ProviderHuggingFace(logger, instanceProvider, model) { InstanceName = instanceName }, + LLMProviders.HUGGINGFACE => new ProviderHuggingFace(logger, inferenceProvider, model) { InstanceName = instanceName }, LLMProviders.SELF_HOSTED => new ProviderSelfHosted(logger, host, hostname) { InstanceName = instanceName }, @@ -234,10 +234,10 @@ public static class LLMProvidersExtensions _ => false, }; - public static string GetModelsOverviewURL(this LLMProviders provider, HFInstanceProvider instanceProvider) => provider switch + public static string GetModelsOverviewURL(this LLMProviders provider, HFInferenceProvider inferenceProvider) => provider switch { LLMProviders.FIREWORKS => "https://fireworks.ai/models?show=Serverless", - LLMProviders.HUGGINGFACE => $"https://huggingface.co/models?inference_provider={instanceProvider.EndpointsId()}", + LLMProviders.HUGGINGFACE => $"https://huggingface.co/models?inference_provider={inferenceProvider.EndpointsId()}", _ => string.Empty, }; diff --git a/app/MindWork AI Studio/Settings/Provider.cs b/app/MindWork AI Studio/Settings/Provider.cs index 6aefc5b5..33d39d3d 100644 --- a/app/MindWork AI Studio/Settings/Provider.cs +++ b/app/MindWork AI Studio/Settings/Provider.cs @@ -25,7 +25,7 @@ public readonly record struct Provider( bool IsSelfHosted = false, string Hostname = "http://localhost:1234", Host Host = Host.NONE, - HFInstanceProvider HFInstanceProvider = HFInstanceProvider.NONE) : ISecretId + HFInferenceProvider HFInferenceProvider = HFInferenceProvider.NONE) : ISecretId { #region Overrides of ValueType diff --git a/app/MindWork AI Studio/Tools/Validation/ProviderValidation.cs b/app/MindWork AI Studio/Tools/Validation/ProviderValidation.cs index 12d27b43..9205b1ef 100644 --- a/app/MindWork AI Studio/Tools/Validation/ProviderValidation.cs +++ b/app/MindWork AI Studio/Tools/Validation/ProviderValidation.cs @@ -94,13 +94,13 @@ public sealed class ProviderValidation return null; } - public string? ValidatingHFInstanceProvider(HFInstanceProvider instanceProvider) + public string? ValidatingHFInstanceProvider(HFInferenceProvider inferenceProvider) { if(this.GetProvider() is not LLMProviders.HUGGINGFACE) return null; - if (instanceProvider is HFInstanceProvider.NONE) - return "Please select an Hugging Face instance provider."; + if (inferenceProvider is HFInferenceProvider.NONE) + return "Please select an Hugging Face inference provider."; return null; } diff --git a/app/MindWork AI Studio/wwwroot/changelog/v0.9.40.md b/app/MindWork AI Studio/wwwroot/changelog/v0.9.40.md index 29426981..a34d2872 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v0.9.40.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v0.9.40.md @@ -2,6 +2,7 @@ - Added support for the announced OpenAI `o4` models. - Added Alibaba Cloud as a new provider. Thanks Peer `peerschuett` for the contribution. - Added the Hugging Face inference provider as an LLM provider to AI Studio. Thanks Peer `peerschuett` for the contribution. +- Added the current CPU architecture to the about page. This information helps us identify issues. - Improved the LLM & embedding provider dialogs by hiding not relevant options. - Improved the provider selection by showing the name of the provider in the provider selection instead of its identifier. - Improved the developer experience by adding a tolerant enum converter for better configuration handling. diff --git a/documentation/Setup.md b/documentation/Setup.md index b06b6cbc..3a3af745 100644 --- a/documentation/Setup.md +++ b/documentation/Setup.md @@ -14,7 +14,7 @@ AI Studio is only available for modern 64-bit Windows systems. When you have an - **Intel/AMD:** In almost all other cases, you have an Intel/AMD system. [Download the x64 version](https://github.com/MindWorkAI/AI-Studio/releases/latest/download/MindWork.AI.Studio_x64-setup.exe) of AI Studio. -When you try to install the app, you get a message regarding protection of your PC (see screenshots below). For Windows to trust our app, we need to purchase a certificate that costs around $1000 per year. Would you like to help us with this? [Please consider supporting us](https://github.com/sponsors/MindWorkAI). You might want to [visit our release page](https://github.com/MindWorkAI/AI-Studio/releases/latest). There, we provide VirusTotal scan results for each release. If you are unsure about the safety of the app, you can check the results there. Ensure that the majority of scanners have a green checkmark. +When you try to install the app, you get a message regarding protection of your PC (see screenshots below). For Windows to trust our app, we need to purchase a certificate that [costs around $1000 per year](https://github.com/MindWorkAI/Planning/issues/56). Would you like to help us with this? [Please consider supporting us](https://github.com/sponsors/MindWorkAI). You might want to [visit our release page](https://github.com/MindWorkAI/AI-Studio/releases/latest). There, we provide VirusTotal scan results for each release. If you are unsure about the safety of the app, you can check the results there. Ensure that the majority of scanners have a green checkmark. When you are confident in the app's safety, click on "More info" and then "Run anyway" to proceed with the installation: @@ -43,7 +43,7 @@ When you try to open the app, you get a message that the app is damaged: ![macOS Installation 2](macOS%20Damage.png) -This is because we don't have an Apple Developer account, which costs around $100 per year. Would you like to help us with this? [Please consider supporting us](https://github.com/sponsors/MindWorkAI). You might want to [visit our release page](https://github.com/MindWorkAI/AI-Studio/releases/latest). There, we provide VirusTotal scan results for each release. If you are unsure about the safety of the app, you can check the results there. Ensure that the majority of scanners have a green checkmark. +This is because we don't have an Apple Developer account, [which costs around $100 per year](https://github.com/MindWorkAI/Planning/issues/56). Would you like to help us with this? [Please consider supporting us](https://github.com/sponsors/MindWorkAI). You might want to [visit our release page](https://github.com/MindWorkAI/AI-Studio/releases/latest). There, we provide VirusTotal scan results for each release. If you are unsure about the safety of the app, you can check the results there. Ensure that the majority of scanners have a green checkmark. When you are confident in the app's safety, follow these steps: diff --git a/metadata.txt b/metadata.txt index c1dcbe32..11d63db7 100644 --- a/metadata.txt +++ b/metadata.txt @@ -7,4 +7,4 @@ 8.5.1 1.8.1 19935769035, release -osx-arm64 +osx-arm64 \ No newline at end of file From 03cd299a86b9c0eed16deff84d7ede2b68177cf7 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sun, 20 Apr 2025 15:36:42 +0200 Subject: [PATCH 18/93] Prepared release v0.9.40 (#410) --- app/MindWork AI Studio/Components/Changelog.Logs.cs | 1 + app/MindWork AI Studio/wwwroot/changelog/v0.9.40.md | 2 +- app/MindWork AI Studio/wwwroot/changelog/v0.9.41.md | 1 + metadata.txt | 8 ++++---- runtime/Cargo.lock | 2 +- runtime/Cargo.toml | 2 +- runtime/tauri.conf.json | 2 +- 7 files changed, 10 insertions(+), 8 deletions(-) create mode 100644 app/MindWork AI Studio/wwwroot/changelog/v0.9.41.md diff --git a/app/MindWork AI Studio/Components/Changelog.Logs.cs b/app/MindWork AI Studio/Components/Changelog.Logs.cs index 7079404f..9a2143de 100644 --- a/app/MindWork AI Studio/Components/Changelog.Logs.cs +++ b/app/MindWork AI Studio/Components/Changelog.Logs.cs @@ -13,6 +13,7 @@ public partial class Changelog public static readonly Log[] LOGS = [ + new (215, "v0.9.40, build 215 (2025-04-20 13:30 UTC)", "v0.9.40.md"), new (214, "v0.9.39, build 214 (2025-04-07 17:39 UTC)", "v0.9.39.md"), new (213, "v0.9.38, build 213 (2025-03-17 18:18 UTC)", "v0.9.38.md"), new (212, "v0.9.37, build 212 (2025-03-16 20:32 UTC)", "v0.9.37.md"), diff --git a/app/MindWork AI Studio/wwwroot/changelog/v0.9.40.md b/app/MindWork AI Studio/wwwroot/changelog/v0.9.40.md index a34d2872..adb67535 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v0.9.40.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v0.9.40.md @@ -1,4 +1,4 @@ -# v0.9.40, build 215 (2025-04-xx xx:xx UTC) +# v0.9.40, build 215 (2025-04-20 13:30 UTC) - Added support for the announced OpenAI `o4` models. - Added Alibaba Cloud as a new provider. Thanks Peer `peerschuett` for the contribution. - Added the Hugging Face inference provider as an LLM provider to AI Studio. Thanks Peer `peerschuett` for the contribution. diff --git a/app/MindWork AI Studio/wwwroot/changelog/v0.9.41.md b/app/MindWork AI Studio/wwwroot/changelog/v0.9.41.md new file mode 100644 index 00000000..97eecf0c --- /dev/null +++ b/app/MindWork AI Studio/wwwroot/changelog/v0.9.41.md @@ -0,0 +1 @@ +# v0.9.41, build 216 (2025-0x-xx xx:xx UTC) \ No newline at end of file diff --git a/metadata.txt b/metadata.txt index 11d63db7..eeb702f0 100644 --- a/metadata.txt +++ b/metadata.txt @@ -1,10 +1,10 @@ -0.9.39 -2025-04-07 17:39:09 UTC -214 +0.9.40 +2025-04-20 13:30:03 UTC +215 9.0.105 (commit 35890ecb87) 9.0.4 (commit f57e6dc747) 1.86.0 (commit 05f9846f8) 8.5.1 1.8.1 -19935769035, release +2144cfe0590, release osx-arm64 \ No newline at end of file diff --git a/runtime/Cargo.lock b/runtime/Cargo.lock index 14dd651e..24e4cc2f 100644 --- a/runtime/Cargo.lock +++ b/runtime/Cargo.lock @@ -2610,7 +2610,7 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "mindwork-ai-studio" -version = "0.9.39" +version = "0.9.40" dependencies = [ "aes", "arboard", diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 685d5001..ec6630d4 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mindwork-ai-studio" -version = "0.9.39" +version = "0.9.40" edition = "2021" description = "MindWork AI Studio" authors = ["Thorsten Sommer"] diff --git a/runtime/tauri.conf.json b/runtime/tauri.conf.json index 91fb4b09..e5d60573 100644 --- a/runtime/tauri.conf.json +++ b/runtime/tauri.conf.json @@ -6,7 +6,7 @@ }, "package": { "productName": "MindWork AI Studio", - "version": "0.9.39" + "version": "0.9.40" }, "tauri": { "allowlist": { From 7bd827e65854a37289f5bd34dfea88ff16d703ad Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Mon, 21 Apr 2025 11:55:15 +0200 Subject: [PATCH 19/93] Improved build script: create next changelog automatically (#412) --- app/Build/Commands/AppVersion.cs | 3 + app/Build/Commands/UpdateMetadataCommands.cs | 76 ++++++++++++++++++-- 2 files changed, 73 insertions(+), 6 deletions(-) create mode 100644 app/Build/Commands/AppVersion.cs diff --git a/app/Build/Commands/AppVersion.cs b/app/Build/Commands/AppVersion.cs new file mode 100644 index 00000000..897e0ce0 --- /dev/null +++ b/app/Build/Commands/AppVersion.cs @@ -0,0 +1,3 @@ +namespace Build.Commands; + +public record AppVersion(string VersionText, int Major, int Minor, int Patch); \ No newline at end of file diff --git a/app/Build/Commands/UpdateMetadataCommands.cs b/app/Build/Commands/UpdateMetadataCommands.cs index 464f8008..a2cc4595 100644 --- a/app/Build/Commands/UpdateMetadataCommands.cs +++ b/app/Build/Commands/UpdateMetadataCommands.cs @@ -19,7 +19,7 @@ public sealed partial class UpdateMetadataCommands return; // Prepare the metadata for the next release: - await this.Prepare(action); + await this.PerformPrepare(action, true); // Build once to allow the Rust compiler to read the changed metadata // and to update all .NET artifacts: @@ -59,14 +59,31 @@ public sealed partial class UpdateMetadataCommands return; Console.WriteLine("=============================="); + Console.Write("- Are you trying to prepare a new release? (y/n) "); + var userAnswer = Console.ReadLine(); + if (userAnswer?.ToLowerInvariant() == "y") + { + Console.WriteLine("- Please use the 'release' command instead"); + return; + } + + await this.PerformPrepare(action, false); + } + + private async Task PerformPrepare(PrepareAction action, bool internalCall) + { + if(internalCall) + Console.WriteLine("=============================="); + Console.WriteLine("- Prepare the metadata for the next release ..."); var appVersion = await this.UpdateAppVersion(action); - if (!string.IsNullOrWhiteSpace(appVersion)) + if (!string.IsNullOrWhiteSpace(appVersion.VersionText)) { var buildNumber = await this.IncreaseBuildNumber(); var buildTime = await this.UpdateBuildTime(); - await this.UpdateChangelog(buildNumber, appVersion, buildTime); + await this.UpdateChangelog(buildNumber, appVersion.VersionText, buildTime); + await this.CreateNextChangelog(buildNumber, appVersion); await this.UpdateDotnetVersion(); await this.UpdateRustVersion(); await this.UpdateMudBlazorVersion(); @@ -219,14 +236,59 @@ public sealed partial class UpdateMetadataCommands } } + private async Task CreateNextChangelog(int currentBuildNumber, AppVersion currentAppVersion) + { + Console.Write("- Create the next changelog ..."); + var pathChangelogs = Path.Combine(Environment.GetAIStudioDirectory(), "wwwroot", "changelog"); + var nextBuildNumber = currentBuildNumber + 1; + + // + // We assume that most of the time, there will be patch releases: + // + var nextMajor = currentAppVersion.Major; + var nextMinor = currentAppVersion.Minor; + var nextPatch = currentAppVersion.Patch + 1; + + var nextAppVersion = $"{nextMajor}.{nextMinor}.{nextPatch}"; + var nextChangelogFilename = $"v{nextAppVersion}.md"; + var nextChangelogFilePath = Path.Combine(pathChangelogs, nextChangelogFilename); + + // + // Regarding the next build time: We assume that the next release will take place in one week from now. + // Thus, we check how many days this month has left. In the end, we want to predict the year and month + // for the next build. Day, hour, minute and second are all set to x. + // + var nextBuildMonth = (DateTime.Today + TimeSpan.FromDays(7)).Month; + var nextBuildYear = (DateTime.Today + TimeSpan.FromDays(7)).Year; + var nextBuildTimeString = $"{nextBuildYear}-{nextBuildMonth:00}-xx xx:xx UTC"; + + var changelogHeader = $""" + # v{nextAppVersion}, build {nextBuildNumber} ({nextBuildTimeString}) + + """; + + if(!File.Exists(nextChangelogFilePath)) + { + await File.WriteAllTextAsync(nextChangelogFilePath, changelogHeader, Environment.UTF8_NO_BOM); + Console.WriteLine($" done. Changelog '{nextChangelogFilename}' created."); + } + else + { + Console.WriteLine(" failed."); + Console.WriteLine("- Error: The changelog file already exists."); + } + } + private async Task UpdateChangelog(int buildNumber, string appVersion, string buildTime) { + Console.Write("- Updating the in-app changelog list ..."); var pathChangelogs = Path.Combine(Environment.GetAIStudioDirectory(), "wwwroot", "changelog"); var expectedLogFilename = $"v{appVersion}.md"; var expectedLogFilePath = Path.Combine(pathChangelogs, expectedLogFilename); if(!File.Exists(expectedLogFilePath)) { + Console.WriteLine(" failed."); Console.WriteLine($"- Error: The changelog file '{expectedLogFilename}' does not exist."); return; } @@ -250,6 +312,7 @@ public sealed partial class UpdateMetadataCommands changelogCode = changelogCode.Replace(CODE_START, updatedCode); await File.WriteAllTextAsync(changelogCodePath, changelogCode, Environment.UTF8_NO_BOM); + Console.WriteLine(" done."); } private async Task UpdateArchitecture(RID rid) @@ -282,14 +345,14 @@ public sealed partial class UpdateMetadataCommands await File.WriteAllLinesAsync(pathMetadata, lines, Environment.UTF8_NO_BOM); } - private async Task UpdateAppVersion(PrepareAction action) + private async Task UpdateAppVersion(PrepareAction action) { const int APP_VERSION_INDEX = 0; if (action == PrepareAction.NONE) { Console.WriteLine("- No action specified. Skipping app version update."); - return string.Empty; + return new(string.Empty, 0, 0, 0); } var pathMetadata = Environment.GetMetadataPath(); @@ -323,7 +386,8 @@ public sealed partial class UpdateMetadataCommands lines[APP_VERSION_INDEX] = updatedAppVersion; await File.WriteAllLinesAsync(pathMetadata, lines, Environment.UTF8_NO_BOM); - return updatedAppVersion; + + return new(updatedAppVersion, currentMajor, currentMinor, currentPatch); } private async Task UpdateLicenceYear(string licenceFilePath) From e594e5c0e88f02a1e779505e575cd8fa21d6d8b4 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Mon, 21 Apr 2025 12:21:54 +0200 Subject: [PATCH 20/93] Updated terminology: "Temporary chats" to "Disappearing Chats" (#413) --- app/MindWork AI Studio/Components/Workspaces.razor.cs | 2 +- app/MindWork AI Studio/Pages/Chat.razor | 2 +- app/MindWork AI Studio/wwwroot/changelog/v0.9.41.md | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/app/MindWork AI Studio/Components/Workspaces.razor.cs b/app/MindWork AI Studio/Components/Workspaces.razor.cs index 9b6b0bb6..c4d68567 100644 --- a/app/MindWork AI Studio/Components/Workspaces.razor.cs +++ b/app/MindWork AI Studio/Components/Workspaces.razor.cs @@ -77,7 +77,7 @@ public partial class Workspaces : ComponentBase { Depth = 0, Branch = WorkspaceBranch.TEMPORARY_CHATS, - Text = "Temporary chats", + Text = "Disappearing Chats", Icon = Icons.Material.Filled.Timer, Expandable = true, Path = "temp", diff --git a/app/MindWork AI Studio/Pages/Chat.razor b/app/MindWork AI Studio/Pages/Chat.razor index b95a9d62..418a17cc 100644 --- a/app/MindWork AI Studio/Pages/Chat.razor +++ b/app/MindWork AI Studio/Pages/Chat.razor @@ -11,7 +11,7 @@ } else { - @(T("Short-Term Chat")) + @(T("Disappearing Chat")) } diff --git a/app/MindWork AI Studio/wwwroot/changelog/v0.9.41.md b/app/MindWork AI Studio/wwwroot/changelog/v0.9.41.md index 97eecf0c..f7999237 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v0.9.41.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v0.9.41.md @@ -1 +1,2 @@ -# v0.9.41, build 216 (2025-0x-xx xx:xx UTC) \ No newline at end of file +# v0.9.41, build 216 (2025-0x-xx xx:xx UTC) +- Changed the terminology from "temporary chats" to "disappearing chats" in the UI. This makes it clearer to understand the purpose of these chats. \ No newline at end of file From 1dc1c6563d4b3be4c7cf18b26c211c6cffba7d05 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Mon, 21 Apr 2025 12:39:48 +0200 Subject: [PATCH 21/93] Added OS language to the about page (#414) --- app/MindWork AI Studio/Pages/About.razor | 1 + app/MindWork AI Studio/Pages/About.razor.cs | 9 +++++++-- app/MindWork AI Studio/wwwroot/changelog/v0.9.41.md | 1 + 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/app/MindWork AI Studio/Pages/About.razor b/app/MindWork AI Studio/Pages/About.razor index 32c8b5df..ce541476 100644 --- a/app/MindWork AI Studio/Pages/About.razor +++ b/app/MindWork AI Studio/Pages/About.razor @@ -17,6 +17,7 @@ + Check for updates diff --git a/app/MindWork AI Studio/Pages/About.razor.cs b/app/MindWork AI Studio/Pages/About.razor.cs index 0be178f7..1d9b92d0 100644 --- a/app/MindWork AI Studio/Pages/About.razor.cs +++ b/app/MindWork AI Studio/Pages/About.razor.cs @@ -25,6 +25,8 @@ public partial class About : ComponentBase private static readonly MetaDataAttribute META_DATA = ASSEMBLY.GetCustomAttribute()!; private static readonly MetaDataArchitecture META_DATA_ARCH = ASSEMBLY.GetCustomAttribute()!; + private string osLanguage = string.Empty; + private static string VersionDotnetRuntime => $"Used .NET runtime: v{META_DATA.DotnetVersion}"; private static string VersionDotnetSdk => $"Used .NET SDK: v{META_DATA.DotnetSdkVersion}"; @@ -38,11 +40,11 @@ public partial class About : ComponentBase private static string MudBlazorVersion => $"MudBlazor: v{META_DATA.MudBlazorVersion}"; private static string TauriVersion => $"Tauri: v{META_DATA.TauriVersion}"; + + private string OSLanguage => $"User-language provided by the OS: '{this.osLanguage}'"; private GetLogPathsResponse logPaths; - #region Overrides of ComponentBase - private async Task CopyStartupLogPath() { await this.RustService.CopyText2Clipboard(this.Snackbar, this.logPaths.LogStartupPath); @@ -53,8 +55,11 @@ public partial class About : ComponentBase await this.RustService.CopyText2Clipboard(this.Snackbar, this.logPaths.LogAppPath); } + #region Overrides of ComponentBase + protected override async Task OnInitializedAsync() { + this.osLanguage = await this.RustService.ReadUserLanguage(); this.logPaths = await this.RustService.GetLogPaths(); await base.OnInitializedAsync(); } diff --git a/app/MindWork AI Studio/wwwroot/changelog/v0.9.41.md b/app/MindWork AI Studio/wwwroot/changelog/v0.9.41.md index f7999237..1cc23068 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v0.9.41.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v0.9.41.md @@ -1,2 +1,3 @@ # v0.9.41, build 216 (2025-0x-xx xx:xx UTC) +- Added the user-language, as provided by the OS, to the about page. This helps in identifying user-specific issues related to language settings. - Changed the terminology from "temporary chats" to "disappearing chats" in the UI. This makes it clearer to understand the purpose of these chats. \ No newline at end of file From 7bf5b5cf7a843f33e970bb636d0b651ff7965f05 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Mon, 21 Apr 2025 12:52:29 +0200 Subject: [PATCH 22/93] Fixed the update notification button color to match the color theme (#415) --- app/MindWork AI Studio/Layout/MainLayout.razor.cs | 1 - app/MindWork AI Studio/wwwroot/changelog/v0.9.41.md | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/MindWork AI Studio/Layout/MainLayout.razor.cs b/app/MindWork AI Studio/Layout/MainLayout.razor.cs index b80de4a5..0bcafbb4 100644 --- a/app/MindWork AI Studio/Layout/MainLayout.razor.cs +++ b/app/MindWork AI Studio/Layout/MainLayout.razor.cs @@ -143,7 +143,6 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, IDis }; config.Action = "Show details"; config.ActionVariant = Variant.Filled; - config.ActionColor = Color.Dark; }); } diff --git a/app/MindWork AI Studio/wwwroot/changelog/v0.9.41.md b/app/MindWork AI Studio/wwwroot/changelog/v0.9.41.md index 1cc23068..4454d93b 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v0.9.41.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v0.9.41.md @@ -1,3 +1,4 @@ # v0.9.41, build 216 (2025-0x-xx xx:xx UTC) - Added the user-language, as provided by the OS, to the about page. This helps in identifying user-specific issues related to language settings. -- Changed the terminology from "temporary chats" to "disappearing chats" in the UI. This makes it clearer to understand the purpose of these chats. \ No newline at end of file +- Changed the terminology from "temporary chats" to "disappearing chats" in the UI. This makes it clearer to understand the purpose of these chats. +- Fixed the color for the update notification button to match the color theme. \ No newline at end of file From fdca581c90e414b8fccb22472dc0343eac64b142 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Wed, 23 Apr 2025 14:07:22 +0200 Subject: [PATCH 23/93] Improved plugin system (#417) --- .../PluginSystem/PluginFactory.HotReload.cs | 10 +- .../PluginSystem/PluginFactory.Loading.cs | 152 ++++++++++++ .../PluginSystem/PluginFactory.Starting.cs | 101 ++++++++ .../Tools/PluginSystem/PluginFactory.cs | 234 +----------------- .../wwwroot/changelog/v0.9.41.md | 1 + 5 files changed, 267 insertions(+), 231 deletions(-) create mode 100644 app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs create mode 100644 app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Starting.cs diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.HotReload.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.HotReload.cs index 0a32c17d..91641af6 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.HotReload.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.HotReload.cs @@ -2,6 +2,8 @@ namespace AIStudio.Tools.PluginSystem; public static partial class PluginFactory { + private static readonly SemaphoreSlim HOT_RELOAD_SEMAPHORE = new(1, 1); + public static void SetUpHotReloading() { if (!IS_INITIALIZED) @@ -20,7 +22,13 @@ public static partial class PluginFactory HOT_RELOAD_WATCHER.Filter = "*.lua"; HOT_RELOAD_WATCHER.Changed += async (_, args) => { - LOG.LogInformation($"File changed: {args.FullPath}"); + if (!await HOT_RELOAD_SEMAPHORE.WaitAsync(0)) + { + LOG.LogInformation($"File changed ({args.ChangeType}): {args.FullPath}. Already processing another change."); + return; + } + + LOG.LogInformation($"File changed ({args.ChangeType}): {args.FullPath}. Reloading plugins..."); await LoadAll(); await messageBus.SendMessage(null, Event.PLUGINS_RELOADED); }; diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs new file mode 100644 index 00000000..b6a39b1f --- /dev/null +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs @@ -0,0 +1,152 @@ +using System.Text; + +using Lua; +using Lua.Standard; + +namespace AIStudio.Tools.PluginSystem; + +public static partial class PluginFactory +{ + private static readonly List AVAILABLE_PLUGINS = []; + private static readonly SemaphoreSlim PLUGIN_LOAD_SEMAPHORE = new(1, 1); + + /// + /// A list of all available plugins. + /// + public static IReadOnlyCollection AvailablePlugins => AVAILABLE_PLUGINS; + + /// + /// Try to load all plugins from the plugins directory. + /// + /// + /// Loading plugins means:
+ /// - Parsing and checking the plugin code
+ /// - Check for forbidden plugins
+ /// - Creating a new instance of the allowed plugin
+ /// - Read the plugin metadata
+ ///
+ /// Loading a plugin does not mean to start the plugin, though. + ///
+ public static async Task LoadAll(CancellationToken cancellationToken = default) + { + if (!IS_INITIALIZED) + { + LOG.LogError("PluginFactory is not initialized. Please call Setup() before using it."); + return; + } + + if (!await PLUGIN_LOAD_SEMAPHORE.WaitAsync(0, cancellationToken)) + return; + + try + { + LOG.LogInformation("Start loading plugins."); + if (!Directory.Exists(PLUGINS_ROOT)) + { + LOG.LogInformation("No plugins found."); + return; + } + + AVAILABLE_PLUGINS.Clear(); + + // + // The easiest way to load all plugins is to find all `plugin.lua` files and load them. + // By convention, each plugin is enforced to have a `plugin.lua` file. + // + var pluginMainFiles = Directory.EnumerateFiles(PLUGINS_ROOT, "plugin.lua", SearchOption.AllDirectories); + foreach (var pluginMainFile in pluginMainFiles) + { + if (cancellationToken.IsCancellationRequested) + break; + + LOG.LogInformation($"Try to load plugin: {pluginMainFile}"); + var code = await File.ReadAllTextAsync(pluginMainFile, Encoding.UTF8, cancellationToken); + var pluginPath = Path.GetDirectoryName(pluginMainFile)!; + var plugin = await Load(pluginPath, code, cancellationToken); + + switch (plugin) + { + case NoPlugin noPlugin when noPlugin.Issues.Any(): + LOG.LogError($"Was not able to load plugin: '{pluginMainFile}'. Reason: {noPlugin.Issues.First()}"); + continue; + + case NoPlugin: + LOG.LogError($"Was not able to load plugin: '{pluginMainFile}'. Reason: Unknown."); + continue; + + case { IsValid: false }: + LOG.LogError($"Was not able to load plugin '{pluginMainFile}', because the Lua code is not a valid AI Studio plugin. There are {plugin.Issues.Count()} issues to fix. First issue is: {plugin.Issues.FirstOrDefault()}"); + #if DEBUG + foreach (var pluginIssue in plugin.Issues) + LOG.LogError($"Plugin issue: {pluginIssue}"); + #endif + continue; + + case { IsMaintained: false }: + LOG.LogWarning($"The plugin '{pluginMainFile}' is not maintained anymore. Please consider to disable it."); + break; + } + + LOG.LogInformation($"Successfully loaded plugin: '{pluginMainFile}' (Id='{plugin.Id}', Type='{plugin.Type}', Name='{plugin.Name}', Version='{plugin.Version}', Authors='{string.Join(", ", plugin.Authors)}')"); + AVAILABLE_PLUGINS.Add(new PluginMetadata(plugin, pluginPath)); + } + + // Start or restart all plugins: + await RestartAllPlugins(cancellationToken); + } + finally + { + PLUGIN_LOAD_SEMAPHORE.Release(); + LOG.LogInformation("Finished loading plugins."); + } + } + + private static async Task Load(string pluginPath, string code, CancellationToken cancellationToken = default) + { + if(ForbiddenPlugins.Check(code) is { IsForbidden: true } forbiddenState) + return new NoPlugin($"This plugin is forbidden: {forbiddenState.Message}"); + + var state = LuaState.Create(); + + // Add the module loader so that the plugin can load other Lua modules: + state.ModuleLoader = new PluginLoader(pluginPath); + + // Add some useful libraries: + state.OpenModuleLibrary(); + state.OpenStringLibrary(); + state.OpenTableLibrary(); + state.OpenMathLibrary(); + state.OpenBitwiseLibrary(); + state.OpenCoroutineLibrary(); + + try + { + await state.DoStringAsync(code, cancellationToken: cancellationToken); + } + catch (LuaParseException e) + { + return new NoPlugin($"Was not able to parse the plugin: {e.Message}"); + } + catch (LuaRuntimeException e) + { + return new NoPlugin($"Was not able to run the plugin: {e.Message}"); + } + + if (!state.Environment["TYPE"].TryRead(out var typeText)) + return new NoPlugin("TYPE does not exist or is not a valid string."); + + if (!Enum.TryParse(typeText, out var type)) + return new NoPlugin($"TYPE is not a valid plugin type. Valid types are: {CommonTools.GetAllEnumValues()}"); + + if(type is PluginType.NONE) + return new NoPlugin($"TYPE is not a valid plugin type. Valid types are: {CommonTools.GetAllEnumValues()}"); + + var isInternal = pluginPath.StartsWith(INTERNAL_PLUGINS_ROOT, StringComparison.OrdinalIgnoreCase); + return type switch + { + PluginType.LANGUAGE => new PluginLanguage(isInternal, state, type), + + _ => new NoPlugin("This plugin type is not supported yet. Please try again with a future version of AI Studio.") + }; + } +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Starting.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Starting.cs new file mode 100644 index 00000000..8fe1b9d8 --- /dev/null +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Starting.cs @@ -0,0 +1,101 @@ +using System.Text; + +namespace AIStudio.Tools.PluginSystem; + +public static partial class PluginFactory +{ + private static readonly List RUNNING_PLUGINS = []; + + /// + /// A list of all running plugins. + /// + public static IReadOnlyCollection RunningPlugins => RUNNING_PLUGINS; + + private static async Task RestartAllPlugins(CancellationToken cancellationToken = default) + { + LOG.LogInformation("Try to start or restart all plugins."); + RUNNING_PLUGINS.Clear(); + + // + // Get the base language plugin. This is the plugin that will be used to fill in missing keys. + // + var baseLanguagePluginId = InternalPlugin.LANGUAGE_EN_US.MetaData().Id; + var baseLanguagePluginMetaData = AVAILABLE_PLUGINS.FirstOrDefault(p => p.Id == baseLanguagePluginId); + if (baseLanguagePluginMetaData is null) + { + LOG.LogError($"Was not able to find the base language plugin: Id='{baseLanguagePluginId}'. Please check your installation."); + return; + } + + var startedBasePlugin = await Start(baseLanguagePluginMetaData, cancellationToken); + if (startedBasePlugin is NoPlugin noPlugin) + { + LOG.LogError($"Was not able to start the base language plugin: Id='{baseLanguagePluginId}'. Reason: {noPlugin.Issues.First()}"); + return; + } + + if (startedBasePlugin is PluginLanguage languagePlugin) + { + BASE_LANGUAGE_PLUGIN = languagePlugin; + RUNNING_PLUGINS.Add(languagePlugin); + LOG.LogInformation($"Successfully started the base language plugin: Id='{languagePlugin.Id}', Type='{languagePlugin.Type}', Name='{languagePlugin.Name}', Version='{languagePlugin.Version}'"); + } + else + { + LOG.LogError($"Was not able to start the base language plugin: Id='{baseLanguagePluginId}'. Reason: {string.Join("; ", startedBasePlugin.Issues)}"); + return; + } + + // + // Iterate over all available plugins and try to start them. + // + foreach (var availablePlugin in AVAILABLE_PLUGINS) + { + if(cancellationToken.IsCancellationRequested) + break; + + if (availablePlugin.Id == baseLanguagePluginId) + continue; + + if (availablePlugin.IsInternal || SETTINGS_MANAGER.IsPluginEnabled(availablePlugin)) + if(await Start(availablePlugin, cancellationToken) is { IsValid: true } plugin) + RUNNING_PLUGINS.Add(plugin); + + // Inform all components that the plugins have been reloaded or started: + await MessageBus.INSTANCE.SendMessage(null, Event.PLUGINS_RELOADED); + } + } + + private static async Task Start(IAvailablePlugin meta, CancellationToken cancellationToken = default) + { + var pluginMainFile = Path.Join(meta.LocalPath, "plugin.lua"); + if(!File.Exists(pluginMainFile)) + { + LOG.LogError($"Was not able to start plugin: Id='{meta.Id}', Type='{meta.Type}', Name='{meta.Name}', Version='{meta.Version}'. Reason: The plugin file does not exist."); + return new NoPlugin($"The plugin file does not exist: {pluginMainFile}"); + } + + var code = await File.ReadAllTextAsync(pluginMainFile, Encoding.UTF8, cancellationToken); + var plugin = await Load(meta.LocalPath, code, cancellationToken); + if (plugin is NoPlugin noPlugin) + { + LOG.LogError($"Was not able to start plugin: Id='{meta.Id}', Type='{meta.Type}', Name='{meta.Name}', Version='{meta.Version}'. Reason: {noPlugin.Issues.First()}"); + return noPlugin; + } + + if (plugin.IsValid) + { + // + // When this is a language plugin, we need to set the base language plugin. + // + if (plugin is PluginLanguage languagePlugin && BASE_LANGUAGE_PLUGIN != NoPluginLanguage.INSTANCE) + languagePlugin.SetBaseLanguage(BASE_LANGUAGE_PLUGIN); + + LOG.LogInformation($"Successfully started plugin: Id='{plugin.Id}', Type='{plugin.Type}', Name='{plugin.Name}', Version='{plugin.Version}'"); + return plugin; + } + + LOG.LogError($"Was not able to start plugin: Id='{meta.Id}', Type='{meta.Type}', Name='{meta.Name}', Version='{meta.Version}'. Reasons: {string.Join("; ", plugin.Issues)}"); + return new NoPlugin($"Was not able to start plugin: Id='{meta.Id}', Type='{meta.Type}', Name='{meta.Name}', Version='{meta.Version}'. Reasons: {string.Join("; ", plugin.Issues)}"); + } +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs index 57ac7ea1..6f808133 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs @@ -1,35 +1,18 @@ -using System.Text; - using AIStudio.Settings; -using Lua; -using Lua.Standard; - namespace AIStudio.Tools.PluginSystem; public static partial class PluginFactory { private static readonly ILogger LOG = Program.LOGGER_FACTORY.CreateLogger(nameof(PluginFactory)); private static readonly SettingsManager SETTINGS_MANAGER = Program.SERVICE_PROVIDER.GetRequiredService(); - private static readonly List AVAILABLE_PLUGINS = []; - private static readonly List RUNNING_PLUGINS = []; - + private static bool IS_INITIALIZED; private static string DATA_DIR = string.Empty; private static string PLUGINS_ROOT = string.Empty; private static string INTERNAL_PLUGINS_ROOT = string.Empty; private static FileSystemWatcher HOT_RELOAD_WATCHER = null!; private static ILanguagePlugin BASE_LANGUAGE_PLUGIN = NoPluginLanguage.INSTANCE; - - /// - /// A list of all available plugins. - /// - public static IReadOnlyCollection AvailablePlugins => AVAILABLE_PLUGINS; - - /// - /// A list of all running plugins. - /// - public static IReadOnlyCollection RunningPlugins => RUNNING_PLUGINS; public static ILanguagePlugin BaseLanguage => BASE_LANGUAGE_PLUGIN; @@ -39,6 +22,9 @@ public static partial class PluginFactory ///
public static void Setup() { + if(IS_INITIALIZED) + return; + DATA_DIR = SettingsManager.DataDirectory!; PLUGINS_ROOT = Path.Join(DATA_DIR, "plugins"); INTERNAL_PLUGINS_ROOT = Path.Join(PLUGINS_ROOT, ".internal"); @@ -50,218 +36,6 @@ public static partial class PluginFactory IS_INITIALIZED = true; } - /// - /// Try to load all plugins from the plugins directory. - /// - /// - /// Loading plugins means:
- /// - Parsing and checking the plugin code
- /// - Check for forbidden plugins
- /// - Creating a new instance of the allowed plugin
- /// - Read the plugin metadata
- ///
- /// Loading a plugin does not mean to start the plugin, though. - ///
- public static async Task LoadAll(CancellationToken cancellationToken = default) - { - if (!IS_INITIALIZED) - { - LOG.LogError("PluginFactory is not initialized. Please call Setup() before using it."); - return; - } - - LOG.LogInformation("Start loading plugins."); - if (!Directory.Exists(PLUGINS_ROOT)) - { - LOG.LogInformation("No plugins found."); - return; - } - - AVAILABLE_PLUGINS.Clear(); - - // - // The easiest way to load all plugins is to find all `plugin.lua` files and load them. - // By convention, each plugin is enforced to have a `plugin.lua` file. - // - var pluginMainFiles = Directory.EnumerateFiles(PLUGINS_ROOT, "plugin.lua", SearchOption.AllDirectories); - foreach (var pluginMainFile in pluginMainFiles) - { - if (cancellationToken.IsCancellationRequested) - break; - - LOG.LogInformation($"Try to load plugin: {pluginMainFile}"); - var code = await File.ReadAllTextAsync(pluginMainFile, Encoding.UTF8, cancellationToken); - var pluginPath = Path.GetDirectoryName(pluginMainFile)!; - var plugin = await Load(pluginPath, code, cancellationToken); - - switch (plugin) - { - case NoPlugin noPlugin when noPlugin.Issues.Any(): - LOG.LogError($"Was not able to load plugin: '{pluginMainFile}'. Reason: {noPlugin.Issues.First()}"); - continue; - - case NoPlugin: - LOG.LogError($"Was not able to load plugin: '{pluginMainFile}'. Reason: Unknown."); - continue; - - case { IsValid: false }: - LOG.LogError($"Was not able to load plugin '{pluginMainFile}', because the Lua code is not a valid AI Studio plugin. There are {plugin.Issues.Count()} issues to fix."); - #if DEBUG - foreach (var pluginIssue in plugin.Issues) - LOG.LogError($"Plugin issue: {pluginIssue}"); - #endif - continue; - - case { IsMaintained: false }: - LOG.LogWarning($"The plugin '{pluginMainFile}' is not maintained anymore. Please consider to disable it."); - break; - } - - LOG.LogInformation($"Successfully loaded plugin: '{pluginMainFile}' (Id='{plugin.Id}', Type='{plugin.Type}', Name='{plugin.Name}', Version='{plugin.Version}', Authors='{string.Join(", ", plugin.Authors)}')"); - AVAILABLE_PLUGINS.Add(new PluginMetadata(plugin, pluginPath)); - } - - // Start or restart all plugins: - await RestartAllPlugins(cancellationToken); - } - - private static async Task Load(string pluginPath, string code, CancellationToken cancellationToken = default) - { - if(ForbiddenPlugins.Check(code) is { IsForbidden: true } forbiddenState) - return new NoPlugin($"This plugin is forbidden: {forbiddenState.Message}"); - - var state = LuaState.Create(); - - // Add the module loader so that the plugin can load other Lua modules: - state.ModuleLoader = new PluginLoader(pluginPath); - - // Add some useful libraries: - state.OpenModuleLibrary(); - state.OpenStringLibrary(); - state.OpenTableLibrary(); - state.OpenMathLibrary(); - state.OpenBitwiseLibrary(); - state.OpenCoroutineLibrary(); - - try - { - await state.DoStringAsync(code, cancellationToken: cancellationToken); - } - catch (LuaParseException e) - { - return new NoPlugin($"Was not able to parse the plugin: {e.Message}"); - } - catch (LuaRuntimeException e) - { - return new NoPlugin($"Was not able to run the plugin: {e.Message}"); - } - - if (!state.Environment["TYPE"].TryRead(out var typeText)) - return new NoPlugin("TYPE does not exist or is not a valid string."); - - if (!Enum.TryParse(typeText, out var type)) - return new NoPlugin($"TYPE is not a valid plugin type. Valid types are: {CommonTools.GetAllEnumValues()}"); - - if(type is PluginType.NONE) - return new NoPlugin($"TYPE is not a valid plugin type. Valid types are: {CommonTools.GetAllEnumValues()}"); - - var isInternal = pluginPath.StartsWith(INTERNAL_PLUGINS_ROOT, StringComparison.OrdinalIgnoreCase); - return type switch - { - PluginType.LANGUAGE => new PluginLanguage(isInternal, state, type), - - _ => new NoPlugin("This plugin type is not supported yet. Please try again with a future version of AI Studio.") - }; - } - - private static async Task RestartAllPlugins(CancellationToken cancellationToken = default) - { - LOG.LogInformation("Try to start or restart all plugins."); - RUNNING_PLUGINS.Clear(); - - // - // Get the base language plugin. This is the plugin that will be used to fill in missing keys. - // - var baseLanguagePluginId = InternalPlugin.LANGUAGE_EN_US.MetaData().Id; - var baseLanguagePluginMetaData = AVAILABLE_PLUGINS.FirstOrDefault(p => p.Id == baseLanguagePluginId); - if (baseLanguagePluginMetaData is null) - { - LOG.LogError($"Was not able to find the base language plugin: Id='{baseLanguagePluginId}'. Please check your installation."); - return; - } - - var startedBasePlugin = await Start(baseLanguagePluginMetaData, cancellationToken); - if (startedBasePlugin is NoPlugin noPlugin) - { - LOG.LogError($"Was not able to start the base language plugin: Id='{baseLanguagePluginId}'. Reason: {noPlugin.Issues.First()}"); - return; - } - - if (startedBasePlugin is PluginLanguage languagePlugin) - { - BASE_LANGUAGE_PLUGIN = languagePlugin; - RUNNING_PLUGINS.Add(languagePlugin); - LOG.LogInformation($"Successfully started the base language plugin: Id='{languagePlugin.Id}', Type='{languagePlugin.Type}', Name='{languagePlugin.Name}', Version='{languagePlugin.Version}'"); - } - else - { - LOG.LogError($"Was not able to start the base language plugin: Id='{baseLanguagePluginId}'. Reason: {string.Join("; ", startedBasePlugin.Issues)}"); - return; - } - - // - // Iterate over all available plugins and try to start them. - // - foreach (var availablePlugin in AVAILABLE_PLUGINS) - { - if(cancellationToken.IsCancellationRequested) - break; - - if (availablePlugin.Id == baseLanguagePluginId) - continue; - - if (availablePlugin.IsInternal || SETTINGS_MANAGER.IsPluginEnabled(availablePlugin)) - if(await Start(availablePlugin, cancellationToken) is { IsValid: true } plugin) - RUNNING_PLUGINS.Add(plugin); - - // Inform all components that the plugins have been reloaded or started: - await MessageBus.INSTANCE.SendMessage(null, Event.PLUGINS_RELOADED); - } - } - - private static async Task Start(IAvailablePlugin meta, CancellationToken cancellationToken = default) - { - var pluginMainFile = Path.Join(meta.LocalPath, "plugin.lua"); - if(!File.Exists(pluginMainFile)) - { - LOG.LogError($"Was not able to start plugin: Id='{meta.Id}', Type='{meta.Type}', Name='{meta.Name}', Version='{meta.Version}'. Reason: The plugin file does not exist."); - return new NoPlugin($"The plugin file does not exist: {pluginMainFile}"); - } - - var code = await File.ReadAllTextAsync(pluginMainFile, Encoding.UTF8, cancellationToken); - var plugin = await Load(meta.LocalPath, code, cancellationToken); - if (plugin is NoPlugin noPlugin) - { - LOG.LogError($"Was not able to start plugin: Id='{meta.Id}', Type='{meta.Type}', Name='{meta.Name}', Version='{meta.Version}'. Reason: {noPlugin.Issues.First()}"); - return noPlugin; - } - - if (plugin.IsValid) - { - // - // When this is a language plugin, we need to set the base language plugin. - // - if (plugin is PluginLanguage languagePlugin && BASE_LANGUAGE_PLUGIN != NoPluginLanguage.INSTANCE) - languagePlugin.SetBaseLanguage(BASE_LANGUAGE_PLUGIN); - - LOG.LogInformation($"Successfully started plugin: Id='{plugin.Id}', Type='{plugin.Type}', Name='{plugin.Name}', Version='{plugin.Version}'"); - return plugin; - } - - LOG.LogError($"Was not able to start plugin: Id='{meta.Id}', Type='{meta.Type}', Name='{meta.Name}', Version='{meta.Version}'. Reasons: {string.Join("; ", plugin.Issues)}"); - return new NoPlugin($"Was not able to start plugin: Id='{meta.Id}', Type='{meta.Type}', Name='{meta.Name}', Version='{meta.Version}'. Reasons: {string.Join("; ", plugin.Issues)}"); - } - public static void Dispose() { if(!IS_INITIALIZED) diff --git a/app/MindWork AI Studio/wwwroot/changelog/v0.9.41.md b/app/MindWork AI Studio/wwwroot/changelog/v0.9.41.md index 4454d93b..007c68d4 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v0.9.41.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v0.9.41.md @@ -1,4 +1,5 @@ # v0.9.41, build 216 (2025-0x-xx xx:xx UTC) - Added the user-language, as provided by the OS, to the about page. This helps in identifying user-specific issues related to language settings. - Changed the terminology from "temporary chats" to "disappearing chats" in the UI. This makes it clearer to understand the purpose of these chats. +- Improved the hot reloading of the plugin system to prevent overlapping reloads. - Fixed the color for the update notification button to match the color theme. \ No newline at end of file From 9cc3c68ddad95924561d0580c5f38863578d3980 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Thu, 24 Apr 2025 09:50:03 +0200 Subject: [PATCH 24/93] Improved app behavior after system was waked up from sleep (#418) --- .../Layout/MainLayout.razor.cs | 23 ++++++++++--------- app/MindWork AI Studio/Program.cs | 2 +- .../Tools/PluginSystem/PluginFactory.cs | 6 +++-- .../wwwroot/changelog/v0.9.41.md | 3 ++- 4 files changed, 19 insertions(+), 15 deletions(-) diff --git a/app/MindWork AI Studio/Layout/MainLayout.razor.cs b/app/MindWork AI Studio/Layout/MainLayout.razor.cs index 0bcafbb4..a97e973f 100644 --- a/app/MindWork AI Studio/Layout/MainLayout.razor.cs +++ b/app/MindWork AI Studio/Layout/MainLayout.razor.cs @@ -175,17 +175,18 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, IDis _ = Task.Run(async () => { // Set up the plugin system: - PluginFactory.Setup(); - - // Ensure that all internal plugins are present: - await PluginFactory.EnsureInternalPlugins(); - - // Load (but not start) all plugins, without waiting for them: - var pluginLoadingTimeout = new CancellationTokenSource(TimeSpan.FromSeconds(5)); - await PluginFactory.LoadAll(pluginLoadingTimeout.Token); - - // Set up hot reloading for plugins: - PluginFactory.SetUpHotReloading(); + if (PluginFactory.Setup()) + { + // Ensure that all internal plugins are present: + await PluginFactory.EnsureInternalPlugins(); + + // Load (but not start) all plugins, without waiting for them: + var pluginLoadingTimeout = new CancellationTokenSource(TimeSpan.FromSeconds(5)); + await PluginFactory.LoadAll(pluginLoadingTimeout.Token); + + // Set up hot reloading for plugins: + PluginFactory.SetUpHotReloading(); + } }); } diff --git a/app/MindWork AI Studio/Program.cs b/app/MindWork AI Studio/Program.cs index 8283a481..033751be 100644 --- a/app/MindWork AI Studio/Program.cs +++ b/app/MindWork AI Studio/Program.cs @@ -130,7 +130,7 @@ internal sealed class Program .AddHubOptions(options => { options.MaximumReceiveMessageSize = null; - options.ClientTimeoutInterval = TimeSpan.FromSeconds(1_200); + options.ClientTimeoutInterval = TimeSpan.FromDays(14); options.HandshakeTimeout = TimeSpan.FromSeconds(30); }); diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs index 6f808133..8dc83966 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.cs @@ -20,10 +20,10 @@ public static partial class PluginFactory /// Set up the plugin factory. We will read the data directory from the settings manager. /// Afterward, we will create the plugins directory and the internal plugin directory. ///
- public static void Setup() + public static bool Setup() { if(IS_INITIALIZED) - return; + return false; DATA_DIR = SettingsManager.DataDirectory!; PLUGINS_ROOT = Path.Join(DATA_DIR, "plugins"); @@ -34,6 +34,8 @@ public static partial class PluginFactory HOT_RELOAD_WATCHER = new(PLUGINS_ROOT); IS_INITIALIZED = true; + + return true; } public static void Dispose() diff --git a/app/MindWork AI Studio/wwwroot/changelog/v0.9.41.md b/app/MindWork AI Studio/wwwroot/changelog/v0.9.41.md index 007c68d4..c25d55f0 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v0.9.41.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v0.9.41.md @@ -1,5 +1,6 @@ # v0.9.41, build 216 (2025-0x-xx xx:xx UTC) - Added the user-language, as provided by the OS, to the about page. This helps in identifying user-specific issues related to language settings. - Changed the terminology from "temporary chats" to "disappearing chats" in the UI. This makes it clearer to understand the purpose of these chats. -- Improved the hot reloading of the plugin system to prevent overlapping reloads. +- Improved the hot reloading of the plugin system to prevent overlapping reloads. +- Improved the app behavior when the user system was waked up from sleep mode. - Fixed the color for the update notification button to match the color theme. \ No newline at end of file From 69fc4d764f5f6f0d23d35c74d0dd6b0ed1ccf2e4 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Thu, 24 Apr 2025 10:20:37 +0200 Subject: [PATCH 25/93] Updated build instructions (#419) --- documentation/Build.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/documentation/Build.md b/documentation/Build.md index 41b874d9..16b97aec 100644 --- a/documentation/Build.md +++ b/documentation/Build.md @@ -1,10 +1,16 @@ # Building You just want to use the app? Then simply [download the appropriate setup for your operating system](Setup.md). This chapter is intended for developers who want to modify and customize the code. +## Prefaces regarding Linux development systems +Unfortunately, we have to provide a note regarding development on Linux systems. MindWork AI Studio consists of a Rust and a .NET part. Compiling the .NET code works smoothly on all operating systems. However, this is not the case for our Rust part. More specifically, it is not the Rust code itself that is problematic, but rather the Tauri framework on which we base our work. Tauri has certain dependencies that depend on the operating system. The specific dependencies vary between different Linux distributions and between versions of distributions. + +Therefore, we cannot provide a static list here that is valid for all Linux systems. Unfortunately, the situation is even more complex: Tauri requires dependencies that are not available in current Linux distributions because they already include newer versions. **For these reasons, we currently advise against developing AI Studio on Linux.** In case you still want to try, you will need a lot of patience and willingness to experiment. We ask for your understanding. Thank you very much. + ## Prerequisites 1. Install the [.NET 9 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/9.0). 2. [Install the Rust compiler](https://www.rust-lang.org/tools/install) in the latest version. 3. Met the prerequisites for building [Tauri](https://tauri.app/v1/guides/getting-started/prerequisites/). Node.js is **not** required, though. +4. The core team uses [JetBrains](https://www.jetbrains.com/) [Rider](https://www.jetbrains.com/rider/) and [RustRover](https://www.jetbrains.com/rust/) for development. Both IDEs are free to use for open-source projects for non-commercial use. They are available for macOS, Linux, and Windows systems. Profiles are provided for these IDEs, so you can get started right away. However, you can also use a different IDE. 4. Clone the repository. ## One-time mandatory steps From bafd62429d10de32afc48ed7807e6c5d6897c415 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Thu, 24 Apr 2025 11:43:16 +0200 Subject: [PATCH 26/93] Improve the provider dialog (#420) --- .../Dialogs/ProviderDialog.razor | 69 ++++++++++++------- .../Dialogs/ProviderDialog.razor.cs | 2 - .../wwwroot/changelog/v0.9.41.md | 1 + 3 files changed, 44 insertions(+), 28 deletions(-) diff --git a/app/MindWork AI Studio/Dialogs/ProviderDialog.razor b/app/MindWork AI Studio/Dialogs/ProviderDialog.razor index 3664062a..439a2dea 100644 --- a/app/MindWork AI Studio/Dialogs/ProviderDialog.razor +++ b/app/MindWork AI Studio/Dialogs/ProviderDialog.razor @@ -30,6 +30,7 @@ AdornmentIcon="@Icons.Material.Filled.VpnKey" AdornmentColor="Color.Info" InputType="InputType.Password" + Immediate="true" Validation="@this.providerValidation.ValidatingAPIKey"/> } @@ -61,7 +62,7 @@ @if (this.DataLLMProvider.IsHFInstanceProviderNeeded()) { - + @foreach (HFInferenceProvider inferenceProvider in Enum.GetValues(typeof(HFInferenceProvider))) { @@ -73,33 +74,49 @@ Please double-check if your model name matches the curl specifications provided by the inference provider. If it doesn't, you might get a Not Found error when trying to use the model. Here's a curl example. } - - @if (this.DataLLMProvider.IsLLMModelProvidedManually()) - { - Show available models - - } - else - { - Load - - @foreach (var model in this.availableModels) + + + @if (this.DataLLMProvider.IsLLMModelProvidedManually()) + { + + Show available models + + + } + else + { + + Load models + + @if(this.availableModels.Count is 0) { - @model + + No models loaded or available. + } - - } - + else + { + + @foreach (var model in this.availableModels) + { + @model + } + + } + } +
+ @* ReSharper disable once CSharpWarnings::CS8974 *@ "(Optional) API Key", _ => "API Key", }; - - private bool IsNoneProvider => this.DataLLMProvider is LLMProviders.NONE; } \ No newline at end of file diff --git a/app/MindWork AI Studio/wwwroot/changelog/v0.9.41.md b/app/MindWork AI Studio/wwwroot/changelog/v0.9.41.md index c25d55f0..83a0358e 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v0.9.41.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v0.9.41.md @@ -3,4 +3,5 @@ - Changed the terminology from "temporary chats" to "disappearing chats" in the UI. This makes it clearer to understand the purpose of these chats. - Improved the hot reloading of the plugin system to prevent overlapping reloads. - Improved the app behavior when the user system was waked up from sleep mode. +- Improved the provider dialog with better input handling for API keys and an optimized model selection. - Fixed the color for the update notification button to match the color theme. \ No newline at end of file From 02c3e4c8170ab45cfab47c5b40e6887accecc125 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Thu, 24 Apr 2025 12:45:43 +0200 Subject: [PATCH 27/93] Refined model filters across providers (#421) --- .../Provider/Google/ProviderGoogle.cs | 2 +- app/MindWork AI Studio/Provider/Groq/ProviderGroq.cs | 5 +++-- .../Provider/Mistral/ProviderMistral.cs | 5 +++-- .../Provider/OpenAI/ProviderOpenAI.cs | 11 ++++++++--- app/MindWork AI Studio/Provider/X/ProviderX.cs | 5 +++-- app/MindWork AI Studio/wwwroot/changelog/v0.9.41.md | 1 + 6 files changed, 19 insertions(+), 10 deletions(-) diff --git a/app/MindWork AI Studio/Provider/Google/ProviderGoogle.cs b/app/MindWork AI Studio/Provider/Google/ProviderGoogle.cs index 942cb245..d4a7dc65 100644 --- a/app/MindWork AI Studio/Provider/Google/ProviderGoogle.cs +++ b/app/MindWork AI Studio/Provider/Google/ProviderGoogle.cs @@ -98,7 +98,7 @@ public class ProviderGoogle(ILogger logger) : BaseProvider("https://generativela return []; return modelResponse.Models.Where(model => - model.Name.StartsWith("models/gemini-", StringComparison.InvariantCultureIgnoreCase)) + model.Name.StartsWith("models/gemini-", StringComparison.OrdinalIgnoreCase)) .Select(n => new Provider.Model(n.Name.Replace("models/", string.Empty), n.DisplayName)); } diff --git a/app/MindWork AI Studio/Provider/Groq/ProviderGroq.cs b/app/MindWork AI Studio/Provider/Groq/ProviderGroq.cs index f32a31b5..ddf5c002 100644 --- a/app/MindWork AI Studio/Provider/Groq/ProviderGroq.cs +++ b/app/MindWork AI Studio/Provider/Groq/ProviderGroq.cs @@ -136,7 +136,8 @@ public class ProviderGroq(ILogger logger) : BaseProvider("https://api.groq.com/o var modelResponse = await response.Content.ReadFromJsonAsync(token); return modelResponse.Data.Where(n => - !n.Id.StartsWith("whisper-", StringComparison.InvariantCultureIgnoreCase) && - !n.Id.StartsWith("distil-", StringComparison.InvariantCultureIgnoreCase)); + !n.Id.StartsWith("whisper-", StringComparison.OrdinalIgnoreCase) && + !n.Id.StartsWith("distil-", StringComparison.OrdinalIgnoreCase) && + !n.Id.Contains("-tts", StringComparison.OrdinalIgnoreCase)); } } \ No newline at end of file diff --git a/app/MindWork AI Studio/Provider/Mistral/ProviderMistral.cs b/app/MindWork AI Studio/Provider/Mistral/ProviderMistral.cs index 024f60d3..1039ab45 100644 --- a/app/MindWork AI Studio/Provider/Mistral/ProviderMistral.cs +++ b/app/MindWork AI Studio/Provider/Mistral/ProviderMistral.cs @@ -99,8 +99,9 @@ public sealed class ProviderMistral(ILogger logger) : BaseProvider("https://api. return []; return modelResponse.Data.Where(n => - !n.Id.StartsWith("code", StringComparison.InvariantCulture) && - !n.Id.Contains("embed", StringComparison.InvariantCulture)) + !n.Id.StartsWith("code", StringComparison.OrdinalIgnoreCase) && + !n.Id.Contains("embed", StringComparison.OrdinalIgnoreCase) && + !n.Id.Contains("moderation", StringComparison.OrdinalIgnoreCase)) .Select(n => new Provider.Model(n.Id, null)); } diff --git a/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs b/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs index b45bb16a..bc70ecb8 100644 --- a/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs +++ b/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs @@ -120,15 +120,20 @@ public sealed class ProviderOpenAI(ILogger logger) : BaseProvider("https://api.o #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously /// - public override Task> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) + public override async Task> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) { - return this.LoadModels(["gpt-", "o1-", "o3-", "o4-"], token, apiKeyProvisional); + var models = await this.LoadModels(["gpt-", "o1-", "o3-", "o4-"], token, apiKeyProvisional); + return models.Where(model => !model.Id.Contains("image", StringComparison.OrdinalIgnoreCase) && + !model.Id.Contains("realtime", StringComparison.OrdinalIgnoreCase) && + !model.Id.Contains("audio", StringComparison.OrdinalIgnoreCase) && + !model.Id.Contains("tts", StringComparison.OrdinalIgnoreCase) && + !model.Id.Contains("transcribe", StringComparison.OrdinalIgnoreCase)); } /// public override Task> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default) { - return this.LoadModels(["dall-e-"], token, apiKeyProvisional); + return this.LoadModels(["dall-e-", "gpt-image"], token, apiKeyProvisional); } /// diff --git a/app/MindWork AI Studio/Provider/X/ProviderX.cs b/app/MindWork AI Studio/Provider/X/ProviderX.cs index a8334c8d..0292a501 100644 --- a/app/MindWork AI Studio/Provider/X/ProviderX.cs +++ b/app/MindWork AI Studio/Provider/X/ProviderX.cs @@ -93,9 +93,10 @@ public sealed class ProviderX(ILogger logger) : BaseProvider("https://api.x.ai/v #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously /// - public override Task> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) + public override async Task> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) { - return this.LoadModels(["grok-"], token, apiKeyProvisional); + var models = await this.LoadModels(["grok-"], token, apiKeyProvisional); + return models.Where(n => !n.Id.Contains("-image", StringComparison.OrdinalIgnoreCase)); } /// diff --git a/app/MindWork AI Studio/wwwroot/changelog/v0.9.41.md b/app/MindWork AI Studio/wwwroot/changelog/v0.9.41.md index 83a0358e..2e5fb449 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v0.9.41.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v0.9.41.md @@ -4,4 +4,5 @@ - Improved the hot reloading of the plugin system to prevent overlapping reloads. - Improved the app behavior when the user system was waked up from sleep mode. - Improved the provider dialog with better input handling for API keys and an optimized model selection. +- Improved provider's model selection by filtering added non-text outputting models, which are not supported yet. - Fixed the color for the update notification button to match the color theme. \ No newline at end of file From 3e13d50302d96fee47a779c4fccccb39b652ce89 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Thu, 24 Apr 2025 13:50:14 +0200 Subject: [PATCH 28/93] Created I18N data, part 1 (#404) --- app/.run/Collect I18N content.run.xml | 17 + app/.run/Tauri Dev.run.xml | 4 +- app/Build/Commands/CollectI18NKeysCommand.cs | 152 +++- app/Build/Commands/UpdateWebAssetsCommand.cs | 2 - .../Assistants/I18N/allTexts.lua | 724 ++++++++++++++++++ .../Components/ChatComponent.razor.cs | 4 +- .../Components/InnerScrolling.razor.cs | 4 +- .../Components/MSGComponentBase.cs | 34 +- .../Settings/SettingsDialogAgenda.razor | 42 +- .../SettingsDialogAssistantBias.razor | 22 +- .../SettingsDialogAssistantBias.razor.cs | 8 +- .../Dialogs/Settings/SettingsDialogBase.cs | 37 +- .../Settings/SettingsDialogCoding.razor | 12 +- .../Settings/SettingsDialogDataSources.razor | 40 +- .../SettingsDialogDataSources.razor.cs | 28 +- .../Settings/SettingsDialogERIServer.razor | 13 +- .../SettingsDialogGrammarSpelling.razor | 10 +- .../Settings/SettingsDialogIconFinder.razor | 10 +- .../Settings/SettingsDialogJobPostings.razor | 24 +- .../Settings/SettingsDialogLegalCheck.razor | 16 +- .../Settings/SettingsDialogMyTasks.razor | 14 +- .../Settings/SettingsDialogRewrite.razor | 14 +- .../Settings/SettingsDialogSynonyms.razor | 12 +- .../SettingsDialogTextSummarizer.razor | 22 +- .../Settings/SettingsDialogTranslation.razor | 22 +- .../SettingsDialogWritingEMails.razor | 20 +- .../Layout/MainLayout.razor | 6 +- .../Layout/MainLayout.razor.cs | 44 +- .../MindWork AI Studio.csproj | 5 + app/MindWork AI Studio/Pages/About.razor | 104 ++- app/MindWork AI Studio/Pages/About.razor.cs | 46 +- app/MindWork AI Studio/Pages/Assistants.razor | 29 +- .../Pages/Assistants.razor.cs | 10 +- app/MindWork AI Studio/Pages/Chat.razor | 4 +- app/MindWork AI Studio/Pages/Chat.razor.cs | 2 - app/MindWork AI Studio/Pages/Home.razor | 18 +- app/MindWork AI Studio/Pages/Home.razor.cs | 35 +- app/MindWork AI Studio/Pages/Plugins.razor | 14 +- app/MindWork AI Studio/Pages/Plugins.razor.cs | 25 +- app/MindWork AI Studio/Pages/Settings.razor | 5 +- .../Pages/Settings.razor.cs | 35 +- app/MindWork AI Studio/Pages/Supporters.razor | 53 +- .../Pages/Supporters.razor.cs | 6 +- app/MindWork AI Studio/Pages/Writer.razor | 8 +- app/MindWork AI Studio/Pages/Writer.razor.cs | 18 +- .../Settings/SettingsManager.cs | 1 - .../Tools/IMessageBusReceiver.cs | 2 - app/MindWork AI Studio/Tools/MessageBus.cs | 10 +- .../Tools/PluginSystem/ILangExtensions.cs | 29 + .../UsageAnalyzers/ThisUsageAnalyzer.cs | 49 +- 50 files changed, 1384 insertions(+), 481 deletions(-) create mode 100644 app/.run/Collect I18N content.run.xml create mode 100644 app/MindWork AI Studio/Assistants/I18N/allTexts.lua create mode 100644 app/MindWork AI Studio/Tools/PluginSystem/ILangExtensions.cs diff --git a/app/.run/Collect I18N content.run.xml b/app/.run/Collect I18N content.run.xml new file mode 100644 index 00000000..837832a3 --- /dev/null +++ b/app/.run/Collect I18N content.run.xml @@ -0,0 +1,17 @@ + + + + \ No newline at end of file diff --git a/app/.run/Tauri Dev.run.xml b/app/.run/Tauri Dev.run.xml index 7c319e82..4dc7c19c 100644 --- a/app/.run/Tauri Dev.run.xml +++ b/app/.run/Tauri Dev.run.xml @@ -12,6 +12,8 @@
@@ -53,16 +51,26 @@ @if (this.SettingsManager.ConfigurationData.DataSources.Count == 0) { - No data sources configured yet. + + @T("No data sources configured yet.") + } - - External Data (ERI-Server v1) - Local Directory - Local File + + + @T("External Data (ERI-Server v1)") + + + @T("Local Directory") + + + @T("Local File") + - Close + + @T("Close") + \ No newline at end of file diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogDataSources.razor.cs b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogDataSources.razor.cs index d2c6cef8..b35ce8f7 100644 --- a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogDataSources.razor.cs +++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogDataSources.razor.cs @@ -12,15 +12,15 @@ public partial class SettingsDialogDataSources : SettingsDialogBase { var matchedEmbedding = this.SettingsManager.ConfigurationData.EmbeddingProviders.FirstOrDefault(x => x.Id == internalDataSource.EmbeddingId); if(matchedEmbedding == default) - return "No valid embedding"; + return "T(No valid embedding)"; return matchedEmbedding.Name; } if(dataSource is IExternalDataSource) - return "External (ERI)"; + return "T(External (ERI))"; - return "Unknown"; + return T("Unknown"); } private async Task AddDataSource(DataSourceType type) @@ -35,7 +35,7 @@ public partial class SettingsDialogDataSources : SettingsDialogBase { x => x.AvailableEmbeddings, this.availableEmbeddingProviders } }; - var localFileDialogReference = await this.DialogService.ShowAsync("Add Local File as Data Source", localFileDialogParameters, DialogOptions.FULLSCREEN); + var localFileDialogReference = await this.DialogService.ShowAsync(T("Add Local File as Data Source"), localFileDialogParameters, DialogOptions.FULLSCREEN); var localFileDialogResult = await localFileDialogReference.Result; if (localFileDialogResult is null || localFileDialogResult.Canceled) return; @@ -52,7 +52,7 @@ public partial class SettingsDialogDataSources : SettingsDialogBase { x => x.AvailableEmbeddings, this.availableEmbeddingProviders } }; - var localDirectoryDialogReference = await this.DialogService.ShowAsync("Add Local Directory as Data Source", localDirectoryDialogParameters, DialogOptions.FULLSCREEN); + var localDirectoryDialogReference = await this.DialogService.ShowAsync(T("Add Local Directory as Data Source"), localDirectoryDialogParameters, DialogOptions.FULLSCREEN); var localDirectoryDialogResult = await localDirectoryDialogReference.Result; if (localDirectoryDialogResult is null || localDirectoryDialogResult.Canceled) return; @@ -68,7 +68,7 @@ public partial class SettingsDialogDataSources : SettingsDialogBase { x => x.IsEditing, false }, }; - var eriDialogReference = await this.DialogService.ShowAsync("Add ERI v1 Data Source", eriDialogParameters, DialogOptions.FULLSCREEN); + var eriDialogReference = await this.DialogService.ShowAsync(T("Add ERI v1 Data Source"), eriDialogParameters, DialogOptions.FULLSCREEN); var eriDialogResult = await eriDialogReference.Result; if (eriDialogResult is null || eriDialogResult.Canceled) return; @@ -100,7 +100,7 @@ public partial class SettingsDialogDataSources : SettingsDialogBase { x => x.AvailableEmbeddings, this.availableEmbeddingProviders } }; - var localFileDialogReference = await this.DialogService.ShowAsync("Edit Local File Data Source", localFileDialogParameters, DialogOptions.FULLSCREEN); + var localFileDialogReference = await this.DialogService.ShowAsync(T("Edit Local File Data Source"), localFileDialogParameters, DialogOptions.FULLSCREEN); var localFileDialogResult = await localFileDialogReference.Result; if (localFileDialogResult is null || localFileDialogResult.Canceled) return; @@ -116,7 +116,7 @@ public partial class SettingsDialogDataSources : SettingsDialogBase { x => x.AvailableEmbeddings, this.availableEmbeddingProviders } }; - var localDirectoryDialogReference = await this.DialogService.ShowAsync("Edit Local Directory Data Source", localDirectoryDialogParameters, DialogOptions.FULLSCREEN); + var localDirectoryDialogReference = await this.DialogService.ShowAsync(T("Edit Local Directory Data Source"), localDirectoryDialogParameters, DialogOptions.FULLSCREEN); var localDirectoryDialogResult = await localDirectoryDialogReference.Result; if (localDirectoryDialogResult is null || localDirectoryDialogResult.Canceled) return; @@ -131,7 +131,7 @@ public partial class SettingsDialogDataSources : SettingsDialogBase { x => x.DataSource, eriDataSource }, }; - var eriDialogReference = await this.DialogService.ShowAsync("Edit ERI v1 Data Source", eriDialogParameters, DialogOptions.FULLSCREEN); + var eriDialogReference = await this.DialogService.ShowAsync(T("Edit ERI v1 Data Source"), eriDialogParameters, DialogOptions.FULLSCREEN); var eriDialogResult = await eriDialogReference.Result; if (eriDialogResult is null || eriDialogResult.Canceled) return; @@ -153,10 +153,10 @@ public partial class SettingsDialogDataSources : SettingsDialogBase { var dialogParameters = new DialogParameters { - { "Message", $"Are you sure you want to delete the data source '{dataSource.Name}' of type {dataSource.Type.GetDisplayName()}?" }, + { "Message", string.Format(T("Are you sure you want to delete the data source '{0}' of type {1}?"), dataSource.Name, dataSource.Type.GetDisplayName()) }, }; - var dialogReference = await this.DialogService.ShowAsync("Delete Data Source", dialogParameters, DialogOptions.FULLSCREEN); + var dialogReference = await this.DialogService.ShowAsync(T("Delete Data Source"), dialogParameters, DialogOptions.FULLSCREEN); var dialogResult = await dialogReference.Result; if (dialogResult is null || dialogResult.Canceled) return; @@ -198,7 +198,7 @@ public partial class SettingsDialogDataSources : SettingsDialogBase { x => x.DataSource, localFile }, }; - await this.DialogService.ShowAsync("Local File Data Source Information", localFileDialogParameters, DialogOptions.FULLSCREEN); + await this.DialogService.ShowAsync(T("Local File Data Source Information"), localFileDialogParameters, DialogOptions.FULLSCREEN); break; case DataSourceLocalDirectory localDirectory: @@ -207,7 +207,7 @@ public partial class SettingsDialogDataSources : SettingsDialogBase { x => x.DataSource, localDirectory }, }; - await this.DialogService.ShowAsync("Local Directory Data Source Information", localDirectoryDialogParameters, DialogOptions.FULLSCREEN); + await this.DialogService.ShowAsync(T("Local Directory Data Source Information"), localDirectoryDialogParameters, DialogOptions.FULLSCREEN); break; case DataSourceERI_V1 eriV1DataSource: @@ -216,7 +216,7 @@ public partial class SettingsDialogDataSources : SettingsDialogBase { x => x.DataSource, eriV1DataSource }, }; - await this.DialogService.ShowAsync("ERI v1 Data Source Information", eriV1DialogParameters, DialogOptions.FULLSCREEN); + await this.DialogService.ShowAsync(T("ERI v1 Data Source Information"), eriV1DialogParameters, DialogOptions.FULLSCREEN); break; } } diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogERIServer.razor b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogERIServer.razor index 7ce8cc3b..cb4e20ac 100644 --- a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogERIServer.razor +++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogERIServer.razor @@ -6,22 +6,23 @@ - Assistant: ERI Server Options + @T("Assistant: ERI Server Options") - + - + - Most ERI server options can be customized and saved directly in the ERI server assistant. - For this, the ERI server assistant has an auto-save function. + @T("Most ERI server options can be customized and saved directly in the ERI server assistant. For this, the ERI server assistant has an auto-save function.") - Close + + @T("Close") + \ No newline at end of file diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogGrammarSpelling.razor b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogGrammarSpelling.razor index c7ae29dd..e322aa3f 100644 --- a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogGrammarSpelling.razor +++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogGrammarSpelling.razor @@ -5,22 +5,24 @@ - Assistant: Grammar & Spelling Checker Options + @T("Assistant: Grammar & Spelling Checker Options") - + @if (this.SettingsManager.ConfigurationData.GrammarSpelling.PreselectedTargetLanguage is CommonLanguages.OTHER) { - + } - Close + + @T("Close") + \ No newline at end of file diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogIconFinder.razor b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogIconFinder.razor index 0562b38e..187e0523 100644 --- a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogIconFinder.razor +++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogIconFinder.razor @@ -5,18 +5,20 @@ - Assistant: Icon Finder Options + @T("Assistant: Icon Finder Options") - - + + - Close + + @T("Close") + \ No newline at end of file diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogJobPostings.razor b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogJobPostings.razor index 505d5624..9d2c47bc 100644 --- a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogJobPostings.razor +++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogJobPostings.razor @@ -5,29 +5,31 @@ - Assistant: Job Posting Options + @T("Assistant: Job Posting Options") - - - - - - - + + + + + + + - + @if (this.SettingsManager.ConfigurationData.JobPostings.PreselectedTargetLanguage is CommonLanguages.OTHER) { - + } - Close + + @T("Close") + \ No newline at end of file diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogLegalCheck.razor b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogLegalCheck.razor index 10b2c286..42cb70d4 100644 --- a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogLegalCheck.razor +++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogLegalCheck.razor @@ -4,21 +4,23 @@ - Assistant: Legal Check Options + @T("Assistant: Legal Check Options") - + - Close + + @T("Close") + \ No newline at end of file diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogMyTasks.razor b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogMyTasks.razor index 91d318af..626f421d 100644 --- a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogMyTasks.razor +++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogMyTasks.razor @@ -5,23 +5,25 @@ - Assistant: My Tasks Options + @T("Assistant: My Tasks Options") - - + + @if (this.SettingsManager.ConfigurationData.MyTasks.PreselectedTargetLanguage is CommonLanguages.OTHER) { - + } - + - Close + + @T("Close") + \ No newline at end of file diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogRewrite.razor b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogRewrite.razor index e6bcf79f..e6f107cc 100644 --- a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogRewrite.razor +++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogRewrite.razor @@ -5,24 +5,26 @@ - Assistant: Rewrite & Improve Text Options + @T("Assistant: Rewrite & Improve Text Options") - + @if (this.SettingsManager.ConfigurationData.RewriteImprove.PreselectedTargetLanguage is CommonLanguages.OTHER) { - + } - - + + - Close + + @T("Close") + \ No newline at end of file diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogSynonyms.razor b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogSynonyms.razor index 2d7e29f4..0a78e616 100644 --- a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogSynonyms.razor +++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogSynonyms.razor @@ -5,22 +5,24 @@ - Assistant: Synonyms Options + @T("Assistant: Synonyms Options") - - + + @if (this.SettingsManager.ConfigurationData.Synonyms.PreselectedLanguage is CommonLanguages.OTHER) { - + } - Close + + @T("Close") + \ No newline at end of file diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogTextSummarizer.razor b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogTextSummarizer.razor index 111fda45..0568c491 100644 --- a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogTextSummarizer.razor +++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogTextSummarizer.razor @@ -6,30 +6,32 @@ - Assistant: Text Summarizer Options + @T("Assistant: Text Summarizer Options") - + - Close + + @T("Close") + \ No newline at end of file diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogTranslation.razor b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogTranslation.razor index 8cb87ac7..259ddc52 100644 --- a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogTranslation.razor +++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogTranslation.razor @@ -4,27 +4,29 @@ - Assistant: Translator Options + @T("Assistant: Translator Options") - - + + - Close + + @T("Close") + \ No newline at end of file diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogWritingEMails.razor b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogWritingEMails.razor index a6f4bca9..6f31f266 100644 --- a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogWritingEMails.razor +++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogWritingEMails.razor @@ -5,26 +5,28 @@ - Assistant: Writing E-Mails Options + @T("Assistant: Writing E-Mails Options") - - - - + + + + @if (this.SettingsManager.ConfigurationData.EMail.PreselectedTargetLanguage is CommonLanguages.OTHER) { - + } - + - + - Close + + @T("Close") + \ No newline at end of file diff --git a/app/MindWork AI Studio/Layout/MainLayout.razor b/app/MindWork AI Studio/Layout/MainLayout.razor index 96ebb50a..23937719 100644 --- a/app/MindWork AI Studio/Layout/MainLayout.razor +++ b/app/MindWork AI Studio/Layout/MainLayout.razor @@ -15,7 +15,9 @@ @foreach (var navBarItem in this.navItems) { - @navBarItem.Name + + @navBarItem.Name + } @@ -51,7 +53,7 @@ } - Please wait for the update to complete... + @T("Please wait for the update to complete...") diff --git a/app/MindWork AI Studio/Layout/MainLayout.razor.cs b/app/MindWork AI Studio/Layout/MainLayout.razor.cs index a97e973f..ba970cc4 100644 --- a/app/MindWork AI Studio/Layout/MainLayout.razor.cs +++ b/app/MindWork AI Studio/Layout/MainLayout.razor.cs @@ -12,7 +12,7 @@ using DialogOptions = AIStudio.Dialogs.DialogOptions; namespace AIStudio.Layout; -public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, IDisposable +public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILang, IDisposable { [Inject] private SettingsManager SettingsManager { get; init; } = null!; @@ -38,6 +38,8 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, IDis [Inject] private MudTheme ColorTheme { get; init; } = null!; + private ILanguagePlugin Lang { get; set; } = PluginFactory.BaseLanguage; + private string PaddingLeft => this.navBarOpen ? $"padding-left: {NAVBAR_EXPANDED_WIDTH_INT - NAVBAR_COLLAPSED_WIDTH_INT}em;" : "padding-left: 0em;"; private const int NAVBAR_COLLAPSED_WIDTH_INT = 4; @@ -78,7 +80,7 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, IDis // Read the user language from Rust: // var userLanguage = await this.RustService.ReadUserLanguage(); - this.Logger.LogInformation($"The user language is: '{userLanguage}'"); + this.Logger.LogInformation($"The OS says '{userLanguage}' is the user language."); // Ensure that all settings are loaded: await this.SettingsManager.LoadSettings(); @@ -105,6 +107,9 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, IDis // Send a message to start the plugin system: await this.MessageBus.SendMessage(this, Event.STARTUP_PLUGIN_SYSTEM); + // Load the language plugin: + this.Lang = await this.SettingsManager.GetActiveLanguagePlugin(); + await this.themeProvider.WatchSystemPreference(this.SystemeThemeChanged); await this.UpdateThemeConfiguration(); this.LoadNavItems(); @@ -119,11 +124,17 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, IDis #endregion + #region Implementation of ILang + + public string T(string fallbackEN) => this.GetText(this.Lang, fallbackEN); + + #endregion + #region Implementation of IMessageBusReceiver public string ComponentName => nameof(MainLayout); - public async Task ProcessMessage(ComponentBase? sendingComponent, Event triggeredEvent, T? data) + public async Task ProcessMessage(ComponentBase? sendingComponent, Event triggeredEvent, TMessage? data) { switch (triggeredEvent) { @@ -131,7 +142,8 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, IDis if (data is UpdateResponse updateResponse) { this.currentUpdateResponse = updateResponse; - this.Snackbar.Add($"An update to version {updateResponse.NewVersion} is available.", Severity.Info, config => + var message = string.Format(T("An update to version {0} is available."), updateResponse.NewVersion); + this.Snackbar.Add(message, Severity.Info, config => { config.Icon = Icons.Material.Filled.Update; config.IconSize = Size.Large; @@ -141,7 +153,7 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, IDis { await this.ShowUpdateDialog(); }; - config.Action = "Show details"; + config.Action = T("Show details"); config.ActionVariant = Variant.Filled; }); } @@ -209,19 +221,19 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, IDis { var palette = this.ColorTheme.GetCurrentPalette(this.SettingsManager); - yield return new("Home", Icons.Material.Filled.Home, palette.DarkLighten, palette.GrayLight, Routes.HOME, true); - yield return new("Chat", Icons.Material.Filled.Chat, palette.DarkLighten, palette.GrayLight, Routes.CHAT, false); - yield return new("Assistants", Icons.Material.Filled.Apps, palette.DarkLighten, palette.GrayLight, Routes.ASSISTANTS, false); + yield return new(T("Home"), Icons.Material.Filled.Home, palette.DarkLighten, palette.GrayLight, Routes.HOME, true); + yield return new(T("Chat"), Icons.Material.Filled.Chat, palette.DarkLighten, palette.GrayLight, Routes.CHAT, false); + yield return new(T("Assistants"), Icons.Material.Filled.Apps, palette.DarkLighten, palette.GrayLight, Routes.ASSISTANTS, false); if (PreviewFeatures.PRE_WRITER_MODE_2024.IsEnabled(this.SettingsManager)) - yield return new("Writer", Icons.Material.Filled.Create, palette.DarkLighten, palette.GrayLight, Routes.WRITER, false); + yield return new(T("Writer"), Icons.Material.Filled.Create, palette.DarkLighten, palette.GrayLight, Routes.WRITER, false); if (PreviewFeatures.PRE_PLUGINS_2025.IsEnabled(this.SettingsManager)) - yield return new("Plugins", Icons.Material.TwoTone.Extension, palette.DarkLighten, palette.GrayLight, Routes.PLUGINS, false); + yield return new(T("Plugins"), Icons.Material.TwoTone.Extension, palette.DarkLighten, palette.GrayLight, Routes.PLUGINS, false); - yield return new("Supporters", Icons.Material.Filled.Favorite, palette.Error.Value, "#801a00", Routes.SUPPORTERS, false); - yield return new("About", Icons.Material.Filled.Info, palette.DarkLighten, palette.GrayLight, Routes.ABOUT, false); - yield return new("Settings", Icons.Material.Filled.Settings, palette.DarkLighten, palette.GrayLight, Routes.SETTINGS, false); + yield return new(T("Supporters"), Icons.Material.Filled.Favorite, palette.Error.Value, "#801a00", Routes.SUPPORTERS, false); + yield return new(T("About"), Icons.Material.Filled.Info, palette.DarkLighten, palette.GrayLight, Routes.ABOUT, false); + yield return new(T("Settings"), Icons.Material.Filled.Settings, palette.DarkLighten, palette.GrayLight, Routes.SETTINGS, false); } private async Task ShowUpdateDialog() @@ -248,7 +260,7 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, IDis { x => x.UpdateResponse, updatedResponse } }; - var dialogReference = await this.DialogService.ShowAsync("Update", dialogParameters, DialogOptions.FULLSCREEN_NO_HEADER); + var dialogReference = await this.DialogService.ShowAsync(T("Update"), dialogParameters, DialogOptions.FULLSCREEN_NO_HEADER); var dialogResult = await dialogReference.Result; if (dialogResult is null || dialogResult.Canceled) return; @@ -264,10 +276,10 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, IDis { var dialogParameters = new DialogParameters { - { "Message", "Are you sure you want to leave the chat page? All unsaved changes will be lost." }, + { "Message", T("Are you sure you want to leave the chat page? All unsaved changes will be lost.") }, }; - var dialogReference = await this.DialogService.ShowAsync("Leave Chat Page", dialogParameters, DialogOptions.FULLSCREEN); + var dialogReference = await this.DialogService.ShowAsync(T("Leave Chat Page"), dialogParameters, DialogOptions.FULLSCREEN); var dialogResult = await dialogReference.Result; if (dialogResult is null || dialogResult.Canceled) { diff --git a/app/MindWork AI Studio/MindWork AI Studio.csproj b/app/MindWork AI Studio/MindWork AI Studio.csproj index 8a579b26..2480a8fe 100644 --- a/app/MindWork AI Studio/MindWork AI Studio.csproj +++ b/app/MindWork AI Studio/MindWork AI Studio.csproj @@ -43,6 +43,7 @@ + @@ -60,6 +61,10 @@ + + + + diff --git a/app/MindWork AI Studio/Pages/About.razor b/app/MindWork AI Studio/Pages/About.razor index ce541476..4e7811f5 100644 --- a/app/MindWork AI Studio/Pages/About.razor +++ b/app/MindWork AI Studio/Pages/About.razor @@ -1,13 +1,16 @@ @attribute [Route(Routes.ABOUT)] +@inherits MSGComponentBase
- About MindWork AI Studio + + @T("About MindWork AI Studio") + - + - The following list shows the versions of the MindWork AI Studio, the used compilers, build time, etc.: + @T("The following list shows the versions of the MindWork AI Studio, the used compilers, build time, etc.:") @@ -20,105 +23,100 @@ - Check for updates + @T("Check for updates") - + - Discover MindWork AI's mission and vision on our official homepage. + @T("Discover MindWork AI's mission and vision on our official homepage.") - Browse AI Studio's source code on GitHub — we welcome your contributions. + @T("Browse AI Studio's source code on GitHub — we welcome your contributions.") - Connect AI Studio to your organization's data with our External Retrieval Interface (ERI). + @T("Connect AI Studio to your organization's data with our External Retrieval Interface (ERI).") - View our project roadmap and help shape AI Studio's future development. - + @T("View our project roadmap and help shape AI Studio's future development.") + - Did you find a bug or are you experiencing issues? Report your concern here. + @T("Did you find a bug or are you experiencing issues? Report your concern here.") - Have feature ideas? Submit suggestions for future AI Studio enhancements. + @T("Have feature ideas? Submit suggestions for future AI Studio enhancements.") - + - + - Explanation + @T("Explanation") - AI Studio creates a log file at startup, in which events during startup are recorded. After startup, - another log file is created that records all events that occur during the use of the app. This - includes any errors that may occur. Depending on when an error occurs (at startup or during use), - the contents of these log files can be helpful for troubleshooting. Sensitive information such as - passwords is not included in the log files. + @T("AI Studio creates a log file at startup, in which events during startup are recorded. After startup, another log file is created that records all events that occur during the use of the app. This includes any errors that may occur. Depending on when an error occurs (at startup or during use), the contents of these log files can be helpful for troubleshooting. Sensitive information such as passwords is not included in the log files.") - By clicking on the respective path, the path is copied to the clipboard. You might open these files - with a text editor to view their contents. + @T("By clicking on the respective path, the path is copied to the clipboard. You might open these files with a text editor to view their contents.") - Startup log file + @T("Startup log file") - Usage log file + @T("Usage log file") - + - + - + - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/MindWork AI Studio/Pages/About.razor.cs b/app/MindWork AI Studio/Pages/About.razor.cs index 1d9b92d0..4445a290 100644 --- a/app/MindWork AI Studio/Pages/About.razor.cs +++ b/app/MindWork AI Studio/Pages/About.razor.cs @@ -1,5 +1,6 @@ using System.Reflection; +using AIStudio.Components; using AIStudio.Tools.Metadata; using AIStudio.Tools.Rust; using AIStudio.Tools.Services; @@ -10,11 +11,8 @@ using SharedTools; namespace AIStudio.Pages; -public partial class About : ComponentBase +public partial class About : MSGComponentBase { - [Inject] - private MessageBus MessageBus { get; init; } = null!; - [Inject] private RustService RustService { get; init; } = null!; @@ -24,36 +22,26 @@ public partial class About : ComponentBase private static readonly Assembly ASSEMBLY = Assembly.GetExecutingAssembly(); private static readonly MetaDataAttribute META_DATA = ASSEMBLY.GetCustomAttribute()!; private static readonly MetaDataArchitecture META_DATA_ARCH = ASSEMBLY.GetCustomAttribute()!; - + private string osLanguage = string.Empty; - private static string VersionDotnetRuntime => $"Used .NET runtime: v{META_DATA.DotnetVersion}"; - - private static string VersionDotnetSdk => $"Used .NET SDK: v{META_DATA.DotnetSdkVersion}"; - - private static string VersionRust => $"Used Rust compiler: v{META_DATA.RustVersion}"; - private static string VersionApp => $"MindWork AI Studio: v{META_DATA.Version} (commit {META_DATA.AppCommitHash}, build {META_DATA.BuildNum}, {META_DATA_ARCH.Architecture.ToRID().ToUserFriendlyName()})"; - private static string BuildTime => $"Build time: {META_DATA.BuildTime}"; - private static string MudBlazorVersion => $"MudBlazor: v{META_DATA.MudBlazorVersion}"; private static string TauriVersion => $"Tauri: v{META_DATA.TauriVersion}"; - private string OSLanguage => $"User-language provided by the OS: '{this.osLanguage}'"; + private string OSLanguage => $"{T("User-language provided by the OS")}: '{this.osLanguage}'"; + + private string VersionRust => $"{T("Used Rust compiler")}: v{META_DATA.RustVersion}"; + + private string VersionDotnetRuntime => $"{T("Used .NET runtime")}: v{META_DATA.DotnetVersion}"; + + private string VersionDotnetSdk => $"{T("Used .NET SDK")}: v{META_DATA.DotnetSdkVersion}"; + + private string BuildTime => $"{T("Build time")}: {META_DATA.BuildTime}"; private GetLogPathsResponse logPaths; - - private async Task CopyStartupLogPath() - { - await this.RustService.CopyText2Clipboard(this.Snackbar, this.logPaths.LogStartupPath); - } - - private async Task CopyAppLogPath() - { - await this.RustService.CopyText2Clipboard(this.Snackbar, this.logPaths.LogAppPath); - } #region Overrides of ComponentBase @@ -65,6 +53,16 @@ public partial class About : ComponentBase } #endregion + + private async Task CopyStartupLogPath() + { + await this.RustService.CopyText2Clipboard(this.Snackbar, this.logPaths.LogStartupPath); + } + + private async Task CopyAppLogPath() + { + await this.RustService.CopyText2Clipboard(this.Snackbar, this.logPaths.LogAppPath); + } private const string LICENSE = """ # Functional Source License, Version 1.1, MIT Future License diff --git a/app/MindWork AI Studio/Pages/Assistants.razor b/app/MindWork AI Studio/Pages/Assistants.razor index 58c0deb1..2acaa4ca 100644 --- a/app/MindWork AI Studio/Pages/Assistants.razor +++ b/app/MindWork AI Studio/Pages/Assistants.razor @@ -1,6 +1,7 @@ @using AIStudio.Dialogs.Settings @using AIStudio.Settings.DataModel @attribute [Route(Routes.ASSISTANTS)] +@inherits MSGComponentBase
@@ -13,40 +14,40 @@ General - - - - - + + + + + Business - - - - - - + + + + + + Learning - + Software Engineering - + @if (PreviewFeatures.PRE_RAG_2024.IsEnabled(this.SettingsManager)) { - + } diff --git a/app/MindWork AI Studio/Pages/Assistants.razor.cs b/app/MindWork AI Studio/Pages/Assistants.razor.cs index ef2af6db..e2c2de49 100644 --- a/app/MindWork AI Studio/Pages/Assistants.razor.cs +++ b/app/MindWork AI Studio/Pages/Assistants.razor.cs @@ -1,11 +1,5 @@ -using AIStudio.Settings; - -using Microsoft.AspNetCore.Components; +using AIStudio.Components; namespace AIStudio.Pages; -public partial class Assistants : ComponentBase -{ - [Inject] - public SettingsManager SettingsManager { get; set; } = null!; -} \ No newline at end of file +public partial class Assistants : MSGComponentBase; \ No newline at end of file diff --git a/app/MindWork AI Studio/Pages/Chat.razor b/app/MindWork AI Studio/Pages/Chat.razor index 418a17cc..5586f262 100644 --- a/app/MindWork AI Studio/Pages/Chat.razor +++ b/app/MindWork AI Studio/Pages/Chat.razor @@ -25,7 +25,7 @@ // Case: Sidebar can be toggled and is currently visible - + @@ -88,7 +88,7 @@ - Your workspaces + @T("Your workspaces") diff --git a/app/MindWork AI Studio/Pages/Chat.razor.cs b/app/MindWork AI Studio/Pages/Chat.razor.cs index f3c5c8b5..56a5535e 100644 --- a/app/MindWork AI Studio/Pages/Chat.razor.cs +++ b/app/MindWork AI Studio/Pages/Chat.razor.cs @@ -82,8 +82,6 @@ public partial class Chat : MSGComponentBase #region Overrides of MSGComponentBase - public override string ComponentName => nameof(Chat); - protected override Task ProcessIncomingMessage(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default { switch (triggeredEvent) diff --git a/app/MindWork AI Studio/Pages/Home.razor b/app/MindWork AI Studio/Pages/Home.razor index 0b035995..d2bfdb93 100644 --- a/app/MindWork AI Studio/Pages/Home.razor +++ b/app/MindWork AI Studio/Pages/Home.razor @@ -12,31 +12,29 @@ - Welcome to MindWork AI Studio! + @T("Welcome to MindWork AI Studio!") - Thank you for considering MindWork AI Studio for your AI needs. This app is designed to help you harness - the power of Large Language Models (LLMs). Please note that this app doesn't come with an integrated - LLM. Instead, you will need to bring an API key from a suitable provider. + @T("Thank you for considering MindWork AI Studio for your AI needs. This app is designed to help you harness the power of Large Language Models (LLMs). Please note that this app doesn't come with an integrated LLM. Instead, you will need to bring an API key from a suitable provider.") - Here's what makes MindWork AI Studio stand out: + @T("Here's what makes MindWork AI Studio stand out:") - + - We hope you enjoy using MindWork AI Studio to bring your AI projects to life! + @T("We hope you enjoy using MindWork AI Studio to bring your AI projects to life!") - + - + - + diff --git a/app/MindWork AI Studio/Pages/Home.razor.cs b/app/MindWork AI Studio/Pages/Home.razor.cs index 6a5dfff0..85cae0e9 100644 --- a/app/MindWork AI Studio/Pages/Home.razor.cs +++ b/app/MindWork AI Studio/Pages/Home.razor.cs @@ -13,41 +13,38 @@ public partial class Home : MSGComponentBase private string LastChangeContent { get; set; } = string.Empty; + private TextItem[] itemsAdvantages = []; + #region Overrides of ComponentBase protected override async Task OnInitializedAsync() { await this.ReadLastChangeAsync(); await base.OnInitializedAsync(); + + this.itemsAdvantages = [ + new(T("Free of charge"), T("The app is free to use, both for personal and commercial purposes.")), + new(T("Independence"), 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 (GPT4o, o1, etc.), Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), Hugging Face, and self-hosted models using 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(T("Assistants"), 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(T("Unrestricted usage"), T("Unlike services like ChatGPT, which impose limits after intensive use, MindWork AI Studio offers unlimited usage through the providers API.")), + new(T("Cost-effective"), T("You only pay for what you use, which can be cheaper than monthly subscription services like ChatGPT Plus, especially if used infrequently. But beware, here be dragons: For extremely intensive usage, the API costs can be significantly higher. Unfortunately, providers currently do not offer a way to display current costs in the app. Therefore, check your account with the respective provider to see how your costs are developing. When available, use prepaid and set a cost limit.")), + new(T("Privacy"), T("You can control which providers receive your data using the provider confidence settings. For example, you can set different protection levels for writing emails compared to general chats, etc. Additionally, most providers guarantee that they won't use your data to train new AI systems.")), + new(T("Flexibility"), T("Choose the provider and model best suited for your current task.")), + new(T("No bloatware"), T("The app requires minimal storage for installation and operates with low memory usage. Additionally, it has a minimal impact on system resources, which is beneficial for battery life.")), + ]; + + this.StateHasChanged(); } #endregion - #region Overrides of MSGComponentBase - - public override string ComponentName => nameof(Home); - - #endregion - private async Task ReadLastChangeAsync() { var latest = Changelog.LOGS.MaxBy(n => n.Build); using var response = await this.HttpClient.GetAsync($"changelog/{latest.Filename}"); this.LastChangeContent = await response.Content.ReadAsStringAsync(); } - - private static readonly TextItem[] ITEMS_ADVANTAGES = - [ - new("Free of charge", "The app is free to use, both for personal and commercial purposes."), - new("Independence", "You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT4o, o1, etc.), Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), Hugging Face, and self-hosted models using 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("Assistants", "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("Unrestricted usage", "Unlike services like ChatGPT, which impose limits after intensive use, MindWork AI Studio offers unlimited usage through the providers API."), - new("Cost-effective", "You only pay for what you use, which can be cheaper than monthly subscription services like ChatGPT Plus, especially if used infrequently. But beware, here be dragons: For extremely intensive usage, the API costs can be significantly higher. Unfortunately, providers currently do not offer a way to display current costs in the app. Therefore, check your account with the respective provider to see how your costs are developing. When available, use prepaid and set a cost limit."), - new("Privacy", "You can control which providers receive your data using the provider confidence settings. For example, you can set different protection levels for writing emails compared to general chats, etc. Additionally, most providers guarantee that they won't use your data to train new AI systems."), - new("Flexibility", "Choose the provider and model best suited for your current task."), - new("No bloatware", "The app requires minimal storage for installation and operates with low memory usage. Additionally, it has a minimal impact on system resources, which is beneficial for battery life."), - ]; - + private const string QUICK_START_GUIDE = """ Ready to dive in and get started with MindWork AI Studio? This quick start guide will help you set up everything you need to start using the app. diff --git a/app/MindWork AI Studio/Pages/Plugins.razor b/app/MindWork AI Studio/Pages/Plugins.razor index 8306d6d1..0db15706 100644 --- a/app/MindWork AI Studio/Pages/Plugins.razor +++ b/app/MindWork AI Studio/Pages/Plugins.razor @@ -4,7 +4,7 @@
- Plugins + @T("Plugins") @@ -18,8 +18,8 @@ - Plugins - Actions + @T("Plugins") + @T("Actions") @@ -27,19 +27,19 @@ { case GROUP_ENABLED: - Enabled Plugins + @T("Enabled Plugins") break; case GROUP_DISABLED: - Disabled Plugins + @T("Disabled Plugins") break; case GROUP_INTERNAL: - Internal Plugins + @T("Internal Plugins") break; } @@ -67,7 +67,7 @@ @if (!context.IsInternal) { var isEnabled = this.SettingsManager.IsPluginEnabled(context); - + } diff --git a/app/MindWork AI Studio/Pages/Plugins.razor.cs b/app/MindWork AI Studio/Pages/Plugins.razor.cs index 88c90433..4eb6078c 100644 --- a/app/MindWork AI Studio/Pages/Plugins.razor.cs +++ b/app/MindWork AI Studio/Pages/Plugins.razor.cs @@ -1,6 +1,8 @@ using AIStudio.Components; using AIStudio.Tools.PluginSystem; +using Microsoft.AspNetCore.Components; + namespace AIStudio.Pages; public partial class Plugins : MSGComponentBase @@ -15,8 +17,7 @@ public partial class Plugins : MSGComponentBase protected override async Task OnInitializedAsync() { - this.MessageBus.RegisterComponent(this); - this.MessageBus.ApplyFilters(this, [], [ Event.PLUGINS_RELOADED ]); + this.ApplyFilters([], [ Event.PLUGINS_RELOADED ]); this.groupConfig = new TableGroupDefinition { @@ -38,12 +39,6 @@ public partial class Plugins : MSGComponentBase #endregion - #region Overrides of MSGComponentBase - - public override string ComponentName => nameof(Plugins); - - #endregion - private async Task PluginActivationStateChanged(IPluginMetadata pluginMeta) { if (this.SettingsManager.IsPluginEnabled(pluginMeta)) @@ -54,4 +49,18 @@ public partial class Plugins : MSGComponentBase await this.SettingsManager.StoreSettings(); await this.MessageBus.SendMessage(this, Event.CONFIGURATION_CHANGED); } + + #region Overrides of MSGComponentBase + + protected override async Task ProcessIncomingMessage(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default + { + switch (triggeredEvent) + { + case Event.PLUGINS_RELOADED: + await this.InvokeAsync(this.StateHasChanged); + break; + } + } + + #endregion } \ No newline at end of file diff --git a/app/MindWork AI Studio/Pages/Settings.razor b/app/MindWork AI Studio/Pages/Settings.razor index c048a542..9cc28223 100644 --- a/app/MindWork AI Studio/Pages/Settings.razor +++ b/app/MindWork AI Studio/Pages/Settings.razor @@ -1,9 +1,10 @@ -@attribute [Route(Routes.SETTINGS)] @using AIStudio.Components.Settings @using AIStudio.Settings.DataModel +@attribute [Route(Routes.SETTINGS)] +@inherits MSGComponentBase
- Settings + @T("Settings") diff --git a/app/MindWork AI Studio/Pages/Settings.razor.cs b/app/MindWork AI Studio/Pages/Settings.razor.cs index 57c34c5a..89c7338b 100644 --- a/app/MindWork AI Studio/Pages/Settings.razor.cs +++ b/app/MindWork AI Studio/Pages/Settings.razor.cs @@ -1,17 +1,12 @@ +using AIStudio.Components; using AIStudio.Settings; using Microsoft.AspNetCore.Components; namespace AIStudio.Pages; -public partial class Settings : ComponentBase, IMessageBusReceiver, IDisposable +public partial class Settings : MSGComponentBase { - [Inject] - private SettingsManager SettingsManager { get; init; } = null!; - - [Inject] - private MessageBus MessageBus { get; init; } = null!; - private List> availableLLMProviders = new(); private List> availableEmbeddingProviders = new(); @@ -19,20 +14,16 @@ public partial class Settings : ComponentBase, IMessageBusReceiver, IDisposable protected override async Task OnInitializedAsync() { - // Register this component with the message bus: - this.MessageBus.RegisterComponent(this); - this.MessageBus.ApplyFilters(this, [], [ Event.CONFIGURATION_CHANGED ]); + this.ApplyFilters([], [ Event.CONFIGURATION_CHANGED ]); await base.OnInitializedAsync(); } #endregion - - #region Implementation of IMessageBusReceiver - public string ComponentName => nameof(Settings); + #region Overrides of MSGComponentBase - public Task ProcessMessage(ComponentBase? sendingComponent, Event triggeredEvent, TMsg? data) + protected override Task ProcessIncomingMessage(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default { switch (triggeredEvent) { @@ -40,23 +31,9 @@ public partial class Settings : ComponentBase, IMessageBusReceiver, IDisposable this.StateHasChanged(); break; } - + return Task.CompletedTask; } - public Task ProcessMessageWithResult(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data) - { - return Task.FromResult(default); - } - - #endregion - - #region Implementation of IDisposable - - public void Dispose() - { - this.MessageBus.Unregister(this); - } - #endregion } \ No newline at end of file diff --git a/app/MindWork AI Studio/Pages/Supporters.razor b/app/MindWork AI Studio/Pages/Supporters.razor index c9350408..a35d4fd1 100644 --- a/app/MindWork AI Studio/Pages/Supporters.razor +++ b/app/MindWork AI Studio/Pages/Supporters.razor @@ -1,22 +1,27 @@ @attribute [Route(Routes.SUPPORTERS)] +@inherits MSGComponentBase
- Supporters + + @T("Supporters") + - +
- Our Titans - - In this section, we highlight the titan supporters of MindWork AI Studio. Titans are prestigious companies that provide significant support to our mission. + + @T("Our Titans") - For companies, sponsoring MindWork AI Studio is not only a way to support innovation but also a valuable opportunity for public relations and marketing. Your company's name and logo will be featured prominently, showcasing your commitment to using cutting-edge AI tools and enhancing your reputation as an innovative enterprise. + @T("In this section, we highlight the titan supporters of MindWork AI Studio. Titans are prestigious companies that provide significant support to our mission.") + + + @T("For companies, sponsoring MindWork AI Studio is not only a way to support innovation but also a valuable opportunity for public relations and marketing. Your company's name and logo will be featured prominently, showcasing your commitment to using cutting-edge AI tools and enhancing your reputation as an innovative enterprise.") - Become our first Titan + @T("Become our first Titan")
@@ -25,27 +30,27 @@ - Individual Contributors + @T("Individual Contributors") - Become a contributor + @T("Become a contributor") - The first 10 supporters who make a monthly contribution: + @T("The first 10 supporters who make a monthly contribution:") - - - + + + - The first 10 supporters who make a one-time contribution: + @T("The first 10 supporters who make a one-time contribution:") - + @@ -54,11 +59,11 @@ - Business Contributors + @T("Business Contributors") - Become a contributor + @T("Become a contributor") @@ -72,14 +77,14 @@ - Code Contributions + @T("Code Contributions") - - - - + + + + @@ -88,11 +93,11 @@ - Moderation, Design, Wiki, and Documentation + @T("Moderation, Design, Wiki, and Documentation") - + diff --git a/app/MindWork AI Studio/Pages/Supporters.razor.cs b/app/MindWork AI Studio/Pages/Supporters.razor.cs index 45065ad9..dcd937c9 100644 --- a/app/MindWork AI Studio/Pages/Supporters.razor.cs +++ b/app/MindWork AI Studio/Pages/Supporters.razor.cs @@ -1,7 +1,7 @@ +using AIStudio.Components; + using Microsoft.AspNetCore.Components; namespace AIStudio.Pages; -public partial class Supporters : ComponentBase -{ -} \ No newline at end of file +public partial class Supporters : MSGComponentBase; \ No newline at end of file diff --git a/app/MindWork AI Studio/Pages/Writer.razor b/app/MindWork AI Studio/Pages/Writer.razor index 790e3e07..bead1792 100644 --- a/app/MindWork AI Studio/Pages/Writer.razor +++ b/app/MindWork AI Studio/Pages/Writer.razor @@ -3,7 +3,7 @@
- Writer + @T("Writer") @@ -13,7 +13,7 @@ Logger { get; init; } = null!; @@ -29,7 +29,6 @@ public partial class Writer : MSGComponentBase, IAsyncDisposable protected override async Task OnInitializedAsync() { - this.ApplyFilters([], []); this.SettingsManager.InjectSpellchecking(USER_INPUT_ATTRIBUTES); this.typeTimer.Elapsed += async (_, _) => await this.InvokeAsync(this.GetSuggestions); this.typeTimer.AutoReset = false; @@ -39,12 +38,6 @@ public partial class Writer : MSGComponentBase, IAsyncDisposable #endregion - #region Overrides of MSGComponentBase - - public override string ComponentName => nameof(Writer); - - #endregion - private bool IsProviderSelected => this.providerSettings.UsedLLMProvider != LLMProviders.NONE; private async Task InputKeyEvent(KeyboardEventArgs keyEvent) @@ -159,13 +152,4 @@ public partial class Writer : MSGComponentBase, IAsyncDisposable this.suggestion = string.Join(' ', words.Skip(1)); this.StateHasChanged(); } - - #region Implementation of IAsyncDisposable - - public ValueTask DisposeAsync() - { - return ValueTask.CompletedTask; - } - - #endregion } \ No newline at end of file diff --git a/app/MindWork AI Studio/Settings/SettingsManager.cs b/app/MindWork AI Studio/Settings/SettingsManager.cs index 24bd1ded..1f1b543d 100644 --- a/app/MindWork AI Studio/Settings/SettingsManager.cs +++ b/app/MindWork AI Studio/Settings/SettingsManager.cs @@ -1,6 +1,5 @@ using System.Diagnostics.CodeAnalysis; using System.Text.Json; -using System.Text.Json.Serialization; using AIStudio.Provider; using AIStudio.Settings.DataModel; diff --git a/app/MindWork AI Studio/Tools/IMessageBusReceiver.cs b/app/MindWork AI Studio/Tools/IMessageBusReceiver.cs index 044e425b..019ce115 100644 --- a/app/MindWork AI Studio/Tools/IMessageBusReceiver.cs +++ b/app/MindWork AI Studio/Tools/IMessageBusReceiver.cs @@ -4,8 +4,6 @@ namespace AIStudio.Tools; public interface IMessageBusReceiver { - public string ComponentName { get; } - public Task ProcessMessage(ComponentBase? sendingComponent, Event triggeredEvent, T? data); public Task ProcessMessageWithResult(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data); diff --git a/app/MindWork AI Studio/Tools/MessageBus.cs b/app/MindWork AI Studio/Tools/MessageBus.cs index 06a2dfd8..7840ce75 100644 --- a/app/MindWork AI Studio/Tools/MessageBus.cs +++ b/app/MindWork AI Studio/Tools/MessageBus.cs @@ -19,9 +19,15 @@ public sealed class MessageBus { } - public void ApplyFilters(IMessageBusReceiver receiver, ComponentBase[] components, Event[] events) + /// + /// Define for which components and events you want to receive messages. + /// + /// That's you, the receiver. + /// A list of components for which you want to receive messages. Use an empty list to receive messages from all components. + /// A list of events for which you want to receive messages. + public void ApplyFilters(IMessageBusReceiver receiver, ComponentBase[] filterComponents, Event[] events) { - this.componentFilters[receiver] = components; + this.componentFilters[receiver] = filterComponents; this.componentEvents[receiver] = events; } diff --git a/app/MindWork AI Studio/Tools/PluginSystem/ILangExtensions.cs b/app/MindWork AI Studio/Tools/PluginSystem/ILangExtensions.cs new file mode 100644 index 00000000..d1c26856 --- /dev/null +++ b/app/MindWork AI Studio/Tools/PluginSystem/ILangExtensions.cs @@ -0,0 +1,29 @@ +using SharedTools; + +namespace AIStudio.Tools.PluginSystem; + +public static class ILangExtensions +{ + private static readonly ILogger LOGGER = Program.LOGGER_FACTORY.CreateLogger(); + + public static string GetText(this ILang lang, ILanguagePlugin plugin, string fallbackEN) + { + var type = lang.GetType(); + var ns = $"{type.Namespace!}::{type.Name}".ToUpperInvariant().Replace(".", "::"); + var key = $"root::{ns}::T{fallbackEN.ToFNV32()}"; + + if(plugin is NoPluginLanguage) + return fallbackEN; + + if(plugin.TryGetText(key, out var text, logWarning: false)) + { + if(string.IsNullOrWhiteSpace(text)) + return fallbackEN; + + return text; + } + + LOGGER.LogDebug($"Missing translation key '{key}' for content '{fallbackEN}'."); + return fallbackEN; + } +} \ No newline at end of file diff --git a/app/SourceCodeRules/SourceCodeRules/UsageAnalyzers/ThisUsageAnalyzer.cs b/app/SourceCodeRules/SourceCodeRules/UsageAnalyzers/ThisUsageAnalyzer.cs index f48c374b..1e601a6f 100644 --- a/app/SourceCodeRules/SourceCodeRules/UsageAnalyzers/ThisUsageAnalyzer.cs +++ b/app/SourceCodeRules/SourceCodeRules/UsageAnalyzers/ThisUsageAnalyzer.cs @@ -36,7 +36,7 @@ public sealed class ThisUsageAnalyzer : DiagnosticAnalyzer { var genericNameSyntax = (GenericNameSyntax)context.Node; - // Skip if already part of a 'this' expression + // Skip if already part of a 'this' expression: if (IsAccessedThroughThis(genericNameSyntax)) return; @@ -46,7 +46,11 @@ public sealed class ThisUsageAnalyzer : DiagnosticAnalyzer if (IsPartOfMemberAccess(genericNameSyntax)) return; - // Get symbol info for the generic name + // Skip if it's the 'T' translation method + if (IsTranslationMethod(genericNameSyntax)) + return; + + // Get symbol info for the generic name: var symbolInfo = context.SemanticModel.GetSymbolInfo(genericNameSyntax); var symbol = symbolInfo.Symbol; @@ -83,15 +87,24 @@ public sealed class ThisUsageAnalyzer : DiagnosticAnalyzer } } + private static bool IsTranslationMethod(SyntaxNode node) + { + // Check if this is a method called 'T' (translation method) + if (node is IdentifierNameSyntax { Identifier.Text: "T" }) + return true; + + return false; + } + private void AnalyzeIdentifier(SyntaxNodeAnalysisContext context) { var identifierNameSyntax = (IdentifierNameSyntax)context.Node; - // Skip if this identifier is part of a generic name - we'll handle that separately + // Skip if this identifier is part of a generic name - we'll handle that separately: if (identifierNameSyntax.Parent is GenericNameSyntax) return; - // Skip if already part of a 'this' expression + // Skip if already part of a 'this' expression: if (IsAccessedThroughThis(identifierNameSyntax)) return; @@ -101,55 +114,59 @@ public sealed class ThisUsageAnalyzer : DiagnosticAnalyzer if (IsPartOfMemberAccess(identifierNameSyntax)) return; - // Also skip if it's part of static import statements + // Also skip if it's part of static import statements: if (IsPartOfUsingStaticDirective(identifierNameSyntax)) return; - // Skip if it's part of a namespace or type name + // Skip if it's part of a namespace or type name: if (IsPartOfNamespaceOrTypeName(identifierNameSyntax)) return; - // Get symbol info + // Skip if it's the 'T' translation method: + if (IsTranslationMethod(identifierNameSyntax)) + return; + + // Get symbol info: var symbolInfo = context.SemanticModel.GetSymbolInfo(identifierNameSyntax); var symbol = symbolInfo.Symbol; if (symbol == null) return; - // Skip local variables, parameters, and range variables + // Skip local variables, parameters, and range variables: if (symbol.Kind is SymbolKind.Local or SymbolKind.Parameter or SymbolKind.RangeVariable or SymbolKind.TypeParameter) return; - // Skip types and namespaces + // Skip types and namespaces: if (symbol.Kind is SymbolKind.NamedType or SymbolKind.Namespace) return; - // Explicitly check if this is a local function + // Explicitly check if this is a local function: if (symbol is IMethodSymbol methodSymbol && IsLocalFunction(methodSymbol)) return; - // Get the containing type of the current context + // Get the containing type of the current context: var containingSymbol = context.ContainingSymbol; var currentType = containingSymbol?.ContainingType; - // If we're in a static context, allow accessing members without this + // If we're in a static context, allow accessing members without this: if (IsInStaticContext(containingSymbol)) return; - // Now check if the symbol is an instance member of the current class + // Now check if the symbol is an instance member of the current class: if (symbol is IFieldSymbol or IPropertySymbol or IMethodSymbol or IEventSymbol) { - // Skip static members + // Skip static members: if (symbol.IsStatic) return; - // Skip constants + // Skip constants: if (symbol is IFieldSymbol { IsConst: true }) return; var containingType = symbol.ContainingType; - // If the symbol is a member of the current type or a base type, then require this + // If the symbol is a member of the current type or a base type, then require this: if (currentType != null && (SymbolEqualityComparer.Default.Equals(containingType, currentType) || IsBaseTypeOf(containingType, currentType))) { From 151830ef1523eef042509c22f3bde9951dd25ab3 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Fri, 25 Apr 2025 09:31:02 +0200 Subject: [PATCH 29/93] Updated the Tauri bundle to support AppImage for ARM (#423) --- .github/workflows/build-and-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index a7015fc5..c1ad8ffa 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -107,7 +107,7 @@ jobs: rust_target: 'aarch64-unknown-linux-gnu' dotnet_runtime: 'linux-arm64' dotnet_name_postfix: '-aarch64-unknown-linux-gnu' - tauri_bundle: 'deb' + tauri_bundle: 'appimage deb' - platform: 'windows-latest' # for x86-based Windows rust_target: 'x86_64-pc-windows-msvc' From 45974f9bcf0909bf78aab02d04a8e072339bb962 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Fri, 25 Apr 2025 09:46:55 +0200 Subject: [PATCH 30/93] Updated the README (#424) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 16b57720..8ec2036c 100644 --- a/README.md +++ b/README.md @@ -31,8 +31,8 @@ Things we are currently working on: - [x] ~~Plan & implement the base plugin system ([PR #322](https://github.com/MindWorkAI/AI-Studio/pull/322))~~ - [x] ~~Start the plugin system ([PR #372](https://github.com/MindWorkAI/AI-Studio/pull/372))~~ - [x] ~~Added hot-reload support for plugins ([PR #377](https://github.com/MindWorkAI/AI-Studio/pull/377), [PR #391](https://github.com/MindWorkAI/AI-Studio/pull/391))~~ - - [ ] Add support for other languages (I18N) to AI Studio (~~[PR #381](https://github.com/MindWorkAI/AI-Studio/pull/381), [PR #400](https://github.com/MindWorkAI/AI-Studio/pull/400)~~, [PR #404](https://github.com/MindWorkAI/AI-Studio/pull/404)) - - [ ] Add an I18N assistant to translate all AI Studio texts to a certain language & culture + - [ ] Add support for other languages (I18N) to AI Studio (~~[PR #381](https://github.com/MindWorkAI/AI-Studio/pull/381), [PR #400](https://github.com/MindWorkAI/AI-Studio/pull/400), [PR #404](https://github.com/MindWorkAI/AI-Studio/pull/404))~~ + - [ ] Add an I18N assistant to translate all AI Studio texts to a certain language & culture ([PR #422](https://github.com/MindWorkAI/AI-Studio/pull/422)) - [ ] Provide MindWork AI Studio in German ([#31](https://github.com/MindWorkAI/Planning/issues/31)) - [ ] Add configuration plugins, which allow pre-defining some LLM providers in organizations - [ ] Add an app store for plugins, showcasing community-contributed plugins from public GitHub and GitLab repositories. This will enable AI Studio users to discover, install, and update plugins directly within the platform. From 201fb2514de3bed9f20cfaea31e40eb0b0917eed Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Fri, 25 Apr 2025 09:56:43 +0200 Subject: [PATCH 31/93] Enabled ARM AppImage updater (#425) --- .github/workflows/build-and-release.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-and-release.yml b/.github/workflows/build-and-release.yml index c1ad8ffa..3479f9f8 100644 --- a/.github/workflows/build-and-release.yml +++ b/.github/workflows/build-and-release.yml @@ -107,7 +107,7 @@ jobs: rust_target: 'aarch64-unknown-linux-gnu' dotnet_runtime: 'linux-arm64' dotnet_name_postfix: '-aarch64-unknown-linux-gnu' - tauri_bundle: 'appimage deb' + tauri_bundle: 'appimage deb updater' - platform: 'windows-latest' # for x86-based Windows rust_target: 'x86_64-pc-windows-msvc' @@ -506,6 +506,7 @@ jobs: # - platform=darwin-aarch64 when path contains 'aarch64-apple-darwin' # - platform=darwin-x86_64 when path contains 'x86_64-apple-darwin' # - platform=linux-x86_64 when path contains 'x86_64-unknown-linux-' + # - platform=linux-aarch64 when path contains 'aarch64-unknown-linux-' # - platform=windows-x86_64 when path contains 'x86_64-pc-windows-' # - platform=windows-aarch64 when path contains 'aarch64-pc-windows-' # @@ -515,6 +516,8 @@ jobs: platform="darwin-x86_64" elif [[ "$sig_file" == *"amd64.AppImage"* ]]; then platform="linux-x86_64" + elif [[ "$sig_file" == *"aarch64.AppImage"* ]]; then + platform="linux-aarch64" elif [[ "$sig_file" == *"x64-setup.nsis"* ]]; then platform="windows-x86_64" elif [[ "$sig_file" == *"arm64-setup.nsis"* ]]; then From 81030019c7f375b6fb49f400ba1007a68e036cf6 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sat, 26 Apr 2025 18:55:23 +0200 Subject: [PATCH 32/93] Added I18N assistant for localization of AI Studio content (#422) --- app/Build/Commands/CollectI18NKeysCommand.cs | 184 +++------ app/MindWork AI Studio.sln.DotSettings | 1 + .../Assistants/AssistantBase.razor | 6 +- .../Assistants/AssistantBase.razor.cs | 30 +- .../Assistants/I18N/AssistantI18N.razor | 124 +++++++ .../Assistants/I18N/AssistantI18N.razor.cs | 351 ++++++++++++++++++ .../Components/MSGComponentBase.cs | 1 + .../Dialogs/ProviderDialog.razor | 7 +- .../SettingsDialogAssistantBias.razor | 1 - .../Dialogs/Settings/SettingsDialogI18N.razor | 28 ++ .../Settings/SettingsDialogI18N.razor.cs | 5 + .../MindWork AI Studio.csproj | 4 - app/MindWork AI Studio/Pages/Assistants.razor | 12 +- .../Pages/Supporters.razor.cs | 2 - app/MindWork AI Studio/Routes.razor.cs | 1 + .../Settings/DataModel/Data.cs | 2 + .../Settings/DataModel/DataI18N.cs | 29 ++ app/MindWork AI Studio/Tools/ButtonData.cs | 17 +- .../Tools/CommonLanguageExtensions.cs | 18 + app/MindWork AI Studio/Tools/Components.cs | 4 + .../Tools/ComponentsExtensions.cs | 5 + .../Tools/PluginSystem/ILanguagePlugin.cs | 5 + .../Tools/PluginSystem/NoPluginLanguage.cs | 2 + .../PluginSystem/PluginFactory.Loading.cs | 14 +- .../Tools/PluginSystem/PluginLanguage.cs | 3 + .../wwwroot/changelog/v0.9.41.md | 1 + app/SharedTools/LuaTable.cs | 41 ++ app/SharedTools/LuaTools.cs | 10 + 28 files changed, 750 insertions(+), 158 deletions(-) create mode 100644 app/MindWork AI Studio/Assistants/I18N/AssistantI18N.razor create mode 100644 app/MindWork AI Studio/Assistants/I18N/AssistantI18N.razor.cs create mode 100644 app/MindWork AI Studio/Dialogs/Settings/SettingsDialogI18N.razor create mode 100644 app/MindWork AI Studio/Dialogs/Settings/SettingsDialogI18N.razor.cs create mode 100644 app/MindWork AI Studio/Settings/DataModel/DataI18N.cs create mode 100644 app/SharedTools/LuaTable.cs create mode 100644 app/SharedTools/LuaTools.cs diff --git a/app/Build/Commands/CollectI18NKeysCommand.cs b/app/Build/Commands/CollectI18NKeysCommand.cs index d10ff0e0..896c606c 100644 --- a/app/Build/Commands/CollectI18NKeysCommand.cs +++ b/app/Build/Commands/CollectI18NKeysCommand.cs @@ -59,7 +59,7 @@ public sealed partial class CollectI18NKeysCommand Console.WriteLine($" {counter:###,###} files processed, {allI18NContent.Count:###,###} keys found."); Console.Write("- Creating Lua code ..."); - var luaCode = this.ExportToLuaTable(allI18NContent); + var luaCode = this.ExportToLuaAssignments(allI18NContent); // Build the path, where we want to store the Lua code: var luaPath = Path.Join(cwd, "Assistants", "I18N", "allTexts.lua"); @@ -69,134 +69,68 @@ public sealed partial class CollectI18NKeysCommand Console.WriteLine(" done."); } - - private string ExportToLuaTable(Dictionary keyValuePairs) - { - // Collect all nodes: - var root = new Dictionary(); - - // - // Split all collected keys into nodes: - // - foreach (var key in keyValuePairs.Keys.Order()) - { - var path = key.Split('.'); - var current = root; - for (var i = 0; i < path.Length - 1; i++) - { - // We ignore the AISTUDIO segment of the path: - if(path[i] == "AISTUDIO") - continue; - - if (!current.TryGetValue(path[i], out var child) || child is not Dictionary childDict) - { - childDict = new Dictionary(); - current[path[i]] = childDict; - } - - current = childDict; - } - - current[path.Last()] = keyValuePairs[key]; - } - // - // Inner method to build Lua code from the collected nodes: - // - void ToLuaTable(StringBuilder sb, Dictionary innerDict, int indent = 0) - { - sb.AppendLine("{"); - var prefix = new string(' ', indent * 4); - foreach (var kvp in innerDict) - { - if (kvp.Value is Dictionary childDict) - { - sb.Append($"{prefix} {kvp.Key}"); - sb.Append(" = "); - - ToLuaTable(sb, childDict, indent + 1); - } - else if (kvp.Value is string s) - { - sb.AppendLine($"{prefix} -- {s.Trim().Replace("\n", " ")}"); - sb.Append($"{prefix} {kvp.Key}"); - sb.Append(" = "); - sb.Append($""" - "{s}" - """); - sb.AppendLine(","); - sb.AppendLine(); - } - } + private string ExportToLuaAssignments(Dictionary keyValuePairs) + { + var sb = new StringBuilder(); + + // Add the mandatory plugin metadata: + sb.AppendLine( + """ + -- The ID for this plugin: + ID = "77c2688a-a68f-45cc-820e-fa8f3038a146" - sb.AppendLine(prefix + "},"); - sb.AppendLine(); - } + -- The icon for the plugin: + ICON_SVG = "" + + -- The name of the plugin: + NAME = "Collected I18N keys" + + -- The description of the plugin: + DESCRIPTION = "This plugin is not meant to be used directly. Its a collection of all I18N keys found in the project." + + -- The version of the plugin: + VERSION = "1.0.0" + + -- The type of the plugin: + TYPE = "LANGUAGE" + + -- The authors of the plugin: + AUTHORS = {"MindWork AI Community"} + + -- The support contact for the plugin: + SUPPORT_CONTACT = "MindWork AI Community" + + -- The source URL for the plugin: + SOURCE_URL = "https://github.com/MindWorkAI/AI-Studio" + + -- The categories for the plugin: + CATEGORIES = { "CORE" } + + -- The target groups for the plugin: + TARGET_GROUPS = { "EVERYONE" } + + -- The flag for whether the plugin is maintained: + IS_MAINTAINED = true + + -- When the plugin is deprecated, this message will be shown to users: + DEPRECATION_MESSAGE = "" + + -- The IETF BCP 47 tag for the language. It's the ISO 639 language + -- code followed by the ISO 3166-1 country code: + IETF_TAG = "en-US" + + -- The language name in the user's language: + LANG_NAME = "English (United States)" + + """ + ); - // - // Write the Lua code: - // - var sbLua = new StringBuilder(); - - // To make the later parsing easier, we add the mandatory plugin - // metadata: - sbLua.AppendLine( - """ - -- The ID for this plugin: - ID = "77c2688a-a68f-45cc-820e-fa8f3038a146" - - -- The icon for the plugin: - ICON_SVG = "" - - -- The name of the plugin: - NAME = "Collected I18N keys" - - -- The description of the plugin: - DESCRIPTION = "This plugin is not meant to be used directly. Its a collection of all I18N keys found in the project." - - -- The version of the plugin: - VERSION = "1.0.0" - - -- The type of the plugin: - TYPE = "LANGUAGE" - - -- The authors of the plugin: - AUTHORS = {"MindWork AI Community"} - - -- The support contact for the plugin: - SUPPORT_CONTACT = "MindWork AI Community" - - -- The source URL for the plugin: - SOURCE_URL = "https://github.com/MindWorkAI/AI-Studio" - - -- The categories for the plugin: - CATEGORIES = { "CORE" } - - -- The target groups for the plugin: - TARGET_GROUPS = { "EVERYONE" } - - -- The flag for whether the plugin is maintained: - IS_MAINTAINED = true - - -- When the plugin is deprecated, this message will be shown to users: - DEPRECATION_MESSAGE = "" - - -- The IETF BCP 47 tag for the language. It's the ISO 639 language - -- code followed by the ISO 3166-1 country code: - IETF_TAG = "en-US" - - -- The language name in the user's language: - LANG_NAME = "English (United States)" - - """); - - sbLua.Append("UI_TEXT_CONTENT = "); - if(root["UI_TEXT_CONTENT"] is Dictionary dict) - ToLuaTable(sbLua, dict); - - return sbLua.ToString(); + // Add the UI_TEXT_CONTENT table: + LuaTable.Create(ref sb, "UI_TEXT_CONTENT", keyValuePairs); + return sb.ToString(); } - + private List FindAllTextTags(ReadOnlySpan fileContent) { const string START_TAG = """ diff --git a/app/MindWork AI Studio.sln.DotSettings b/app/MindWork AI Studio.sln.DotSettings index a626186b..f8edfd31 100644 --- a/app/MindWork AI Studio.sln.DotSettings +++ b/app/MindWork AI Studio.sln.DotSettings @@ -13,6 +13,7 @@ RID UI URL + I18N True True True diff --git a/app/MindWork AI Studio/Assistants/AssistantBase.razor b/app/MindWork AI Studio/Assistants/AssistantBase.razor index 38d4b8b8..c27704c4 100644 --- a/app/MindWork AI Studio/Assistants/AssistantBase.razor +++ b/app/MindWork AI Studio/Assistants/AssistantBase.razor @@ -29,7 +29,7 @@ - + @this.SubmitText @if (this.isProcessing && this.cancellationTokenSource is not null) @@ -97,14 +97,14 @@ { case ButtonData buttonData when !string.IsNullOrWhiteSpace(buttonData.Tooltip): - + @buttonData.Text break; case ButtonData buttonData: - + @buttonData.Text break; diff --git a/app/MindWork AI Studio/Assistants/AssistantBase.razor.cs b/app/MindWork AI Studio/Assistants/AssistantBase.razor.cs index 1f0c7364..1a9ff997 100644 --- a/app/MindWork AI Studio/Assistants/AssistantBase.razor.cs +++ b/app/MindWork AI Studio/Assistants/AssistantBase.razor.cs @@ -97,10 +97,10 @@ public abstract partial class AssistantBase : AssistantLowerBase, IMe protected Profile currentProfile = Profile.NO_PROFILE; protected ChatThread? chatThread; protected IContent? lastUserPrompt; + protected CancellationTokenSource? cancellationTokenSource; private readonly Timer formChangeTimer = new(TimeSpan.FromSeconds(1.6)); - - private CancellationTokenSource? cancellationTokenSource; + private ContentBlock? resultingContentBlock; private string[] inputIssues = []; private bool isProcessing; @@ -179,6 +179,16 @@ public abstract partial class AssistantBase : AssistantLowerBase, IMe return null; } + private async Task Start() + { + using (this.cancellationTokenSource = new()) + { + await this.SubmitAction(); + } + + this.cancellationTokenSource = null; + } + private void TriggerFormChange(FormFieldChangedEventArgs _) { this.formChangeTimer.Stop(); @@ -286,16 +296,12 @@ public abstract partial class AssistantBase : AssistantLowerBase, IMe this.isProcessing = true; this.StateHasChanged(); - - using (this.cancellationTokenSource = new()) - { - // Use the selected provider to get the AI response. - // By awaiting this line, we wait for the entire - // content to be streamed. - this.chatThread = await aiText.CreateFromProviderAsync(this.providerSettings.CreateProvider(this.Logger), this.providerSettings.Model, this.lastUserPrompt, this.chatThread, this.cancellationTokenSource.Token); - } - - this.cancellationTokenSource = null; + + // Use the selected provider to get the AI response. + // By awaiting this line, we wait for the entire + // content to be streamed. + this.chatThread = await aiText.CreateFromProviderAsync(this.providerSettings.CreateProvider(this.Logger), this.providerSettings.Model, this.lastUserPrompt, this.chatThread, this.cancellationTokenSource!.Token); + this.isProcessing = false; this.StateHasChanged(); diff --git a/app/MindWork AI Studio/Assistants/I18N/AssistantI18N.razor b/app/MindWork AI Studio/Assistants/I18N/AssistantI18N.razor new file mode 100644 index 00000000..9f19d966 --- /dev/null +++ b/app/MindWork AI Studio/Assistants/I18N/AssistantI18N.razor @@ -0,0 +1,124 @@ +@attribute [Route(Routes.ASSISTANT_AI_STUDIO_I18N)] +@using AIStudio.Settings +@inherits AssistantBaseCore + + + +@if (this.isLoading) +{ + + The data is being loaded, please wait... + +} else if (!this.isLoading && !string.IsNullOrWhiteSpace(this.loadingIssue)) +{ + + While loading the I18N data, an issue occurred: @this.loadingIssue + +} +else if (!this.isLoading && string.IsNullOrWhiteSpace(this.loadingIssue)) +{ + + Added Content (@this.addedContent.Count entries) + + + + + + + + + + + Key + Text + + + +
+                    @context.Key
+                
+
+ + @context.Value + +
+ + + +
+ + + Removed Content (@this.removedContent.Count entries) + + + + + + + + + + + Key + Text + + + +
+                    @context.Key
+                
+
+ + @context.Value + +
+ + + +
+ + @if (this.selectedTargetLanguage is CommonLanguages.EN_US) + { + + Please note: neither is a translation needed nor performed for English (USA). Anyway, you might want to generate the related Lua code. + + } + else + { + + } + + @if (this.localizedContent.Count > 0) + { +
+ + Localized Content (@this.localizedContent.Count entries of @this.NumTotalItems) + + + + + + + + + + + Key + Text + + + +
+                    @context.Key
+                
+
+ + @context.Value + +
+ + + +
+ } +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Assistants/I18N/AssistantI18N.razor.cs b/app/MindWork AI Studio/Assistants/I18N/AssistantI18N.razor.cs new file mode 100644 index 00000000..3fc2b63c --- /dev/null +++ b/app/MindWork AI Studio/Assistants/I18N/AssistantI18N.razor.cs @@ -0,0 +1,351 @@ +using System.Diagnostics; +using System.Text; + +using AIStudio.Dialogs.Settings; +using AIStudio.Tools.PluginSystem; + +using Microsoft.Extensions.FileProviders; + +using SharedTools; + +namespace AIStudio.Assistants.I18N; + +public partial class AssistantI18N : AssistantBaseCore +{ + public override Tools.Components Component => Tools.Components.I18N_ASSISTANT; + + protected override string Title => "Localization"; + + protected override string Description => + """ + Translate MindWork AI Studio text content into another language. + """; + + protected override string SystemPrompt => + $""" + # Assignment + You are an expert in professional translations from English (US) to {this.SystemPromptLanguage()}. + You translate the texts without adding any new information. When necessary, you correct + spelling and grammar. + + # Context + The texts to be translated come from the open source app "MindWork AI Studio". The goal + is to localize the app so that it can be offered in other languages. You will always + receive one text at a time. A text may be, for example, for a button, a label, or an + explanation within the app. The app "AI Studio" is a desktop app for macOS, Linux, + and Windows. Users can use Large Language Models (LLMs) in practical ways in their + daily lives with it. The app offers the regular chat mode for which LLMs have become + known. However, AI Studio also offers so-called assistants, where users no longer + have to prompt. + + # Target Audience + The app is intended for everyone, not just IT specialists or scientists. When translating, + make sure the texts are easy for everyone to understand. + """; + + protected override bool AllowProfiles => false; + + protected override bool ShowResult => false; + + protected override bool ShowCopyResult => false; + + protected override bool ShowSendTo => false; + + protected override IReadOnlyList FooterButtons => + [ + new ButtonData + { + Text = "Copy Lua code to clipboard", + Icon = Icons.Material.Filled.Extension, + Color = Color.Default, + AsyncAction = async () => await this.RustService.CopyText2Clipboard(this.Snackbar, this.finalLuaCode.ToString()), + DisabledActionParam = () => this.finalLuaCode.Length == 0, + }, + ]; + + protected override string SubmitText => "Localize AI Studio & generate the Lua code"; + + protected override Func SubmitAction => this.LocalizeTextContent; + + protected override bool SubmitDisabled => !this.localizationPossible; + + protected override bool ShowDedicatedProgress => true; + + protected override void ResetForm() + { + if (!this.MightPreselectValues()) + { + this.selectedLanguagePluginId = InternalPlugin.LANGUAGE_EN_US.MetaData().Id; + this.selectedTargetLanguage = CommonLanguages.AS_IS; + this.customTargetLanguage = string.Empty; + } + + _ = this.OnChangedLanguage(); + } + + protected override bool MightPreselectValues() + { + if (this.SettingsManager.ConfigurationData.I18N.PreselectOptions) + { + this.selectedLanguagePluginId = this.SettingsManager.ConfigurationData.I18N.PreselectedLanguagePluginId; + this.selectedTargetLanguage = this.SettingsManager.ConfigurationData.I18N.PreselectedTargetLanguage; + this.customTargetLanguage = this.SettingsManager.ConfigurationData.I18N.PreselectOtherLanguage; + return true; + } + + return false; + } + + private CommonLanguages selectedTargetLanguage; + private string customTargetLanguage = string.Empty; + private bool isLoading = true; + private string loadingIssue = string.Empty; + private bool localizationPossible; + private string searchString = string.Empty; + private Guid selectedLanguagePluginId; + private ILanguagePlugin? selectedLanguagePlugin; + private Dictionary addedContent = []; + private Dictionary removedContent = []; + private Dictionary localizedContent = []; + private StringBuilder finalLuaCode = new(); + + #region Overrides of AssistantBase + + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + await this.OnLanguagePluginChanged(this.selectedLanguagePluginId); + await this.LoadData(); + } + + #endregion + + private string SystemPromptLanguage() => this.selectedTargetLanguage switch + { + CommonLanguages.OTHER => this.customTargetLanguage, + _ => $"{this.selectedTargetLanguage.Name()}", + }; + + private async Task OnLanguagePluginChanged(Guid pluginId) + { + this.selectedLanguagePluginId = pluginId; + await this.OnChangedLanguage(); + } + + private async Task OnChangedLanguage() + { + this.finalLuaCode.Clear(); + this.localizedContent.Clear(); + this.localizationPossible = false; + if (PluginFactory.RunningPlugins.FirstOrDefault(n => n is PluginLanguage && n.Id == this.selectedLanguagePluginId) is not PluginLanguage comparisonPlugin) + { + this.loadingIssue = $"Was not able to load the language plugin for comparison ({this.selectedLanguagePluginId}). Please select a valid, loaded & running language plugin."; + this.selectedLanguagePlugin = null; + } + else if (comparisonPlugin.IETFTag != this.selectedTargetLanguage.ToIETFTag()) + { + this.loadingIssue = $"The selected language plugin for comparison uses the IETF tag '{comparisonPlugin.IETFTag}' which does not match the selected target language '{this.selectedTargetLanguage.ToIETFTag()}'. Please select a valid, loaded & running language plugin which matches the target language."; + this.selectedLanguagePlugin = null; + } + else + { + this.selectedLanguagePlugin = comparisonPlugin; + this.loadingIssue = string.Empty; + await this.LoadData(); + } + + this.StateHasChanged(); + } + + private async Task LoadData() + { + if (this.selectedLanguagePlugin is null) + { + this.loadingIssue = "Please select a language plugin for comparison."; + this.localizationPossible = false; + this.isLoading = false; + this.StateHasChanged(); + return; + } + + this.isLoading = true; + this.StateHasChanged(); + + // + // Read the file `Assistants\I18N\allTexts.lua`: + // + #if DEBUG + var filePath = Path.Join(Environment.CurrentDirectory, "Assistants", "I18N"); + var resourceFileProvider = new PhysicalFileProvider(filePath); + #else + var resourceFileProvider = new ManifestEmbeddedFileProvider(Assembly.GetAssembly(type: typeof(Program))!, "Assistants.I18N"); + #endif + + var file = resourceFileProvider.GetFileInfo("allTexts.lua"); + await using var fileStream = file.CreateReadStream(); + using var reader = new StreamReader(fileStream); + var newI18NDataLuaCode = await reader.ReadToEndAsync(); + + // + // Next, we try to load the text as a language plugin -- without + // actually starting the plugin: + // + var newI18NPlugin = await PluginFactory.Load(null, newI18NDataLuaCode); + switch (newI18NPlugin) + { + case NoPlugin noPlugin when noPlugin.Issues.Any(): + this.loadingIssue = noPlugin.Issues.First(); + break; + + case NoPlugin: + this.loadingIssue = "Was not able to load the I18N plugin. Please check the plugin code."; + break; + + case { IsValid: false } plugin when plugin.Issues.Any(): + this.loadingIssue = plugin.Issues.First(); + break; + + case PluginLanguage pluginLanguage: + this.loadingIssue = string.Empty; + var newI18NContent = pluginLanguage.Content; + + var currentI18NContent = this.selectedLanguagePlugin.Content; + this.addedContent = newI18NContent.ExceptBy(currentI18NContent.Keys, n => n.Key).ToDictionary(); + this.removedContent = currentI18NContent.ExceptBy(newI18NContent.Keys, n => n.Key).ToDictionary(); + this.localizationPossible = true; + break; + } + + this.isLoading = false; + this.StateHasChanged(); + } + + private bool FilterFunc(KeyValuePair element) + { + if (string.IsNullOrWhiteSpace(this.searchString)) + return true; + + if (element.Key.Contains(this.searchString, StringComparison.OrdinalIgnoreCase)) + return true; + + if (element.Value.Contains(this.searchString, StringComparison.OrdinalIgnoreCase)) + return true; + + return false; + } + + private string? ValidatingTargetLanguage(CommonLanguages language) + { + if(language == CommonLanguages.AS_IS) + return "Please select a target language."; + + return null; + } + + private string? ValidateCustomLanguage(string language) + { + if(this.selectedTargetLanguage == CommonLanguages.OTHER && string.IsNullOrWhiteSpace(language)) + return "Please provide a custom language."; + + return null; + } + + private int NumTotalItems => (this.selectedLanguagePlugin?.Content.Count ?? 0) + this.addedContent.Count - this.removedContent.Count; + + private async Task LocalizeTextContent() + { + await this.form!.Validate(); + if (!this.inputIsValid) + return; + + if(this.selectedLanguagePlugin is null) + return; + + if (this.selectedLanguagePlugin.IETFTag != this.selectedTargetLanguage.ToIETFTag()) + return; + + this.localizedContent.Clear(); + if (this.selectedTargetLanguage is not CommonLanguages.EN_US) + { + // Phase 1: Translate added content + await this.Phase1TranslateAddedContent(); + } + else + { + // Case: no translation needed + this.localizedContent = this.addedContent.ToDictionary(); + } + + if(this.cancellationTokenSource!.IsCancellationRequested) + return; + + // + // Now, we have localized the added content. Next, we must merge + // the localized content with the existing content. However, we + // must skip the removed content. We use the localizedContent + // dictionary for the final result: + // + foreach (var keyValuePair in this.selectedLanguagePlugin.Content) + { + if (this.cancellationTokenSource!.IsCancellationRequested) + break; + + if (this.localizedContent.ContainsKey(keyValuePair.Key)) + continue; + + if (this.removedContent.ContainsKey(keyValuePair.Key)) + continue; + + this.localizedContent.Add(keyValuePair.Key, keyValuePair.Value); + } + + if(this.cancellationTokenSource!.IsCancellationRequested) + return; + + // Phase 2: Create the Lua code + this.Phase2CreateLuaCode(); + } + + private async Task Phase1TranslateAddedContent() + { + var stopwatch = new Stopwatch(); + var minimumTime = TimeSpan.FromMilliseconds(500); + foreach (var keyValuePair in this.addedContent) + { + if(this.cancellationTokenSource!.IsCancellationRequested) + break; + + // + // We measure the time for each translation. + // We do not want to make more than 120 requests + // per minute, i.e., 2 requests per second. + // + stopwatch.Reset(); + stopwatch.Start(); + + // + // Translate one text at a time: + // + this.CreateChatThread(); + var time = this.AddUserRequest(keyValuePair.Value); + this.localizedContent.Add(keyValuePair.Key, await this.AddAIResponseAsync(time)); + + if (this.cancellationTokenSource!.IsCancellationRequested) + break; + + // + // Ensure that we do not exceed the rate limit of 2 requests per second: + // + stopwatch.Stop(); + if (stopwatch.Elapsed < minimumTime) + await Task.Delay(minimumTime - stopwatch.Elapsed); + } + } + + private void Phase2CreateLuaCode() + { + this.finalLuaCode.Clear(); + var commentContent = this.addedContent.Concat(PluginFactory.BaseLanguage.Content).ToDictionary(); + LuaTable.Create(ref this.finalLuaCode, "UI_TEXT_CONTENT", this.localizedContent, commentContent, this.cancellationTokenSource!.Token); + } +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Components/MSGComponentBase.cs b/app/MindWork AI Studio/Components/MSGComponentBase.cs index 2ad3b96e..65d770c1 100644 --- a/app/MindWork AI Studio/Components/MSGComponentBase.cs +++ b/app/MindWork AI Studio/Components/MSGComponentBase.cs @@ -14,6 +14,7 @@ public abstract class MSGComponentBase : ComponentBase, IDisposable, IMessageBus protected MessageBus MessageBus { get; init; } = null!; [Inject] + // ReSharper disable once UnusedAutoPropertyAccessor.Local private ILogger Logger { get; init; } = null!; private ILanguagePlugin Lang { get; set; } = PluginFactory.BaseLanguage; diff --git a/app/MindWork AI Studio/Dialogs/ProviderDialog.razor b/app/MindWork AI Studio/Dialogs/ProviderDialog.razor index 439a2dea..6e28d6fe 100644 --- a/app/MindWork AI Studio/Dialogs/ProviderDialog.razor +++ b/app/MindWork AI Studio/Dialogs/ProviderDialog.razor @@ -68,11 +68,12 @@ @inferenceProvider.ToName() - } + } - + @* ReSharper disable Asp.Entity *@ Please double-check if your model name matches the curl specifications provided by the inference provider. If it doesn't, you might get a Not Found error when trying to use the model. Here's a curl example. - } + @* ReSharper restore Asp.Entity *@ + } diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogAssistantBias.razor b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogAssistantBias.razor index 389a6719..ec6776f0 100644 --- a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogAssistantBias.razor +++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogAssistantBias.razor @@ -1,5 +1,4 @@ @using AIStudio.Settings -@using AIStudio.Settings.DataModel @inherits SettingsDialogBase diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogI18N.razor b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogI18N.razor new file mode 100644 index 00000000..6a89328b --- /dev/null +++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogI18N.razor @@ -0,0 +1,28 @@ +@using AIStudio.Settings +@inherits SettingsDialogBase + + + + + + @T("Assistant: Localization") + + + + + + + @if (this.SettingsManager.ConfigurationData.I18N.PreselectedTargetLanguage is CommonLanguages.OTHER) + { + + } + + + + + + + @T("Close") + + + \ No newline at end of file diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogI18N.razor.cs b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogI18N.razor.cs new file mode 100644 index 00000000..22cbbfb9 --- /dev/null +++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogI18N.razor.cs @@ -0,0 +1,5 @@ +namespace AIStudio.Dialogs.Settings; + +public partial class SettingsDialogI18N : SettingsDialogBase +{ +} \ No newline at end of file diff --git a/app/MindWork AI Studio/MindWork AI Studio.csproj b/app/MindWork AI Studio/MindWork AI Studio.csproj index 2480a8fe..267f377e 100644 --- a/app/MindWork AI Studio/MindWork AI Studio.csproj +++ b/app/MindWork AI Studio/MindWork AI Studio.csproj @@ -61,10 +61,6 @@ - - - - diff --git a/app/MindWork AI Studio/Pages/Assistants.razor b/app/MindWork AI Studio/Pages/Assistants.razor index 2acaa4ca..8106ad83 100644 --- a/app/MindWork AI Studio/Pages/Assistants.razor +++ b/app/MindWork AI Studio/Pages/Assistants.razor @@ -50,6 +50,16 @@ } - + + @if (PreviewFeatures.PRE_PLUGINS_2025.IsEnabled(this.SettingsManager)) + { + + AI Studio Development + + + + + } +
\ No newline at end of file diff --git a/app/MindWork AI Studio/Pages/Supporters.razor.cs b/app/MindWork AI Studio/Pages/Supporters.razor.cs index dcd937c9..bb82da0b 100644 --- a/app/MindWork AI Studio/Pages/Supporters.razor.cs +++ b/app/MindWork AI Studio/Pages/Supporters.razor.cs @@ -1,7 +1,5 @@ using AIStudio.Components; -using Microsoft.AspNetCore.Components; - namespace AIStudio.Pages; public partial class Supporters : MSGComponentBase; \ No newline at end of file diff --git a/app/MindWork AI Studio/Routes.razor.cs b/app/MindWork AI Studio/Routes.razor.cs index b6318820..d59bffac 100644 --- a/app/MindWork AI Studio/Routes.razor.cs +++ b/app/MindWork AI Studio/Routes.razor.cs @@ -26,5 +26,6 @@ public sealed partial class Routes public const string ASSISTANT_JOB_POSTING = "/assistant/job-posting"; public const string ASSISTANT_BIAS = "/assistant/bias-of-the-day"; public const string ASSISTANT_ERI = "/assistant/eri"; + public const string ASSISTANT_AI_STUDIO_I18N = "/assistant/ai-studio/i18n"; // ReSharper restore InconsistentNaming } \ No newline at end of file diff --git a/app/MindWork AI Studio/Settings/DataModel/Data.cs b/app/MindWork AI Studio/Settings/DataModel/Data.cs index b47eba49..439427bc 100644 --- a/app/MindWork AI Studio/Settings/DataModel/Data.cs +++ b/app/MindWork AI Studio/Settings/DataModel/Data.cs @@ -100,4 +100,6 @@ public sealed class Data public DataJobPostings JobPostings { get; init; } = new(); public DataBiasOfTheDay BiasOfTheDay { get; init; } = new(); + + public DataI18N I18N { get; init; } = new(); } \ No newline at end of file diff --git a/app/MindWork AI Studio/Settings/DataModel/DataI18N.cs b/app/MindWork AI Studio/Settings/DataModel/DataI18N.cs new file mode 100644 index 00000000..7f8ddb19 --- /dev/null +++ b/app/MindWork AI Studio/Settings/DataModel/DataI18N.cs @@ -0,0 +1,29 @@ +namespace AIStudio.Settings.DataModel; + +public class DataI18N +{ + /// + /// Preselect any I18N options? + /// + public bool PreselectOptions { get; set; } + + /// + /// Preselect a language plugin to where the new content should compare to? + /// + public Guid PreselectedLanguagePluginId { get; set; } + + /// + /// Preselect the target language? + /// + public CommonLanguages PreselectedTargetLanguage { get; set; } = CommonLanguages.EN_GB; + + /// + /// Preselect any other language? + /// + public string PreselectOtherLanguage { get; set; } = string.Empty; + + /// + /// Which LLM provider should be preselected? + /// + public string PreselectedProvider { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/ButtonData.cs b/app/MindWork AI Studio/Tools/ButtonData.cs index 3d05dee8..da0c69c3 100644 --- a/app/MindWork AI Studio/Tools/ButtonData.cs +++ b/app/MindWork AI Studio/Tools/ButtonData.cs @@ -1,6 +1,21 @@ namespace AIStudio.Tools; -public readonly record struct ButtonData(string Text, string Icon, Color Color, string Tooltip, Func AsyncAction) : IButtonData +public readonly record struct ButtonData(string Text, string Icon, Color Color, string Tooltip, Func AsyncAction, Func? DisabledActionParam) : IButtonData { public ButtonTypes Type => ButtonTypes.BUTTON; + + public Func DisabledAction + { + get + { + var data = this; + return () => + { + if (data.DisabledActionParam is null) + return false; + + return data.DisabledActionParam(); + }; + } + } } \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/CommonLanguageExtensions.cs b/app/MindWork AI Studio/Tools/CommonLanguageExtensions.cs index 96dfafe0..39de88e8 100644 --- a/app/MindWork AI Studio/Tools/CommonLanguageExtensions.cs +++ b/app/MindWork AI Studio/Tools/CommonLanguageExtensions.cs @@ -20,6 +20,24 @@ public static class CommonLanguageExtensions _ => "Other", }; + public static string ToIETFTag(this CommonLanguages language) => language switch + { + CommonLanguages.AS_IS => string.Empty, + + CommonLanguages.EN_US => "en-US", + CommonLanguages.EN_GB => "en-GB", + CommonLanguages.ZH_CN => "zh-CN", + CommonLanguages.HI_IN => "hi-IN", + CommonLanguages.ES_ES => "es-ES", + CommonLanguages.FR_FR => "fr-FR", + CommonLanguages.DE_DE => "de-DE", + CommonLanguages.DE_AT => "de-AT", + CommonLanguages.DE_CH => "de-CH", + CommonLanguages.JA_JP => "ja-JP", + + _ => string.Empty, + }; + public static string PromptSummarizing(this CommonLanguages language, string customLanguage) => language switch { CommonLanguages.AS_IS => "Do not change the language of the text.", diff --git a/app/MindWork AI Studio/Tools/Components.cs b/app/MindWork AI Studio/Tools/Components.cs index d65a5c5d..94148d5e 100644 --- a/app/MindWork AI Studio/Tools/Components.cs +++ b/app/MindWork AI Studio/Tools/Components.cs @@ -19,6 +19,10 @@ public enum Components BIAS_DAY_ASSISTANT, ERI_ASSISTANT, + // ReSharper disable InconsistentNaming + I18N_ASSISTANT, + // ReSharper restore InconsistentNaming + CHAT, APP_SETTINGS, diff --git a/app/MindWork AI Studio/Tools/ComponentsExtensions.cs b/app/MindWork AI Studio/Tools/ComponentsExtensions.cs index 6112debb..2247b6e6 100644 --- a/app/MindWork AI Studio/Tools/ComponentsExtensions.cs +++ b/app/MindWork AI Studio/Tools/ComponentsExtensions.cs @@ -10,8 +10,11 @@ public static class ComponentsExtensions public static bool AllowSendTo(this Components component) => component switch { Components.NONE => false, + Components.ERI_ASSISTANT => false, Components.BIAS_DAY_ASSISTANT => false, + Components.I18N_ASSISTANT => false, + Components.APP_SETTINGS => false, Components.AGENT_TEXT_CONTENT_CLEANER => false, @@ -36,6 +39,7 @@ public static class ComponentsExtensions Components.MY_TASKS_ASSISTANT => "My Tasks Assistant", Components.JOB_POSTING_ASSISTANT => "Job Posting Assistant", Components.ERI_ASSISTANT => "ERI Server", + Components.I18N_ASSISTANT => "Localization Assistant", Components.CHAT => "New Chat", @@ -99,6 +103,7 @@ public static class ComponentsExtensions Components.JOB_POSTING_ASSISTANT => settingsManager.ConfigurationData.JobPostings.PreselectOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.JobPostings.PreselectedProvider) : default, Components.BIAS_DAY_ASSISTANT => settingsManager.ConfigurationData.BiasOfTheDay.PreselectOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.BiasOfTheDay.PreselectedProvider) : default, Components.ERI_ASSISTANT => settingsManager.ConfigurationData.ERI.PreselectOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.ERI.PreselectedProvider) : default, + Components.I18N_ASSISTANT => settingsManager.ConfigurationData.I18N.PreselectOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.I18N.PreselectedProvider) : default, Components.CHAT => settingsManager.ConfigurationData.Chat.PreselectOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.Chat.PreselectedProvider) : default, diff --git a/app/MindWork AI Studio/Tools/PluginSystem/ILanguagePlugin.cs b/app/MindWork AI Studio/Tools/PluginSystem/ILanguagePlugin.cs index 4f214d68..869331da 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/ILanguagePlugin.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/ILanguagePlugin.cs @@ -29,4 +29,9 @@ public interface ILanguagePlugin /// Gets the name of the language. ///
public string LangName { get; } + + /// + /// Get all keys and texts from the language plugin. + /// + public IReadOnlyDictionary Content { get; } } \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/PluginSystem/NoPluginLanguage.cs b/app/MindWork AI Studio/Tools/PluginSystem/NoPluginLanguage.cs index 26d35849..bb8ec4fc 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/NoPluginLanguage.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/NoPluginLanguage.cs @@ -22,5 +22,7 @@ public sealed class NoPluginLanguage : PluginBase, ILanguagePlugin public string LangName => string.Empty; + public IReadOnlyDictionary Content => new Dictionary(); + #endregion } \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs index b6a39b1f..fd9329cc 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs @@ -101,16 +101,18 @@ public static partial class PluginFactory } } - private static async Task Load(string pluginPath, string code, CancellationToken cancellationToken = default) + public static async Task Load(string? pluginPath, string code, CancellationToken cancellationToken = default) { if(ForbiddenPlugins.Check(code) is { IsForbidden: true } forbiddenState) return new NoPlugin($"This plugin is forbidden: {forbiddenState.Message}"); var state = LuaState.Create(); - - // Add the module loader so that the plugin can load other Lua modules: - state.ModuleLoader = new PluginLoader(pluginPath); - + if (!string.IsNullOrWhiteSpace(pluginPath)) + { + // Add the module loader so that the plugin can load other Lua modules: + state.ModuleLoader = new PluginLoader(pluginPath); + } + // Add some useful libraries: state.OpenModuleLibrary(); state.OpenStringLibrary(); @@ -141,7 +143,7 @@ public static partial class PluginFactory if(type is PluginType.NONE) return new NoPlugin($"TYPE is not a valid plugin type. Valid types are: {CommonTools.GetAllEnumValues()}"); - var isInternal = pluginPath.StartsWith(INTERNAL_PLUGINS_ROOT, StringComparison.OrdinalIgnoreCase); + var isInternal = !string.IsNullOrWhiteSpace(pluginPath) && pluginPath.StartsWith(INTERNAL_PLUGINS_ROOT, StringComparison.OrdinalIgnoreCase); return type switch { PluginType.LANGUAGE => new PluginLanguage(isInternal, state, type), diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginLanguage.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginLanguage.cs index 1c201a11..f1a8ce5b 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginLanguage.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginLanguage.cs @@ -165,6 +165,9 @@ public sealed class PluginLanguage : PluginBase, ILanguagePlugin /// public string LangName => this.langName; + + /// + public IReadOnlyDictionary Content => this.content.AsReadOnly(); #endregion } \ No newline at end of file diff --git a/app/MindWork AI Studio/wwwroot/changelog/v0.9.41.md b/app/MindWork AI Studio/wwwroot/changelog/v0.9.41.md index 2e5fb449..51c24900 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v0.9.41.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v0.9.41.md @@ -1,5 +1,6 @@ # v0.9.41, build 216 (2025-0x-xx xx:xx UTC) - Added the user-language, as provided by the OS, to the about page. This helps in identifying user-specific issues related to language settings. +- Added the localization assistant as a preview feature behind the plugin preview flag. This helps AI Studio developers to translate AI Studio to different languages. - Changed the terminology from "temporary chats" to "disappearing chats" in the UI. This makes it clearer to understand the purpose of these chats. - Improved the hot reloading of the plugin system to prevent overlapping reloads. - Improved the app behavior when the user system was waked up from sleep mode. diff --git a/app/SharedTools/LuaTable.cs b/app/SharedTools/LuaTable.cs new file mode 100644 index 00000000..16d092b4 --- /dev/null +++ b/app/SharedTools/LuaTable.cs @@ -0,0 +1,41 @@ +using System.Text; + +namespace SharedTools; + +public static class LuaTable +{ + public static string Create(ref StringBuilder sb, string tableVariableName, IReadOnlyDictionary keyValuePairs, IReadOnlyDictionary? commentContent = null, CancellationToken cancellationToken = default) + { + // + // Add the UI_TEXT_CONTENT table: + // + sb.AppendLine($$"""{{tableVariableName}} = {}"""); + foreach (var kvp in keyValuePairs.OrderBy(x => x.Key)) + { + if (cancellationToken.IsCancellationRequested) + return sb.ToString(); + + var key = kvp.Key; + var value = kvp.Value.Replace("\n", " ").Trim(); + var commentValue = commentContent is null ? value : commentContent.GetValueOrDefault(key, value); + + // Remove the "UI_TEXT_CONTENT." prefix from the key: + const string UI_TEXT_CONTENT = "UI_TEXT_CONTENT."; + var keyWithoutPrefix = key.StartsWith(UI_TEXT_CONTENT, StringComparison.OrdinalIgnoreCase) ? key[UI_TEXT_CONTENT.Length..] : key; + + // Replace all dots in the key with colons: + keyWithoutPrefix = keyWithoutPrefix.Replace(".", "::"); + + // Add a comment with the original text content: + sb.AppendLine(); + sb.AppendLine($"-- {commentContent}"); + + // Add the assignment to the UI_TEXT_CONTENT table: + sb.AppendLine($""" + UI_TEXT_CONTENT["{keyWithoutPrefix}"] = "{LuaTools.EscapeLuaString(value)}" + """); + } + + return sb.ToString(); + } +} \ No newline at end of file diff --git a/app/SharedTools/LuaTools.cs b/app/SharedTools/LuaTools.cs new file mode 100644 index 00000000..53bd07c6 --- /dev/null +++ b/app/SharedTools/LuaTools.cs @@ -0,0 +1,10 @@ +namespace SharedTools; + +public static class LuaTools +{ + public static string EscapeLuaString(string value) + { + // Replace backslashes with double backslashes and escape double quotes: + return value.Replace("\\", @"\\").Replace("\"", "\\\""); + } +} \ No newline at end of file From cdc155890f0c95bb65bc3b367cd621c1e2f4cfee Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sat, 26 Apr 2025 19:00:10 +0200 Subject: [PATCH 33/93] Fixed release builds (#426) --- app/MindWork AI Studio/Assistants/I18N/AssistantI18N.razor.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/MindWork AI Studio/Assistants/I18N/AssistantI18N.razor.cs b/app/MindWork AI Studio/Assistants/I18N/AssistantI18N.razor.cs index 3fc2b63c..14dea5d7 100644 --- a/app/MindWork AI Studio/Assistants/I18N/AssistantI18N.razor.cs +++ b/app/MindWork AI Studio/Assistants/I18N/AssistantI18N.razor.cs @@ -8,6 +8,10 @@ using Microsoft.Extensions.FileProviders; using SharedTools; +#if RELEASE +using System.Reflection; +#endif + namespace AIStudio.Assistants.I18N; public partial class AssistantI18N : AssistantBaseCore From d272d619cb01f67e65c3bbcf1ba942ab83494b4d Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sat, 26 Apr 2025 19:51:21 +0200 Subject: [PATCH 34/93] Marked I18N assistant task as completed in README (#427) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8ec2036c..d935f019 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ Things we are currently working on: - [x] ~~Start the plugin system ([PR #372](https://github.com/MindWorkAI/AI-Studio/pull/372))~~ - [x] ~~Added hot-reload support for plugins ([PR #377](https://github.com/MindWorkAI/AI-Studio/pull/377), [PR #391](https://github.com/MindWorkAI/AI-Studio/pull/391))~~ - [ ] Add support for other languages (I18N) to AI Studio (~~[PR #381](https://github.com/MindWorkAI/AI-Studio/pull/381), [PR #400](https://github.com/MindWorkAI/AI-Studio/pull/400), [PR #404](https://github.com/MindWorkAI/AI-Studio/pull/404))~~ - - [ ] Add an I18N assistant to translate all AI Studio texts to a certain language & culture ([PR #422](https://github.com/MindWorkAI/AI-Studio/pull/422)) + - [x] ~~Add an I18N assistant to translate all AI Studio texts to a certain language & culture ([PR #422](https://github.com/MindWorkAI/AI-Studio/pull/422))~~ - [ ] Provide MindWork AI Studio in German ([#31](https://github.com/MindWorkAI/Planning/issues/31)) - [ ] Add configuration plugins, which allow pre-defining some LLM providers in organizations - [ ] Add an app store for plugins, showcasing community-contributed plugins from public GitHub and GitLab repositories. This will enable AI Studio users to discover, install, and update plugins directly within the platform. From 90bd450193b1238cb73bf01518043bedc4b9ac0e Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sat, 26 Apr 2025 19:55:01 +0200 Subject: [PATCH 35/93] Fixed Lua code generation by using the correct data for comments (#428) --- app/SharedTools/LuaTable.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/SharedTools/LuaTable.cs b/app/SharedTools/LuaTable.cs index 16d092b4..e03eea55 100644 --- a/app/SharedTools/LuaTable.cs +++ b/app/SharedTools/LuaTable.cs @@ -28,7 +28,7 @@ public static class LuaTable // Add a comment with the original text content: sb.AppendLine(); - sb.AppendLine($"-- {commentContent}"); + sb.AppendLine($"-- {commentValue}"); // Add the assignment to the UI_TEXT_CONTENT table: sb.AppendLine($""" From 2494ee22945cca849fe74dc09c1e809c832f3f4c Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sun, 27 Apr 2025 09:06:05 +0200 Subject: [PATCH 36/93] Create I18N data, part 2 (#429) --- README.md | 2 +- app/Build/Commands/CollectI18NKeysCommand.cs | 11 +- .../Assistants/AssistantBase.razor.cs | 42 +- .../Assistants/AssistantLowerBase.cs | 4 +- .../Assistants/EMail/AssistantEMail.razor | 20 +- .../Assistants/EMail/AssistantEMail.razor.cs | 21 +- .../AssistantGrammarSpelling.razor | 4 +- .../AssistantGrammarSpelling.razor.cs | 13 +- .../Assistants/I18N/AssistantI18N.razor.cs | 5 + .../Assistants/I18N/allTexts.lua | 2438 ++++++++++++++--- .../IconFinder/AssistantIconFinder.razor | 12 +- .../IconFinder/AssistantIconFinder.razor.cs | 16 +- .../LegalCheck/AssistantLegalCheck.razor | 4 +- .../LegalCheck/AssistantLegalCheck.razor.cs | 15 +- .../Assistants/MyTasks/AssistantMyTasks.razor | 4 +- .../MyTasks/AssistantMyTasks.razor.cs | 18 +- .../AssistantRewriteImprove.razor | 8 +- .../AssistantRewriteImprove.razor.cs | 13 +- .../Synonym/AssistantSynonyms.razor | 6 +- .../Synonym/AssistantSynonyms.razor.cs | 13 +- .../AssistantTextSummarizer.razor | 6 +- .../AssistantTextSummarizer.razor.cs | 18 +- .../Translation/AssistantTranslation.razor | 8 +- .../Translation/AssistantTranslation.razor.cs | 15 +- .../Chat/ContentBlockComponent.razor | 18 +- .../Chat/ContentBlockComponent.razor.cs | 33 +- .../Components/AssistantBlock.razor | 1 + .../Components/AssistantBlock.razor.cs | 56 +- .../Components/Changelog.razor | 3 +- .../Components/Changelog.razor.cs | 2 +- .../Components/ChatComponent.razor | 6 +- .../Components/ChatComponent.razor.cs | 26 +- .../Components/ConfidenceInfo.razor | 26 +- .../Components/ConfidenceInfo.razor.cs | 45 +- .../Components/ConfigurationBase.razor | 1 + .../Components/ConfigurationBase.razor.cs | 39 +- .../ConfigurationMinConfidenceSelection.razor | 3 +- ...nfigurationMinConfidenceSelection.razor.cs | 6 +- .../Components/ConfigurationMultiSelect.razor | 4 +- .../ConfigurationMultiSelect.razor.cs | 20 +- .../ConfigurationProviderSelection.razor | 3 +- .../ConfigurationProviderSelection.razor.cs | 37 +- .../Components/ExpansionPanel.razor | 4 +- .../Components/Issues.razor | 5 +- .../Components/Issues.razor.cs | 2 +- .../Components/MSGComponentBase.cs | 5 + .../Components/Motivation.razor | 17 +- .../Components/Motivation.razor.cs | 6 +- .../Components/PreviewAlpha.razor | 9 +- .../Components/PreviewAlpha.razor.cs | 2 +- .../Components/PreviewBeta.razor | 8 +- .../Components/PreviewBeta.razor.cs | 2 +- .../Components/PreviewExperimental.razor | 11 +- .../Components/PreviewExperimental.razor.cs | 2 +- .../Components/PreviewPrototype.razor | 10 +- .../Components/PreviewPrototype.razor.cs | 2 +- .../Components/PreviewReleaseCandidate.razor | 8 +- .../PreviewReleaseCandidate.razor.cs | 2 +- .../Components/ProfileFormSelection.razor | 4 +- .../Components/ProfileFormSelection.razor.cs | 5 +- .../Components/ProfileSelection.razor | 3 +- .../Components/ProfileSelection.razor.cs | 5 +- .../Components/ProviderSelection.razor | 4 +- .../Components/ProviderSelection.razor.cs | 6 +- .../Components/ReadWebContent.razor | 9 +- .../Components/ReadWebContent.razor.cs | 14 +- .../Settings/SettingsPanelApp.razor | 24 +- .../Components/Settings/SettingsPanelBase.cs | 8 +- .../Settings/SettingsPanelChat.razor | 18 +- .../Settings/SettingsPanelProfiles.razor | 29 +- .../Settings/SettingsPanelProfiles.razor.cs | 8 +- .../Settings/SettingsPanelProviders.razor | 53 +- .../Settings/SettingsPanelProviders.razor.cs | 14 +- .../Settings/SettingsPanelWorkspaces.razor | 8 +- .../Components/TextInfoLine.razor | 1 + .../Components/TextInfoLine.razor.cs | 8 +- .../Components/TextInfoLines.razor | 1 + .../Components/TextInfoLines.razor.cs | 8 +- .../Components/ThirdPartyComponent.razor | 11 +- .../Components/ThirdPartyComponent.razor.cs | 2 +- .../Components/Vision.razor | 9 +- .../Components/Vision.razor.cs | 35 +- .../Components/Workspaces.razor | 23 +- .../Components/Workspaces.razor.cs | 54 +- .../Layout/MainLayout.razor.cs | 3 - .../contentHome.lua | 3 - .../plugin.lua | 2225 ++++++++++++++- app/MindWork AI Studio/Provider/Confidence.cs | 5 +- .../Settings/SettingsManager.cs | 15 +- .../PluginSystem/PluginFactory.Loading.cs | 3 +- 90 files changed, 4648 insertions(+), 1110 deletions(-) delete mode 100644 app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/contentHome.lua diff --git a/README.md b/README.md index d935f019..d1955382 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Things we are currently working on: - [x] ~~Plan & implement the base plugin system ([PR #322](https://github.com/MindWorkAI/AI-Studio/pull/322))~~ - [x] ~~Start the plugin system ([PR #372](https://github.com/MindWorkAI/AI-Studio/pull/372))~~ - [x] ~~Added hot-reload support for plugins ([PR #377](https://github.com/MindWorkAI/AI-Studio/pull/377), [PR #391](https://github.com/MindWorkAI/AI-Studio/pull/391))~~ - - [ ] Add support for other languages (I18N) to AI Studio (~~[PR #381](https://github.com/MindWorkAI/AI-Studio/pull/381), [PR #400](https://github.com/MindWorkAI/AI-Studio/pull/400), [PR #404](https://github.com/MindWorkAI/AI-Studio/pull/404))~~ + - [ ] Add support for other languages (I18N) to AI Studio (~~[PR #381](https://github.com/MindWorkAI/AI-Studio/pull/381), [PR #400](https://github.com/MindWorkAI/AI-Studio/pull/400), [PR #404](https://github.com/MindWorkAI/AI-Studio/pull/404), [PR #429](https://github.com/MindWorkAI/AI-Studio/pull/429))~~ - [x] ~~Add an I18N assistant to translate all AI Studio texts to a certain language & culture ([PR #422](https://github.com/MindWorkAI/AI-Studio/pull/422))~~ - [ ] Provide MindWork AI Studio in German ([#31](https://github.com/MindWorkAI/Planning/issues/31)) - [ ] Add configuration plugins, which allow pre-defining some LLM providers in organizations diff --git a/app/Build/Commands/CollectI18NKeysCommand.cs b/app/Build/Commands/CollectI18NKeysCommand.cs index 896c606c..f7a533f9 100644 --- a/app/Build/Commands/CollectI18NKeysCommand.cs +++ b/app/Build/Commands/CollectI18NKeysCommand.cs @@ -146,14 +146,23 @@ public sealed partial class CollectI18NKeysCommand var content = fileContent; while (startIdx > -1) { + // + // In some cases, after the initial " there follow more " characters. + // We need to skip them: + // content = content[(startIdx + START_TAG.Length)..]; + while(content[0] == '"') + content = content[1..]; + var endIdx = content.IndexOf(END_TAG); if (endIdx == -1) break; var match = content[..endIdx]; - matches.Add(match.ToString()); + while (match[^1] == '"') + match = match[..^1]; + matches.Add(match.ToString()); startIdx = content.IndexOf(START_TAG); } diff --git a/app/MindWork AI Studio/Assistants/AssistantBase.razor.cs b/app/MindWork AI Studio/Assistants/AssistantBase.razor.cs index 1a9ff997..877374ad 100644 --- a/app/MindWork AI Studio/Assistants/AssistantBase.razor.cs +++ b/app/MindWork AI Studio/Assistants/AssistantBase.razor.cs @@ -13,11 +13,8 @@ using DialogOptions = AIStudio.Dialogs.DialogOptions; namespace AIStudio.Assistants; -public abstract partial class AssistantBase : AssistantLowerBase, IMessageBusReceiver, IDisposable where TSettings : IComponent +public abstract partial class AssistantBase : AssistantLowerBase where TSettings : IComponent { - [Inject] - protected SettingsManager SettingsManager { get; init; } = null!; - [Inject] private IDialogService DialogService { get; init; } = null!; @@ -42,9 +39,6 @@ public abstract partial class AssistantBase : AssistantLowerBase, IMe [Inject] private MudTheme ColorTheme { get; init; } = null!; - [Inject] - private MessageBus MessageBus { get; init; } = null!; - protected abstract string Title { get; } protected abstract string Description { get; } @@ -119,10 +113,6 @@ public abstract partial class AssistantBase : AssistantLowerBase, IMe this.MightPreselectValues(); this.providerSettings = this.SettingsManager.GetPreselectedProvider(this.Component); this.currentProfile = this.SettingsManager.GetPreselectedProfile(this.Component); - - this.MessageBus.RegisterComponent(this); - this.MessageBus.ApplyFilters(this, [], [ Event.COLOR_THEME_CHANGED ]); - await base.OnInitializedAsync(); } @@ -144,29 +134,6 @@ public abstract partial class AssistantBase : AssistantLowerBase, IMe await base.OnAfterRenderAsync(firstRender); } - #endregion - - #region Implementation of IMessageBusReceiver - - public string ComponentName => nameof(AssistantBase); - - public Task ProcessMessage(ComponentBase? sendingComponent, Event triggeredEvent, T? data) - { - switch (triggeredEvent) - { - case Event.COLOR_THEME_CHANGED: - this.StateHasChanged(); - break; - } - - return Task.CompletedTask; - } - - public Task ProcessMessageWithResult(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data) - { - return Task.FromResult(default); - } - #endregion private string SubmitButtonStyle => this.SettingsManager.ConfigurationData.LLMProviders.ShowProviderConfidence ? this.providerSettings.UsedLLMProvider.GetConfidence(this.SettingsManager).StyleBorder(this.SettingsManager) : string.Empty; @@ -226,7 +193,7 @@ public abstract partial class AssistantBase : AssistantLowerBase, IMe SystemPrompt = this.SystemPrompt, WorkspaceId = Guid.Empty, ChatId = Guid.NewGuid(), - Name = $"Assistant - {this.Title}", + Name = string.Format(T("Assistant - {0}"), this.Title), Seed = this.RNG.Next(), Blocks = [], }; @@ -399,11 +366,10 @@ public abstract partial class AssistantBase : AssistantLowerBase, IMe false => $"background-color: {this.ColorTheme.GetCurrentPalette(this.SettingsManager).InfoLighten}", }; - #region Implementation of IDisposable + #region Overrides of MSGComponentBase - public void Dispose() + protected override void DisposeResources() { - this.MessageBus.Unregister(this); this.formChangeTimer.Dispose(); } diff --git a/app/MindWork AI Studio/Assistants/AssistantLowerBase.cs b/app/MindWork AI Studio/Assistants/AssistantLowerBase.cs index 8e053015..2f9e804f 100644 --- a/app/MindWork AI Studio/Assistants/AssistantLowerBase.cs +++ b/app/MindWork AI Studio/Assistants/AssistantLowerBase.cs @@ -1,8 +1,8 @@ -using Microsoft.AspNetCore.Components; +using AIStudio.Components; namespace AIStudio.Assistants; -public abstract class AssistantLowerBase : ComponentBase +public abstract class AssistantLowerBase : MSGComponentBase { protected static readonly Dictionary USER_INPUT_ATTRIBUTES = new(); diff --git a/app/MindWork AI Studio/Assistants/EMail/AssistantEMail.razor b/app/MindWork AI Studio/Assistants/EMail/AssistantEMail.razor index 7a441dc0..9220f499 100644 --- a/app/MindWork AI Studio/Assistants/EMail/AssistantEMail.razor +++ b/app/MindWork AI Studio/Assistants/EMail/AssistantEMail.razor @@ -1,23 +1,25 @@ @attribute [Route(Routes.ASSISTANT_EMAIL)] @inherits AssistantBaseCore - + @if (this.provideHistory) { - + } - - - + + + @foreach (var contentLine in this.bulletPointsLines) { - @contentLine + + @contentLine + } - - - + + + \ No newline at end of file diff --git a/app/MindWork AI Studio/Assistants/EMail/AssistantEMail.razor.cs b/app/MindWork AI Studio/Assistants/EMail/AssistantEMail.razor.cs index 7a91f1c8..cc0629d0 100644 --- a/app/MindWork AI Studio/Assistants/EMail/AssistantEMail.razor.cs +++ b/app/MindWork AI Studio/Assistants/EMail/AssistantEMail.razor.cs @@ -9,12 +9,9 @@ public partial class AssistantEMail : AssistantBaseCore Tools.Components.EMAIL_ASSISTANT; - protected override string Title => "E-Mail"; + protected override string Title => T("E-Mail"); - protected override string Description => - """ - 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. - """; + protected override string Description => T("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."); protected override string SystemPrompt => $""" @@ -25,7 +22,7 @@ public partial class AssistantEMail : AssistantBaseCore FooterButtons => []; - protected override string SubmitText => "Create email"; + protected override string SubmitText => T("Create email"); protected override Func SubmitAction => this.CreateMail; @@ -100,12 +97,12 @@ public partial class AssistantEMail : AssistantBaseCore - - + + \ No newline at end of file diff --git a/app/MindWork AI Studio/Assistants/GrammarSpelling/AssistantGrammarSpelling.razor.cs b/app/MindWork AI Studio/Assistants/GrammarSpelling/AssistantGrammarSpelling.razor.cs index 4a0e53c1..6025f133 100644 --- a/app/MindWork AI Studio/Assistants/GrammarSpelling/AssistantGrammarSpelling.razor.cs +++ b/app/MindWork AI Studio/Assistants/GrammarSpelling/AssistantGrammarSpelling.razor.cs @@ -7,12 +7,9 @@ public partial class AssistantGrammarSpelling : AssistantBaseCore Tools.Components.GRAMMAR_SPELLING_ASSISTANT; - protected override string Title => "Grammar & Spelling Checker"; + protected override string Title => T("Grammar & Spelling Checker"); - protected override string Description => - """ - Check the grammar and spelling of a text. - """; + protected override string Description => T("Check the grammar and spelling of a text."); protected override string SystemPrompt => $""" @@ -40,7 +37,7 @@ public partial class AssistantGrammarSpelling : AssistantBaseCore "Proofread"; + protected override string SubmitText => T("Proofread"); protected override Func SubmitAction => this.ProofreadText; @@ -93,7 +90,7 @@ public partial class AssistantGrammarSpelling : AssistantBaseCore this.finalLuaCode.Clear(); var commentContent = this.addedContent.Concat(PluginFactory.BaseLanguage.Content).ToDictionary(); LuaTable.Create(ref this.finalLuaCode, "UI_TEXT_CONTENT", this.localizedContent, commentContent, this.cancellationTokenSource!.Token); + + // Next, we must remove the `root::` prefix from the keys: + this.finalLuaCode.Replace("""UI_TEXT_CONTENT["root::""", """ + UI_TEXT_CONTENT[" + """); } } \ No newline at end of file diff --git a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua index ae3b9929..4791d157 100644 --- a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua +++ b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua @@ -44,681 +44,2215 @@ IETF_TAG = "en-US" -- The language name in the user's language: LANG_NAME = "English (United States)" -UI_TEXT_CONTENT = { - DIALOGS = { - SETTINGS = { - SETTINGSDIALOGAGENDA = { - -- There is no social event - T1222800281 = "There is no social event", +UI_TEXT_CONTENT = {} - -- Agenda options are preselected - T1249372829 = "Agenda options are preselected", +-- Assistant - {0} +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::ASSISTANTBASE::T3043922"] = "Assistant - {0}" - -- Preselect a duration? - T1404615656 = "Preselect a duration?", +-- 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." - -- Preselect the number of participants - T1444356399 = "Preselect the number of participants", +-- Your name for the closing salutation of your e-mail. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::EMAIL::ASSISTANTEMAIL::T134060413"] = "Your name for the closing salutation of your e-mail." - -- Meeting is virtual - T1446638309 = "Meeting is virtual", +-- Please start each line of your content list with a dash (-) to create a bullet point list. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::EMAIL::ASSISTANTEMAIL::T1384718254"] = "Please start each line of your content list with a dash (-) to create a bullet point list." - -- Preselect a name? - T1471770981 = "Preselect a name?", +-- Create email +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::EMAIL::ASSISTANTEMAIL::T1686330485"] = "Create email" - -- Preselect whether participants needs to arrive and depart - T1648427207 = "Preselect whether participants needs to arrive and depart", +-- Previous conversation +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::EMAIL::ASSISTANTEMAIL::T2074063439"] = "Previous conversation" - -- Preselect a start time? - T1901151023 = "Preselect a start time?", +-- Select the writing style +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::EMAIL::ASSISTANTEMAIL::T2241531659"] = "Select the writing style" - -- Preselect a location? - T1908318849 = "Preselect a location?", +-- Target language +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::EMAIL::ASSISTANTEMAIL::T237828418"] = "Target language" - -- How many participants should be preselected? - T1998244307 = "How many participants should be preselected?", +-- Please provide some content for the e-mail. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::EMAIL::ASSISTANTEMAIL::T2381517938"] = "Please provide some content for the e-mail." - -- Preselect whether the meeting is virtual - T2084951012 = "Preselect whether the meeting is virtual", +-- Please provide some history for the e-mail. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::EMAIL::ASSISTANTEMAIL::T2471325767"] = "Please provide some history for the e-mail." - -- Would you like to preselect one of your profiles? - T2221665527 = "Would you like to preselect one of your profiles?", +-- Your bullet points +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::EMAIL::ASSISTANTEMAIL::T2582330385"] = "Your bullet points" - -- When enabled, you can preselect most agenda options. This is might be useful when you need to create similar agendas often. - T2373110543 = "When enabled, you can preselect most agenda options. This is might be useful when you need to create similar agendas often.", +-- Yes, I provide the previous conversation +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::EMAIL::ASSISTANTEMAIL::T2652980489"] = "Yes, I provide the previous conversation" - -- Preselect whether the participants should get to know each other - T2519703500 = "Preselect whether the participants should get to know each other", +-- Please select a writing style for the e-mail. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::EMAIL::ASSISTANTEMAIL::T2969942806"] = "Please select a writing style for the e-mail." - -- Which agenda language should be preselected? - T2801220321 = "Which agenda language should be preselected?", +-- E-Mail +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::EMAIL::ASSISTANTEMAIL::T3026443472"] = "E-Mail" - -- Preselect another agenda language - T2915422331 = "Preselect another agenda language", +-- (Optional) The greeting phrase to use +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::EMAIL::ASSISTANTEMAIL::T306513209"] = "(Optional) The greeting phrase to use" - -- Participants do not need to get to know each other - T2949002251 = "Participants do not need to get to know each other", +-- Bullet list the content of the e-mail roughly. Use dashes (-) to separate the items. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::EMAIL::ASSISTANTEMAIL::T3259604530"] = "Bullet list the content of the e-mail roughly. Use dashes (-) to separate the items." - -- There is a social event - T296183299 = "There is a social event", +-- Is there a history, a previous conversation? +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::EMAIL::ASSISTANTEMAIL::T3438127996"] = "Is there a history, a previous conversation?" - -- Participants should be actively involved - T298324727 = "Participants should be actively involved", +-- Provide the previous conversation, e.g., the last e-mail, the last chat, etc. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::EMAIL::ASSISTANTEMAIL::T3706154604"] = "Provide the previous conversation, e.g., the last e-mail, the last chat, etc." - -- Meeting is in person - T3008159782 = "Meeting is in person", +-- No, I don't provide a previous conversation +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::EMAIL::ASSISTANTEMAIL::T3823693145"] = "No, I don't provide a previous conversation" - -- Participants do not need to arrive and depart - T3087504452 = "Participants do not need to arrive and depart", +-- (Optional) Are any of your points particularly important? +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::EMAIL::ASSISTANTEMAIL::T3843104162"] = "(Optional) Are any of your points particularly important?" - -- Preselect whether there is a joint dinner - T3175009548 = "Preselect whether there is a joint dinner", +-- Custom target language +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::EMAIL::ASSISTANTEMAIL::T3848935911"] = "Custom target language" - -- Preselect an objective? - T3439476935 = "Preselect an objective?", +-- (Optional) Your name for the closing salutation +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::EMAIL::ASSISTANTEMAIL::T453962275"] = "(Optional) Your name for the closing salutation" - -- Preselect a moderator? - T3482798491 = "Preselect a moderator?", +-- Please provide a custom language. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::EMAIL::ASSISTANTEMAIL::T656744944"] = "Please provide a custom language." - -- Participants need to arrive and depart - T3591032034 = "Participants need to arrive and depart", +-- Dear Colleagues +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::EMAIL::ASSISTANTEMAIL::T759263763"] = "Dear Colleagues" - -- Participants do not need to be actively involved - T3679899885 = "Participants do not need to be actively involved", +-- Please select a target language for the e-mail. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::EMAIL::ASSISTANTEMAIL::T891073054"] = "Please select a target language for the e-mail." - -- Preselect the approx. lunch time - T3709527588 = "Preselect the approx. lunch time", +-- Please provide a text as input. You might copy the desired text from a document or a website. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::GRAMMARSPELLING::ASSISTANTGRAMMARSPELLING::T137304886"] = "Please provide a text as input. You might copy the desired text from a document or a website." - -- Preselect a topic? - T3835166371 = "Preselect a topic?", +-- Proofread +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::GRAMMARSPELLING::ASSISTANTGRAMMARSPELLING::T2325568297"] = "Proofread" - -- Preselect one of your profiles? - T4004501229 = "Preselect one of your profiles?", +-- Language +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::GRAMMARSPELLING::ASSISTANTGRAMMARSPELLING::T2591284123"] = "Language" - -- Preselect the agenda language - T4055846391 = "Preselect the agenda language", +-- Your input to check +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::GRAMMARSPELLING::ASSISTANTGRAMMARSPELLING::T2861221443"] = "Your input to check" - -- No agenda options are preselected - T4094211586 = "No agenda options are preselected", +-- Custom language +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::GRAMMARSPELLING::ASSISTANTGRAMMARSPELLING::T3032662264"] = "Custom language" - -- Participants should get to know each other - T464127805 = "Participants should get to know each other", +-- Grammar & Spelling Checker +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::GRAMMARSPELLING::ASSISTANTGRAMMARSPELLING::T3169549433"] = "Grammar & Spelling Checker" - -- Assistant: Agenda Planner Options - T677962779 = "Assistant: Agenda Planner Options", +-- Check the grammar and spelling of a text. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::GRAMMARSPELLING::ASSISTANTGRAMMARSPELLING::T3184716499"] = "Check the grammar and spelling of a text." - -- There is a joint dinner - T707310400 = "There is a joint dinner", +-- Please provide a custom language. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::GRAMMARSPELLING::ASSISTANTGRAMMARSPELLING::T656744944"] = "Please provide a custom language." - -- Preselect the approx. break time - T722113273 = "Preselect the approx. break time", +-- Your icon source +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::ICONFINDER::ASSISTANTICONFINDER::T1302165948"] = "Your icon source" - -- There is no joint dinner - T768936730 = "There is no joint dinner", +-- Find Icon +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::ICONFINDER::ASSISTANTICONFINDER::T1975161003"] = "Find Icon" - -- Preselect agenda options? - T800921421 = "Preselect agenda options?", +-- Finding the right icon for a context, such as for a piece of text, is not easy. The first challenge: You need to extract a concept from your context, such as from a text. Let's take an example where your text contains statements about multiple departments. The sought-after concept could be "departments." The next challenge is that we need to anticipate the bias of the icon designers: under the search term "departments," there may be no relevant icons or only unsuitable ones. Depending on the icon source, it might be more effective to search for "buildings," for instance. LLMs assist you with both steps. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::ICONFINDER::ASSISTANTICONFINDER::T347756684"] = "Finding the right icon for a context, such as for a piece of text, is not easy. The first challenge: You need to extract a concept from your context, such as from a text. Let's take an example where your text contains statements about multiple departments. The sought-after concept could be \"departments.\" The next challenge is that we need to anticipate the bias of the icon designers: under the search term \"departments,\" there may be no relevant icons or only unsuitable ones. Depending on the icon source, it might be more effective to search for \"buildings,\" for instance. LLMs assist you with both steps." - -- Preselect whether there is a social event - T816053055 = "Preselect whether there is a social event", +-- Icon Finder +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::ICONFINDER::ASSISTANTICONFINDER::T3693102312"] = "Icon Finder" - -- Preselect whether the participants should actively involved - T817726429 = "Preselect whether the participants should actively involved", +-- Open website +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::ICONFINDER::ASSISTANTICONFINDER::T4239378936"] = "Open website" - }, +-- Your context +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::ICONFINDER::ASSISTANTICONFINDER::T596802185"] = "Your context" - SETTINGSDIALOGASSISTANTBIAS = { - -- Restrict to one bias a day? - T1608129203 = "Restrict to one bias a day?", +-- Please provide a context. This will help the AI to find the right icon. You might type just a keyword or copy a sentence from your text, e.g., from a slide where you want to use the icon. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::ICONFINDER::ASSISTANTICONFINDER::T653229070"] = "Please provide a context. This will help the AI to find the right icon. You might type just a keyword or copy a sentence from your text, e.g., from a slide where you want to use the icon." - -- Yes, you can only retrieve one bias per day - T1765683725 = "Yes, you can only retrieve one bias per day", +-- Please provide a legal document as input. You might copy the desired text from a document or a website. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LEGALCHECK::ASSISTANTLEGALCHECK::T1160217683"] = "Please provide a legal document as input. You might copy the desired text from a document or a website." - -- Reset - T180921696 = "Reset", +-- Legal Check +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LEGALCHECK::ASSISTANTLEGALCHECK::T1348190638"] = "Legal Check" - -- Would you like to preselect one of your profiles? - T2221665527 = "Would you like to preselect one of your profiles?", +-- Legal document +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LEGALCHECK::ASSISTANTLEGALCHECK::T1887742531"] = "Legal document" - -- No restriction. You can retrieve as many biases as you want per day. - T2305356277 = "No restriction. You can retrieve as many biases as you want per day.", +-- Your questions +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LEGALCHECK::ASSISTANTLEGALCHECK::T1947954583"] = "Your questions" - -- Which language should be preselected? - T2345162613 = "Which language should be preselected?", +-- Provide a legal document and ask a question about it. This assistant does not replace legal advice. Consult a lawyer to get professional advice. Remember that LLMs can invent answers and facts. Please do not rely on this answers. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LEGALCHECK::ASSISTANTLEGALCHECK::T4016275181"] = "Provide a legal document and ask a question about it. This assistant does not replace legal advice. Consult a lawyer to get professional advice. Remember that LLMs can invent answers and facts. Please do not rely on this answers." - -- Reset your bias-of-the-day statistics - T2350981714 = "Reset your bias-of-the-day statistics", +-- Please provide your questions as input. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LEGALCHECK::ASSISTANTLEGALCHECK::T4154383818"] = "Please provide your questions as input." - -- Preselect another language - T2382415529 = "Preselect another language", +-- Ask your questions +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LEGALCHECK::ASSISTANTLEGALCHECK::T467099852"] = "Ask your questions" - -- Preselect the language - T2571465005 = "Preselect the language", +-- Please provide some text as input. For example, an email. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T1962809521"] = "Please provide some text as input. For example, an email." - -- Close - T3448155331 = "Close", +-- Analyze text +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T2268303626"] = "Analyze text" - -- No options are preselected - T354528094 = "No options are preselected", +-- Target language +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T237828418"] = "Target language" - -- Assistant: Bias of the Day - T384887684 = "Assistant: Bias of the Day", +-- My Tasks +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T3011450657"] = "My Tasks" - -- Options are preselected - T3875604319 = "Options are preselected", +-- You received a cryptic email that was sent to many recipients and you are now wondering if you need to do something? Copy the email into the input field. You also need to select a personal profile. In this profile, you should describe your role in the organization. The AI will then try to give you hints on what your tasks might be. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T3646084045"] = "You received a cryptic email that was sent to many recipients and you are now wondering if you need to do something? Copy the email into the input field. You also need to select a personal profile. In this profile, you should describe your role in the organization. The AI will then try to give you hints on what your tasks might be." - -- Preselect one of your profiles? - T4004501229 = "Preselect one of your profiles?", +-- Custom target language +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T3848935911"] = "Custom target language" - -- Are you sure you want to reset your bias-of-the-day statistics? The system will no longer remember which biases you already know. As a result, biases you are already familiar with may be addressed again. - T405627382 = "Are you sure you want to reset your bias-of-the-day statistics? The system will no longer remember which biases you already know. As a result, biases you are already familiar with may be addressed again.", +-- Please select one of your profiles. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T465395981"] = "Please select one of your profiles." - -- Assistant: Bias of the Day Options - T4235808594 = "Assistant: Bias of the Day Options", +-- Text or email +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T534887559"] = "Text or email" - -- Preselect options? - T42672465 = "Preselect options?", +-- Please provide a custom language. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T656744944"] = "Please provide a custom language." - -- You have learned about {0} out of {1} biases. - T679061561 = "You have learned about {0} out of {1} biases.", +-- Please provide a text as input. You might copy the desired text from a document or a website. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::REWRITEIMPROVE::ASSISTANTREWRITEIMPROVE::T137304886"] = "Please provide a text as input. You might copy the desired text from a document or a website." - -- When enabled, you can preselect options. This is might be useful when you prefer a specific language or LLM model. - T711745239 = "When enabled, you can preselect options. This is might be useful when you prefer a specific language or LLM model.", +-- Sentence structure +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::REWRITEIMPROVE::ASSISTANTREWRITEIMPROVE::T1714063121"] = "Sentence structure" - }, +-- Rewrite & Improve Text +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::REWRITEIMPROVE::ASSISTANTREWRITEIMPROVE::T1994150308"] = "Rewrite & Improve Text" - }, +-- Improve your text +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::REWRITEIMPROVE::ASSISTANTREWRITEIMPROVE::T2163831433"] = "Improve your text" - }, +-- Language +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::REWRITEIMPROVE::ASSISTANTREWRITEIMPROVE::T2591284123"] = "Language" - LAYOUT = { - MAINLAYOUT = { - -- Settings - T1258653480 = "Settings", +-- Custom language +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::REWRITEIMPROVE::ASSISTANTREWRITEIMPROVE::T3032662264"] = "Custom language" - -- Home - T1391791790 = "Home", +-- Your input to improve +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::REWRITEIMPROVE::ASSISTANTREWRITEIMPROVE::T3037449423"] = "Your input to improve" - -- About - T1491113694 = "About", +-- Writing style +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::REWRITEIMPROVE::ASSISTANTREWRITEIMPROVE::T3754048862"] = "Writing style" - -- Are you sure you want to leave the chat page? All unsaved changes will be lost. - T1563130494 = "Are you sure you want to leave the chat page? All unsaved changes will be lost.", +-- Rewrite and improve your text. Please note, that the capabilities of the different LLM providers will vary. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::REWRITEIMPROVE::ASSISTANTREWRITEIMPROVE::T480915300"] = "Rewrite and improve your text. Please note, that the capabilities of the different LLM providers will vary." - -- Assistants - T1614176092 = "Assistants", +-- Please provide a custom language. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::REWRITEIMPROVE::ASSISTANTREWRITEIMPROVE::T656744944"] = "Please provide a custom language." - -- Update - T1847791252 = "Update", +-- Your word or phrase +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::SYNONYM::ASSISTANTSYNONYMS::T1847246020"] = "Your word or phrase" - -- Leave Chat Page - T2124749705 = "Leave Chat Page", +-- (Optional) The context for the given word or phrase +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::SYNONYM::ASSISTANTSYNONYMS::T2250963999"] = "(Optional) The context for the given word or phrase" - -- Plugins - T2222816203 = "Plugins", +-- Synonyms +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::SYNONYM::ASSISTANTSYNONYMS::T2547582747"] = "Synonyms" - -- An update to version {0} is available. - T2800137365 = "An update to version {0} is available.", +-- Language +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::SYNONYM::ASSISTANTSYNONYMS::T2591284123"] = "Language" - -- Please wait for the update to complete... - T2864211629 = "Please wait for the update to complete...", +-- Find synonyms for words or phrases. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::SYNONYM::ASSISTANTSYNONYMS::T2733641217"] = "Find synonyms for words or phrases." - -- Supporters - T2929332068 = "Supporters", +-- Find synonyms +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::SYNONYM::ASSISTANTSYNONYMS::T3106607224"] = "Find synonyms" - -- Writer - T2979224202 = "Writer", +-- Please provide a word or phrase as input. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::SYNONYM::ASSISTANTSYNONYMS::T3501110371"] = "Please provide a word or phrase as input." - -- Show details - T3692372066 = "Show details", +-- Custom target language +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::SYNONYM::ASSISTANTSYNONYMS::T3848935911"] = "Custom target language" - -- Chat - T578410699 = "Chat", +-- Please provide a custom language. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::SYNONYM::ASSISTANTSYNONYMS::T656744944"] = "Please provide a custom language." - }, +-- Your input +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::TEXTSUMMARIZER::ASSISTANTTEXTSUMMARIZER::T1249704194"] = "Your input" - }, +-- Target complexity +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::TEXTSUMMARIZER::ASSISTANTTEXTSUMMARIZER::T1318882688"] = "Target complexity" - PAGES = { - ABOUT = { - -- Startup log file - T1019424746 = "Startup log file", +-- Please provide a text as input. You might copy the desired text from a document or a website. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::TEXTSUMMARIZER::ASSISTANTTEXTSUMMARIZER::T137304886"] = "Please provide a text as input. You might copy the desired text from a document or a website." - -- About MindWork AI Studio - T1020427799 = "About MindWork AI Studio", +-- Text Summarizer +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::TEXTSUMMARIZER::ASSISTANTTEXTSUMMARIZER::T1907192403"] = "Text Summarizer" - -- Browse AI Studio's source code on GitHub — we welcome your contributions. - T1107156991 = "Browse AI Studio's source code on GitHub — we welcome your contributions.", +-- Target language +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::TEXTSUMMARIZER::ASSISTANTTEXTSUMMARIZER::T237828418"] = "Target language" - -- This library is used to read PDF files. This is necessary, e.g., for using PDFs as a data source for a chat. - T1388816916 = "This library is used to read PDF files. This is necessary, e.g., for using PDFs as a data source for a chat.", +-- Summarize long text into a shorter version while retaining the main points. You might want to change the language of the summary to make it more readable. It is also possible to change the complexity of the summary to make it easy to understand. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::TEXTSUMMARIZER::ASSISTANTTEXTSUMMARIZER::T359929871"] = "Summarize long text into a shorter version while retaining the main points. You might want to change the language of the summary to make it more readable. It is also possible to change the complexity of the summary to make it easy to understand." - -- This library is used to extend the MudBlazor library. It provides additional components that are not part of the MudBlazor library. - T1421513382 = "This library is used to extend the MudBlazor library. It provides additional components that are not part of the MudBlazor library.", +-- Please provide your field of expertise. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::TEXTSUMMARIZER::ASSISTANTTEXTSUMMARIZER::T3610378685"] = "Please provide your field of expertise." - -- 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. - 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.", +-- Custom target language +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::TEXTSUMMARIZER::ASSISTANTTEXTSUMMARIZER::T3848935911"] = "Custom target language" - -- Building on .NET, ASP.NET Core, and Blazor, MudBlazor is used as a library for designing and developing the user interface. It is a great project that significantly accelerates the development of advanced user interfaces with Blazor. - T1629800076 = "Building on .NET, ASP.NET Core, and Blazor, MudBlazor is used as a library for designing and developing the user interface. It is a great project that significantly accelerates the development of advanced user interfaces with Blazor.", +-- Summarize +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::TEXTSUMMARIZER::ASSISTANTTEXTSUMMARIZER::T502888730"] = "Summarize" - -- AI Studio creates a log file at startup, in which events during startup are recorded. After startup, another log file is created that records all events that occur during the use of the app. This includes any errors that may occur. Depending on when an error occurs (at startup or during use), the contents of these log files can be helpful for troubleshooting. Sensitive information such as passwords is not included in the log files. - T1630237140 = "AI Studio creates a log file at startup, in which events during startup are recorded. After startup, another log file is created that records all events that occur during the use of the app. This includes any errors that may occur. Depending on when an error occurs (at startup or during use), the contents of these log files can be helpful for troubleshooting. Sensitive information such as passwords is not included in the log files.", +-- Please provide a custom language. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::TEXTSUMMARIZER::ASSISTANTTEXTSUMMARIZER::T656744944"] = "Please provide a custom language." - -- This library is used to display the differences between two texts. This is necessary, e.g., for the grammar and spelling assistant. - T1772678682 = "This library is used to display the differences between two texts. This is necessary, e.g., for the grammar and spelling assistant.", +-- Your expertise +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::TEXTSUMMARIZER::ASSISTANTTEXTSUMMARIZER::T970222193"] = "Your expertise" - -- By clicking on the respective path, the path is copied to the clipboard. You might open these files with a text editor to view their contents. - T1806897624 = "By clicking on the respective path, the path is copied to the clipboard. You might open these files with a text editor to view their contents.", +-- Please select a target language. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::TRANSLATION::ASSISTANTTRANSLATION::T1173859091"] = "Please select a target language." - -- Check for updates - T1890416390 = "Check for updates", +-- Your input +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::TRANSLATION::ASSISTANTTRANSLATION::T1249704194"] = "Your input" - -- Vision - T1892426825 = "Vision", +-- Please provide a text as input. You might copy the desired text from a document or a website. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::TRANSLATION::ASSISTANTTRANSLATION::T137304886"] = "Please provide a text as input. You might copy the desired text from a document or a website." - -- This library is used to convert HTML to Markdown. This is necessary, e.g., when you provide a URL as input for an assistant. - 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.", +-- Translate +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::TRANSLATION::ASSISTANTTRANSLATION::T2028202101"] = "Translate" - -- 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. - 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.", +-- Target language +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::TRANSLATION::ASSISTANTTRANSLATION::T237828418"] = "Target language" - -- This library is used to determine the file type of a file. This is necessary, e.g., when we want to stream a file. - T2173617769 = "This library is used to determine the file type of a file. This is necessary, e.g., when we want to stream a file.", +-- Translate text from one language to another. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::TRANSLATION::ASSISTANTTRANSLATION::T3230457846"] = "Translate text from one language to another." - -- For the secure communication between the user interface and the runtime, we need to create certificates. This Rust library is great for this purpose. - T2174764529 = "For the secure communication between the user interface and the runtime, we need to create certificates. This Rust library is great for this purpose.", +-- No live translation +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::TRANSLATION::ASSISTANTTRANSLATION::T3556243327"] = "No live translation" - -- 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. - 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.", +-- Custom target language +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::TRANSLATION::ASSISTANTTRANSLATION::T3848935911"] = "Custom target language" - -- In order to use any LLM, each user must store their so-called token for each LLM provider. This token 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. - T228561878 = "In order to use any LLM, each user must store their so-called token for each LLM provider. This token 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.", +-- Live translation +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::TRANSLATION::ASSISTANTTRANSLATION::T4279308324"] = "Live translation" - -- The C# language is used for the implementation of the user interface and the backend. To implement the user interface with C#, the Blazor technology from ASP.NET Core is used. All these technologies are integrated into the .NET SDK. - T2329884315 = "The C# language is used for the implementation of the user interface and the backend. To implement the user interface with C#, the Blazor technology from ASP.NET Core is used. All these technologies are integrated into the .NET SDK.", +-- Translation +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::TRANSLATION::ASSISTANTTRANSLATION::T613888204"] = "Translation" - -- This library is used to determine the language of the operating system. This is necessary to set the language of the user interface. - T2557014401 = "This library is used to determine the language of the operating system. This is necessary to set the language of the user interface.", +-- Please provide a custom language. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::TRANSLATION::ASSISTANTTRANSLATION::T656744944"] = "Please provide a custom language." - -- Used Open Source Projects - T2557066213 = "Used Open Source Projects", +-- Edit Message +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1183581066"] = "Edit Message" - -- Build time - T260228112 = "Build time", +-- Copies the content to the clipboard +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T12948066"] = "Copies the content to the clipboard" - -- 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. - 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.", +-- Do you really want to remove this message? +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1347427447"] = "Do you really want to remove this message?" - -- Usage log file - T2689995864 = "Usage log file", +-- Yes, remove the AI response and edit it +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1350385882"] = "Yes, remove the AI response and edit it" - -- Logbook - T2706940196 = "Logbook", +-- Yes, regenerate it +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1603883875"] = "Yes, regenerate it" - -- This component is used to render Markdown text. This is important because the LLM often responds with Markdown-formatted text, allowing us to present it in a way that is easier to read. - T2726131107 = "This component is used to render Markdown text. This is important because the LLM often responds with Markdown-formatted text, allowing us to present it in a way that is easier to read.", +-- Yes, remove it +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1820166585"] = "Yes, remove it" - -- 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. - T2777988282 = "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.", +-- Do you really want to edit this message? In order to edit this message, the AI response will be deleted. +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2018431076"] = "Do you really want to edit this message? In order to edit this message, the AI response will be deleted." - -- View our project roadmap and help shape AI Studio's future development. - T2829971158 = "View our project roadmap and help shape AI Studio's future development.", +-- Removes this block +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2093355991"] = "Removes this block" - -- Used .NET runtime - T2840227993 = "Used .NET runtime", +-- Regenerate Message +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2308444540"] = "Regenerate Message" - -- Explanation - T2840582448 = "Explanation", +-- Cannot render content of type {0} yet. +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3175548294"] = "Cannot render content of type {0} yet." - -- 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. - 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.", +-- Edit +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3267849393"] = "Edit" - -- Changelog - T3017574265 = "Changelog", +-- Regenerate +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3587744975"] = "Regenerate" - -- Connect AI Studio to your organization's data with our External Retrieval Interface (ERI). - T313276297 = "Connect AI Studio to your organization's data with our External Retrieval Interface (ERI).", +-- Do you really want to regenerate this message? +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3878878761"] = "Do you really want to regenerate this message?" - -- Have feature ideas? Submit suggestions for future AI Studio enhancements. - T3178730036 = "Have feature ideas? Submit suggestions for future AI Studio enhancements.", +-- Cannot copy this content type to clipboard! +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T4021525742"] = "Cannot copy this content type to clipboard!" - -- Discover MindWork AI's mission and vision on our official homepage. - T3294830584 = "Discover MindWork AI's mission and vision on our official homepage.", +-- Remove Message +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T4070211974"] = "Remove Message" - -- The following list shows the versions of the MindWork AI Studio, the used compilers, build time, etc.: - T3405978777 = "The following list shows the versions of the MindWork AI Studio, the used compilers, build time, etc.:", +-- No, keep it +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T4188329028"] = "No, keep it" - -- Used Rust compiler - T3440211747 = "Used Rust compiler", +-- Open Settings +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTBLOCK::T1172211894"] = "Open Settings" - -- 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! - 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!", +-- Changelog +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHANGELOG::T3017574265"] = "Changelog" - -- Motivation - T3563271893 = "Motivation", +-- Move chat +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1133040906"] = "Move chat" - -- 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. - 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.", +-- Are you sure you want to move this chat? All unsaved changes will be lost. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1142475422"] = "Are you sure you want to move this chat? All unsaved changes will be lost." - -- 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. - T3908558992 = "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.", +-- Type your input here... +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1849313532"] = "Type your input here..." - -- Versions - T4010195468 = "Versions", +-- Your Prompt (use selected instance '{0}', provider '{1}') +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1967611328"] = "Your Prompt (use selected instance '{0}', provider '{1}')" - -- This library is used to create asynchronous streams in Rust. It allows us to work with streams of data that can be produced asynchronously, making it easier to handle events or data that arrive over time. We use this, e.g., to stream arbitrary data from the file system to the embedding system. - T4079152443 = "This library is used to create asynchronous streams in Rust. It allows us to work with streams of data that can be produced asynchronously, making it easier to handle events or data that arrive over time. We use this, e.g., to stream arbitrary data from the file system to the embedding system.", +-- Delete this chat & start a new one. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T2991985411"] = "Delete this chat & start a new one." - -- Community & Code - T4158546761 = "Community & Code", +-- Move Chat to Workspace +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T3045856778"] = "Move Chat to Workspace" - -- 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. - T4184485147 = "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.", +-- The selected provider is not allowed in this chat due to data security reasons. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T3403290862"] = "The selected provider is not allowed in this chat due to data security reasons." - -- When transferring sensitive data between Rust runtime and .NET app, we encrypt the data. We use some libraries from the Rust Crypto project for this purpose: cipher, aes, cbc, pbkdf2, hmac, and sha2. We are thankful for the great work of the Rust Crypto project. - T4229014037 = "When transferring sensitive data between Rust runtime and .NET app, we encrypt the data. We use some libraries from the Rust Crypto project for this purpose: cipher, aes, cbc, pbkdf2, hmac, and sha2. We are thankful for the great work of the Rust Crypto project.", +-- Select a provider first +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T3654197869"] = "Select a provider first" - -- This is a library providing the foundations for asynchronous programming in Rust. It includes key trait definitions like Stream, as well as utilities like join!, select!, and various futures combinator methods which enable expressive asynchronous control flow. - T566998575 = "This is a library providing the foundations for asynchronous programming in Rust. It includes key trait definitions like Stream, as well as utilities like join!, select!, and various futures combinator methods which enable expressive asynchronous control flow.", +-- Please select the workspace where you want to move the chat to. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T474393241"] = "Please select the workspace where you want to move the chat to." - -- Used .NET SDK - T585329785 = "Used .NET SDK", +-- Move the chat to a workspace, or to another if it is already in one. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T636393754"] = "Move the chat to a workspace, or to another if it is already in one." - -- Did you find a bug or are you experiencing issues? Report your concern here. - T639371534 = "Did you find a bug or are you experiencing issues? Report your concern here.", +-- Region +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIDENCEINFO::T1227782301"] = "Region" - -- This Rust library is used to output the app's messages to the terminal. This is helpful during development and troubleshooting. This feature is initially invisible; when the app is started via the terminal, the messages become visible. - T64689067 = "This Rust library is used to output the app's messages to the terminal. This is helpful during development and troubleshooting. This feature is initially invisible; when the app is started via the terminal, the messages become visible.", +-- Description +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIDENCEINFO::T1725856265"] = "Description" - -- For some data transfers, we need to encode the data in base64. This Rust library is great for this purpose. - T870640199 = "For some data transfers, we need to encode the data in base64. This Rust library is great for this purpose.", +-- Confidence Level +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIDENCEINFO::T2492230131"] = "Confidence Level" - }, +-- Sources +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIDENCEINFO::T2730980305"] = "Sources" - ASSISTANTS = { - -- Get coding and debugging support from an LLM. - T1243850917 = "Get coding and debugging support from an LLM.", +-- Confidence Card +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIDENCEINFO::T2960002005"] = "Confidence Card" - -- Legal Check - T1348190638 = "Legal Check", +-- Confidence +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIDENCEINFO::T3243388657"] = "Confidence" - -- Coding - T1617786407 = "Coding", +-- Shows and hides the confidence card with information about the selected LLM provider. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIDENCEINFO::T847071819"] = "Shows and hides the confidence card with information about the selected LLM provider." - -- Analyze a text or an email for tasks you need to complete. - T1728590051 = "Analyze a text or an email for tasks you need to complete.", +-- 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. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONMINCONFIDENCESELECTION::T2526727283"] = "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." - -- Text Summarizer - T1907192403 = "Text Summarizer", +-- Select a minimum confidence level +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONMINCONFIDENCESELECTION::T2579793544"] = "Select a minimum confidence level" - -- Check grammar and spelling of a given text. - T1934717573 = "Check grammar and spelling of a given text.", +-- You have selected 1 preview feature. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONMULTISELECT::T1384241824"] = "You have selected 1 preview feature." - -- Translate text into another language. - T209791153 = "Translate text into another language.", +-- No preview features selected. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONMULTISELECT::T2809641588"] = "No preview features selected." - -- Generate an e-mail for a given context. - T2383649630 = "Generate an e-mail for a given context.", +-- You have selected {0} preview features. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONMULTISELECT::T3513450626"] = "You have selected {0} preview features." - -- Generate an agenda for a given meeting, seminar, etc. - T2406168562 = "Generate an agenda for a given meeting, seminar, etc.", +-- Preselected provider +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONPROVIDERSELECTION::T1469984996"] = "Preselected provider" - -- Agenda Planner - T2435638853 = "Agenda Planner", +-- Use app default +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONPROVIDERSELECTION::T3672477670"] = "Use app default" - -- Synonyms - T2547582747 = "Synonyms", +-- Issues +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ISSUES::T3229841001"] = "Issues" - -- Find synonyms for a given word or phrase. - T2712131461 = "Find synonyms for a given word or phrase.", +-- Given that my employer's workplace uses both Windows and Linux, I wanted a cross-platform solution that would work seamlessly across all major operating systems, including macOS. Additionally, I wanted to demonstrate that it is possible to create modern, efficient, cross-platform applications without resorting to Electron bloatware. The combination of .NET and Rust with Tauri proved to be an excellent technology stack for building such robust applications. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MOTIVATION::T1057189794"] = "Given that my employer's workplace uses both Windows and Linux, I wanted a cross-platform solution that would work seamlessly across all major operating systems, including macOS. Additionally, I wanted to demonstrate that it is possible to create modern, efficient, cross-platform applications without resorting to Electron bloatware. The combination of .NET and Rust with Tauri proved to be an excellent technology stack for building such robust applications." - -- Generate a job posting for a given job description. - T2831103254 = "Generate a job posting for a given job description.", +-- Limitations of Existing Solutions +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MOTIVATION::T1086130692"] = "Limitations of Existing Solutions" - -- My Tasks - T3011450657 = "My Tasks", +-- Personal Needs and Limitations of Web Services +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MOTIVATION::T1839655973"] = "Personal Needs and Limitations of Web Services" - -- Icon Finder - T3693102312 = "Icon Finder", +-- 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." - -- Generate an ERI server to integrate business systems. - T3756213118 = "Generate an ERI server to integrate business systems.", +-- 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." - -- Use an LLM to find an icon for a given context. - T3881504200 = "Use an LLM to find an icon for a given context.", +-- 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." - -- Job Posting - T3930052338 = "Job Posting", +-- 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." - -- Ask a question about a legal document. - T3970214537 = "Ask a question about a legal document.", +-- Cross-Platform and Modern Development +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MOTIVATION::T843057510"] = "Cross-Platform and Modern Development" - -- ERI Server - T4204533420 = "ERI Server", +-- Alpha phase means that we are working on the last details before the beta phase. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PREVIEWALPHA::T166807685"] = "Alpha phase means that we are working on the last details before the beta phase." - -- Use an LLM to summarize a given text. - T502222021 = "Use an LLM to summarize a given text.", +-- This feature is currently in the alpha phase. Expect bugs and unfinished work. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PREVIEWALPHA::T2635524607"] = "This feature is currently in the alpha phase. Expect bugs and unfinished work." - -- Translation - T613888204 = "Translation", +-- Alpha +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PREVIEWALPHA::T55079499"] = "Alpha" - -- Rewrite and improve a given text for a chosen style. - T722167136 = "Rewrite and improve a given text for a chosen style.", +-- This feature is currently in the beta phase. It is still be possible that there are some bugs. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PREVIEWBETA::T1045026949"] = "This feature is currently in the beta phase. It is still be possible that there are some bugs." - -- Bias of the Day - T782102948 = "Bias of the Day", +-- Beta phase means that we are testing the feature. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PREVIEWBETA::T3605158616"] = "Beta phase means that we are testing the feature." - -- Learn about one cognitive bias every day. - T878695986 = "Learn about one cognitive bias every day.", +-- Beta +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PREVIEWBETA::T375487463"] = "Beta" - }, +-- This feature is currently in the experimental phase. Expect bugs, unfinished work, changes in future versions, and more. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PREVIEWEXPERIMENTAL::T1735169242"] = "This feature is currently in the experimental phase. Expect bugs, unfinished work, changes in future versions, and more." - CHAT = { - -- Hide your workspaces - T2351468526 = "Hide your workspaces", +-- Experimental phase means that we have a vision for a feature but not a clear plan yet. We are still exploring the possibilities. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PREVIEWEXPERIMENTAL::T3709099979"] = "Experimental phase means that we have a vision for a feature but not a clear plan yet. We are still exploring the possibilities." - -- Short-Term Chat - T3718856736 = "Short-Term Chat", +-- Experimental +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PREVIEWEXPERIMENTAL::T3729365343"] = "Experimental" - -- Your workspaces - T3745240468 = "Your workspaces", +-- Prototype +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PREVIEWPROTOTYPE::T1043365177"] = "Prototype" - -- Chat in Workspace - T582100343 = "Chat in Workspace", +-- Prototype phase means that we have a plan but we are still working on it. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PREVIEWPROTOTYPE::T2995187557"] = "Prototype phase means that we have a plan but we are still working on it." - }, +-- This feature is currently in the prototype phase. Expect bugs, unfinished work, changes in future versions, and more. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PREVIEWPROTOTYPE::T4145334644"] = "This feature is currently in the prototype phase. Expect bugs, unfinished work, changes in future versions, and more." - HOME = { - -- Unlike services like ChatGPT, which impose limits after intensive use, MindWork AI Studio offers unlimited usage through the providers API. - T1009708591 = "Unlike services like ChatGPT, which impose limits after intensive use, MindWork AI Studio offers unlimited usage through the providers API.", +-- This feature is about to be released. We think it's ready for production. There should be no more bugs. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PREVIEWRELEASECANDIDATE::T2003588956"] = "This feature is about to be released. We think it's ready for production. There should be no more bugs." - -- Welcome to MindWork AI Studio! - T1024253064 = "Welcome to MindWork AI Studio!", +-- Release Candidate +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PREVIEWRELEASECANDIDATE::T3451939995"] = "Release Candidate" - -- You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT4o, o1, etc.), Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, and self-hosted models using 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. - T1059104744 = "You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT4o, o1, etc.), Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, and self-hosted models using 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.", +-- Release candidates are the final step before a feature is proven to be stable. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PREVIEWRELEASECANDIDATE::T696585888"] = "Release candidates are the final step before a feature is proven to be stable." - -- Thank you for considering MindWork AI Studio for your AI needs. This app is designed to help you harness the power of Large Language Models (LLMs). Please note that this app doesn't come with an integrated LLM. Instead, you will need to bring an API key from a suitable provider. - T1146553980 = "Thank you for considering MindWork AI Studio for your AI needs. This app is designed to help you harness the power of Large Language Models (LLMs). Please note that this app doesn't come with an integrated LLM. Instead, you will need to bring an API key from a suitable provider.", +-- Select one of your profiles +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PROFILEFORMSELECTION::T2003449133"] = "Select one of your profiles" - -- The app requires minimal storage for installation and operates with low memory usage. Additionally, it has a minimal impact on system resources, which is beneficial for battery life. - T144565305 = "The app requires minimal storage for installation and operates with low memory usage. Additionally, it has a minimal impact on system resources, which is beneficial for battery life.", +-- You can switch between your profiles here +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PROFILESELECTION::T918741365"] = "You can switch between your profiles here" - -- You only pay for what you use, which can be cheaper than monthly subscription services like ChatGPT Plus, especially if used infrequently. But beware, here be dragons: For extremely intensive usage, the API costs can be significantly higher. Unfortunately, providers currently do not offer a way to display current costs in the app. Therefore, check your account with the respective provider to see how your costs are developing. When available, use prepaid and set a cost limit. - T149711988 = "You only pay for what you use, which can be cheaper than monthly subscription services like ChatGPT Plus, especially if used infrequently. But beware, here be dragons: For extremely intensive usage, the API costs can be significantly higher. Unfortunately, providers currently do not offer a way to display current costs in the app. Therefore, check your account with the respective provider to see how your costs are developing. When available, use prepaid and set a cost limit.", +-- Provider +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PROVIDERSELECTION::T900237532"] = "Provider" - -- Assistants - T1614176092 = "Assistants", +-- The content is cleaned using an LLM agent: the main content is extracted, advertisements and other irrelevant things are attempted to be removed; relative links are attempted to be converted into absolute links so that they can be used. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T1164201762"] = "The content is cleaned using an LLM agent: the main content is extracted, advertisements and other irrelevant things are attempted to be removed; relative links are attempted to be converted into absolute links so that they can be used." - -- Unrestricted usage - T1686815996 = "Unrestricted usage", +-- Fetch +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T1396322691"] = "Fetch" - -- Vision - T1892426825 = "Vision", +-- Please select a provider to use the cleanup agent. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T2035652317"] = "Please select a provider to use the cleanup agent." - -- Let's get started - T2331588413 = "Let's get started", +-- Please provide a URL to load the content from. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T2235427807"] = "Please provide a URL to load the content from." - -- Last Changelog - T2348849647 = "Last Changelog", +-- Loads the content from your URL. Does not work when the content is hidden behind a paywall. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T2672192696"] = "Loads the content from your URL. Does not work when the content is hidden behind a paywall." - -- Choose the provider and model best suited for your current task. - T2588488920 = "Choose the provider and model best suited for your current task.", +-- URL from which to load the content +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T2883163022"] = "URL from which to load the content" - -- Quick Start Guide - T3002014720 = "Quick Start Guide", +-- Read content from web? +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T2927391091"] = "Read content from web?" - -- 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. - T3228075421 = "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.", +-- Cleanup content by using an LLM agent? +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T2939928117"] = "Cleanup content by using an LLM agent?" - -- We hope you enjoy using MindWork AI Studio to bring your AI projects to life! - T3275341342 = "We hope you enjoy using MindWork AI Studio to bring your AI projects to life!", +-- Hide web content options +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T3031774728"] = "Hide web content options" - -- Cost-effective - T3341379752 = "Cost-effective", +-- Please provide a valid HTTP or HTTPS URL. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T307442288"] = "Please provide a valid HTTP or HTTPS URL." - -- Flexibility - T3723223888 = "Flexibility", +-- No content cleaning +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T3588401674"] = "No content cleaning" - -- Privacy - T3959064551 = "Privacy", +-- Please provide a valid URL. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T3825586228"] = "Please provide a valid URL." - -- You can control which providers receive your data using the provider confidence settings. For example, you can set different protection levels for writing emails compared to general chats, etc. Additionally, most providers guarantee that they won't use your data to train new AI systems. - T457410099 = "You can control which providers receive your data using the provider confidence settings. For example, you can set different protection levels for writing emails compared to general chats, etc. Additionally, most providers guarantee that they won't use your data to train new AI systems.", +-- Show web content options +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T4249712357"] = "Show web content options" - -- Free of charge - T617579208 = "Free of charge", +-- Spellchecking is disabled +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1059411425"] = "Spellchecking is disabled" - -- Independence - T649448159 = "Independence", +-- 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?" - -- No bloatware - T858047957 = "No bloatware", +-- How often should we check for app updates? +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1364944735"] = "How often should we check for app updates?" - -- Here's what makes MindWork AI Studio stand out: - T873851215 = "Here's what makes MindWork AI Studio stand out:", +-- Select preview features +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1439783084"] = "Select preview features" - -- The app is free to use, both for personal and commercial purposes. - T91074375 = "The app is free to use, both for personal and commercial purposes.", +-- Select the desired behavior for the navigation bar. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1555038969"] = "Select the desired behavior for the navigation bar." - }, +-- Color theme +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1599198973"] = "Color theme" - PLUGINS = { - -- Disable plugin - T1430375822 = "Disable plugin", +-- 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. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1666052109"] = "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." - -- Internal Plugins - T158493184 = "Internal Plugins", +-- Select the language behavior for the app. The default is to use the system language. You might want to choose a language manually? +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T186780842"] = "Select the language behavior for the app. The default is to use the system language. You might want to choose a language manually?" - -- Disabled Plugins - T1724138133 = "Disabled Plugins", +-- Check for updates +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1890416390"] = "Check for updates" - -- Enable plugin - T2057806005 = "Enable plugin", +-- Which preview features would you like to enable? +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1898060643"] = "Which preview features would you like to enable?" - -- Plugins - T2222816203 = "Plugins", +-- Select the language for the app. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1907446663"] = "Select the language for the app." - -- Enabled Plugins - T2738444034 = "Enabled Plugins", +-- Language behavior +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2341504363"] = "Language behavior" - -- Actions - T3865031940 = "Actions", +-- Language +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2591284123"] = "Language" - }, +-- Save energy? +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T3100928009"] = "Save energy?" - SETTINGS = { - -- Settings - T1258653480 = "Settings", +-- Spellchecking is enabled +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T3165555978"] = "Spellchecking is enabled" - }, +-- App Options +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T3577148634"] = "App Options" - SUPPORTERS = { - -- Thank you for being the first to contribute a one-time donation. - T1470916504 = "Thank you for being the first to contribute a one-time donation.", +-- 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." - -- Thank you, Peer, for your courage in being the second person to support the project financially. - T1714878838 = "Thank you, Peer, for your courage in being the second person to support the project financially.", +-- Enable spellchecking? +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T3914529369"] = "Enable spellchecking?" - -- Individual Contributors - T1874835680 = "Individual Contributors", +-- Preselect one of your profiles? +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T4004501229"] = "Preselect one of your profiles?" - -- Thanks, Nils, for taking the time to learn Rust and build the foundation for local retrieval. - T2355807535 = "Thanks, Nils, for taking the time to learn Rust and build the foundation for local retrieval.", +-- When enabled, spellchecking will be active in all input fields. Depending on your operating system, errors may not be visually highlighted, but right-clicking may still offer possible corrections. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T4067492921"] = "When enabled, spellchecking will be active in all input fields. Depending on your operating system, errors may not be visually highlighted, but right-clicking may still offer possible corrections." - -- The first 10 supporters who make a one-time contribution: - T2410456125 = "The first 10 supporters who make a one-time contribution:", +-- Navigation bar behavior +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T602293588"] = "Navigation bar behavior" - -- Supporters - T2929332068 = "Supporters", +-- Choose the color theme that best suits for you. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T654667432"] = "Choose the color theme that best suits for you." - -- Financial Support - T3061261435 = "Financial Support", +-- Energy saving is enabled +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T71162186"] = "Energy saving is enabled" - -- The first 10 supporters who make a monthly contribution: - T3364384944 = "The first 10 supporters who make a monthly contribution:", +-- Energy saving is disabled +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T716338721"] = "Energy saving is disabled" - -- Thank you, Richard, for being the first. - T3660718138 = "Thank you, Richard, for being the first.", +-- Preview feature visibility +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T817101267"] = "Preview feature visibility" - -- Thanks Dominic for being the third supporter. - T3664780201 = "Thanks Dominic for being the third supporter.", +-- 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." - -- Our Titans - T3805270964 = "Our Titans", +-- Control how the LLM provider for loaded chats is selected and when assistant results are sent to chat. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCHAT::T172255919"] = "Control how the LLM provider for loaded chats is selected and when assistant results are sent to chat." - -- Moderation, Design, Wiki, and Documentation - T3821668394 = "Moderation, Design, Wiki, and Documentation", +-- Chat Options +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCHAT::T1757092713"] = "Chat Options" - -- Thank you, Peer, for familiarizing yourself with C#, providing excellent contributions like the Alibaba and Hugging Face providers, and revising the settings management. - T4106820759 = "Thank you, Peer, for familiarizing yourself with C#, providing excellent contributions like the Alibaba and Hugging Face providers, and revising the settings management.", +-- Shortcut to send input +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCHAT::T1773585398"] = "Shortcut to send input" - -- Code Contributions - T4135925647 = "Code Contributions", +-- Provider selection when creating new chats +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCHAT::T189306836"] = "Provider selection when creating new chats" - -- Become our first Titan - T414428338 = "Become our first Titan", +-- Would you like to set one of your profiles as the default for chats? +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCHAT::T1933521846"] = "Would you like to set one of your profiles as the default for chats?" - -- Become a contributor - T414604046 = "Become a contributor", +-- Apply default data source option when sending assistant results to chat +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCHAT::T2510376349"] = "Apply default data source option when sending assistant results to chat" - -- In this section, we highlight the titan supporters of MindWork AI Studio. Titans are prestigious companies that provide significant support to our mission. - T4270177642 = "In this section, we highlight the titan supporters of MindWork AI Studio. Titans are prestigious companies that provide significant support to our mission.", +-- Control how the LLM provider for added chats is selected. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCHAT::T263621180"] = "Control how the LLM provider for added chats is selected." - -- Thanks Luc for your build script contribution. - T432023389 = "Thanks Luc for your build script contribution.", +-- Provider selection when loading a chat and sending assistant results to chat +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCHAT::T2868379953"] = "Provider selection when loading a chat and sending assistant results to chat" - -- For companies, sponsoring MindWork AI Studio is not only a way to support innovation but also a valuable opportunity for public relations and marketing. Your company's name and logo will be featured prominently, showcasing your commitment to using cutting-edge AI tools and enhancing your reputation as an innovative enterprise. - T68519158 = "For companies, sponsoring MindWork AI Studio is not only a way to support innovation but also a valuable opportunity for public relations and marketing. Your company's name and logo will be featured prominently, showcasing your commitment to using cutting-edge AI tools and enhancing your reputation as an innovative enterprise.", +-- Show the latest message after loading? +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCHAT::T2913693228"] = "Show the latest message after loading?" - -- Thanks for your build script contribution. - T686206269 = "Thanks for your build script contribution.", +-- Do you want to use any shortcut to send your input? +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCHAT::T2936560092"] = "Do you want to use any shortcut to send your input?" - -- Business Contributors - T838479287 = "Business Contributors", +-- No chat options are preselected +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCHAT::T3383186996"] = "No chat options are preselected" - -- Thank you very much, Kerstin, for taking care of creating the Wiki. - T991294232 = "Thank you very much, Kerstin, for taking care of creating the Wiki.", +-- First (oldest) message is shown, after loading a chat +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCHAT::T3507181366"] = "First (oldest) message is shown, after loading a chat" - }, +-- Preselect chat options? +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCHAT::T3728624759"] = "Preselect chat options?" - WRITER = { - -- Write your text - T2220943334 = "Write your text", +-- Chat options are preselected +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCHAT::T3730599555"] = "Chat options are preselected" - -- Writer - T2979224202 = "Writer", +-- Latest message is shown, after loading a chat +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCHAT::T3755993611"] = "Latest message is shown, after loading a chat" - -- Suggestion - T3948127789 = "Suggestion", +-- Preselect one of your profiles? +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCHAT::T4004501229"] = "Preselect one of your profiles?" - -- Your stage directions - T779923726 = "Your stage directions", +-- Do you want to apply the default data source options when sending assistant results to chat? +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCHAT::T4033153439"] = "Do you want to apply the default data source options when sending assistant results to chat?" - }, +-- When enabled, you can preselect chat options. This is might be useful when you prefer a specific provider. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCHAT::T477675197"] = "When enabled, you can preselect chat options. This is might be useful when you prefer a specific provider." - }, +-- You can set default data sources and options for new chats. You can change these settings later for each individual chat. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCHAT::T492357592"] = "You can set default data sources and options for new chats. You can change these settings later for each individual chat." -}, +-- When enabled, the latest message is shown after loading a chat. When disabled, the first (oldest) message is shown. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCHAT::T582516016"] = "When enabled, the latest message is shown after loading a chat. When disabled, the first (oldest) message is shown." +-- Edit Profile +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROFILES::T1143111468"] = "Edit Profile" + +-- Configure Profiles +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROFILES::T1352823555"] = "Configure Profiles" + +-- No profiles configured yet. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROFILES::T1433534732"] = "No profiles configured yet." + +-- Delete +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROFILES::T1469573738"] = "Delete" + +-- Your Profiles +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROFILES::T2378610256"] = "Your Profiles" + +-- Edit +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROFILES::T3267849393"] = "Edit" + +-- Profile Name +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROFILES::T3392578705"] = "Profile Name" + +-- Delete Profile +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROFILES::T3804515427"] = "Delete Profile" + +-- Actions +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROFILES::T3865031940"] = "Actions" + +-- Store personal data about yourself in various profiles so that the AIs know your personal context. This saves you from having to explain your context each time, for example, in every chat. When you have different roles, you can create a profile for each role. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROFILES::T4125557797"] = "Store personal data about yourself in various profiles so that the AIs know your personal context. This saves you from having to explain your context each time, for example, in every chat. When you have different roles, you can create a profile for each role." + +-- Add Profile +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROFILES::T4248067241"] = "Add Profile" + +-- Are you sure you want to delete the profile '{0}'? +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROFILES::T55364659"] = "Are you sure you want to delete the profile '{0}'?" + +-- Are you a project manager in a research facility? You might want to create a profile for your project management activities, one for your scientific work, and a profile for when you need to write program code. In these profiles, you can record how much experience you have or which methods you like or dislike using. Later, you can choose when and where you want to use each profile. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROFILES::T56359901"] = "Are you a project manager in a research facility? You might want to create a profile for your project management activities, one for your scientific work, and a profile for when you need to write program code. In these profiles, you can record how much experience you have or which methods you like or dislike using. Later, you can choose when and where you want to use each profile." + +-- Show provider's confidence level? +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1052533048"] = "Show provider's confidence level?" + +-- Delete +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1469573738"] = "Delete" + +-- When enabled, we show you the confidence level for the selected provider in the app. This helps you assess where you are sending your data at any time. Example: are you currently working with sensitive data? Then choose a particularly trustworthy provider, etc. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1505516304"] = "When enabled, we show you the confidence level for the selected provider in the app. This helps you assess where you are sending your data at any time. Example: are you currently working with sensitive data? Then choose a particularly trustworthy provider, etc." + +-- No, please hide the confidence level +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1628475119"] = "No, please hide the confidence level" + +-- Description +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1725856265"] = "Description" + +-- Add Provider +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1806589097"] = "Add Provider" + +-- Edit LLM Provider +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1868766523"] = "Edit LLM Provider" + +-- Are you sure you want to delete the provider '{0}'? +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T2031310917"] = "Are you sure you want to delete the provider '{0}'?" + +-- Do you want to always be able to recognize how trustworthy your LLM providers are? This way, you keep control over which provider you send your data to. You have two options for this: Either you choose a common schema, or you configure the trust levels for each LLM provider yourself. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T2082904277"] = "Do you want to always be able to recognize how trustworthy your LLM providers are? This way, you keep control over which provider you send your data to. You have two options for this: Either you choose a common schema, or you configure the trust levels for each LLM provider yourself." + +-- Model +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T2189814010"] = "Model" + +-- Choose the scheme that best suits you and your life. Do you trust any western provider? Or only providers from the USA or exclusively European providers? Then choose the appropriate scheme. Alternatively, you can assign the confidence levels to each provider yourself. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T2283885378"] = "Choose the scheme that best suits you and your life. Do you trust any western provider? Or only providers from the USA or exclusively European providers? Then choose the appropriate scheme. Alternatively, you can assign the confidence levels to each provider yourself." + +-- LLM Provider Confidence +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T2349972795"] = "LLM Provider Confidence" + +-- What we call a provider is the combination of an LLM provider such as OpenAI and a model like GPT-4o. You can configure as many providers as you want. This way, you can use the appropriate model for each task. As an LLM provider, you can also choose local providers. However, to use this app, you must configure at least one provider. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T2460361126"] = "What we call a provider is the combination of an LLM provider such as OpenAI and a model like GPT-4o. You can configure as many providers as you want. This way, you can use the appropriate model for each task. As an LLM provider, you can also choose local providers. However, to use this app, you must configure at least one provider." + +-- Confidence Level +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T2492230131"] = "Confidence Level" + +-- When enabled, you can enforce a minimum confidence level for all LLM providers. This way, you can ensure that only trustworthy providers are used. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T281063702"] = "When enabled, you can enforce a minimum confidence level for all LLM providers. This way, you can ensure that only trustworthy providers are used." + +-- Instance Name +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T2842060373"] = "Instance Name" + +-- No providers configured yet. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T2911731076"] = "No providers configured yet." + +-- Configure Providers +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3027859089"] = "Configure 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" + +-- Couldn't delete the provider '{0}'. The issue: {1}. We can ignore this issue and delete the provider anyway. Do you want to ignore it and delete this provider? +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3313715309"] = "Couldn't delete the provider '{0}'. The issue: {1}. We can ignore this issue and delete the provider anyway. Do you want to ignore it and delete this provider?" + +-- Add LLM Provider +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3346433704"] = "Add LLM Provider" + +-- LLM Provider +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3612415205"] = "LLM Provider" + +-- No, do not enforce a minimum confidence level +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3642102079"] = "No, do not enforce a minimum confidence level" + +-- Configured Providers +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3850871263"] = "Configured Providers" + +-- Actions +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3865031940"] = "Actions" + +-- Select a confidence scheme +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T4144206465"] = "Select a confidence scheme" + +-- Do you want to enforce an app-wide minimum confidence level? +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T4258968041"] = "Do you want to enforce an app-wide minimum confidence level?" + +-- Delete LLM Provider +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T4269256234"] = "Delete LLM Provider" + +-- Yes, enforce a minimum confidence level +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T458854917"] = "Yes, enforce a minimum confidence level" + +-- Not yet configured +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T48051324"] = "Not yet configured" + +-- Open Dashboard +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T78223861"] = "Open Dashboard" + +-- Yes, show me the confidence level +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T853225204"] = "Yes, show me the confidence level" + +-- Provider +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T900237532"] = "Provider" + +-- If and when should we delete your temporary chats? +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELWORKSPACES::T1014418451"] = "If and when should we delete your temporary chats?" + +-- Workspace display behavior +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELWORKSPACES::T2151409362"] = "Workspace display behavior" + +-- Workspace behavior +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELWORKSPACES::T2562846516"] = "Workspace behavior" + +-- How should we display your workspaces? +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELWORKSPACES::T3566924898"] = "How should we display your workspaces?" + +-- Should we store your chats? +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELWORKSPACES::T3942969162"] = "Should we store your chats?" + +-- Workspace Options +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELWORKSPACES::T476474348"] = "Workspace Options" + +-- Workspace maintenance +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELWORKSPACES::T49653413"] = "Workspace maintenance" + +-- Copy {0} to the clipboard +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TEXTINFOLINE::T2206391442"] = "Copy {0} to the clipboard" + +-- Copy {0} to the clipboard +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TEXTINFOLINES::T2206391442"] = "Copy {0} to the clipboard" + +-- Open the repository or website +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::THIRDPARTYCOMPONENT::T1392042694"] = "Open the repository or website" + +-- License: +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::THIRDPARTYCOMPONENT::T1908172666"] = "License:" + +-- You'll interact with the AI systems using your voice. To achieve this, we want to integrate voice input (speech-to-text) and output (text-to-speech). However, later on, it should also have a natural conversation flow, i.e., seamless conversation. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T1015366320"] = "You'll interact with the AI systems using your voice. To achieve this, we want to integrate voice input (speech-to-text) and output (text-to-speech). However, later on, it should also have a natural conversation flow, i.e., seamless conversation." + +-- We hope this vision excites you as much as it excites us. Together, let's build a powerful and flexible AI toolkit to support all your creative, professional, and everyday needs with MindWork AI Studio. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T1061000046"] = "We hope this vision excites you as much as it excites us. Together, let's build a powerful and flexible AI toolkit to support all your creative, professional, and everyday needs with MindWork AI Studio." + +-- Integration of enterprise data +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T1127694951"] = "Integration of enterprise data" + +-- Meet your needs +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T127032776"] = "Meet your needs" + +-- We're integrating a writing mode to help you create extensive works, like comprehensive project proposals, tenders, or your next fantasy novel. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T1457213518"] = "We're integrating a writing mode to help you create extensive works, like comprehensive project proposals, tenders, or your next fantasy novel." + +-- Email monitoring +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T1520989255"] = "Email monitoring" + +-- You'll be able to integrate your data into AI Studio, like your PDF or Office files, or your Markdown notes. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T1648606751"] = "You'll be able to integrate your data into AI Studio, like your PDF or Office files, or your Markdown notes." + +-- 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." + +-- 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." + +-- 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." + +-- Browser usage +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T2345974992"] = "Browser usage" + +-- Integrating your data +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T2416595938"] = "Integrating your data" + +-- Curious about the vision for MindWork AI Studio and what the future holds? We're here to address just that. Remember, this is a free, open-source project, meaning we can't guarantee when or if this vision will be fully realized. Our aim is to share our vision with you to help you decide whether this app is right for you. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T2491403346"] = "Curious about the vision for MindWork AI Studio and what the future holds? We're here to address just that. Remember, this is a free, open-source project, meaning we can't guarantee when or if this vision will be fully realized. Our aim is to share our vision with you to help you decide whether this app is right for you." + +-- Voice control +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T2827242540"] = "Voice control" + +-- Specific requirements +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T2868740431"] = "Specific requirements" + +-- We'll develop more assistants for everyday tasks. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T2899555955"] = "We'll develop more assistants for everyday tasks." + +-- We're working on offering AI Studio features in your browser via a plugin, allowing, e.g., for spell-checking or text rewriting directly in the browser. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T308543246"] = "We're working on offering AI Studio features in your browser via a plugin, allowing, e.g., for spell-checking or text rewriting directly in the browser." + +-- There will be an interface for AI Studio to create content in other apps. You could, for example, create blog posts directly on the target platform or add entries to an internal knowledge management tool. This requires development work by the tool developers. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T3290746961"] = "There will be an interface for AI Studio to create content in other apps. You could, for example, create blog posts directly on the target platform or add entries to an internal knowledge management tool. This requires development work by the tool developers." + +-- Want an assistant that suits your specific needs? We aim to offer a plugin architecture so organizations and enthusiasts can implement such ideas. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T3440464089"] = "Want an assistant that suits your specific needs? We aim to offer a plugin architecture so organizations and enthusiasts can implement such ideas." + +-- Writing mode +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T3640675146"] = "Writing mode" + +-- So, where are we headed, and how could the app evolve in the coming months and years? The following list outlines our ideas, though not in order of priority: +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T4106960135"] = "So, where are we headed, and how could the app evolve in the coming months and years? The following list outlines our ideas, though not in order of priority:" + +-- Content creation +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T428040679"] = "Content creation" + +-- Useful assistants +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T586430036"] = "Useful assistants" + +-- Are you sure you want to delete the chat '{0}' in the workspace '{1}'? +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1016188706"] = "Are you sure you want to delete the chat '{0}' in the workspace '{1}'?" + +-- Move chat +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1133040906"] = "Move chat" + +-- Delete +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1469573738"] = "Delete" + +-- Rename Workspace +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1474303418"] = "Rename Workspace" + +-- Rename Chat +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T156144855"] = "Rename Chat" + +-- Add workspace +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1586005241"] = "Add workspace" + +-- Add chat +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1874060138"] = "Add chat" + +-- Create Chat +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1939006681"] = "Create Chat" + +-- Please name your workspace: +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T201482774"] = "Please name your workspace:" + +-- Are you sure you want to load another chat? All unsaved changes will be lost. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2133593288"] = "Are you sure you want to load another chat? All unsaved changes will be lost." + +-- Are you sure you want to delete the workspace '{0}'? This will also delete {1} chat(s) in this workspace. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2151341762"] = "Are you sure you want to delete the workspace '{0}'? This will also delete {1} chat(s) in this workspace." + +-- Are you sure you want to create a another chat? All unsaved changes will be lost. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2237618267"] = "Are you sure you want to create a another chat? All unsaved changes will be lost." + +-- Delete Chat +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2244038752"] = "Delete Chat" + +-- Move to workspace +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2509305748"] = "Move to workspace" + +-- Are you sure you want to delete the temporary chat '{0}'? +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3043761007"] = "Are you sure you want to delete the temporary chat '{0}'?" + +-- Move Chat to Workspace +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3045856778"] = "Move Chat to Workspace" + +-- Please enter a new or edit the name for your workspace '{0}': +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T323280982"] = "Please enter a new or edit the name for your workspace '{0}':" + +-- Rename +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3355849203"] = "Rename" + +-- Please enter a new or edit the name for your chat '{0}': +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3419791373"] = "Please enter a new or edit the name for your chat '{0}':" + +-- Load Chat +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3555709365"] = "Load Chat" + +-- Add Workspace +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3672981145"] = "Add Workspace" + +-- Empty chat +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T4019509364"] = "Empty chat" + +-- Workspaces +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T4048389951"] = "Workspaces" + +-- Disappearing Chats +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T4201703117"] = "Disappearing Chats" + +-- Please select the workspace where you want to move the chat to. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T474393241"] = "Please select the workspace where you want to move the chat to." + +-- Delete Workspace +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T701874671"] = "Delete Workspace" + +-- There is no social event +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T1222800281"] = "There is no social event" + +-- Agenda options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T1249372829"] = "Agenda options are preselected" + +-- Preselect a duration? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T1404615656"] = "Preselect a duration?" + +-- Preselect the number of participants +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T1444356399"] = "Preselect the number of participants" + +-- Meeting is virtual +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T1446638309"] = "Meeting is virtual" + +-- Preselect a name? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T1471770981"] = "Preselect a name?" + +-- Preselect whether participants needs to arrive and depart +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T1648427207"] = "Preselect whether participants needs to arrive and depart" + +-- Preselect a start time? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T1901151023"] = "Preselect a start time?" + +-- Preselect a location? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T1908318849"] = "Preselect a location?" + +-- How many participants should be preselected? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T1998244307"] = "How many participants should be preselected?" + +-- Preselect whether the meeting is virtual +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T2084951012"] = "Preselect whether the meeting is virtual" + +-- Would you like to preselect one of your profiles? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T2221665527"] = "Would you like to preselect one of your profiles?" + +-- When enabled, you can preselect most agenda options. This is might be useful when you need to create similar agendas often. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T2373110543"] = "When enabled, you can preselect most agenda options. This is might be useful when you need to create similar agendas often." + +-- Preselect whether the participants should get to know each other +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T2519703500"] = "Preselect whether the participants should get to know each other" + +-- Which agenda language should be preselected? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T2801220321"] = "Which agenda language should be preselected?" + +-- Preselect another agenda language +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T2915422331"] = "Preselect another agenda language" + +-- Participants do not need to get to know each other +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T2949002251"] = "Participants do not need to get to know each other" + +-- There is a social event +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T296183299"] = "There is a social event" + +-- Participants should be actively involved +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T298324727"] = "Participants should be actively involved" + +-- Meeting is in person +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T3008159782"] = "Meeting is in person" + +-- Participants do not need to arrive and depart +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T3087504452"] = "Participants do not need to arrive and depart" + +-- Preselect whether there is a joint dinner +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T3175009548"] = "Preselect whether there is a joint dinner" + +-- Preselect an objective? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T3439476935"] = "Preselect an objective?" + +-- Preselect a moderator? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T3482798491"] = "Preselect a moderator?" + +-- Participants need to arrive and depart +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T3591032034"] = "Participants need to arrive and depart" + +-- Participants do not need to be actively involved +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T3679899885"] = "Participants do not need to be actively involved" + +-- Preselect the approx. lunch time +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T3709527588"] = "Preselect the approx. lunch time" + +-- Preselect a topic? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T3835166371"] = "Preselect a topic?" + +-- Preselect one of your profiles? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T4004501229"] = "Preselect one of your profiles?" + +-- Preselect the agenda language +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T4055846391"] = "Preselect the agenda language" + +-- No agenda options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T4094211586"] = "No agenda options are preselected" + +-- Participants should get to know each other +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T464127805"] = "Participants should get to know each other" + +-- Assistant: Agenda Planner Options +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T677962779"] = "Assistant: Agenda Planner Options" + +-- There is a joint dinner +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T707310400"] = "There is a joint dinner" + +-- Preselect the approx. break time +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T722113273"] = "Preselect the approx. break time" + +-- There is no joint dinner +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T768936730"] = "There is no joint dinner" + +-- Preselect agenda options? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T800921421"] = "Preselect agenda options?" + +-- Preselect whether there is a social event +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T816053055"] = "Preselect whether there is a social event" + +-- Preselect whether the participants should actively involved +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T817726429"] = "Preselect whether the participants should actively involved" + +-- Restrict to one bias a day? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBIAS::T1608129203"] = "Restrict to one bias a day?" + +-- Yes, you can only retrieve one bias per day +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBIAS::T1765683725"] = "Yes, you can only retrieve one bias per day" + +-- Reset +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBIAS::T180921696"] = "Reset" + +-- Would you like to preselect one of your profiles? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBIAS::T2221665527"] = "Would you like to preselect one of your profiles?" + +-- No restriction. You can retrieve as many biases as you want per day. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBIAS::T2305356277"] = "No restriction. You can retrieve as many biases as you want per day." + +-- Which language should be preselected? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBIAS::T2345162613"] = "Which language should be preselected?" + +-- Reset your bias-of-the-day statistics +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBIAS::T2350981714"] = "Reset your bias-of-the-day statistics" + +-- Preselect another language +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBIAS::T2382415529"] = "Preselect another language" + +-- Preselect the language +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBIAS::T2571465005"] = "Preselect the language" + +-- Close +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBIAS::T3448155331"] = "Close" + +-- No options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBIAS::T354528094"] = "No options are preselected" + +-- Assistant: Bias of the Day +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBIAS::T384887684"] = "Assistant: Bias of the Day" + +-- Options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBIAS::T3875604319"] = "Options are preselected" + +-- Preselect one of your profiles? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBIAS::T4004501229"] = "Preselect one of your profiles?" + +-- Are you sure you want to reset your bias-of-the-day statistics? The system will no longer remember which biases you already know. As a result, biases you are already familiar with may be addressed again. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBIAS::T405627382"] = "Are you sure you want to reset your bias-of-the-day statistics? The system will no longer remember which biases you already know. As a result, biases you are already familiar with may be addressed again." + +-- Assistant: Bias of the Day Options +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBIAS::T4235808594"] = "Assistant: Bias of the Day Options" + +-- Preselect options? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBIAS::T42672465"] = "Preselect options?" + +-- You have learned about {0} out of {1} biases. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBIAS::T679061561"] = "You have learned about {0} out of {1} biases." + +-- When enabled, you can preselect options. This is might be useful when you prefer a specific language or LLM model. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBIAS::T711745239"] = "When enabled, you can preselect options. This is might be useful when you prefer a specific language or LLM model." + +-- Which programming language should be preselected for added contexts? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCODING::T1073540083"] = "Which programming language should be preselected for added contexts?" + +-- Compiler messages are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCODING::T1110902070"] = "Compiler messages are preselected" + +-- Preselect a programming language +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCODING::T2181567002"] = "Preselect a programming language" + +-- Would you like to preselect one of your profiles? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCODING::T2221665527"] = "Would you like to preselect one of your profiles?" + +-- When enabled, you can preselect the coding options. This is might be useful when you prefer a specific programming language or LLM model. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCODING::T2619641701"] = "When enabled, you can preselect the coding options. This is might be useful when you prefer a specific programming language or LLM model." + +-- Preselect coding options? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCODING::T2790579667"] = "Preselect coding options?" + +-- Preselect compiler messages? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCODING::T2970689954"] = "Preselect compiler messages?" + +-- No coding options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCODING::T3015105896"] = "No coding options are preselected" + +-- Coding options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCODING::T3567850751"] = "Coding options are preselected" + +-- Preselect one of your profiles? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCODING::T4004501229"] = "Preselect one of your profiles?" + +-- Preselect another programming language +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCODING::T4230412334"] = "Preselect another programming language" + +-- Compiler messages are not preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCODING::T516498299"] = "Compiler messages are not preselected" + +-- Assistant: Coding Options +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCODING::T585868261"] = "Assistant: Coding Options" + +-- You might configure different data sources. A data source can include one file, all files in a directory, or data from your company. Later, you can incorporate these data sources as needed when the AI requires this data to complete a certain task. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T1084943026"] = "You might configure different data sources. A data source can include one file, all files in a directory, or data from your company. Later, you can incorporate these data sources as needed when the AI requires this data to complete a certain task." + +-- Are you sure you want to delete the data source '{0}' of type {1}? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T1096979935"] = "Are you sure you want to delete the data source '{0}' of type {1}?" + +-- Edit Local Directory Data Source +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T1215599168"] = "Edit Local Directory Data Source" + +-- Add Local Directory as Data Source +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T1454193397"] = "Add Local Directory as Data Source" + +-- Delete +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T1469573738"] = "Delete" + +-- Local File +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T1687345358"] = "Local File" + +-- Delete Data Source +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T1849107431"] = "Delete Data Source" + +-- Local Directory Data Source Information +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T2146756020"] = "Local Directory Data Source Information" + +-- Edit ERI v1 Data Source +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T221059217"] = "Edit ERI v1 Data Source" + +-- Edit Local File Data Source +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T2453292893"] = "Edit Local File Data Source" + +-- ERI v1 Data Source Information +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T26243729"] = "ERI v1 Data Source Information" + +-- Name +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T266367750"] = "Name" + +-- Embedding +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T2838542994"] = "Embedding" + +-- Edit +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T3267849393"] = "Edit" + +-- Add Data Source +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T3387511033"] = "Add Data Source" + +-- Unknown +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T3424652889"] = "Unknown" + +-- Close +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T3448155331"] = "Close" + +-- Add Local File as Data Source +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T3500365052"] = "Add Local File as Data Source" + +-- Type +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T3512062061"] = "Type" + +-- Local File Data Source Information +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T3525663993"] = "Local File Data Source Information" + +-- No data sources configured yet. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T3549650120"] = "No data sources configured yet." + +-- Actions +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T3865031940"] = "Actions" + +-- Configured Data Sources +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T543942217"] = "Configured Data Sources" + +-- Add ERI v1 Data Source +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T590005498"] = "Add ERI v1 Data Source" + +-- External Data (ERI-Server v1) +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T774473996"] = "External Data (ERI-Server v1)" + +-- Local Directory +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T926703547"] = "Local Directory" + +-- 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." + +-- Preselect ERI server options? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGERISERVER::T1664055662"] = "Preselect ERI server options?" + +-- No ERI server options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGERISERVER::T1793785587"] = "No ERI server options are preselected" + +-- Most ERI server options can be customized and saved directly in the ERI server assistant. For this, the ERI server assistant has an auto-save function. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGERISERVER::T2093534613"] = "Most ERI server options can be customized and saved directly in the ERI server assistant. For this, the ERI server assistant has an auto-save function." + +-- Would you like to preselect one of your profiles? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGERISERVER::T2221665527"] = "Would you like to preselect one of your profiles?" + +-- Close +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGERISERVER::T3448155331"] = "Close" + +-- Assistant: ERI Server Options +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGERISERVER::T3629372826"] = "Assistant: ERI Server Options" + +-- Preselect one of your profiles? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGERISERVER::T4004501229"] = "Preselect one of your profiles?" + +-- ERI server options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGERISERVER::T488190224"] = "ERI server options are preselected" + +-- Preselect the target language +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGGRAMMARSPELLING::T1417990312"] = "Preselect the target language" + +-- Preselect another target language +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGGRAMMARSPELLING::T1462295644"] = "Preselect another target language" + +-- Close +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGGRAMMARSPELLING::T3448155331"] = "Close" + +-- Which target language should be preselected? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGGRAMMARSPELLING::T3547337928"] = "Which target language should be preselected?" + +-- Assistant: Grammar & Spelling Checker Options +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGGRAMMARSPELLING::T886675455"] = "Assistant: Grammar & Spelling Checker Options" + +-- Preselect the target language +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGI18N::T1417990312"] = "Preselect the target language" + +-- Preselect another target language +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGI18N::T1462295644"] = "Preselect another target language" + +-- Assistant: Localization +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGI18N::T2573041664"] = "Assistant: Localization" + +-- Close +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGI18N::T3448155331"] = "Close" + +-- Which target language should be preselected? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGI18N::T3547337928"] = "Which target language should be preselected?" + +-- Preselect the icon source +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGICONFINDER::T1116652851"] = "Preselect the icon source" + +-- Assistant: Icon Finder Options +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGICONFINDER::T1570765862"] = "Assistant: Icon Finder Options" + +-- No icon options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGICONFINDER::T1694910115"] = "No icon options are preselected" + +-- Icon options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGICONFINDER::T1792507476"] = "Icon options are preselected" + +-- Close +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGICONFINDER::T3448155331"] = "Close" + +-- Preselect icon options? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGICONFINDER::T725252382"] = "Preselect icon options?" + +-- No job posting options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGJOBPOSTINGS::T1257718691"] = "No job posting options are preselected" + +-- Preselect some mandatory information about the job posting? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGJOBPOSTINGS::T1332068481"] = "Preselect some mandatory information about the job posting?" + +-- Preselect another target language +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGJOBPOSTINGS::T1462295644"] = "Preselect another target language" + +-- Job posting options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGJOBPOSTINGS::T1827578822"] = "Job posting options are preselected" + +-- Preselect the work location? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGJOBPOSTINGS::T1867962106"] = "Preselect the work location?" + +-- Preselect the language +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGJOBPOSTINGS::T2571465005"] = "Preselect the language" + +-- Preselect job posting options? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGJOBPOSTINGS::T2624983038"] = "Preselect job posting options?" + +-- Preselect the company name? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGJOBPOSTINGS::T2679442990"] = "Preselect the company name?" + +-- When enabled, you can preselect some job posting options. This is might be useful when you prefer a specific LLM model. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGJOBPOSTINGS::T2907036553"] = "When enabled, you can preselect some job posting options. This is might be useful when you prefer a specific LLM model." + +-- Preselect the job qualifications? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGJOBPOSTINGS::T3223375709"] = "Preselect the job qualifications?" + +-- Assistant: Job Posting Options +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGJOBPOSTINGS::T3307661496"] = "Assistant: Job Posting Options" + +-- Close +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGJOBPOSTINGS::T3448155331"] = "Close" + +-- Which target language should be preselected? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGJOBPOSTINGS::T3547337928"] = "Which target language should be preselected?" + +-- Preselect the job responsibilities? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGJOBPOSTINGS::T3788397013"] = "Preselect the job responsibilities?" + +-- Preselect the job description? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGJOBPOSTINGS::T3825475093"] = "Preselect the job description?" + +-- Content cleaner agent is preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T1013787967"] = "Content cleaner agent is preselected" + +-- Web content reader is shown +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T1030372436"] = "Web content reader is shown" + +-- When enabled, the web content reader is preselected. This is might be useful when you prefer to load legal content from the web very often. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T1507288278"] = "When enabled, the web content reader is preselected. This is might be useful when you prefer to load legal content from the web very often." + +-- Preselect legal check options? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T1563865738"] = "Preselect legal check options?" + +-- No legal check options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T1591931823"] = "No legal check options are preselected" + +-- When activated, the web content reader is hidden and cannot be used. As a result, the user interface becomes a bit easier to use. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T1633101895"] = "When activated, the web content reader is hidden and cannot be used. As a result, the user interface becomes a bit easier to use." + +-- Web content reader is not preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T1701127912"] = "Web content reader is not preselected" + +-- Content cleaner agent is not preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T1969816694"] = "Content cleaner agent is not preselected" + +-- Hide the web content reader? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T2090693677"] = "Hide the web content reader?" + +-- When enabled, you can preselect some legal check options. This is might be useful when you prefer a specific LLM model. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T2164667361"] = "When enabled, you can preselect some legal check options. This is might be useful when you prefer a specific LLM model." + +-- Would you like to preselect one of your profiles? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T2221665527"] = "Would you like to preselect one of your profiles?" + +-- Legal check options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T252916114"] = "Legal check options are preselected" + +-- When enabled, the content cleaner agent is preselected. This is might be useful when you prefer to clean up the legal content before translating it. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T2746583995"] = "When enabled, the content cleaner agent is preselected. This is might be useful when you prefer to clean up the legal content before translating it." + +-- Web content reader is hidden +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T2799795311"] = "Web content reader is hidden" + +-- Close +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T3448155331"] = "Close" + +-- Web content reader is preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T3641773985"] = "Web content reader is preselected" + +-- Preselect the content cleaner agent? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T3649428096"] = "Preselect the content cleaner agent?" + +-- Preselect one of your profiles? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T4004501229"] = "Preselect one of your profiles?" + +-- Assistant: Legal Check Options +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T4033382756"] = "Assistant: Legal Check Options" + +-- Preselect the web content reader? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T629158142"] = "Preselect the web content reader?" + +-- Would you like to preselect one of your profiles? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGMYTASKS::T2221665527"] = "Would you like to preselect one of your profiles?" + +-- Which language should be preselected? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGMYTASKS::T2345162613"] = "Which language should be preselected?" + +-- Preselect another language +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGMYTASKS::T2382415529"] = "Preselect another language" + +-- Preselect the language +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGMYTASKS::T2571465005"] = "Preselect the language" + +-- Close +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGMYTASKS::T3448155331"] = "Close" + +-- No options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGMYTASKS::T354528094"] = "No options are preselected" + +-- Assistant: My Tasks Options +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGMYTASKS::T3710380967"] = "Assistant: My Tasks Options" + +-- Options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGMYTASKS::T3875604319"] = "Options are preselected" + +-- Preselect one of your profiles? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGMYTASKS::T4004501229"] = "Preselect one of your profiles?" + +-- Preselect options? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGMYTASKS::T42672465"] = "Preselect options?" + +-- When enabled, you can preselect options. This is might be useful when you prefer a specific language or LLM model. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGMYTASKS::T711745239"] = "When enabled, you can preselect options. This is might be useful when you prefer a specific language or LLM model." + +-- Which writing style should be preselected? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGREWRITE::T1173034744"] = "Which writing style should be preselected?" + +-- Preselect the target language +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGREWRITE::T1417990312"] = "Preselect the target language" + +-- Preselect another target language +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGREWRITE::T1462295644"] = "Preselect another target language" + +-- Preselect a sentence structure +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGREWRITE::T1621537655"] = "Preselect a sentence structure" + +-- Assistant: Rewrite & Improve Text Options +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGREWRITE::T1995708818"] = "Assistant: Rewrite & Improve Text Options" + +-- Which voice should be preselected for the sentence structure? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGREWRITE::T2661599097"] = "Which voice should be preselected for the sentence structure?" + +-- Preselect a writing style +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGREWRITE::T28456020"] = "Preselect a writing style" + +-- Close +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGREWRITE::T3448155331"] = "Close" + +-- Which target language should be preselected? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGREWRITE::T3547337928"] = "Which target language should be preselected?" + +-- When enabled, you can preselect synonym options. This is might be useful when you prefer a specific language or LLM model. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSYNONYMS::T183953912"] = "When enabled, you can preselect synonym options. This is might be useful when you prefer a specific language or LLM model." + +-- No synonym options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSYNONYMS::T2183758387"] = "No synonym options are preselected" + +-- Which language should be preselected? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSYNONYMS::T2345162613"] = "Which language should be preselected?" + +-- Preselect another language +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSYNONYMS::T2382415529"] = "Preselect another language" + +-- Synonym options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSYNONYMS::T2390458990"] = "Synonym options are preselected" + +-- Preselect the language +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSYNONYMS::T2571465005"] = "Preselect the language" + +-- Close +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSYNONYMS::T3448155331"] = "Close" + +-- Assistant: Synonyms Options +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSYNONYMS::T3889117881"] = "Assistant: Synonyms Options" + +-- Preselect synonym options? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSYNONYMS::T4170921846"] = "Preselect synonym options?" + +-- Content cleaner agent is preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTEXTSUMMARIZER::T1013787967"] = "Content cleaner agent is preselected" + +-- Web content reader is shown +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTEXTSUMMARIZER::T1030372436"] = "Web content reader is shown" + +-- Preselect the summarizer complexity +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTEXTSUMMARIZER::T104409170"] = "Preselect the summarizer complexity" + +-- Preselect summarizer options? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTEXTSUMMARIZER::T108151178"] = "Preselect summarizer options?" + +-- Preselect the target language +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTEXTSUMMARIZER::T1417990312"] = "Preselect the target language" + +-- Preselect another target language +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTEXTSUMMARIZER::T1462295644"] = "Preselect another target language" + +-- When activated, the web content reader is hidden and cannot be used. As a result, the user interface becomes a bit easier to use. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTEXTSUMMARIZER::T1633101895"] = "When activated, the web content reader is hidden and cannot be used. As a result, the user interface becomes a bit easier to use." + +-- Web content reader is not preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTEXTSUMMARIZER::T1701127912"] = "Web content reader is not preselected" + +-- Assistant: Text Summarizer Options +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTEXTSUMMARIZER::T1767527569"] = "Assistant: Text Summarizer Options" + +-- Content cleaner agent is not preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTEXTSUMMARIZER::T1969816694"] = "Content cleaner agent is not preselected" + +-- Hide the web content reader? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTEXTSUMMARIZER::T2090693677"] = "Hide the web content reader?" + +-- Summarizer options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTEXTSUMMARIZER::T2355441996"] = "Summarizer options are preselected" + +-- Web content reader is hidden +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTEXTSUMMARIZER::T2799795311"] = "Web content reader is hidden" + +-- No summarizer options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTEXTSUMMARIZER::T3215334223"] = "No summarizer options are preselected" + +-- When enabled, the web content reader is preselected. This is might be useful when you prefer to load content from the web very often. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTEXTSUMMARIZER::T3216157681"] = "When enabled, the web content reader is preselected. This is might be useful when you prefer to load content from the web very often." + +-- Close +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTEXTSUMMARIZER::T3448155331"] = "Close" + +-- Which target language should be preselected? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTEXTSUMMARIZER::T3547337928"] = "Which target language should be preselected?" + +-- Web content reader is preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTEXTSUMMARIZER::T3641773985"] = "Web content reader is preselected" + +-- Preselect the content cleaner agent? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTEXTSUMMARIZER::T3649428096"] = "Preselect the content cleaner agent?" + +-- When enabled, the content cleaner agent is preselected. This is might be useful when you prefer to clean up the content before summarize it. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTEXTSUMMARIZER::T3660434400"] = "When enabled, the content cleaner agent is preselected. This is might be useful when you prefer to clean up the content before summarize it." + +-- When enabled, you can preselect the text summarizer options. This is might be useful when you prefer a specific language, complexity, or LLM. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTEXTSUMMARIZER::T3820844575"] = "When enabled, you can preselect the text summarizer options. This is might be useful when you prefer a specific language, complexity, or LLM." + +-- Which summarizer complexity should be preselected? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTEXTSUMMARIZER::T408530182"] = "Which summarizer complexity should be preselected?" + +-- Preselect your expertise +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTEXTSUMMARIZER::T51139714"] = "Preselect your expertise" + +-- Preselect the web content reader? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTEXTSUMMARIZER::T629158142"] = "Preselect the web content reader?" + +-- Content cleaner agent is preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T1013787967"] = "Content cleaner agent is preselected" + +-- Assistant: Translator Options +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T1016384269"] = "Assistant: Translator Options" + +-- Web content reader is shown +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T1030372436"] = "Web content reader is shown" + +-- When enabled, you can preselect the translator options. This is might be useful when you prefer a specific target language or LLM model. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T1111006275"] = "When enabled, you can preselect the translator options. This is might be useful when you prefer a specific target language or LLM model." + +-- Preselect the target language +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T1417990312"] = "Preselect the target language" + +-- Preselect another target language +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T1462295644"] = "Preselect another target language" + +-- When activated, the web content reader is hidden and cannot be used. As a result, the user interface becomes a bit easier to use. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T1633101895"] = "When activated, the web content reader is hidden and cannot be used. As a result, the user interface becomes a bit easier to use." + +-- Web content reader is not preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T1701127912"] = "Web content reader is not preselected" + +-- Live translation is not preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T1825690873"] = "Live translation is not preselected" + +-- Content cleaner agent is not preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T1969816694"] = "Content cleaner agent is not preselected" + +-- Preselect translator options? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T1989346399"] = "Preselect translator options?" + +-- Hide the web content reader? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T2090693677"] = "Hide the web content reader?" + +-- Translator options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T2234531191"] = "Translator options are preselected" + +-- Live translation is preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T2435743076"] = "Live translation is preselected" + +-- Web content reader is hidden +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T2799795311"] = "Web content reader is hidden" + +-- No translator options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T2866358796"] = "No translator options are preselected" + +-- When enabled, the web content reader is preselected. This is might be useful when you prefer to load content from the web very often. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T3216157681"] = "When enabled, the web content reader is preselected. This is might be useful when you prefer to load content from the web very often." + +-- Close +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T3448155331"] = "Close" + +-- Which target language should be preselected? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T3547337928"] = "Which target language should be preselected?" + +-- Web content reader is preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T3641773985"] = "Web content reader is preselected" + +-- Preselect the content cleaner agent? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T3649428096"] = "Preselect the content cleaner agent?" + +-- Preselect the web content reader? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T629158142"] = "Preselect the web content reader?" + +-- How fast should the live translation react? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T884246296"] = "How fast should the live translation react?" + +-- When enabled, the content cleaner agent is preselected. This is might be useful when you prefer to clean up the content before translating it. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T894123480"] = "When enabled, the content cleaner agent is preselected. This is might be useful when you prefer to clean up the content before translating it." + +-- Preselect live translation? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T918172772"] = "Preselect live translation?" + +-- Which writing style should be preselected? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGWRITINGEMAILS::T1173034744"] = "Which writing style should be preselected?" + +-- Preselect a greeting? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGWRITINGEMAILS::T1254399201"] = "Preselect a greeting?" + +-- Preselect the target language +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGWRITINGEMAILS::T1417990312"] = "Preselect the target language" + +-- Preselect another target language +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGWRITINGEMAILS::T1462295644"] = "Preselect another target language" + +-- Assistant: Writing E-Mails Options +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGWRITINGEMAILS::T2021226503"] = "Assistant: Writing E-Mails Options" + +-- When enabled, you can preselect the e-mail options. This is might be useful when you prefer a specific language or LLM model. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGWRITINGEMAILS::T2116404483"] = "When enabled, you can preselect the e-mail options. This is might be useful when you prefer a specific language or LLM model." + +-- Preselect your name for the closing salutation? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGWRITINGEMAILS::T221974240"] = "Preselect your name for the closing salutation?" + +-- Would you like to preselect one of your profiles? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGWRITINGEMAILS::T2221665527"] = "Would you like to preselect one of your profiles?" + +-- Preselect a writing style +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGWRITINGEMAILS::T28456020"] = "Preselect a writing style" + +-- E-Mail options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGWRITINGEMAILS::T2985974420"] = "E-Mail options are preselected" + +-- No e-mail options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGWRITINGEMAILS::T3047605763"] = "No e-mail options are preselected" + +-- Close +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGWRITINGEMAILS::T3448155331"] = "Close" + +-- Which target language should be preselected? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGWRITINGEMAILS::T3547337928"] = "Which target language should be preselected?" + +-- Preselect e-mail options? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGWRITINGEMAILS::T3832719342"] = "Preselect e-mail options?" + +-- Preselect one of your profiles? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGWRITINGEMAILS::T4004501229"] = "Preselect one of your profiles?" + +-- Settings +UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T1258653480"] = "Settings" + +-- Home +UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T1391791790"] = "Home" + +-- About +UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T1491113694"] = "About" + +-- Are you sure you want to leave the chat page? All unsaved changes will be lost. +UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T1563130494"] = "Are you sure you want to leave the chat page? All unsaved changes will be lost." + +-- Assistants +UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T1614176092"] = "Assistants" + +-- Update +UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T1847791252"] = "Update" + +-- Leave Chat Page +UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T2124749705"] = "Leave Chat Page" + +-- Plugins +UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T2222816203"] = "Plugins" + +-- An update to version {0} is available. +UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T2800137365"] = "An update to version {0} is available." + +-- Please wait for the update to complete... +UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T2864211629"] = "Please wait for the update to complete..." + +-- Supporters +UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T2929332068"] = "Supporters" + +-- Writer +UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T2979224202"] = "Writer" + +-- Show details +UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T3692372066"] = "Show details" + +-- Chat +UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T578410699"] = "Chat" + +-- Startup log file +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T1019424746"] = "Startup log file" + +-- About MindWork AI Studio +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T1020427799"] = "About MindWork AI Studio" + +-- Browse AI Studio's source code on GitHub — we welcome your contributions. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T1107156991"] = "Browse AI Studio's source code on GitHub — we welcome your contributions." + +-- 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::ABOUT::T1388816916"] = "This library is used to read PDF files. This is necessary, e.g., for using PDFs as a data source for a chat." + +-- 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::ABOUT::T1421513382"] = "This library is used to extend the MudBlazor library. It provides additional components that are not part of the MudBlazor library." + +-- 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::ABOUT::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." + +-- Building on .NET, ASP.NET Core, and Blazor, MudBlazor is used as a library for designing and developing the user interface. It is a great project that significantly accelerates the development of advanced user interfaces with Blazor. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T1629800076"] = "Building on .NET, ASP.NET Core, and Blazor, MudBlazor is used as a library for designing and developing the user interface. It is a great project that significantly accelerates the development of advanced user interfaces with Blazor." + +-- AI Studio creates a log file at startup, in which events during startup are recorded. After startup, another log file is created that records all events that occur during the use of the app. This includes any errors that may occur. Depending on when an error occurs (at startup or during use), the contents of these log files can be helpful for troubleshooting. Sensitive information such as passwords is not included in the log files. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T1630237140"] = "AI Studio creates a log file at startup, in which events during startup are recorded. After startup, another log file is created that records all events that occur during the use of the app. This includes any errors that may occur. Depending on when an error occurs (at startup or during use), the contents of these log files can be helpful for troubleshooting. Sensitive information such as passwords is not included in the log files." + +-- This library is used to display the differences between two texts. This is necessary, e.g., for the grammar and spelling assistant. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T1772678682"] = "This library is used to display the differences between two texts. This is necessary, e.g., for the grammar and spelling assistant." + +-- By clicking on the respective path, the path is copied to the clipboard. You might open these files with a text editor to view their contents. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T1806897624"] = "By clicking on the respective path, the path is copied to the clipboard. You might open these files with a text editor to view their contents." + +-- Check for updates +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T1890416390"] = "Check for updates" + +-- Vision +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T1892426825"] = "Vision" + +-- 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::ABOUT::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." + +-- 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::ABOUT::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." + +-- This library is used to determine the file type of a file. This is necessary, e.g., when we want to stream a file. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T2173617769"] = "This library is used to determine the file type of a file. This is necessary, e.g., when we want to stream a file." + +-- For the secure communication between the user interface and the runtime, we need to create certificates. This Rust library is great for this purpose. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T2174764529"] = "For the secure communication between the user interface and the runtime, we need to create certificates. This Rust library is great for this purpose." + +-- 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::ABOUT::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." + +-- In order to use any LLM, each user must store their so-called token for each LLM provider. This token 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. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T228561878"] = "In order to use any LLM, each user must store their so-called token for each LLM provider. This token 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." + +-- The C# language is used for the implementation of the user interface and the backend. To implement the user interface with C#, the Blazor technology from ASP.NET Core is used. All these technologies are integrated into the .NET SDK. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T2329884315"] = "The C# language is used for the implementation of the user interface and the backend. To implement the user interface with C#, the Blazor technology from ASP.NET Core is used. All these technologies are integrated into the .NET SDK." + +-- This library is used to determine the language of the operating system. This is necessary to set the language of the user interface. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T2557014401"] = "This library is used to determine the language of the operating system. This is necessary to set the language of the user interface." + +-- Used Open Source Projects +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T2557066213"] = "Used Open Source Projects" + +-- Build time +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T260228112"] = "Build time" + +-- 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::ABOUT::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." + +-- Usage log file +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T2689995864"] = "Usage log file" + +-- Logbook +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T2706940196"] = "Logbook" + +-- This component is used to render Markdown text. This is important because the LLM often responds with Markdown-formatted text, allowing us to present it in a way that is easier to read. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T2726131107"] = "This component is used to render Markdown text. This is important because the LLM often responds with Markdown-formatted text, allowing us to present it in a way that is easier to read." + +-- 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. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T2777988282"] = "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." + +-- View our project roadmap and help shape AI Studio's future development. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T2829971158"] = "View our project roadmap and help shape AI Studio's future development." + +-- Used .NET runtime +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T2840227993"] = "Used .NET runtime" + +-- Explanation +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::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::ABOUT::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." + +-- Changelog +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T3017574265"] = "Changelog" + +-- Connect AI Studio to your organization's data with our External Retrieval Interface (ERI). +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T313276297"] = "Connect AI Studio to your organization's data with our External Retrieval Interface (ERI)." + +-- Have feature ideas? Submit suggestions for future AI Studio enhancements. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T3178730036"] = "Have feature ideas? Submit suggestions for future AI Studio enhancements." + +-- Discover MindWork AI's mission and vision on our official homepage. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T3294830584"] = "Discover MindWork AI's mission and vision on our official homepage." + +-- User-language provided by the OS +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T3334355246"] = "User-language provided by the OS" + +-- The following list shows the versions of the MindWork AI Studio, the used compilers, build time, etc.: +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T3405978777"] = "The following list shows the versions of the MindWork AI Studio, the used compilers, build time, etc.:" + +-- Used Rust compiler +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T3440211747"] = "Used Rust compiler" + +-- 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::ABOUT::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!" + +-- Motivation +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::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::ABOUT::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." + +-- 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. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T3908558992"] = "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." + +-- Versions +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T4010195468"] = "Versions" + +-- This library is used to create asynchronous streams in Rust. It allows us to work with streams of data that can be produced asynchronously, making it easier to handle events or data that arrive over time. We use this, e.g., to stream arbitrary data from the file system to the embedding system. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T4079152443"] = "This library is used to create asynchronous streams in Rust. It allows us to work with streams of data that can be produced asynchronously, making it easier to handle events or data that arrive over time. We use this, e.g., to stream arbitrary data from the file system to the embedding system." + +-- Community & Code +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T4158546761"] = "Community & Code" + +-- 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. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T4184485147"] = "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." + +-- When transferring sensitive data between Rust runtime and .NET app, we encrypt the data. We use some libraries from the Rust Crypto project for this purpose: cipher, aes, cbc, pbkdf2, hmac, and sha2. We are thankful for the great work of the Rust Crypto project. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T4229014037"] = "When transferring sensitive data between Rust runtime and .NET app, we encrypt the data. We use some libraries from the Rust Crypto project for this purpose: cipher, aes, cbc, pbkdf2, hmac, and sha2. We are thankful for the great work of the Rust Crypto project." + +-- This is a library providing the foundations for asynchronous programming in Rust. It includes key trait definitions like Stream, as well as utilities like join!, select!, and various futures combinator methods which enable expressive asynchronous control flow. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T566998575"] = "This is a library providing the foundations for asynchronous programming in Rust. It includes key trait definitions like Stream, as well as utilities like join!, select!, and various futures combinator methods which enable expressive asynchronous control flow." + +-- Used .NET SDK +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T585329785"] = "Used .NET SDK" + +-- Did you find a bug or are you experiencing issues? Report your concern here. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T639371534"] = "Did you find a bug or are you experiencing issues? Report your concern here." + +-- This Rust library is used to output the app's messages to the terminal. This is helpful during development and troubleshooting. This feature is initially invisible; when the app is started via the terminal, the messages become visible. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T64689067"] = "This Rust library is used to output the app's messages to the terminal. This is helpful during development and troubleshooting. This feature is initially invisible; when the app is started via the terminal, the messages become visible." + +-- For some data transfers, we need to encode the data in base64. This Rust library is great for this purpose. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T870640199"] = "For some data transfers, we need to encode the data in base64. This Rust library is great for this purpose." + +-- Get coding and debugging support from an LLM. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T1243850917"] = "Get coding and debugging support from an LLM." + +-- Legal Check +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T1348190638"] = "Legal Check" + +-- Coding +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T1617786407"] = "Coding" + +-- Analyze a text or an email for tasks you need to complete. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T1728590051"] = "Analyze a text or an email for tasks you need to complete." + +-- Text Summarizer +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T1907192403"] = "Text Summarizer" + +-- Check grammar and spelling of a given text. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T1934717573"] = "Check grammar and spelling of a given text." + +-- Translate text into another language. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T209791153"] = "Translate text into another language." + +-- Generate an e-mail for a given context. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T2383649630"] = "Generate an e-mail for a given context." + +-- Generate an agenda for a given meeting, seminar, etc. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T2406168562"] = "Generate an agenda for a given meeting, seminar, etc." + +-- Agenda Planner +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T2435638853"] = "Agenda Planner" + +-- Synonyms +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T2547582747"] = "Synonyms" + +-- Find synonyms for a given word or phrase. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T2712131461"] = "Find synonyms for a given word or phrase." + +-- Generate a job posting for a given job description. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T2831103254"] = "Generate a job posting for a given job description." + +-- My Tasks +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T3011450657"] = "My Tasks" + +-- Translate AI Studio text content into other languages +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T3181803840"] = "Translate AI Studio text content into other languages" + +-- Icon Finder +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T3693102312"] = "Icon Finder" + +-- Generate an ERI server to integrate business systems. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T3756213118"] = "Generate an ERI server to integrate business systems." + +-- Use an LLM to find an icon for a given context. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T3881504200"] = "Use an LLM to find an icon for a given context." + +-- Job Posting +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T3930052338"] = "Job Posting" + +-- Ask a question about a legal document. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T3970214537"] = "Ask a question about a legal document." + +-- ERI Server +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T4204533420"] = "ERI Server" + +-- Use an LLM to summarize a given text. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T502222021"] = "Use an LLM to summarize a given text." + +-- Translation +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T613888204"] = "Translation" + +-- Rewrite and improve a given text for a chosen style. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T722167136"] = "Rewrite and improve a given text for a chosen style." + +-- Bias of the Day +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T782102948"] = "Bias of the Day" + +-- Learn about one cognitive bias every day. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T878695986"] = "Learn about one cognitive bias every day." + +-- Localization +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T897888480"] = "Localization" + +-- Hide your workspaces +UI_TEXT_CONTENT["AISTUDIO::PAGES::CHAT::T2351468526"] = "Hide your workspaces" + +-- Disappearing Chat +UI_TEXT_CONTENT["AISTUDIO::PAGES::CHAT::T3046519404"] = "Disappearing Chat" + +-- Your workspaces +UI_TEXT_CONTENT["AISTUDIO::PAGES::CHAT::T3745240468"] = "Your workspaces" + +-- Chat in Workspace +UI_TEXT_CONTENT["AISTUDIO::PAGES::CHAT::T582100343"] = "Chat in Workspace" + +-- Unlike services like ChatGPT, which impose limits after intensive use, MindWork AI Studio offers unlimited usage through the providers API. +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T1009708591"] = "Unlike services like ChatGPT, which impose limits after intensive use, MindWork AI Studio offers unlimited usage through the providers API." + +-- Welcome to MindWork AI Studio! +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T1024253064"] = "Welcome to MindWork AI Studio!" + +-- Thank you for considering MindWork AI Studio for your AI needs. This app is designed to help you harness the power of Large Language Models (LLMs). Please note that this app doesn't come with an integrated LLM. Instead, you will need to bring an API key from a suitable provider. +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T1146553980"] = "Thank you for considering MindWork AI Studio for your AI needs. This app is designed to help you harness the power of Large Language Models (LLMs). Please note that this app doesn't come with an integrated LLM. Instead, you will need to bring an API key from a suitable provider." + +-- The app requires minimal storage for installation and operates with low memory usage. Additionally, it has a minimal impact on system resources, which is beneficial for battery life. +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T144565305"] = "The app requires minimal storage for installation and operates with low memory usage. Additionally, it has a minimal impact on system resources, which is beneficial for battery life." + +-- You only pay for what you use, which can be cheaper than monthly subscription services like ChatGPT Plus, especially if used infrequently. But beware, here be dragons: For extremely intensive usage, the API costs can be significantly higher. Unfortunately, providers currently do not offer a way to display current costs in the app. Therefore, check your account with the respective provider to see how your costs are developing. When available, use prepaid and set a cost limit. +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T149711988"] = "You only pay for what you use, which can be cheaper than monthly subscription services like ChatGPT Plus, especially if used infrequently. But beware, here be dragons: For extremely intensive usage, the API costs can be significantly higher. Unfortunately, providers currently do not offer a way to display current costs in the app. Therefore, check your account with the respective provider to see how your costs are developing. When available, use prepaid and set a cost limit." + +-- Assistants +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T1614176092"] = "Assistants" + +-- Unrestricted usage +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T1686815996"] = "Unrestricted usage" + +-- Vision +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T1892426825"] = "Vision" + +-- You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT4o, o1, etc.), Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), Hugging Face, and self-hosted models using 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. +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T2217921237"] = "You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT4o, o1, etc.), Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), Hugging Face, and self-hosted models using 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." + +-- Let's get started +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T2331588413"] = "Let's get started" + +-- Last Changelog +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T2348849647"] = "Last Changelog" + +-- Choose the provider and model best suited for your current task. +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T2588488920"] = "Choose the provider and model best suited for your current task." + +-- Quick Start Guide +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T3002014720"] = "Quick Start Guide" + +-- 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. +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T3228075421"] = "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." + +-- We hope you enjoy using MindWork AI Studio to bring your AI projects to life! +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T3275341342"] = "We hope you enjoy using MindWork AI Studio to bring your AI projects to life!" + +-- Cost-effective +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T3341379752"] = "Cost-effective" + +-- Flexibility +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T3723223888"] = "Flexibility" + +-- Privacy +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T3959064551"] = "Privacy" + +-- You can control which providers receive your data using the provider confidence settings. For example, you can set different protection levels for writing emails compared to general chats, etc. Additionally, most providers guarantee that they won't use your data to train new AI systems. +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T457410099"] = "You can control which providers receive your data using the provider confidence settings. For example, you can set different protection levels for writing emails compared to general chats, etc. Additionally, most providers guarantee that they won't use your data to train new AI systems." + +-- Free of charge +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T617579208"] = "Free of charge" + +-- Independence +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T649448159"] = "Independence" + +-- No bloatware +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T858047957"] = "No bloatware" + +-- Here's what makes MindWork AI Studio stand out: +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T873851215"] = "Here's what makes MindWork AI Studio stand out:" + +-- The app is free to use, both for personal and commercial purposes. +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T91074375"] = "The app is free to use, both for personal and commercial purposes." + +-- Disable plugin +UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T1430375822"] = "Disable plugin" + +-- Internal Plugins +UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T158493184"] = "Internal Plugins" + +-- Disabled Plugins +UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T1724138133"] = "Disabled Plugins" + +-- Enable plugin +UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T2057806005"] = "Enable plugin" + +-- Plugins +UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T2222816203"] = "Plugins" + +-- Enabled Plugins +UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T2738444034"] = "Enabled Plugins" + +-- Actions +UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T3865031940"] = "Actions" + +-- Settings +UI_TEXT_CONTENT["AISTUDIO::PAGES::SETTINGS::T1258653480"] = "Settings" + +-- Thank you for being the first to contribute a one-time donation. +UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T1470916504"] = "Thank you for being the first to contribute a one-time donation." + +-- Thank you, Peer, for your courage in being the second person to support the project financially. +UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T1714878838"] = "Thank you, Peer, for your courage in being the second person to support the project financially." + +-- Individual Contributors +UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T1874835680"] = "Individual Contributors" + +-- Thanks, Nils, for taking the time to learn Rust and build the foundation for local retrieval. +UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T2355807535"] = "Thanks, Nils, for taking the time to learn Rust and build the foundation for local retrieval." + +-- The first 10 supporters who make a one-time contribution: +UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T2410456125"] = "The first 10 supporters who make a one-time contribution:" + +-- Supporters +UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T2929332068"] = "Supporters" + +-- Financial Support +UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T3061261435"] = "Financial Support" + +-- The first 10 supporters who make a monthly contribution: +UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T3364384944"] = "The first 10 supporters who make a monthly contribution:" + +-- Thank you, Richard, for being the first. +UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T3660718138"] = "Thank you, Richard, for being the first." + +-- Thanks Dominic for being the third supporter. +UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T3664780201"] = "Thanks Dominic for being the third supporter." + +-- Our Titans +UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T3805270964"] = "Our Titans" + +-- Moderation, Design, Wiki, and Documentation +UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T3821668394"] = "Moderation, Design, Wiki, and Documentation" + +-- Thank you, Peer, for familiarizing yourself with C#, providing excellent contributions like the Alibaba and Hugging Face providers, and revising the settings management. +UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T4106820759"] = "Thank you, Peer, for familiarizing yourself with C#, providing excellent contributions like the Alibaba and Hugging Face providers, and revising the settings management." + +-- Code Contributions +UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T4135925647"] = "Code Contributions" + +-- Become our first Titan +UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T414428338"] = "Become our first Titan" + +-- Become a contributor +UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T414604046"] = "Become a contributor" + +-- In this section, we highlight the titan supporters of MindWork AI Studio. Titans are prestigious companies that provide significant support to our mission. +UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T4270177642"] = "In this section, we highlight the titan supporters of MindWork AI Studio. Titans are prestigious companies that provide significant support to our mission." + +-- Thanks Luc for your build script contribution. +UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T432023389"] = "Thanks Luc for your build script contribution." + +-- For companies, sponsoring MindWork AI Studio is not only a way to support innovation but also a valuable opportunity for public relations and marketing. Your company's name and logo will be featured prominently, showcasing your commitment to using cutting-edge AI tools and enhancing your reputation as an innovative enterprise. +UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T68519158"] = "For companies, sponsoring MindWork AI Studio is not only a way to support innovation but also a valuable opportunity for public relations and marketing. Your company's name and logo will be featured prominently, showcasing your commitment to using cutting-edge AI tools and enhancing your reputation as an innovative enterprise." + +-- Thanks for your build script contribution. +UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T686206269"] = "Thanks for your build script contribution." + +-- Business Contributors +UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T838479287"] = "Business Contributors" + +-- Thank you very much, Kerstin, for taking care of creating the Wiki. +UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T991294232"] = "Thank you very much, Kerstin, for taking care of creating the Wiki." + +-- Write your text +UI_TEXT_CONTENT["AISTUDIO::PAGES::WRITER::T2220943334"] = "Write your text" + +-- Writer +UI_TEXT_CONTENT["AISTUDIO::PAGES::WRITER::T2979224202"] = "Writer" + +-- Suggestion +UI_TEXT_CONTENT["AISTUDIO::PAGES::WRITER::T3948127789"] = "Suggestion" + +-- Your stage directions +UI_TEXT_CONTENT["AISTUDIO::PAGES::WRITER::T779923726"] = "Your stage directions" diff --git a/app/MindWork AI Studio/Assistants/IconFinder/AssistantIconFinder.razor b/app/MindWork AI Studio/Assistants/IconFinder/AssistantIconFinder.razor index 576558ac..278c8bb8 100644 --- a/app/MindWork AI Studio/Assistants/IconFinder/AssistantIconFinder.razor +++ b/app/MindWork AI Studio/Assistants/IconFinder/AssistantIconFinder.razor @@ -1,18 +1,22 @@ @attribute [Route(Routes.ASSISTANT_ICON_FINDER)] @inherits AssistantBaseCore - + - + @foreach (var source in Enum.GetValues()) { - @source.Name() + + @source.Name() + } @if (this.selectedIconSource is not IconSources.GENERIC) { - Open website + + @T("Open website") + } \ No newline at end of file diff --git a/app/MindWork AI Studio/Assistants/IconFinder/AssistantIconFinder.razor.cs b/app/MindWork AI Studio/Assistants/IconFinder/AssistantIconFinder.razor.cs index a071a81e..08d616d8 100644 --- a/app/MindWork AI Studio/Assistants/IconFinder/AssistantIconFinder.razor.cs +++ b/app/MindWork AI Studio/Assistants/IconFinder/AssistantIconFinder.razor.cs @@ -6,17 +6,9 @@ public partial class AssistantIconFinder : AssistantBaseCore Tools.Components.ICON_FINDER_ASSISTANT; - protected override string Title => "Icon Finder"; + protected override string Title => T("Icon Finder"); - protected override string Description => - """ - Finding the right icon for a context, such as for a piece of text, is not easy. The first challenge: - You need to extract a concept from your context, such as from a text. Let's take an example where - your text contains statements about multiple departments. The sought-after concept could be "departments." - The next challenge is that we need to anticipate the bias of the icon designers: under the search term - "departments," there may be no relevant icons or only unsuitable ones. Depending on the icon source, - it might be more effective to search for "buildings," for instance. LLMs assist you with both steps. - """; + protected override string Description => T("""Finding the right icon for a context, such as for a piece of text, is not easy. The first challenge: You need to extract a concept from your context, such as from a text. Let's take an example where your text contains statements about multiple departments. The sought-after concept could be "departments." The next challenge is that we need to anticipate the bias of the icon designers: under the search term "departments," there may be no relevant icons or only unsuitable ones. Depending on the icon source, it might be more effective to search for "buildings," for instance. LLMs assist you with both steps."""); protected override string SystemPrompt => """ @@ -31,7 +23,7 @@ public partial class AssistantIconFinder : AssistantBaseCore FooterButtons => []; - protected override string SubmitText => "Find Icon"; + protected override string SubmitText => T("Find Icon"); protected override Func SubmitAction => this.FindIcon; @@ -74,7 +66,7 @@ public partial class AssistantIconFinder : AssistantBaseCore } - - + + \ No newline at end of file diff --git a/app/MindWork AI Studio/Assistants/LegalCheck/AssistantLegalCheck.razor.cs b/app/MindWork AI Studio/Assistants/LegalCheck/AssistantLegalCheck.razor.cs index 218563fe..e39abbfa 100644 --- a/app/MindWork AI Studio/Assistants/LegalCheck/AssistantLegalCheck.razor.cs +++ b/app/MindWork AI Studio/Assistants/LegalCheck/AssistantLegalCheck.razor.cs @@ -7,14 +7,9 @@ public partial class AssistantLegalCheck : AssistantBaseCore Tools.Components.LEGAL_CHECK_ASSISTANT; - protected override string Title => "Legal Check"; + protected override string Title => T("Legal Check"); - protected override string Description => - """ - Provide a legal document and ask a question about it. This assistant does not - replace legal advice. Consult a lawyer to get professional advice. Remember - that LLMs can invent answers and facts. Please do not rely on this answers. - """; + protected override string Description => T("Provide a legal document and ask a question about it. This assistant does not replace legal advice. Consult a lawyer to get professional advice. Remember that LLMs can invent answers and facts. Please do not rely on this answers."); protected override string SystemPrompt => """ @@ -27,7 +22,7 @@ public partial class AssistantLegalCheck : AssistantBaseCore FooterButtons => []; - protected override string SubmitText => "Ask your questions"; + protected override string SubmitText => T("Ask your questions"); protected override Func SubmitAction => this.AksQuestions; @@ -67,7 +62,7 @@ public partial class AssistantLegalCheck : AssistantBaseCore - - + + \ No newline at end of file diff --git a/app/MindWork AI Studio/Assistants/MyTasks/AssistantMyTasks.razor.cs b/app/MindWork AI Studio/Assistants/MyTasks/AssistantMyTasks.razor.cs index 3a512615..fe1ec919 100644 --- a/app/MindWork AI Studio/Assistants/MyTasks/AssistantMyTasks.razor.cs +++ b/app/MindWork AI Studio/Assistants/MyTasks/AssistantMyTasks.razor.cs @@ -8,15 +8,9 @@ public partial class AssistantMyTasks : AssistantBaseCore { public override Tools.Components Component => Tools.Components.MY_TASKS_ASSISTANT; - protected override string Title => "My Tasks"; + protected override string Title => T("My Tasks"); - protected override string Description => - """ - You received a cryptic email that was sent to many recipients and you are now wondering - if you need to do something? Copy the email into the input field. You also need to select - a personal profile. In this profile, you should describe your role in the organization. - The AI will then try to give you hints on what your tasks might be. - """; + protected override string Description => T("You received a cryptic email that was sent to many recipients and you are now wondering if you need to do something? Copy the email into the input field. You also need to select a personal profile. In this profile, you should describe your role in the organization. The AI will then try to give you hints on what your tasks might be."); protected override string SystemPrompt => $""" @@ -31,7 +25,7 @@ public partial class AssistantMyTasks : AssistantBaseCore protected override IReadOnlyList FooterButtons => []; - protected override string SubmitText => "Analyze text"; + protected override string SubmitText => T("Analyze text"); protected override Func SubmitAction => this.AnalyzeText; @@ -84,7 +78,7 @@ public partial class AssistantMyTasks : AssistantBaseCore private string? ValidatingText(string text) { if(string.IsNullOrWhiteSpace(text)) - return "Please provide some text as input. For example, an email."; + return T("Please provide some text as input. For example, an email."); return null; } @@ -92,7 +86,7 @@ public partial class AssistantMyTasks : AssistantBaseCore private string? ValidateProfile(Profile profile) { if(profile == default || profile == Profile.NO_PROFILE) - return "Please select one of your profiles."; + return T("Please select one of your profiles."); return null; } @@ -100,7 +94,7 @@ public partial class AssistantMyTasks : AssistantBaseCore private string? ValidateCustomLanguage(string language) { if(this.selectedTargetLanguage == CommonLanguages.OTHER && string.IsNullOrWhiteSpace(language)) - return "Please provide a custom language."; + return T("Please provide a custom language."); return null; } diff --git a/app/MindWork AI Studio/Assistants/RewriteImprove/AssistantRewriteImprove.razor b/app/MindWork AI Studio/Assistants/RewriteImprove/AssistantRewriteImprove.razor index 05feda39..952ff997 100644 --- a/app/MindWork AI Studio/Assistants/RewriteImprove/AssistantRewriteImprove.razor +++ b/app/MindWork AI Studio/Assistants/RewriteImprove/AssistantRewriteImprove.razor @@ -1,8 +1,8 @@ @attribute [Route(Routes.ASSISTANT_REWRITE)] @inherits AssistantBaseCore - - - - + + + + \ No newline at end of file diff --git a/app/MindWork AI Studio/Assistants/RewriteImprove/AssistantRewriteImprove.razor.cs b/app/MindWork AI Studio/Assistants/RewriteImprove/AssistantRewriteImprove.razor.cs index 0fb45089..44aa94d2 100644 --- a/app/MindWork AI Studio/Assistants/RewriteImprove/AssistantRewriteImprove.razor.cs +++ b/app/MindWork AI Studio/Assistants/RewriteImprove/AssistantRewriteImprove.razor.cs @@ -7,12 +7,9 @@ public partial class AssistantRewriteImprove : AssistantBaseCore Tools.Components.REWRITE_ASSISTANT; - protected override string Title => "Rewrite & Improve Text"; + protected override string Title => T("Rewrite & Improve Text"); - protected override string Description => - """ - Rewrite and improve your text. Please note, that the capabilities of the different LLM providers will vary. - """; + protected override string Description => T("Rewrite and improve your text. Please note, that the capabilities of the different LLM providers will vary."); protected override string SystemPrompt => $""" @@ -41,7 +38,7 @@ public partial class AssistantRewriteImprove : AssistantBaseCore "Improve"; + protected override string SubmitText => T("Improve your text"); protected override Func SubmitAction => this.RewriteText; @@ -100,7 +97,7 @@ public partial class AssistantRewriteImprove : AssistantBaseCore - - + + - + diff --git a/app/MindWork AI Studio/Assistants/Synonym/AssistantSynonyms.razor.cs b/app/MindWork AI Studio/Assistants/Synonym/AssistantSynonyms.razor.cs index 2244e7af..3581a5d3 100644 --- a/app/MindWork AI Studio/Assistants/Synonym/AssistantSynonyms.razor.cs +++ b/app/MindWork AI Studio/Assistants/Synonym/AssistantSynonyms.razor.cs @@ -7,12 +7,9 @@ public partial class AssistantSynonyms : AssistantBaseCore Tools.Components.SYNONYMS_ASSISTANT; - protected override string Title => "Synonyms"; + protected override string Title => T("Synonyms"); - protected override string Description => - """ - Find synonyms for words or phrases. - """; + protected override string Description => T("Find synonyms for words or phrases."); protected override string SystemPrompt => $""" @@ -52,7 +49,7 @@ public partial class AssistantSynonyms : AssistantBaseCore FooterButtons => []; - protected override string SubmitText => "Find synonyms"; + protected override string SubmitText => T("Find synonyms"); protected override Func SubmitAction => this.FindSynonyms; @@ -105,7 +102,7 @@ public partial class AssistantSynonyms : AssistantBaseCore } - - - + + + \ No newline at end of file diff --git a/app/MindWork AI Studio/Assistants/TextSummarizer/AssistantTextSummarizer.razor.cs b/app/MindWork AI Studio/Assistants/TextSummarizer/AssistantTextSummarizer.razor.cs index c5fc437c..89f81e0d 100644 --- a/app/MindWork AI Studio/Assistants/TextSummarizer/AssistantTextSummarizer.razor.cs +++ b/app/MindWork AI Studio/Assistants/TextSummarizer/AssistantTextSummarizer.razor.cs @@ -7,15 +7,9 @@ public partial class AssistantTextSummarizer : AssistantBaseCore Tools.Components.TEXT_SUMMARIZER_ASSISTANT; - protected override string Title => "Text Summarizer"; + protected override string Title => T("Text Summarizer"); - protected override string Description => - """ - Summarize long text into a shorter version while retaining the main points. - You might want to change the language of the summary to make it more readable. - It is also possible to change the complexity of the summary to make it - easy to understand. - """; + protected override string Description => T("Summarize long text into a shorter version while retaining the main points. You might want to change the language of the summary to make it more readable. It is also possible to change the complexity of the summary to make it easy to understand."); protected override string SystemPrompt => """ @@ -30,7 +24,7 @@ public partial class AssistantTextSummarizer : AssistantBaseCore FooterButtons => []; - protected override string SubmitText => "Summarize"; + protected override string SubmitText => T("Summarize"); protected override Func SubmitAction => this.SummarizeText; @@ -90,7 +84,7 @@ public partial class AssistantTextSummarizer : AssistantBaseCore } - + @if (this.liveTranslation) { - + } else { - + } - + \ No newline at end of file diff --git a/app/MindWork AI Studio/Assistants/Translation/AssistantTranslation.razor.cs b/app/MindWork AI Studio/Assistants/Translation/AssistantTranslation.razor.cs index 628190cd..5a45f3ae 100644 --- a/app/MindWork AI Studio/Assistants/Translation/AssistantTranslation.razor.cs +++ b/app/MindWork AI Studio/Assistants/Translation/AssistantTranslation.razor.cs @@ -7,12 +7,9 @@ public partial class AssistantTranslation : AssistantBaseCore Tools.Components.TRANSLATION_ASSISTANT; - protected override string Title => "Translation"; + protected override string Title => T("Translation"); - protected override string Description => - """ - Translate text from one language to another. - """; + protected override string Description => T("Translate text from one language to another."); protected override string SystemPrompt => """ @@ -26,7 +23,7 @@ public partial class AssistantTranslation : AssistantBaseCore FooterButtons => []; - protected override string SubmitText => "Translate"; + protected override string SubmitText => T("Translate"); protected override Func SubmitAction => () => this.TranslateText(true); @@ -85,7 +82,7 @@ public partial class AssistantTranslation : AssistantBaseCore @@ -9,34 +9,36 @@ - @this.Role.ToName() (@this.Time) + + @this.Role.ToName() (@this.Time) + @if (this.IsSecondToLastBlock && this.Role is ChatRole.USER && this.EditLastUserBlockFunc is not null) { - + } @if (this.IsLastContentBlock && this.Role is ChatRole.USER && this.EditLastBlockFunc is not null) { - + } @if (this.IsLastContentBlock && this.Role is ChatRole.AI && this.RegenerateFunc is not null) { - + } @if (this.RemoveBlockFunc is not null) { - + } - + @@ -87,7 +89,7 @@ default: - Cannot render content of type @this.Type yet. + @string.Format(T("Cannot render content of type {0} yet."), this.Type) break; } diff --git a/app/MindWork AI Studio/Chat/ContentBlockComponent.razor.cs b/app/MindWork AI Studio/Chat/ContentBlockComponent.razor.cs index 815caa6e..008ed897 100644 --- a/app/MindWork AI Studio/Chat/ContentBlockComponent.razor.cs +++ b/app/MindWork AI Studio/Chat/ContentBlockComponent.razor.cs @@ -1,4 +1,4 @@ -using AIStudio.Settings; +using AIStudio.Components; using AIStudio.Tools.Services; using Microsoft.AspNetCore.Components; @@ -8,7 +8,7 @@ namespace AIStudio.Chat; /// /// The UI component for a chat content block, i.e., for any IContent. /// -public partial class ContentBlockComponent : ComponentBase +public partial class ContentBlockComponent : MSGComponentBase { /// /// The role of the chat content block. @@ -67,9 +67,6 @@ public partial class ContentBlockComponent : ComponentBase [Inject] private ISnackbar Snackbar { get; init; } = null!; - [Inject] - private SettingsManager SettingsManager { get; init; } = null!; - [Inject] private IDialogService DialogService { get; init; } = null!; @@ -132,7 +129,7 @@ public partial class ContentBlockComponent : ComponentBase break; default: - this.Snackbar.Add("Cannot copy this content type to clipboard!", Severity.Error, config => + this.Snackbar.Add(T("Cannot copy this content type to clipboard!"), Severity.Error, config => { config.Icon = Icons.Material.Filled.ContentCopy; config.IconSize = Size.Large; @@ -152,10 +149,10 @@ public partial class ContentBlockComponent : ComponentBase return; var remove = await this.DialogService.ShowMessageBox( - "Remove Message", - "Do you really want to remove this message?", - "Yes, remove it", - "No, keep it"); + T("Remove Message"), + T("Do you really want to remove this message?"), + T("Yes, remove it"), + T("No, keep it")); if (remove.HasValue && remove.Value) await this.RemoveBlockFunc(this.Content); @@ -170,10 +167,10 @@ public partial class ContentBlockComponent : ComponentBase return; var regenerate = await this.DialogService.ShowMessageBox( - "Regenerate Message", - "Do you really want to regenerate this message?", - "Yes, regenerate it", - "No, keep it"); + T("Regenerate Message"), + T("Do you really want to regenerate this message?"), + T("Yes, regenerate it"), + T("No, keep it")); if (regenerate.HasValue && regenerate.Value) await this.RegenerateFunc(this.Content); @@ -199,10 +196,10 @@ public partial class ContentBlockComponent : ComponentBase return; var edit = await this.DialogService.ShowMessageBox( - "Edit Message", - "Do you really want to edit this message? In order to edit this message, the AI response will be deleted.", - "Yes, remove the AI response and edit it", - "No, keep it"); + T("Edit Message"), + T("Do you really want to edit this message? In order to edit this message, the AI response will be deleted."), + T("Yes, remove the AI response and edit it"), + T("No, keep it")); if (edit.HasValue && edit.Value) await this.EditLastUserBlockFunc(this.Content); diff --git a/app/MindWork AI Studio/Components/AssistantBlock.razor b/app/MindWork AI Studio/Components/AssistantBlock.razor index 3d0d9723..754a9e24 100644 --- a/app/MindWork AI Studio/Components/AssistantBlock.razor +++ b/app/MindWork AI Studio/Components/AssistantBlock.razor @@ -1,3 +1,4 @@ +@inherits MSGComponentBase @typeparam TSettings diff --git a/app/MindWork AI Studio/Components/AssistantBlock.razor.cs b/app/MindWork AI Studio/Components/AssistantBlock.razor.cs index df5c84cd..ef0e7a4d 100644 --- a/app/MindWork AI Studio/Components/AssistantBlock.razor.cs +++ b/app/MindWork AI Studio/Components/AssistantBlock.razor.cs @@ -1,12 +1,10 @@ -using AIStudio.Settings; - using Microsoft.AspNetCore.Components; using DialogOptions = AIStudio.Dialogs.DialogOptions; namespace AIStudio.Components; -public partial class AssistantBlock : ComponentBase, IMessageBusReceiver, IDisposable where TSettings : IComponent +public partial class AssistantBlock : MSGComponentBase where TSettings : IComponent { [Parameter] public string Name { get; set; } = string.Empty; @@ -26,12 +24,6 @@ public partial class AssistantBlock : ComponentBase, IMessageBusRecei [Inject] private MudTheme ColorTheme { get; init; } = null!; - [Inject] - private SettingsManager SettingsManager { get; init; } = null!; - - [Inject] - private MessageBus MessageBus { get; init; } = null!; - [Inject] private IDialogService DialogService { get; init; } = null!; @@ -39,43 +31,8 @@ public partial class AssistantBlock : ComponentBase, IMessageBusRecei { var dialogParameters = new DialogParameters(); - await this.DialogService.ShowAsync("Open Settings", dialogParameters, DialogOptions.FULLSCREEN); + await this.DialogService.ShowAsync(T("Open Settings"), dialogParameters, DialogOptions.FULLSCREEN); } - - #region Overrides of ComponentBase - - protected override async Task OnInitializedAsync() - { - this.MessageBus.RegisterComponent(this); - this.MessageBus.ApplyFilters(this, [], [ Event.COLOR_THEME_CHANGED ]); - - await base.OnInitializedAsync(); - } - - #endregion - - #region Implementation of IMessageBusReceiver - - public string ComponentName => nameof(AssistantBlock); - - public Task ProcessMessage(ComponentBase? sendingComponent, Event triggeredEvent, T? data) - { - switch (triggeredEvent) - { - case Event.COLOR_THEME_CHANGED: - this.StateHasChanged(); - break; - } - - return Task.CompletedTask; - } - - public Task ProcessMessageWithResult(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data) - { - return Task.FromResult(default); - } - - #endregion private string BorderColor => this.SettingsManager.IsDarkMode switch { @@ -84,13 +41,4 @@ public partial class AssistantBlock : ComponentBase, IMessageBusRecei }; private string BlockStyle => $"border-width: 2px; border-color: {this.BorderColor}; border-radius: 12px; border-style: solid; max-width: 20em;"; - - #region Implementation of IDisposable - - public void Dispose() - { - this.MessageBus.Unregister(this); - } - - #endregion } \ No newline at end of file diff --git a/app/MindWork AI Studio/Components/Changelog.razor b/app/MindWork AI Studio/Components/Changelog.razor index 407aa9ec..b83c659d 100644 --- a/app/MindWork AI Studio/Components/Changelog.razor +++ b/app/MindWork AI Studio/Components/Changelog.razor @@ -1,4 +1,5 @@ - +@inherits MSGComponentBase + @foreach (var log in LOGS) { diff --git a/app/MindWork AI Studio/Components/Changelog.razor.cs b/app/MindWork AI Studio/Components/Changelog.razor.cs index bba8b8a5..6402b1dd 100644 --- a/app/MindWork AI Studio/Components/Changelog.razor.cs +++ b/app/MindWork AI Studio/Components/Changelog.razor.cs @@ -2,7 +2,7 @@ using Microsoft.AspNetCore.Components; namespace AIStudio.Components; -public partial class Changelog : ComponentBase +public partial class Changelog : MSGComponentBase { [Inject] private HttpClient HttpClient { get; set; } = null!; diff --git a/app/MindWork AI Studio/Components/ChatComponent.razor b/app/MindWork AI Studio/Components/ChatComponent.razor index 703d9175..fbcf1828 100644 --- a/app/MindWork AI Studio/Components/ChatComponent.razor +++ b/app/MindWork AI Studio/Components/ChatComponent.razor @@ -83,14 +83,14 @@ @if (this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is WorkspaceStorageBehavior.STORE_CHATS_AUTOMATICALLY) { - + } @if (this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is not WorkspaceStorageBehavior.DISABLE_WORKSPACES) { - + } @@ -116,7 +116,7 @@ @if (!this.ChatThread.IsLLMProviderAllowed(this.Provider)) { - + } diff --git a/app/MindWork AI Studio/Components/ChatComponent.razor.cs b/app/MindWork AI Studio/Components/ChatComponent.razor.cs index 20c80bcf..522a2739 100644 --- a/app/MindWork AI Studio/Components/ChatComponent.razor.cs +++ b/app/MindWork AI Studio/Components/ChatComponent.razor.cs @@ -255,13 +255,22 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable private bool IsProviderSelected => this.Provider.UsedLLMProvider != LLMProviders.NONE; - private string ProviderPlaceholder => this.IsProviderSelected ? "Type your input here..." : "Select a provider first"; + private string ProviderPlaceholder => this.IsProviderSelected ? T("Type your input here...") : T("Select a provider first"); + + private string InputLabel + { + get + { + if (this.IsProviderSelected) + return string.Format(T("Your Prompt (use selected instance '{0}', provider '{1}')"), this.Provider.InstanceName, this.Provider.UsedLLMProvider.ToName()); + + return this.T("Select a provider first"); + } + } - private string InputLabel => this.IsProviderSelected ? $"Your Prompt (use selected instance '{this.Provider.InstanceName}', provider '{this.Provider.UsedLLMProvider.ToName()}')" : "Select a provider first"; - private bool CanThreadBeSaved => this.ChatThread is not null && this.ChatThread.Blocks.Count > 0; - private string TooltipAddChatToWorkspace => $"Start new chat in workspace \"{this.currentWorkspaceName}\""; + private string TooltipAddChatToWorkspace => string.Format(T(@"Start new chat in workspace ""{0}"""), this.currentWorkspaceName); private string UserInputStyle => this.SettingsManager.ConfigurationData.LLMProviders.ShowProviderConfidence ? this.Provider.UsedLLMProvider.GetConfidence(this.SettingsManager).SetColorStyle(this.SettingsManager) : string.Empty; @@ -653,7 +662,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable { var confirmationDialogParameters = new DialogParameters { - { "Message", "Are you sure you want to move this chat? All unsaved changes will be lost." }, + { "Message", T("Are you sure you want to move this chat? All unsaved changes will be lost.") }, }; var confirmationDialogReference = await this.DialogService.ShowAsync("Unsaved Changes", confirmationDialogParameters, DialogOptions.FULLSCREEN); @@ -664,12 +673,12 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable var dialogParameters = new DialogParameters { - { "Message", "Please select the workspace where you want to move the chat to." }, + { "Message", T("Please select the workspace where you want to move the chat to.") }, { "SelectedWorkspace", this.ChatThread?.WorkspaceId }, - { "ConfirmText", "Move chat" }, + { "ConfirmText", T("Move chat") }, }; - var dialogReference = await this.DialogService.ShowAsync("Move Chat to Workspace", dialogParameters, DialogOptions.FULLSCREEN); + var dialogReference = await this.DialogService.ShowAsync(T("Move Chat to Workspace"), dialogParameters, DialogOptions.FULLSCREEN); var dialogResult = await dialogReference.Result; if (dialogResult is null || dialogResult.Canceled) return; @@ -878,7 +887,6 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable public async ValueTask DisposeAsync() { - this.MessageBus.Unregister(this); if(this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is WorkspaceStorageBehavior.STORE_CHATS_AUTOMATICALLY) { await this.SaveThread(); diff --git a/app/MindWork AI Studio/Components/ConfidenceInfo.razor b/app/MindWork AI Studio/Components/ConfidenceInfo.razor index 7870c9a9..f27fe58e 100644 --- a/app/MindWork AI Studio/Components/ConfidenceInfo.razor +++ b/app/MindWork AI Studio/Components/ConfidenceInfo.razor @@ -1,6 +1,8 @@ @using AIStudio.Provider +@inherits MSGComponentBase +
- + @if (this.Mode is PopoverTriggerMode.ICON) { @@ -8,7 +10,7 @@ else { - Confidence + @T("Confidence") } @@ -17,16 +19,22 @@ - Confidence Card + + @T("Confidence Card") + - Description + + @T("Description") + @if (this.currentConfidence.Sources.Count > 0) { - Sources + + @T("Sources") + @foreach (var sourceTuple in this.GetConfidenceSources()) { @@ -37,13 +45,17 @@ @if (!string.IsNullOrWhiteSpace(this.currentConfidence.Region)) { - Region + + @T("Region") + @this.currentConfidence.Region } - Confidence Level + + @T("Confidence Level") + @this.currentConfidence.Level.GetName() diff --git a/app/MindWork AI Studio/Components/ConfidenceInfo.razor.cs b/app/MindWork AI Studio/Components/ConfidenceInfo.razor.cs index 3bad56ea..bb8af03d 100644 --- a/app/MindWork AI Studio/Components/ConfidenceInfo.razor.cs +++ b/app/MindWork AI Studio/Components/ConfidenceInfo.razor.cs @@ -1,23 +1,16 @@ using AIStudio.Provider; -using AIStudio.Settings; using Microsoft.AspNetCore.Components; namespace AIStudio.Components; -public partial class ConfidenceInfo : ComponentBase, IMessageBusReceiver, IDisposable +public partial class ConfidenceInfo : MSGComponentBase { [Parameter] public PopoverTriggerMode Mode { get; set; } = PopoverTriggerMode.BUTTON; [Parameter] public LLMProviders LLMProvider { get; set; } - - [Inject] - private SettingsManager SettingsManager { get; init; } = null!; - - [Inject] - private MessageBus MessageBus { get; init; } = null!; private Confidence currentConfidence; private bool showConfidence; @@ -31,9 +24,6 @@ public partial class ConfidenceInfo : ComponentBase, IMessageBusReceiver, IDispo protected override async Task OnParametersSetAsync() { - this.MessageBus.RegisterComponent(this); - this.MessageBus.ApplyFilters(this, [], [ Event.COLOR_THEME_CHANGED ]); - this.currentConfidence = this.LLMProvider.GetConfidence(this.SettingsManager); await base.OnParametersSetAsync(); } @@ -60,37 +50,4 @@ public partial class ConfidenceInfo : ComponentBase, IMessageBusReceiver, IDispo private string GetCurrentConfidenceColor() => $"color: {this.currentConfidence.Level.GetColor(this.SettingsManager)};"; private string GetPopoverStyle() => $"border-color: {this.currentConfidence.Level.GetColor(this.SettingsManager)};"; - - #region Implementation of IMessageBusReceiver - - public string ComponentName => nameof(ConfidenceInfo); - - public Task ProcessMessage(ComponentBase? sendingComponent, Event triggeredEvent, T? data) - { - switch (triggeredEvent) - { - case Event.COLOR_THEME_CHANGED: - this.showConfidence = false; - this.StateHasChanged(); - break; - } - - return Task.CompletedTask; - } - - public Task ProcessMessageWithResult(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data) - { - return Task.FromResult(default); - } - - #endregion - - #region Implementation of IDisposable - - public void Dispose() - { - this.MessageBus.Unregister(this); - } - - #endregion } \ No newline at end of file diff --git a/app/MindWork AI Studio/Components/ConfigurationBase.razor b/app/MindWork AI Studio/Components/ConfigurationBase.razor index e69de29b..2233093a 100644 --- a/app/MindWork AI Studio/Components/ConfigurationBase.razor +++ b/app/MindWork AI Studio/Components/ConfigurationBase.razor @@ -0,0 +1 @@ +@inherits MSGComponentBase \ No newline at end of file diff --git a/app/MindWork AI Studio/Components/ConfigurationBase.razor.cs b/app/MindWork AI Studio/Components/ConfigurationBase.razor.cs index 64217ffe..10f4ae3f 100644 --- a/app/MindWork AI Studio/Components/ConfigurationBase.razor.cs +++ b/app/MindWork AI Studio/Components/ConfigurationBase.razor.cs @@ -1,5 +1,3 @@ -using AIStudio.Settings; - using Microsoft.AspNetCore.Components; namespace AIStudio.Components; @@ -7,7 +5,7 @@ namespace AIStudio.Components; /// /// A base class for configuration options. /// -public partial class ConfigurationBase : ComponentBase, IMessageBusReceiver, IDisposable +public partial class ConfigurationBase : MSGComponentBase { /// /// The description of the option, i.e., the name. Should be @@ -28,12 +26,6 @@ public partial class ConfigurationBase : ComponentBase, IMessageBusReceiver, IDi [Parameter] public Func Disabled { get; set; } = () => false; - [Inject] - protected SettingsManager SettingsManager { get; init; } = null!; - - [Inject] - protected MessageBus MessageBus { get; init; } = null!; - protected const string MARGIN_CLASS = "mb-6"; protected static readonly Dictionary SPELLCHECK_ATTRIBUTES = new(); @@ -41,25 +33,18 @@ public partial class ConfigurationBase : ComponentBase, IMessageBusReceiver, IDi protected override async Task OnInitializedAsync() { - // Configure the spellchecking for the instance name input: this.SettingsManager.InjectSpellchecking(SPELLCHECK_ATTRIBUTES); - - // Register this component with the message bus: - this.MessageBus.RegisterComponent(this); - this.MessageBus.ApplyFilters(this, [], [ Event.CONFIGURATION_CHANGED ]); - + this.ApplyFilters([], [ Event.CONFIGURATION_CHANGED ]); await base.OnInitializedAsync(); } #endregion protected async Task InformAboutChange() => await this.MessageBus.SendMessage(this, Event.CONFIGURATION_CHANGED); - - #region Implementation of IMessageBusReceiver - public string ComponentName => nameof(ConfigurationBase); - - public Task ProcessMessage(ComponentBase? sendingComponent, Event triggeredEvent, TMsg? data) + #region Overrides of MSGComponentBase + + protected override Task ProcessIncomingMessage(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default { switch (triggeredEvent) { @@ -71,19 +56,5 @@ public partial class ConfigurationBase : ComponentBase, IMessageBusReceiver, IDi return Task.CompletedTask; } - public Task ProcessMessageWithResult(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data) - { - return Task.FromResult(default); - } - - #endregion - - #region Implementation of IDisposable - - public void Dispose() - { - this.MessageBus.Unregister(this); - } - #endregion } \ No newline at end of file diff --git a/app/MindWork AI Studio/Components/ConfigurationMinConfidenceSelection.razor b/app/MindWork AI Studio/Components/ConfigurationMinConfidenceSelection.razor index 75290baa..c3f21593 100644 --- a/app/MindWork AI Studio/Components/ConfigurationMinConfidenceSelection.razor +++ b/app/MindWork AI Studio/Components/ConfigurationMinConfidenceSelection.razor @@ -1,2 +1,3 @@ @using AIStudio.Settings - \ No newline at end of file +@inherits MSGComponentBase + \ No newline at end of file diff --git a/app/MindWork AI Studio/Components/ConfigurationMinConfidenceSelection.razor.cs b/app/MindWork AI Studio/Components/ConfigurationMinConfidenceSelection.razor.cs index 5271b8f6..858bbc01 100644 --- a/app/MindWork AI Studio/Components/ConfigurationMinConfidenceSelection.razor.cs +++ b/app/MindWork AI Studio/Components/ConfigurationMinConfidenceSelection.razor.cs @@ -1,11 +1,10 @@ using AIStudio.Provider; -using AIStudio.Settings; using Microsoft.AspNetCore.Components; namespace AIStudio.Components; -public partial class ConfigurationMinConfidenceSelection : ComponentBase +public partial class ConfigurationMinConfidenceSelection : MSGComponentBase { /// /// The selected value. @@ -30,9 +29,6 @@ public partial class ConfigurationMinConfidenceSelection : ComponentBase /// [Parameter] public bool RestrictToGlobalMinimumConfidence { get; set; } - - [Inject] - private SettingsManager SettingsManager { get; init; } = null!; private ConfidenceLevel FilteredSelectedValue() { diff --git a/app/MindWork AI Studio/Components/ConfigurationMultiSelect.razor b/app/MindWork AI Studio/Components/ConfigurationMultiSelect.razor index 0ab1b73d..9c974e02 100644 --- a/app/MindWork AI Studio/Components/ConfigurationMultiSelect.razor +++ b/app/MindWork AI Studio/Components/ConfigurationMultiSelect.razor @@ -1,8 +1,8 @@ @inherits ConfigurationBase -@typeparam T +@typeparam TData /// Configuration component for selecting many values from a list. /// -/// The type of the value to select. -public partial class ConfigurationMultiSelect : ConfigurationBase +/// The type of the value to select. +public partial class ConfigurationMultiSelect : ConfigurationBase { /// /// The data to select from. /// [Parameter] - public IEnumerable> Data { get; set; } = []; + public IEnumerable> Data { get; set; } = []; /// /// The selected values. /// [Parameter] - public Func> SelectedValues { get; set; } = () => []; + public Func> SelectedValues { get; set; } = () => []; /// /// An action that is called when the selection changes. /// [Parameter] - public Action> SelectionUpdate { get; set; } = _ => { }; + public Action> SelectionUpdate { get; set; } = _ => { }; - private async Task OptionChanged(IEnumerable? updatedValues) + private async Task OptionChanged(IEnumerable? updatedValues) { if(updatedValues is null) this.SelectionUpdate([]); @@ -41,14 +41,14 @@ public partial class ConfigurationMultiSelect : ConfigurationBase private static string GetClass => $"{MARGIN_CLASS} rounded-lg"; - private string GetMultiSelectionText(List? selectedValues) + private string GetMultiSelectionText(List? selectedValues) { if(selectedValues is null || selectedValues.Count == 0) - return "No preview features selected."; + return T("No preview features selected."); if(selectedValues.Count == 1) - return $"You have selected 1 preview feature."; + return T("You have selected 1 preview feature."); - return $"You have selected {selectedValues.Count} preview features."; + return string.Format(T("You have selected {0} preview features."), selectedValues.Count); } } \ No newline at end of file diff --git a/app/MindWork AI Studio/Components/ConfigurationProviderSelection.razor b/app/MindWork AI Studio/Components/ConfigurationProviderSelection.razor index 3f0978cc..4653822f 100644 --- a/app/MindWork AI Studio/Components/ConfigurationProviderSelection.razor +++ b/app/MindWork AI Studio/Components/ConfigurationProviderSelection.razor @@ -1 +1,2 @@ - \ No newline at end of file +@inherits MSGComponentBase + \ No newline at end of file diff --git a/app/MindWork AI Studio/Components/ConfigurationProviderSelection.razor.cs b/app/MindWork AI Studio/Components/ConfigurationProviderSelection.razor.cs index a1e60c87..3d06590f 100644 --- a/app/MindWork AI Studio/Components/ConfigurationProviderSelection.razor.cs +++ b/app/MindWork AI Studio/Components/ConfigurationProviderSelection.razor.cs @@ -7,7 +7,7 @@ using Microsoft.AspNetCore.Components; namespace AIStudio.Components; -public partial class ConfigurationProviderSelection : ComponentBase, IMessageBusReceiver, IDisposable +public partial class ConfigurationProviderSelection : MSGComponentBase { [Parameter] public Func SelectedValue { get; set; } = () => string.Empty; @@ -29,21 +29,12 @@ public partial class ConfigurationProviderSelection : ComponentBase, IMessageBus [Parameter] public Tools.Components Component { get; set; } = Tools.Components.NONE; - - [Inject] - private SettingsManager SettingsManager { get; init; } = null!; - - [Inject] - private MessageBus MessageBus { get; init; } = null!; #region Overrides of ComponentBase protected override async Task OnParametersSetAsync() { - // Register this component with the message bus: - this.MessageBus.RegisterComponent(this); - this.MessageBus.ApplyFilters(this, [], [ Event.CONFIGURATION_CHANGED ]); - + this.ApplyFilters([], [ Event.CONFIGURATION_CHANGED ]); await base.OnParametersSetAsync(); } @@ -53,7 +44,7 @@ public partial class ConfigurationProviderSelection : ComponentBase, IMessageBus private IEnumerable> FilteredData() { if(this.Component is not Tools.Components.NONE and not Tools.Components.APP_SETTINGS) - yield return new("Use app default", string.Empty); + yield return new(T("Use app default"), string.Empty); var minimumLevel = this.SettingsManager.GetMinimumConfidenceLevel(this.Component); foreach (var providerId in this.Data) @@ -63,12 +54,10 @@ public partial class ConfigurationProviderSelection : ComponentBase, IMessageBus yield return providerId; } } - - #region Implementation of IMessageBusReceiver - public string ComponentName => nameof(ConfigurationProviderSelection); - - public async Task ProcessMessage(ComponentBase? sendingComponent, Event triggeredEvent, T? data) + #region Overrides of MSGComponentBase + + protected override async Task ProcessIncomingMessage(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default { switch (triggeredEvent) { @@ -90,19 +79,5 @@ public partial class ConfigurationProviderSelection : ComponentBase, IMessageBus } } - public Task ProcessMessageWithResult(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data) - { - return Task.FromResult(default); - } - - #endregion - - #region Implementation of IDisposable - - public void Dispose() - { - this.MessageBus.Unregister(this); - } - #endregion } \ No newline at end of file diff --git a/app/MindWork AI Studio/Components/ExpansionPanel.razor b/app/MindWork AI Studio/Components/ExpansionPanel.razor index 278207ea..713444ea 100644 --- a/app/MindWork AI Studio/Components/ExpansionPanel.razor +++ b/app/MindWork AI Studio/Components/ExpansionPanel.razor @@ -2,7 +2,9 @@
- @this.HeaderText + + @this.HeaderText + @if (this.ShowEndButton) { diff --git a/app/MindWork AI Studio/Components/Issues.razor b/app/MindWork AI Studio/Components/Issues.razor index 413545de..39eda730 100644 --- a/app/MindWork AI Studio/Components/Issues.razor +++ b/app/MindWork AI Studio/Components/Issues.razor @@ -1,7 +1,10 @@ +@inherits MSGComponentBase @if (this.IssuesData.Any()) { - Issues + + @T("Issues") + @foreach (var issue in this.IssuesData) { diff --git a/app/MindWork AI Studio/Components/Issues.razor.cs b/app/MindWork AI Studio/Components/Issues.razor.cs index effd20f6..5d01a029 100644 --- a/app/MindWork AI Studio/Components/Issues.razor.cs +++ b/app/MindWork AI Studio/Components/Issues.razor.cs @@ -2,7 +2,7 @@ using Microsoft.AspNetCore.Components; namespace AIStudio.Components; -public partial class Issues : ComponentBase +public partial class Issues : MSGComponentBase { [Parameter] public IEnumerable IssuesData { get; set; } = []; diff --git a/app/MindWork AI Studio/Components/MSGComponentBase.cs b/app/MindWork AI Studio/Components/MSGComponentBase.cs index 65d770c1..ad211203 100644 --- a/app/MindWork AI Studio/Components/MSGComponentBase.cs +++ b/app/MindWork AI Studio/Components/MSGComponentBase.cs @@ -74,11 +74,16 @@ public abstract class MSGComponentBase : ComponentBase, IDisposable, IMessageBus return Task.FromResult(default); } + protected virtual void DisposeResources() + { + } + #region Implementation of IDisposable public void Dispose() { this.MessageBus.Unregister(this); + this.DisposeResources(); } #endregion diff --git a/app/MindWork AI Studio/Components/Motivation.razor b/app/MindWork AI Studio/Components/Motivation.razor index 5b938978..a8242f22 100644 --- a/app/MindWork AI Studio/Components/Motivation.razor +++ b/app/MindWork AI Studio/Components/Motivation.razor @@ -1,31 +1,32 @@ +@inherits MSGComponentBase - 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. The motivation behind developing this app stems from several crucial needs and observations I made over time.") - Personal Needs and Limitations of Web Services + @T("Personal Needs and Limitations of Web Services") - 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. + @T("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.") - Limitations of Existing Solutions + @T("Limitations of Existing Solutions") - 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. + @T("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.") - Cross-Platform and Modern Development + @T("Cross-Platform and Modern Development") - Given that my employer's workplace uses both Windows and Linux, I wanted a cross-platform solution that would work seamlessly across all major operating systems, including macOS. Additionally, I wanted to demonstrate that it is possible to create modern, efficient, cross-platform applications without resorting to Electron bloatware. The combination of .NET and Rust with Tauri proved to be an excellent technology stack for building such robust applications. + @T("Given that my employer's workplace uses both Windows and Linux, I wanted a cross-platform solution that would work seamlessly across all major operating systems, including macOS. Additionally, I wanted to demonstrate that it is possible to create modern, efficient, cross-platform applications without resorting to Electron bloatware. The combination of .NET and Rust with Tauri proved to be an excellent technology stack for building such robust applications.") - 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. + @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.") \ No newline at end of file diff --git a/app/MindWork AI Studio/Components/Motivation.razor.cs b/app/MindWork AI Studio/Components/Motivation.razor.cs index 23e1a767..21ed26ee 100644 --- a/app/MindWork AI Studio/Components/Motivation.razor.cs +++ b/app/MindWork AI Studio/Components/Motivation.razor.cs @@ -1,7 +1,3 @@ -using Microsoft.AspNetCore.Components; - namespace AIStudio.Components; -public partial class Motivation : ComponentBase -{ -} \ No newline at end of file +public partial class Motivation : MSGComponentBase; \ No newline at end of file diff --git a/app/MindWork AI Studio/Components/PreviewAlpha.razor b/app/MindWork AI Studio/Components/PreviewAlpha.razor index b1b629d8..46a1a9b3 100644 --- a/app/MindWork AI Studio/Components/PreviewAlpha.razor +++ b/app/MindWork AI Studio/Components/PreviewAlpha.razor @@ -1,19 +1,18 @@ +@inherits MSGComponentBase - Alpha + @T("Alpha")
- This feature is currently in the alpha phase. - Expect bugs and unfinished work. + @T("This feature is currently in the alpha phase. Expect bugs and unfinished work.") - Alpha phase means that we are working on the - last details before the beta phase. + @T("Alpha phase means that we are working on the last details before the beta phase.")
diff --git a/app/MindWork AI Studio/Components/PreviewAlpha.razor.cs b/app/MindWork AI Studio/Components/PreviewAlpha.razor.cs index deb962c4..4af15f39 100644 --- a/app/MindWork AI Studio/Components/PreviewAlpha.razor.cs +++ b/app/MindWork AI Studio/Components/PreviewAlpha.razor.cs @@ -2,7 +2,7 @@ using Microsoft.AspNetCore.Components; namespace AIStudio.Components; -public partial class PreviewAlpha : ComponentBase +public partial class PreviewAlpha : MSGComponentBase { [Parameter] public bool ApplyInnerScrollingFix { get; set; } diff --git a/app/MindWork AI Studio/Components/PreviewBeta.razor b/app/MindWork AI Studio/Components/PreviewBeta.razor index dd54225e..5494f51a 100644 --- a/app/MindWork AI Studio/Components/PreviewBeta.razor +++ b/app/MindWork AI Studio/Components/PreviewBeta.razor @@ -1,18 +1,18 @@ +@inherits MSGComponentBase - Beta + @T("Beta")
- This feature is currently in the beta phase. - It is still be possible that there are some bugs. + @T("This feature is currently in the beta phase. It is still be possible that there are some bugs.") - Beta phase means that we are testing the feature. + @T("Beta phase means that we are testing the feature.")
diff --git a/app/MindWork AI Studio/Components/PreviewBeta.razor.cs b/app/MindWork AI Studio/Components/PreviewBeta.razor.cs index d8fee758..d73a9c53 100644 --- a/app/MindWork AI Studio/Components/PreviewBeta.razor.cs +++ b/app/MindWork AI Studio/Components/PreviewBeta.razor.cs @@ -2,7 +2,7 @@ using Microsoft.AspNetCore.Components; namespace AIStudio.Components; -public partial class PreviewBeta : ComponentBase +public partial class PreviewBeta : MSGComponentBase { [Parameter] public bool ApplyInnerScrollingFix { get; set; } diff --git a/app/MindWork AI Studio/Components/PreviewExperimental.razor b/app/MindWork AI Studio/Components/PreviewExperimental.razor index bedc9e4f..a63f44a5 100644 --- a/app/MindWork AI Studio/Components/PreviewExperimental.razor +++ b/app/MindWork AI Studio/Components/PreviewExperimental.razor @@ -1,21 +1,18 @@ +@inherits MSGComponentBase - Experimental + @T("Experimental")
- This feature is currently in the experimental phase. - Expect bugs, unfinished work, changes in future - versions, and more. + @T("This feature is currently in the experimental phase. Expect bugs, unfinished work, changes in future versions, and more.") - Experimental phase means that we have a vision for a feature - but not a clear plan yet. We are still exploring the - possibilities. + @T("Experimental phase means that we have a vision for a feature but not a clear plan yet. We are still exploring the possibilities.")
diff --git a/app/MindWork AI Studio/Components/PreviewExperimental.razor.cs b/app/MindWork AI Studio/Components/PreviewExperimental.razor.cs index 0588d489..d4184806 100644 --- a/app/MindWork AI Studio/Components/PreviewExperimental.razor.cs +++ b/app/MindWork AI Studio/Components/PreviewExperimental.razor.cs @@ -2,7 +2,7 @@ using Microsoft.AspNetCore.Components; namespace AIStudio.Components; -public partial class PreviewExperimental : ComponentBase +public partial class PreviewExperimental : MSGComponentBase { [Parameter] public bool ApplyInnerScrollingFix { get; set; } diff --git a/app/MindWork AI Studio/Components/PreviewPrototype.razor b/app/MindWork AI Studio/Components/PreviewPrototype.razor index 9aa8bbc0..e3cfc917 100644 --- a/app/MindWork AI Studio/Components/PreviewPrototype.razor +++ b/app/MindWork AI Studio/Components/PreviewPrototype.razor @@ -1,20 +1,18 @@ +@inherits MSGComponentBase - Prototype + @T("Prototype")
- This feature is currently in the prototype phase. - Expect bugs, unfinished work, changes in future - versions, and more. + @T("This feature is currently in the prototype phase. Expect bugs, unfinished work, changes in future versions, and more.") - Prototype phase means that we have a plan but we - are still working on it. + @T("Prototype phase means that we have a plan but we are still working on it.")
diff --git a/app/MindWork AI Studio/Components/PreviewPrototype.razor.cs b/app/MindWork AI Studio/Components/PreviewPrototype.razor.cs index 3ceab4d1..ad4c3c9c 100644 --- a/app/MindWork AI Studio/Components/PreviewPrototype.razor.cs +++ b/app/MindWork AI Studio/Components/PreviewPrototype.razor.cs @@ -2,7 +2,7 @@ using Microsoft.AspNetCore.Components; namespace AIStudio.Components; -public partial class PreviewPrototype : ComponentBase +public partial class PreviewPrototype : MSGComponentBase { [Parameter] public bool ApplyInnerScrollingFix { get; set; } diff --git a/app/MindWork AI Studio/Components/PreviewReleaseCandidate.razor b/app/MindWork AI Studio/Components/PreviewReleaseCandidate.razor index 86954ddd..4ee266b0 100644 --- a/app/MindWork AI Studio/Components/PreviewReleaseCandidate.razor +++ b/app/MindWork AI Studio/Components/PreviewReleaseCandidate.razor @@ -1,18 +1,18 @@ +@inherits MSGComponentBase - Release Candidate + @T("Release Candidate")
- This feature is about to be released. We think it's ready for production. - There should be no more bugs. + @T("This feature is about to be released. We think it's ready for production. There should be no more bugs.") - Release candidates are the final step before a feature is proven to be stable. + @T("Release candidates are the final step before a feature is proven to be stable.")
diff --git a/app/MindWork AI Studio/Components/PreviewReleaseCandidate.razor.cs b/app/MindWork AI Studio/Components/PreviewReleaseCandidate.razor.cs index 249f1f35..4de0ec86 100644 --- a/app/MindWork AI Studio/Components/PreviewReleaseCandidate.razor.cs +++ b/app/MindWork AI Studio/Components/PreviewReleaseCandidate.razor.cs @@ -2,7 +2,7 @@ using Microsoft.AspNetCore.Components; namespace AIStudio.Components; -public partial class PreviewReleaseCandidate : ComponentBase +public partial class PreviewReleaseCandidate : MSGComponentBase { [Parameter] public bool ApplyInnerScrollingFix { get; set; } diff --git a/app/MindWork AI Studio/Components/ProfileFormSelection.razor b/app/MindWork AI Studio/Components/ProfileFormSelection.razor index 075a0b2a..7c85fb64 100644 --- a/app/MindWork AI Studio/Components/ProfileFormSelection.razor +++ b/app/MindWork AI Studio/Components/ProfileFormSelection.razor @@ -1,6 +1,6 @@ @using AIStudio.Settings - - +@inherits MSGComponentBase + @foreach (var profile in this.SettingsManager.ConfigurationData.Profiles.GetAllProfiles()) { diff --git a/app/MindWork AI Studio/Components/ProfileFormSelection.razor.cs b/app/MindWork AI Studio/Components/ProfileFormSelection.razor.cs index 2b27e6c3..1f77ca40 100644 --- a/app/MindWork AI Studio/Components/ProfileFormSelection.razor.cs +++ b/app/MindWork AI Studio/Components/ProfileFormSelection.razor.cs @@ -4,7 +4,7 @@ using Microsoft.AspNetCore.Components; namespace AIStudio.Components; -public partial class ProfileFormSelection : ComponentBase +public partial class ProfileFormSelection : MSGComponentBase { [Parameter] public Profile Profile { get; set; } = Profile.NO_PROFILE; @@ -15,9 +15,6 @@ public partial class ProfileFormSelection : ComponentBase [Parameter] public Func Validation { get; set; } = _ => null; - [Inject] - private SettingsManager SettingsManager { get; init; } = null!; - private async Task SelectionChanged(Profile profile) { this.Profile = profile; diff --git a/app/MindWork AI Studio/Components/ProfileSelection.razor b/app/MindWork AI Studio/Components/ProfileSelection.razor index f4acf35a..9c9a0520 100644 --- a/app/MindWork AI Studio/Components/ProfileSelection.razor +++ b/app/MindWork AI Studio/Components/ProfileSelection.razor @@ -1,4 +1,5 @@ - +@inherits MSGComponentBase + @foreach (var profile in this.SettingsManager.ConfigurationData.Profiles.GetAllProfiles()) { diff --git a/app/MindWork AI Studio/Components/ProfileSelection.razor.cs b/app/MindWork AI Studio/Components/ProfileSelection.razor.cs index d2b41a57..efd15cb5 100644 --- a/app/MindWork AI Studio/Components/ProfileSelection.razor.cs +++ b/app/MindWork AI Studio/Components/ProfileSelection.razor.cs @@ -4,7 +4,7 @@ using Microsoft.AspNetCore.Components; namespace AIStudio.Components; -public partial class ProfileSelection : ComponentBase +public partial class ProfileSelection : MSGComponentBase { [Parameter] public Profile CurrentProfile { get; set; } = Profile.NO_PROFILE; @@ -18,9 +18,6 @@ public partial class ProfileSelection : ComponentBase [Parameter] public string MarginRight { get; set; } = string.Empty; - [Inject] - private SettingsManager SettingsManager { get; init; } = null!; - private string MarginClass => $"{this.MarginLeft} {this.MarginRight}"; private async Task SelectionChanged(Profile profile) diff --git a/app/MindWork AI Studio/Components/ProviderSelection.razor b/app/MindWork AI Studio/Components/ProviderSelection.razor index 9082f016..793f87c7 100644 --- a/app/MindWork AI Studio/Components/ProviderSelection.razor +++ b/app/MindWork AI Studio/Components/ProviderSelection.razor @@ -1,6 +1,6 @@ @using AIStudio.Settings - - +@inherits MSGComponentBase + @foreach (var provider in this.GetAvailableProviders()) { diff --git a/app/MindWork AI Studio/Components/ProviderSelection.razor.cs b/app/MindWork AI Studio/Components/ProviderSelection.razor.cs index 66158211..b476b19f 100644 --- a/app/MindWork AI Studio/Components/ProviderSelection.razor.cs +++ b/app/MindWork AI Studio/Components/ProviderSelection.razor.cs @@ -2,13 +2,12 @@ using System.Diagnostics.CodeAnalysis; using AIStudio.Assistants; using AIStudio.Provider; -using AIStudio.Settings; using Microsoft.AspNetCore.Components; namespace AIStudio.Components; -public partial class ProviderSelection : ComponentBase +public partial class ProviderSelection : MSGComponentBase { [CascadingParameter] public AssistantBase? AssistantBase { get; set; } @@ -22,9 +21,6 @@ public partial class ProviderSelection : ComponentBase [Parameter] public Func ValidateProvider { get; set; } = _ => null; - [Inject] - private SettingsManager SettingsManager { get; init; } = null!; - private async Task SelectionChanged(AIStudio.Settings.Provider provider) { this.ProviderSettings = provider; diff --git a/app/MindWork AI Studio/Components/ReadWebContent.razor b/app/MindWork AI Studio/Components/ReadWebContent.razor index 9cb451b2..8907f383 100644 --- a/app/MindWork AI Studio/Components/ReadWebContent.razor +++ b/app/MindWork AI Studio/Components/ReadWebContent.razor @@ -1,12 +1,13 @@ +@inherits MSGComponentBase - + @if (this.showWebContentReader) { - + - + @if (this.AgentIsRunning) diff --git a/app/MindWork AI Studio/Components/ReadWebContent.razor.cs b/app/MindWork AI Studio/Components/ReadWebContent.razor.cs index 6cf40701..455c87dd 100644 --- a/app/MindWork AI Studio/Components/ReadWebContent.razor.cs +++ b/app/MindWork AI Studio/Components/ReadWebContent.razor.cs @@ -1,21 +1,17 @@ using AIStudio.Agents; using AIStudio.Chat; -using AIStudio.Settings; using Microsoft.AspNetCore.Components; namespace AIStudio.Components; -public partial class ReadWebContent : ComponentBase +public partial class ReadWebContent : MSGComponentBase { [Inject] private HTMLParser HTMLParser { get; init; } = null!; [Inject] private AgentTextContentCleaner AgentTextContentCleaner { get; init; } = null!; - - [Inject] - protected SettingsManager SettingsManager { get; set; } = null!; [Parameter] public string Content { get; set; } = string.Empty; @@ -156,7 +152,7 @@ public partial class ReadWebContent : ComponentBase if(shouldUseAgent && this.providerSettings == default) { this.isProviderValid = false; - return "Please select a provider to use the cleanup agent."; + return T("Please select a provider to use the cleanup agent."); } this.isProviderValid = true; @@ -168,20 +164,20 @@ public partial class ReadWebContent : ComponentBase if(string.IsNullOrWhiteSpace(url)) { this.urlIsValid = false; - return "Please provide a URL to load the content from."; + return T("Please provide a URL to load the content from."); } var urlParsingResult = Uri.TryCreate(url, UriKind.Absolute, out var uriResult); if(!urlParsingResult) { this.urlIsValid = false; - return "Please provide a valid URL."; + return T("Please provide a valid URL."); } if(uriResult is not { Scheme: "http" or "https" }) { this.urlIsValid = false; - return "Please provide a valid HTTP or HTTPS URL."; + return T("Please provide a valid HTTP or HTTPS URL."); } this.urlIsValid = true; diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelApp.razor b/app/MindWork AI Studio/Components/Settings/SettingsPanelApp.razor index da313dda..c54dca8f 100644 --- a/app/MindWork AI Studio/Components/Settings/SettingsPanelApp.razor +++ b/app/MindWork AI Studio/Components/Settings/SettingsPanelApp.razor @@ -2,34 +2,34 @@ @using AIStudio.Settings.DataModel @inherits SettingsPanelBase - + @if (PreviewFeatures.PRE_PLUGINS_2025.IsEnabled(this.SettingsManager)) { - + @if (this.SettingsManager.ConfigurationData.App.LanguageBehavior is LangBehavior.MANUAL) { - + } } - - - - - - + + + + + + @if (this.SettingsManager.ConfigurationData.App.PreviewVisibility > PreviewVisibility.NONE) { var availablePreviewFeatures = ConfigurationSelectDataFactory.GetPreviewFeaturesData(this.SettingsManager).ToList(); if (availablePreviewFeatures.Count > 0) { - + } } - - + + \ No newline at end of file diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelBase.cs b/app/MindWork AI Studio/Components/Settings/SettingsPanelBase.cs index c3384167..bad3fca3 100644 --- a/app/MindWork AI Studio/Components/Settings/SettingsPanelBase.cs +++ b/app/MindWork AI Studio/Components/Settings/SettingsPanelBase.cs @@ -5,20 +5,14 @@ using Microsoft.AspNetCore.Components; namespace AIStudio.Components.Settings; -public abstract class SettingsPanelBase : ComponentBase +public abstract class SettingsPanelBase : MSGComponentBase { [Parameter] public Func>> AvailableLLMProvidersFunc { get; set; } = () => []; - [Inject] - protected SettingsManager SettingsManager { get; init; } = null!; - [Inject] protected IDialogService DialogService { get; init; } = null!; - [Inject] - protected MessageBus MessageBus { get; init; } = null!; - [Inject] protected RustService RustService { get; init; } = null!; } \ No newline at end of file diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelChat.razor b/app/MindWork AI Studio/Components/Settings/SettingsPanelChat.razor index 8b274405..84b34e6a 100644 --- a/app/MindWork AI Studio/Components/Settings/SettingsPanelChat.razor +++ b/app/MindWork AI Studio/Components/Settings/SettingsPanelChat.razor @@ -2,21 +2,21 @@ @using AIStudio.Settings.DataModel @inherits SettingsPanelBase - - - - - + + + + + - + - + @if (PreviewFeatures.PRE_RAG_2024.IsEnabled(this.SettingsManager)) { - - + + } \ No newline at end of file diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelProfiles.razor b/app/MindWork AI Studio/Components/Settings/SettingsPanelProfiles.razor index e49390f0..4c488ffb 100644 --- a/app/MindWork AI Studio/Components/Settings/SettingsPanelProfiles.razor +++ b/app/MindWork AI Studio/Components/Settings/SettingsPanelProfiles.razor @@ -1,18 +1,15 @@ @inherits SettingsPanelBase - - Your Profiles + + + @T("Your Profiles") + - Store personal data about yourself in various profiles so that the AIs know your personal context. - This saves you from having to explain your context each time, for example, in every chat. When you - have different roles, you can create a profile for each role. + @T("Store personal data about yourself in various profiles so that the AIs know your personal context. This saves you from having to explain your context each time, for example, in every chat. When you have different roles, you can create a profile for each role.") - Are you a project manager in a research facility? You might want to create a profile for your project - management activities, one for your scientific work, and a profile for when you need to write program - code. In these profiles, you can record how much experience you have or which methods you like or - dislike using. Later, you can choose when and where you want to use each profile. + @T("Are you a project manager in a research facility? You might want to create a profile for your project management activities, one for your scientific work, and a profile for when you need to write program code. In these profiles, you can record how much experience you have or which methods you like or dislike using. Later, you can choose when and where you want to use each profile.") @@ -22,8 +19,8 @@ # - Profile Name - Actions + @T("Profile Name") + @T("Actions") @context.Num @@ -31,10 +28,10 @@ - Edit + @T("Edit") - Delete + @T("Delete") @@ -43,10 +40,12 @@ @if(this.SettingsManager.ConfigurationData.Profiles.Count == 0) { - No profiles configured yet. + + @T("No profiles configured yet.") + } - Add Profile + @T("Add Profile") \ No newline at end of file diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelProfiles.razor.cs b/app/MindWork AI Studio/Components/Settings/SettingsPanelProfiles.razor.cs index 15b3214d..9391ed38 100644 --- a/app/MindWork AI Studio/Components/Settings/SettingsPanelProfiles.razor.cs +++ b/app/MindWork AI Studio/Components/Settings/SettingsPanelProfiles.razor.cs @@ -14,7 +14,7 @@ public partial class SettingsPanelProfiles : SettingsPanelBase { x => x.IsEditing, false }, }; - var dialogReference = await this.DialogService.ShowAsync("Add Profile", dialogParameters, DialogOptions.FULLSCREEN); + var dialogReference = await this.DialogService.ShowAsync(T("Add Profile"), dialogParameters, DialogOptions.FULLSCREEN); var dialogResult = await dialogReference.Result; if (dialogResult is null || dialogResult.Canceled) return; @@ -40,7 +40,7 @@ public partial class SettingsPanelProfiles : SettingsPanelBase { x => x.IsEditing, true }, }; - var dialogReference = await this.DialogService.ShowAsync("Edit Profile", dialogParameters, DialogOptions.FULLSCREEN); + var dialogReference = await this.DialogService.ShowAsync(T("Edit Profile"), dialogParameters, DialogOptions.FULLSCREEN); var dialogResult = await dialogReference.Result; if (dialogResult is null || dialogResult.Canceled) return; @@ -56,10 +56,10 @@ public partial class SettingsPanelProfiles : SettingsPanelBase { var dialogParameters = new DialogParameters { - { "Message", $"Are you sure you want to delete the profile '{profile.Name}'?" }, + { "Message", string.Format(T("Are you sure you want to delete the profile '{0}'?"), profile.Name) }, }; - var dialogReference = await this.DialogService.ShowAsync("Delete Profile", dialogParameters, DialogOptions.FULLSCREEN); + var dialogReference = await this.DialogService.ShowAsync(T("Delete Profile"), dialogParameters, DialogOptions.FULLSCREEN); var dialogResult = await dialogReference.Result; if (dialogResult is null || dialogResult.Canceled) return; diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelProviders.razor b/app/MindWork AI Studio/Components/Settings/SettingsPanelProviders.razor index 9ab1a81c..573c85ea 100644 --- a/app/MindWork AI Studio/Components/Settings/SettingsPanelProviders.razor +++ b/app/MindWork AI Studio/Components/Settings/SettingsPanelProviders.razor @@ -3,13 +3,12 @@ @using AIStudio.Provider.SelfHosted @inherits SettingsPanelBase - - Configured Providers + + + @T("Configured Providers") + - What we call a provider is the combination of an LLM provider such as OpenAI and a model like GPT-4o. - You can configure as many providers as you want. This way, you can use the appropriate model for each - task. As an LLM provider, you can also choose local providers. However, to use this app, you must - configure at least one provider. + @T("What we call a provider is the combination of an LLM provider such as OpenAI and a model like GPT-4o. You can configure as many providers as you want. This way, you can use the appropriate model for each task. As an LLM provider, you can also choose local providers. However, to use this app, you must configure at least one provider.") @@ -21,10 +20,10 @@ # - Instance Name - Provider - Model - Actions + @T("Instance Name") + @T("Provider") + @T("Model") + @T("Actions") @context.Num @@ -41,19 +40,19 @@ } else { - @("as selected by provider") + @T("as selected by provider") } - Open Dashboard + @T("Open Dashboard") - Edit + @T("Edit") - Delete + @T("Delete") @@ -62,30 +61,32 @@ @if(this.SettingsManager.ConfigurationData.Providers.Count == 0) { - No providers configured yet. + + @T("No providers configured yet.") + } - Add Provider + @T("Add Provider") - LLM Provider Confidence + + @T("LLM Provider Confidence") + - Do you want to always be able to recognize how trustworthy your LLM providers are? This way, - you keep control over which provider you send your data to. You have two options for this: - Either you choose a common schema, or you configure the trust levels for each LLM provider yourself. + @T("Do you want to always be able to recognize how trustworthy your LLM providers are? This way, you keep control over which provider you send your data to. You have two options for this: Either you choose a common schema, or you configure the trust levels for each LLM provider yourself.") - + @if(this.SettingsManager.ConfigurationData.LLMProviders.EnforceGlobalMinimumConfidence) { } - + @if (this.SettingsManager.ConfigurationData.LLMProviders.ShowProviderConfidence) { - + @if (this.SettingsManager.ConfigurationData.LLMProviders.ConfidenceScheme is ConfidenceSchemes.CUSTOM) { @@ -95,9 +96,9 @@ - LLM Provider - Description - Confidence Level + @T("LLM Provider") + @T("Description") + @T("Confidence Level") diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelProviders.razor.cs b/app/MindWork AI Studio/Components/Settings/SettingsPanelProviders.razor.cs index ce03a430..16ba7727 100644 --- a/app/MindWork AI Studio/Components/Settings/SettingsPanelProviders.razor.cs +++ b/app/MindWork AI Studio/Components/Settings/SettingsPanelProviders.razor.cs @@ -36,7 +36,7 @@ public partial class SettingsPanelProviders : SettingsPanelBase { x => x.IsEditing, false }, }; - var dialogReference = await this.DialogService.ShowAsync("Add LLM Provider", dialogParameters, DialogOptions.FULLSCREEN); + var dialogReference = await this.DialogService.ShowAsync(T("Add LLM Provider"), dialogParameters, DialogOptions.FULLSCREEN); var dialogResult = await dialogReference.Result; if (dialogResult is null || dialogResult.Canceled) return; @@ -68,7 +68,7 @@ public partial class SettingsPanelProviders : SettingsPanelBase { x => x.HFInferenceProviderId, provider.HFInferenceProvider }, }; - var dialogReference = await this.DialogService.ShowAsync("Edit LLM Provider", dialogParameters, DialogOptions.FULLSCREEN); + var dialogReference = await this.DialogService.ShowAsync(T("Edit LLM Provider"), dialogParameters, DialogOptions.FULLSCREEN); var dialogResult = await dialogReference.Result; if (dialogResult is null || dialogResult.Canceled) return; @@ -92,10 +92,10 @@ public partial class SettingsPanelProviders : SettingsPanelBase { var dialogParameters = new DialogParameters { - { "Message", $"Are you sure you want to delete the provider '{provider.InstanceName}'?" }, + { "Message", string.Format(T("Are you sure you want to delete the provider '{0}'?"), provider.InstanceName) }, }; - var dialogReference = await this.DialogService.ShowAsync("Delete LLM Provider", dialogParameters, DialogOptions.FULLSCREEN); + var dialogReference = await this.DialogService.ShowAsync(T("Delete LLM Provider"), dialogParameters, DialogOptions.FULLSCREEN); var dialogResult = await dialogReference.Result; if (dialogResult is null || dialogResult.Canceled) return; @@ -110,10 +110,10 @@ public partial class SettingsPanelProviders : SettingsPanelBase { var issueDialogParameters = new DialogParameters { - { "Message", $"Couldn't delete the provider '{provider.InstanceName}'. The issue: {deleteSecretResponse.Issue}. We can ignore this issue and delete the provider anyway. Do you want to ignore it and delete this provider?" }, + { "Message", string.Format(T("Couldn't delete the provider '{0}'. The issue: {1}. We can ignore this issue and delete the provider anyway. Do you want to ignore it and delete this provider?"), provider.InstanceName, deleteSecretResponse.Issue) }, }; - var issueDialogReference = await this.DialogService.ShowAsync("Delete LLM Provider", issueDialogParameters, DialogOptions.FULLSCREEN); + var issueDialogReference = await this.DialogService.ShowAsync(T("Delete LLM Provider"), issueDialogParameters, DialogOptions.FULLSCREEN); var issueDialogResult = await issueDialogReference.Result; if (issueDialogResult is null || issueDialogResult.Canceled) return; @@ -149,7 +149,7 @@ public partial class SettingsPanelProviders : SettingsPanelBase if (this.SettingsManager.ConfigurationData.LLMProviders.CustomConfidenceScheme.TryGetValue(llmProvider, out var level)) return level.GetName(); - return "Not yet configured"; + return T("Not yet configured"); } private string SetCurrentConfidenceLevelColorStyle(LLMProviders llmProvider) diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelWorkspaces.razor b/app/MindWork AI Studio/Components/Settings/SettingsPanelWorkspaces.razor index b3042982..8f86a9e6 100644 --- a/app/MindWork AI Studio/Components/Settings/SettingsPanelWorkspaces.razor +++ b/app/MindWork AI Studio/Components/Settings/SettingsPanelWorkspaces.razor @@ -2,12 +2,12 @@ @using AIStudio.Settings.DataModel @inherits SettingsPanelBase - - + + @if (this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is not WorkspaceStorageBehavior.DISABLE_WORKSPACES) { - - + + } \ No newline at end of file diff --git a/app/MindWork AI Studio/Components/TextInfoLine.razor b/app/MindWork AI Studio/Components/TextInfoLine.razor index 5e5de4da..342c495d 100644 --- a/app/MindWork AI Studio/Components/TextInfoLine.razor +++ b/app/MindWork AI Studio/Components/TextInfoLine.razor @@ -1,3 +1,4 @@ +@inherits MSGComponentBase USER_INPUT_ATTRIBUTES = new(); - private string ClipboardTooltip => $"Copy {this.ClipboardTooltipSubject} to the clipboard"; + private string ClipboardTooltip => string.Format(T("Copy {0} to the clipboard"), this.ClipboardTooltipSubject); private async Task CopyToClipboard(string content) => await this.RustService.CopyText2Clipboard(this.Snackbar, content); } \ No newline at end of file diff --git a/app/MindWork AI Studio/Components/TextInfoLines.razor b/app/MindWork AI Studio/Components/TextInfoLines.razor index 68186316..a6a8e824 100644 --- a/app/MindWork AI Studio/Components/TextInfoLines.razor +++ b/app/MindWork AI Studio/Components/TextInfoLines.razor @@ -1,3 +1,4 @@ +@inherits MSGComponentBase USER_INPUT_ATTRIBUTES = new(); - private string ClipboardTooltip => $"Copy {this.ClipboardTooltipSubject} to the clipboard"; + private string ClipboardTooltip => string.Format(T("Copy {0} to the clipboard"), this.ClipboardTooltipSubject); private async Task CopyToClipboard(string content) => await this.RustService.CopyText2Clipboard(this.Snackbar, content); diff --git a/app/MindWork AI Studio/Components/ThirdPartyComponent.razor b/app/MindWork AI Studio/Components/ThirdPartyComponent.razor index 29c6e8c6..06e1c5c5 100644 --- a/app/MindWork AI Studio/Components/ThirdPartyComponent.razor +++ b/app/MindWork AI Studio/Components/ThirdPartyComponent.razor @@ -1,11 +1,14 @@ +@inherits MSGComponentBase - @this.Header + + @this.Header + - + @@ -16,7 +19,9 @@ - License: @this.LicenseName + + @T("License:") @this.LicenseName + \ No newline at end of file diff --git a/app/MindWork AI Studio/Components/ThirdPartyComponent.razor.cs b/app/MindWork AI Studio/Components/ThirdPartyComponent.razor.cs index 77fdc3c0..c1d6113c 100644 --- a/app/MindWork AI Studio/Components/ThirdPartyComponent.razor.cs +++ b/app/MindWork AI Studio/Components/ThirdPartyComponent.razor.cs @@ -2,7 +2,7 @@ using Microsoft.AspNetCore.Components; namespace AIStudio.Components; -public partial class ThirdPartyComponent : ComponentBase +public partial class ThirdPartyComponent : MSGComponentBase { [Parameter] public string Name { get; set; } = string.Empty; diff --git a/app/MindWork AI Studio/Components/Vision.razor b/app/MindWork AI Studio/Components/Vision.razor index 25d0d2fd..463812bc 100644 --- a/app/MindWork AI Studio/Components/Vision.razor +++ b/app/MindWork AI Studio/Components/Vision.razor @@ -1,10 +1,11 @@ +@inherits MSGComponentBase - Curious about the vision for MindWork AI Studio and what the future holds? We're here to address just that. Remember, this is a free, open-source project, meaning we can't guarantee when or if this vision will be fully realized. Our aim is to share our vision with you to help you decide whether this app is right for you. + @T("Curious about the vision for MindWork AI Studio and what the future holds? We're here to address just that. Remember, this is a free, open-source project, meaning we can't guarantee when or if this vision will be fully realized. Our aim is to share our vision with you to help you decide whether this app is right for you.") - So, where are we headed, and how could the app evolve in the coming months and years? The following list outlines our ideas, though not in order of priority: + @T("So, where are we headed, and how could the app evolve in the coming months and years? The following list outlines our ideas, though not in order of priority:") - + - We hope this vision excites you as much as it excites us. Together, let's build a powerful and flexible AI toolkit to support all your creative, professional, and everyday needs with MindWork AI Studio. + @T("We hope this vision excites you as much as it excites us. Together, let's build a powerful and flexible AI toolkit to support all your creative, professional, and everyday needs with MindWork AI Studio.") \ No newline at end of file diff --git a/app/MindWork AI Studio/Components/Vision.razor.cs b/app/MindWork AI Studio/Components/Vision.razor.cs index 2f7f2659..73750d4a 100644 --- a/app/MindWork AI Studio/Components/Vision.razor.cs +++ b/app/MindWork AI Studio/Components/Vision.razor.cs @@ -1,20 +1,23 @@ -using Microsoft.AspNetCore.Components; - namespace AIStudio.Components; -public partial class Vision : ComponentBase +public partial class Vision : MSGComponentBase { - private static readonly TextItem[] ITEMS_VISION = - [ - new("Meet your needs", "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("Integrating your data", "You'll be able to integrate your data into AI Studio, like your PDF or Office files, or your Markdown notes."), - new("Integration of enterprise data", "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("Useful assistants", "We'll develop more assistants for everyday tasks."), - new("Writing mode", "We're integrating a writing mode to help you create extensive works, like comprehensive project proposals, tenders, or your next fantasy novel."), - new("Specific requirements", "Want an assistant that suits your specific needs? We aim to offer a plugin architecture so organizations and enthusiasts can implement such ideas."), - new("Voice control", "You'll interact with the AI systems using your voice. To achieve this, we want to integrate voice input (speech-to-text) and output (text-to-speech). However, later on, it should also have a natural conversation flow, i.e., seamless conversation."), - new("Content creation", "There will be an interface for AI Studio to create content in other apps. You could, for example, create blog posts directly on the target platform or add entries to an internal knowledge management tool. This requires development work by the tool developers."), - new("Email monitoring", "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."), - new("Browser usage", "We're working on offering AI Studio features in your browser via a plugin, allowing, e.g., for spell-checking or text rewriting directly in the browser."), - ]; + private readonly TextItem[] itemsVision; + + public Vision() + { + 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("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.")), + new(T("Writing mode"), T("We're integrating a writing mode to help you create extensive works, like comprehensive project proposals, tenders, or your next fantasy novel.")), + new(T("Specific requirements"), T("Want an assistant that suits your specific needs? We aim to offer a plugin architecture so organizations and enthusiasts can implement such ideas.")), + new(T("Voice control"), T("You'll interact with the AI systems using your voice. To achieve this, we want to integrate voice input (speech-to-text) and output (text-to-speech). However, later on, it should also have a natural conversation flow, i.e., seamless conversation.")), + new(T("Content creation"), T("There will be an interface for AI Studio to create content in other apps. You could, for example, create blog posts directly on the target platform or add entries to an internal knowledge management tool. This requires development work by the tool developers.")), + new(T("Email monitoring"), T("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.")), + new(T("Browser usage"), T("We're working on offering AI Studio features in your browser via a plugin, allowing, e.g., for spell-checking or text rewriting directly in the browser.")), + ]; + } } \ No newline at end of file diff --git a/app/MindWork AI Studio/Components/Workspaces.razor b/app/MindWork AI Studio/Components/Workspaces.razor index 3046577d..9083af84 100644 --- a/app/MindWork AI Studio/Components/Workspaces.razor +++ b/app/MindWork AI Studio/Components/Workspaces.razor @@ -1,4 +1,5 @@ - +@inherits MSGComponentBase + @switch (item.Value) { @@ -17,7 +18,7 @@ @if (string.IsNullOrWhiteSpace(treeItem.Text)) { - @("Empty chat") + @T("Empty chat") } else { @@ -26,15 +27,15 @@
- + - + - +
@@ -47,13 +48,15 @@
- @treeItem.Text + + @treeItem.Text +
- + - +
@@ -66,7 +69,9 @@
- @treeItem.Text + + @treeItem.Text +
diff --git a/app/MindWork AI Studio/Components/Workspaces.razor.cs b/app/MindWork AI Studio/Components/Workspaces.razor.cs index c4d68567..bcf95425 100644 --- a/app/MindWork AI Studio/Components/Workspaces.razor.cs +++ b/app/MindWork AI Studio/Components/Workspaces.razor.cs @@ -11,7 +11,7 @@ using DialogOptions = AIStudio.Dialogs.DialogOptions; namespace AIStudio.Components; -public partial class Workspaces : ComponentBase +public partial class Workspaces : MSGComponentBase { [Inject] private IDialogService DialogService { get; init; } = null!; @@ -61,7 +61,7 @@ public partial class Workspaces : ComponentBase { Depth = 0, Branch = WorkspaceBranch.WORKSPACES, - Text = "Workspaces", + Text = T("Workspaces"), Icon = Icons.Material.Filled.Folder, Expandable = true, Path = "root", @@ -77,7 +77,7 @@ public partial class Workspaces : ComponentBase { Depth = 0, Branch = WorkspaceBranch.TEMPORARY_CHATS, - Text = "Disappearing Chats", + Text = T("Disappearing Chats"), Icon = Icons.Material.Filled.Timer, Expandable = true, Path = "temp", @@ -178,7 +178,7 @@ public partial class Workspaces : ComponentBase workspaces.Add(new TreeItemData { Expandable = false, - Value = new TreeButton(WorkspaceBranch.WORKSPACES, 1, "Add workspace",Icons.Material.Filled.LibraryAdd, this.AddWorkspace), + Value = new TreeButton(WorkspaceBranch.WORKSPACES, 1, T("Add workspace"),Icons.Material.Filled.LibraryAdd, this.AddWorkspace), }); return workspaces; } @@ -220,7 +220,7 @@ public partial class Workspaces : ComponentBase result.Add(new() { Expandable = false, - Value = new TreeButton(WorkspaceBranch.WORKSPACES, 2, "Add chat",Icons.Material.Filled.AddComment, () => this.AddChat(workspacePath)), + Value = new TreeButton(WorkspaceBranch.WORKSPACES, 2, T("Add chat"),Icons.Material.Filled.AddComment, () => this.AddChat(workspacePath)), }); return result; @@ -250,10 +250,10 @@ public partial class Workspaces : ComponentBase { var dialogParameters = new DialogParameters { - { "Message", "Are you sure you want to load another chat? All unsaved changes will be lost." }, + { "Message", T("Are you sure you want to load another chat? All unsaved changes will be lost.") }, }; - var dialogReference = await this.DialogService.ShowAsync("Load Chat", dialogParameters, DialogOptions.FULLSCREEN); + var dialogReference = await this.DialogService.ShowAsync(T("Load Chat"), dialogParameters, DialogOptions.FULLSCREEN); var dialogResult = await dialogReference.Result; if (dialogResult is null || dialogResult.Canceled) return null; @@ -294,13 +294,13 @@ public partial class Workspaces : ComponentBase { "Message", (chat.WorkspaceId == Guid.Empty) switch { - true => $"Are you sure you want to delete the temporary chat '{chat.Name}'?", - false => $"Are you sure you want to delete the chat '{chat.Name}' in the workspace '{workspaceName}'?", + true => string.Format(T("Are you sure you want to delete the temporary chat '{0}'?"), chat.Name), + false => string.Format(T("Are you sure you want to delete the chat '{0}' in the workspace '{1}'?"), chat.Name, workspaceName), } }, }; - var dialogReference = await this.DialogService.ShowAsync("Delete Chat", dialogParameters, DialogOptions.FULLSCREEN); + var dialogReference = await this.DialogService.ShowAsync(T("Delete Chat"), dialogParameters, DialogOptions.FULLSCREEN); var dialogResult = await dialogReference.Result; if (dialogResult is null || dialogResult.Canceled) return; @@ -331,13 +331,13 @@ public partial class Workspaces : ComponentBase var dialogParameters = new DialogParameters { - { "Message", $"Please enter a new or edit the name for your chat '{chat.Name}':" }, + { "Message", string.Format(T("Please enter a new or edit the name for your chat '{0}':"), chat.Name) }, { "UserInput", chat.Name }, - { "ConfirmText", "Rename" }, + { "ConfirmText", T("Rename") }, { "ConfirmColor", Color.Info }, }; - var dialogReference = await this.DialogService.ShowAsync("Rename Chat", dialogParameters, DialogOptions.FULLSCREEN); + var dialogReference = await this.DialogService.ShowAsync(T("Rename Chat"), dialogParameters, DialogOptions.FULLSCREEN); var dialogResult = await dialogReference.Result; if (dialogResult is null || dialogResult.Canceled) return; @@ -356,13 +356,13 @@ public partial class Workspaces : ComponentBase var workspaceName = await WorkspaceBehaviour.LoadWorkspaceName(workspaceId); var dialogParameters = new DialogParameters { - { "Message", $"Please enter a new or edit the name for your workspace '{workspaceName}':" }, + { "Message", string.Format(T("Please enter a new or edit the name for your workspace '{0}':"), workspaceName) }, { "UserInput", workspaceName }, - { "ConfirmText", "Rename" }, + { "ConfirmText", T("Rename") }, { "ConfirmColor", Color.Info }, }; - var dialogReference = await this.DialogService.ShowAsync("Rename Workspace", dialogParameters, DialogOptions.FULLSCREEN); + var dialogReference = await this.DialogService.ShowAsync(T("Rename Workspace"), dialogParameters, DialogOptions.FULLSCREEN); var dialogResult = await dialogReference.Result; if (dialogResult is null || dialogResult.Canceled) return; @@ -377,13 +377,13 @@ public partial class Workspaces : ComponentBase { var dialogParameters = new DialogParameters { - { "Message", "Please name your workspace:" }, + { "Message", T("Please name your workspace:") }, { "UserInput", string.Empty }, - { "ConfirmText", "Add workspace" }, + { "ConfirmText", T("Add workspace") }, { "ConfirmColor", Color.Info }, }; - var dialogReference = await this.DialogService.ShowAsync("Add Workspace", dialogParameters, DialogOptions.FULLSCREEN); + var dialogReference = await this.DialogService.ShowAsync(T("Add Workspace"), dialogParameters, DialogOptions.FULLSCREEN); var dialogResult = await dialogReference.Result; if (dialogResult is null || dialogResult.Canceled) return; @@ -411,10 +411,10 @@ public partial class Workspaces : ComponentBase var dialogParameters = new DialogParameters { - { "Message", $"Are you sure you want to delete the workspace '{workspaceName}'? This will also delete {chatCount} chat(s) in this workspace." }, + { "Message", string.Format(T("Are you sure you want to delete the workspace '{0}'? This will also delete {1} chat(s) in this workspace."), workspaceName, chatCount) }, }; - var dialogReference = await this.DialogService.ShowAsync("Delete Workspace", dialogParameters, DialogOptions.FULLSCREEN); + var dialogReference = await this.DialogService.ShowAsync(T("Delete Workspace"), dialogParameters, DialogOptions.FULLSCREEN); var dialogResult = await dialogReference.Result; if (dialogResult is null || dialogResult.Canceled) return; @@ -431,17 +431,17 @@ public partial class Workspaces : ComponentBase var dialogParameters = new DialogParameters { - { "Message", "Please select the workspace where you want to move the chat to." }, + { "Message", T("Please select the workspace where you want to move the chat to.") }, { "SelectedWorkspace", chat.WorkspaceId }, - { "ConfirmText", "Move chat" }, + { "ConfirmText", T("Move chat") }, }; - var dialogReference = await this.DialogService.ShowAsync("Move Chat to Workspace", dialogParameters, DialogOptions.FULLSCREEN); + var dialogReference = await this.DialogService.ShowAsync(T("Move Chat to Workspace"), dialogParameters, DialogOptions.FULLSCREEN); var dialogResult = await dialogReference.Result; if (dialogResult is null || dialogResult.Canceled) return; - var workspaceId = dialogResult.Data is Guid id ? id : default; + var workspaceId = dialogResult.Data is Guid id ? id : Guid.Empty; if (workspaceId == Guid.Empty) return; @@ -478,10 +478,10 @@ public partial class Workspaces : ComponentBase { var dialogParameters = new DialogParameters { - { "Message", "Are you sure you want to create a another chat? All unsaved changes will be lost." }, + { "Message", T("Are you sure you want to create a another chat? All unsaved changes will be lost.") }, }; - var dialogReference = await this.DialogService.ShowAsync("Create Chat", dialogParameters, DialogOptions.FULLSCREEN); + var dialogReference = await this.DialogService.ShowAsync(T("Create Chat"), dialogParameters, DialogOptions.FULLSCREEN); var dialogResult = await dialogReference.Result; if (dialogResult is null || dialogResult.Canceled) return; diff --git a/app/MindWork AI Studio/Layout/MainLayout.razor.cs b/app/MindWork AI Studio/Layout/MainLayout.razor.cs index ba970cc4..4baa48f7 100644 --- a/app/MindWork AI Studio/Layout/MainLayout.razor.cs +++ b/app/MindWork AI Studio/Layout/MainLayout.razor.cs @@ -107,9 +107,6 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan // Send a message to start the plugin system: await this.MessageBus.SendMessage(this, Event.STARTUP_PLUGIN_SYSTEM); - // Load the language plugin: - this.Lang = await this.SettingsManager.GetActiveLanguagePlugin(); - await this.themeProvider.WatchSystemPreference(this.SystemeThemeChanged); await this.UpdateThemeConfiguration(); this.LoadNavItems(); diff --git a/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/contentHome.lua b/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/contentHome.lua deleted file mode 100644 index d1805fd0..00000000 --- a/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/contentHome.lua +++ /dev/null @@ -1,3 +0,0 @@ -CONTENT_HOME = { - LetsGetStarted = "Let's get started", -} \ No newline at end of file diff --git a/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua b/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua index 2687f722..6fc7161b 100644 --- a/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua +++ b/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua @@ -1,4 +1,3 @@ -require("contentHome") require("icon") -- The ID for this plugin: @@ -47,17 +46,2215 @@ IETF_TAG = "en-US" -- The language name in the user's language: LANG_NAME = "English (United States)" -UI_TEXT_CONTENT = { - HOME = CONTENT_HOME, - AISTUDIO = { - PAGES = { - HOME = { - T2331588413 = "Let's get started", - }, +UI_TEXT_CONTENT = {} - CHAT = { - T3718856736 = "Short-Term Chat", - }, - }, - } -} +-- Assistant - {0} +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::ASSISTANTBASE::T3043922"] = "Assistant - {0}" + +-- 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." + +-- Your name for the closing salutation of your e-mail. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::EMAIL::ASSISTANTEMAIL::T134060413"] = "Your name for the closing salutation of your e-mail." + +-- Please start each line of your content list with a dash (-) to create a bullet point list. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::EMAIL::ASSISTANTEMAIL::T1384718254"] = "Please start each line of your content list with a dash (-) to create a bullet point list." + +-- Create email +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::EMAIL::ASSISTANTEMAIL::T1686330485"] = "Create email" + +-- Previous conversation +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::EMAIL::ASSISTANTEMAIL::T2074063439"] = "Previous conversation" + +-- Select the writing style +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::EMAIL::ASSISTANTEMAIL::T2241531659"] = "Select the writing style" + +-- Target language +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::EMAIL::ASSISTANTEMAIL::T237828418"] = "Target language" + +-- Please provide some content for the e-mail. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::EMAIL::ASSISTANTEMAIL::T2381517938"] = "Please provide some content for the e-mail." + +-- Please provide some history for the e-mail. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::EMAIL::ASSISTANTEMAIL::T2471325767"] = "Please provide some history for the e-mail." + +-- Your bullet points +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::EMAIL::ASSISTANTEMAIL::T2582330385"] = "Your bullet points" + +-- Yes, I provide the previous conversation +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::EMAIL::ASSISTANTEMAIL::T2652980489"] = "Yes, I provide the previous conversation" + +-- Please select a writing style for the e-mail. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::EMAIL::ASSISTANTEMAIL::T2969942806"] = "Please select a writing style for the e-mail." + +-- E-Mail +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::EMAIL::ASSISTANTEMAIL::T3026443472"] = "E-Mail" + +-- (Optional) The greeting phrase to use +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::EMAIL::ASSISTANTEMAIL::T306513209"] = "(Optional) The greeting phrase to use" + +-- Bullet list the content of the e-mail roughly. Use dashes (-) to separate the items. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::EMAIL::ASSISTANTEMAIL::T3259604530"] = "Bullet list the content of the e-mail roughly. Use dashes (-) to separate the items." + +-- Is there a history, a previous conversation? +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::EMAIL::ASSISTANTEMAIL::T3438127996"] = "Is there a history, a previous conversation?" + +-- Provide the previous conversation, e.g., the last e-mail, the last chat, etc. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::EMAIL::ASSISTANTEMAIL::T3706154604"] = "Provide the previous conversation, e.g., the last e-mail, the last chat, etc." + +-- No, I don't provide a previous conversation +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::EMAIL::ASSISTANTEMAIL::T3823693145"] = "No, I don't provide a previous conversation" + +-- (Optional) Are any of your points particularly important? +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::EMAIL::ASSISTANTEMAIL::T3843104162"] = "(Optional) Are any of your points particularly important?" + +-- Custom target language +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::EMAIL::ASSISTANTEMAIL::T3848935911"] = "Custom target language" + +-- (Optional) Your name for the closing salutation +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::EMAIL::ASSISTANTEMAIL::T453962275"] = "(Optional) Your name for the closing salutation" + +-- Please provide a custom language. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::EMAIL::ASSISTANTEMAIL::T656744944"] = "Please provide a custom language." + +-- Dear Colleagues +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::EMAIL::ASSISTANTEMAIL::T759263763"] = "Dear Colleagues" + +-- Please select a target language for the e-mail. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::EMAIL::ASSISTANTEMAIL::T891073054"] = "Please select a target language for the e-mail." + +-- Please provide a text as input. You might copy the desired text from a document or a website. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::GRAMMARSPELLING::ASSISTANTGRAMMARSPELLING::T137304886"] = "Please provide a text as input. You might copy the desired text from a document or a website." + +-- Proofread +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::GRAMMARSPELLING::ASSISTANTGRAMMARSPELLING::T2325568297"] = "Proofread" + +-- Language +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::GRAMMARSPELLING::ASSISTANTGRAMMARSPELLING::T2591284123"] = "Language" + +-- Your input to check +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::GRAMMARSPELLING::ASSISTANTGRAMMARSPELLING::T2861221443"] = "Your input to check" + +-- Custom language +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::GRAMMARSPELLING::ASSISTANTGRAMMARSPELLING::T3032662264"] = "Custom language" + +-- Grammar & Spelling Checker +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::GRAMMARSPELLING::ASSISTANTGRAMMARSPELLING::T3169549433"] = "Grammar & Spelling Checker" + +-- Check the grammar and spelling of a text. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::GRAMMARSPELLING::ASSISTANTGRAMMARSPELLING::T3184716499"] = "Check the grammar and spelling of a text." + +-- Please provide a custom language. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::GRAMMARSPELLING::ASSISTANTGRAMMARSPELLING::T656744944"] = "Please provide a custom language." + +-- Your icon source +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::ICONFINDER::ASSISTANTICONFINDER::T1302165948"] = "Your icon source" + +-- Find Icon +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::ICONFINDER::ASSISTANTICONFINDER::T1975161003"] = "Find Icon" + +-- Finding the right icon for a context, such as for a piece of text, is not easy. The first challenge: You need to extract a concept from your context, such as from a text. Let's take an example where your text contains statements about multiple departments. The sought-after concept could be "departments." The next challenge is that we need to anticipate the bias of the icon designers: under the search term "departments," there may be no relevant icons or only unsuitable ones. Depending on the icon source, it might be more effective to search for "buildings," for instance. LLMs assist you with both steps. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::ICONFINDER::ASSISTANTICONFINDER::T347756684"] = "Finding the right icon for a context, such as for a piece of text, is not easy. The first challenge: You need to extract a concept from your context, such as from a text. Let's take an example where your text contains statements about multiple departments. The sought-after concept could be \"departments.\" The next challenge is that we need to anticipate the bias of the icon designers: under the search term \"departments,\" there may be no relevant icons or only unsuitable ones. Depending on the icon source, it might be more effective to search for \"buildings,\" for instance. LLMs assist you with both steps." + +-- Icon Finder +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::ICONFINDER::ASSISTANTICONFINDER::T3693102312"] = "Icon Finder" + +-- Open website +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::ICONFINDER::ASSISTANTICONFINDER::T4239378936"] = "Open website" + +-- Your context +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::ICONFINDER::ASSISTANTICONFINDER::T596802185"] = "Your context" + +-- Please provide a context. This will help the AI to find the right icon. You might type just a keyword or copy a sentence from your text, e.g., from a slide where you want to use the icon. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::ICONFINDER::ASSISTANTICONFINDER::T653229070"] = "Please provide a context. This will help the AI to find the right icon. You might type just a keyword or copy a sentence from your text, e.g., from a slide where you want to use the icon." + +-- Please provide a legal document as input. You might copy the desired text from a document or a website. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LEGALCHECK::ASSISTANTLEGALCHECK::T1160217683"] = "Please provide a legal document as input. You might copy the desired text from a document or a website." + +-- Legal Check +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LEGALCHECK::ASSISTANTLEGALCHECK::T1348190638"] = "Legal Check" + +-- Legal document +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LEGALCHECK::ASSISTANTLEGALCHECK::T1887742531"] = "Legal document" + +-- Your questions +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LEGALCHECK::ASSISTANTLEGALCHECK::T1947954583"] = "Your questions" + +-- Provide a legal document and ask a question about it. This assistant does not replace legal advice. Consult a lawyer to get professional advice. Remember that LLMs can invent answers and facts. Please do not rely on this answers. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LEGALCHECK::ASSISTANTLEGALCHECK::T4016275181"] = "Provide a legal document and ask a question about it. This assistant does not replace legal advice. Consult a lawyer to get professional advice. Remember that LLMs can invent answers and facts. Please do not rely on this answers." + +-- Please provide your questions as input. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LEGALCHECK::ASSISTANTLEGALCHECK::T4154383818"] = "Please provide your questions as input." + +-- Ask your questions +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::LEGALCHECK::ASSISTANTLEGALCHECK::T467099852"] = "Ask your questions" + +-- Please provide some text as input. For example, an email. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T1962809521"] = "Please provide some text as input. For example, an email." + +-- Analyze text +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T2268303626"] = "Analyze text" + +-- Target language +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T237828418"] = "Target language" + +-- My Tasks +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T3011450657"] = "My Tasks" + +-- You received a cryptic email that was sent to many recipients and you are now wondering if you need to do something? Copy the email into the input field. You also need to select a personal profile. In this profile, you should describe your role in the organization. The AI will then try to give you hints on what your tasks might be. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T3646084045"] = "You received a cryptic email that was sent to many recipients and you are now wondering if you need to do something? Copy the email into the input field. You also need to select a personal profile. In this profile, you should describe your role in the organization. The AI will then try to give you hints on what your tasks might be." + +-- Custom target language +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T3848935911"] = "Custom target language" + +-- Please select one of your profiles. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T465395981"] = "Please select one of your profiles." + +-- Text or email +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T534887559"] = "Text or email" + +-- Please provide a custom language. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::MYTASKS::ASSISTANTMYTASKS::T656744944"] = "Please provide a custom language." + +-- Please provide a text as input. You might copy the desired text from a document or a website. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::REWRITEIMPROVE::ASSISTANTREWRITEIMPROVE::T137304886"] = "Please provide a text as input. You might copy the desired text from a document or a website." + +-- Sentence structure +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::REWRITEIMPROVE::ASSISTANTREWRITEIMPROVE::T1714063121"] = "Sentence structure" + +-- Rewrite & Improve Text +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::REWRITEIMPROVE::ASSISTANTREWRITEIMPROVE::T1994150308"] = "Rewrite & Improve Text" + +-- Improve your text +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::REWRITEIMPROVE::ASSISTANTREWRITEIMPROVE::T2163831433"] = "Improve your text" + +-- Language +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::REWRITEIMPROVE::ASSISTANTREWRITEIMPROVE::T2591284123"] = "Language" + +-- Custom language +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::REWRITEIMPROVE::ASSISTANTREWRITEIMPROVE::T3032662264"] = "Custom language" + +-- Your input to improve +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::REWRITEIMPROVE::ASSISTANTREWRITEIMPROVE::T3037449423"] = "Your input to improve" + +-- Writing style +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::REWRITEIMPROVE::ASSISTANTREWRITEIMPROVE::T3754048862"] = "Writing style" + +-- Rewrite and improve your text. Please note, that the capabilities of the different LLM providers will vary. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::REWRITEIMPROVE::ASSISTANTREWRITEIMPROVE::T480915300"] = "Rewrite and improve your text. Please note, that the capabilities of the different LLM providers will vary." + +-- Please provide a custom language. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::REWRITEIMPROVE::ASSISTANTREWRITEIMPROVE::T656744944"] = "Please provide a custom language." + +-- Your word or phrase +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::SYNONYM::ASSISTANTSYNONYMS::T1847246020"] = "Your word or phrase" + +-- (Optional) The context for the given word or phrase +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::SYNONYM::ASSISTANTSYNONYMS::T2250963999"] = "(Optional) The context for the given word or phrase" + +-- Synonyms +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::SYNONYM::ASSISTANTSYNONYMS::T2547582747"] = "Synonyms" + +-- Language +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::SYNONYM::ASSISTANTSYNONYMS::T2591284123"] = "Language" + +-- Find synonyms for words or phrases. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::SYNONYM::ASSISTANTSYNONYMS::T2733641217"] = "Find synonyms for words or phrases." + +-- Find synonyms +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::SYNONYM::ASSISTANTSYNONYMS::T3106607224"] = "Find synonyms" + +-- Please provide a word or phrase as input. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::SYNONYM::ASSISTANTSYNONYMS::T3501110371"] = "Please provide a word or phrase as input." + +-- Custom target language +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::SYNONYM::ASSISTANTSYNONYMS::T3848935911"] = "Custom target language" + +-- Please provide a custom language. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::SYNONYM::ASSISTANTSYNONYMS::T656744944"] = "Please provide a custom language." + +-- Your input +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::TEXTSUMMARIZER::ASSISTANTTEXTSUMMARIZER::T1249704194"] = "Your input" + +-- Target complexity +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::TEXTSUMMARIZER::ASSISTANTTEXTSUMMARIZER::T1318882688"] = "Target complexity" + +-- Please provide a text as input. You might copy the desired text from a document or a website. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::TEXTSUMMARIZER::ASSISTANTTEXTSUMMARIZER::T137304886"] = "Please provide a text as input. You might copy the desired text from a document or a website." + +-- Text Summarizer +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::TEXTSUMMARIZER::ASSISTANTTEXTSUMMARIZER::T1907192403"] = "Text Summarizer" + +-- Target language +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::TEXTSUMMARIZER::ASSISTANTTEXTSUMMARIZER::T237828418"] = "Target language" + +-- Summarize long text into a shorter version while retaining the main points. You might want to change the language of the summary to make it more readable. It is also possible to change the complexity of the summary to make it easy to understand. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::TEXTSUMMARIZER::ASSISTANTTEXTSUMMARIZER::T359929871"] = "Summarize long text into a shorter version while retaining the main points. You might want to change the language of the summary to make it more readable. It is also possible to change the complexity of the summary to make it easy to understand." + +-- Please provide your field of expertise. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::TEXTSUMMARIZER::ASSISTANTTEXTSUMMARIZER::T3610378685"] = "Please provide your field of expertise." + +-- Custom target language +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::TEXTSUMMARIZER::ASSISTANTTEXTSUMMARIZER::T3848935911"] = "Custom target language" + +-- Summarize +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::TEXTSUMMARIZER::ASSISTANTTEXTSUMMARIZER::T502888730"] = "Summarize" + +-- Please provide a custom language. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::TEXTSUMMARIZER::ASSISTANTTEXTSUMMARIZER::T656744944"] = "Please provide a custom language." + +-- Your expertise +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::TEXTSUMMARIZER::ASSISTANTTEXTSUMMARIZER::T970222193"] = "Your expertise" + +-- Please select a target language. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::TRANSLATION::ASSISTANTTRANSLATION::T1173859091"] = "Please select a target language." + +-- Your input +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::TRANSLATION::ASSISTANTTRANSLATION::T1249704194"] = "Your input" + +-- Please provide a text as input. You might copy the desired text from a document or a website. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::TRANSLATION::ASSISTANTTRANSLATION::T137304886"] = "Please provide a text as input. You might copy the desired text from a document or a website." + +-- Translate +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::TRANSLATION::ASSISTANTTRANSLATION::T2028202101"] = "Translate" + +-- Target language +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::TRANSLATION::ASSISTANTTRANSLATION::T237828418"] = "Target language" + +-- Translate text from one language to another. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::TRANSLATION::ASSISTANTTRANSLATION::T3230457846"] = "Translate text from one language to another." + +-- No live translation +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::TRANSLATION::ASSISTANTTRANSLATION::T3556243327"] = "No live translation" + +-- Custom target language +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::TRANSLATION::ASSISTANTTRANSLATION::T3848935911"] = "Custom target language" + +-- Live translation +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::TRANSLATION::ASSISTANTTRANSLATION::T4279308324"] = "Live translation" + +-- Translation +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::TRANSLATION::ASSISTANTTRANSLATION::T613888204"] = "Translation" + +-- Please provide a custom language. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::TRANSLATION::ASSISTANTTRANSLATION::T656744944"] = "Please provide a custom language." + +-- Edit Message +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1183581066"] = "Edit Message" + +-- Copies the content to the clipboard +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T12948066"] = "Copies the content to the clipboard" + +-- Do you really want to remove this message? +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1347427447"] = "Do you really want to remove this message?" + +-- Yes, remove the AI response and edit it +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1350385882"] = "Yes, remove the AI response and edit it" + +-- Yes, regenerate it +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1603883875"] = "Yes, regenerate it" + +-- Yes, remove it +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T1820166585"] = "Yes, remove it" + +-- Do you really want to edit this message? In order to edit this message, the AI response will be deleted. +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2018431076"] = "Do you really want to edit this message? In order to edit this message, the AI response will be deleted." + +-- Removes this block +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2093355991"] = "Removes this block" + +-- Regenerate Message +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T2308444540"] = "Regenerate Message" + +-- Cannot render content of type {0} yet. +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3175548294"] = "Cannot render content of type {0} yet." + +-- Edit +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3267849393"] = "Edit" + +-- Regenerate +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3587744975"] = "Regenerate" + +-- Do you really want to regenerate this message? +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T3878878761"] = "Do you really want to regenerate this message?" + +-- Cannot copy this content type to clipboard! +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T4021525742"] = "Cannot copy this content type to clipboard!" + +-- Remove Message +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T4070211974"] = "Remove Message" + +-- No, keep it +UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T4188329028"] = "No, keep it" + +-- Open Settings +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTBLOCK::T1172211894"] = "Open Settings" + +-- Changelog +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHANGELOG::T3017574265"] = "Changelog" + +-- Move chat +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1133040906"] = "Move chat" + +-- Are you sure you want to move this chat? All unsaved changes will be lost. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1142475422"] = "Are you sure you want to move this chat? All unsaved changes will be lost." + +-- Type your input here... +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1849313532"] = "Type your input here..." + +-- Your Prompt (use selected instance '{0}', provider '{1}') +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1967611328"] = "Your Prompt (use selected instance '{0}', provider '{1}')" + +-- Delete this chat & start a new one. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T2991985411"] = "Delete this chat & start a new one." + +-- Move Chat to Workspace +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T3045856778"] = "Move Chat to Workspace" + +-- The selected provider is not allowed in this chat due to data security reasons. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T3403290862"] = "The selected provider is not allowed in this chat due to data security reasons." + +-- Select a provider first +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T3654197869"] = "Select a provider first" + +-- Please select the workspace where you want to move the chat to. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T474393241"] = "Please select the workspace where you want to move the chat to." + +-- Move the chat to a workspace, or to another if it is already in one. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T636393754"] = "Move the chat to a workspace, or to another if it is already in one." + +-- Region +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIDENCEINFO::T1227782301"] = "Region" + +-- Description +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIDENCEINFO::T1725856265"] = "Description" + +-- Confidence Level +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIDENCEINFO::T2492230131"] = "Confidence Level" + +-- Sources +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIDENCEINFO::T2730980305"] = "Sources" + +-- Confidence Card +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIDENCEINFO::T2960002005"] = "Confidence Card" + +-- Confidence +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIDENCEINFO::T3243388657"] = "Confidence" + +-- Shows and hides the confidence card with information about the selected LLM provider. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIDENCEINFO::T847071819"] = "Shows and hides the confidence card with information about the selected LLM provider." + +-- 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. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONMINCONFIDENCESELECTION::T2526727283"] = "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." + +-- Select a minimum confidence level +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONMINCONFIDENCESELECTION::T2579793544"] = "Select a minimum confidence level" + +-- You have selected 1 preview feature. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONMULTISELECT::T1384241824"] = "You have selected 1 preview feature." + +-- No preview features selected. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONMULTISELECT::T2809641588"] = "No preview features selected." + +-- You have selected {0} preview features. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONMULTISELECT::T3513450626"] = "You have selected {0} preview features." + +-- Preselected provider +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONPROVIDERSELECTION::T1469984996"] = "Preselected provider" + +-- Use app default +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONPROVIDERSELECTION::T3672477670"] = "Use app default" + +-- Issues +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ISSUES::T3229841001"] = "Issues" + +-- Given that my employer's workplace uses both Windows and Linux, I wanted a cross-platform solution that would work seamlessly across all major operating systems, including macOS. Additionally, I wanted to demonstrate that it is possible to create modern, efficient, cross-platform applications without resorting to Electron bloatware. The combination of .NET and Rust with Tauri proved to be an excellent technology stack for building such robust applications. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MOTIVATION::T1057189794"] = "Given that my employer's workplace uses both Windows and Linux, I wanted a cross-platform solution that would work seamlessly across all major operating systems, including macOS. Additionally, I wanted to demonstrate that it is possible to create modern, efficient, cross-platform applications without resorting to Electron bloatware. The combination of .NET and Rust with Tauri proved to be an excellent technology stack for building such robust applications." + +-- Limitations of Existing Solutions +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MOTIVATION::T1086130692"] = "Limitations of Existing Solutions" + +-- Personal Needs and Limitations of Web Services +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MOTIVATION::T1839655973"] = "Personal Needs and Limitations of Web Services" + +-- 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." + +-- 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." + +-- Cross-Platform and Modern Development +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MOTIVATION::T843057510"] = "Cross-Platform and Modern Development" + +-- Alpha phase means that we are working on the last details before the beta phase. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PREVIEWALPHA::T166807685"] = "Alpha phase means that we are working on the last details before the beta phase." + +-- This feature is currently in the alpha phase. Expect bugs and unfinished work. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PREVIEWALPHA::T2635524607"] = "This feature is currently in the alpha phase. Expect bugs and unfinished work." + +-- Alpha +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PREVIEWALPHA::T55079499"] = "Alpha" + +-- This feature is currently in the beta phase. It is still be possible that there are some bugs. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PREVIEWBETA::T1045026949"] = "This feature is currently in the beta phase. It is still be possible that there are some bugs." + +-- Beta phase means that we are testing the feature. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PREVIEWBETA::T3605158616"] = "Beta phase means that we are testing the feature." + +-- Beta +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PREVIEWBETA::T375487463"] = "Beta" + +-- This feature is currently in the experimental phase. Expect bugs, unfinished work, changes in future versions, and more. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PREVIEWEXPERIMENTAL::T1735169242"] = "This feature is currently in the experimental phase. Expect bugs, unfinished work, changes in future versions, and more." + +-- Experimental phase means that we have a vision for a feature but not a clear plan yet. We are still exploring the possibilities. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PREVIEWEXPERIMENTAL::T3709099979"] = "Experimental phase means that we have a vision for a feature but not a clear plan yet. We are still exploring the possibilities." + +-- Experimental +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PREVIEWEXPERIMENTAL::T3729365343"] = "Experimental" + +-- Prototype +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PREVIEWPROTOTYPE::T1043365177"] = "Prototype" + +-- Prototype phase means that we have a plan but we are still working on it. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PREVIEWPROTOTYPE::T2995187557"] = "Prototype phase means that we have a plan but we are still working on it." + +-- This feature is currently in the prototype phase. Expect bugs, unfinished work, changes in future versions, and more. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PREVIEWPROTOTYPE::T4145334644"] = "This feature is currently in the prototype phase. Expect bugs, unfinished work, changes in future versions, and more." + +-- This feature is about to be released. We think it's ready for production. There should be no more bugs. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PREVIEWRELEASECANDIDATE::T2003588956"] = "This feature is about to be released. We think it's ready for production. There should be no more bugs." + +-- Release Candidate +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PREVIEWRELEASECANDIDATE::T3451939995"] = "Release Candidate" + +-- Release candidates are the final step before a feature is proven to be stable. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PREVIEWRELEASECANDIDATE::T696585888"] = "Release candidates are the final step before a feature is proven to be stable." + +-- Select one of your profiles +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PROFILEFORMSELECTION::T2003449133"] = "Select one of your profiles" + +-- You can switch between your profiles here +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PROFILESELECTION::T918741365"] = "You can switch between your profiles here" + +-- Provider +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::PROVIDERSELECTION::T900237532"] = "Provider" + +-- The content is cleaned using an LLM agent: the main content is extracted, advertisements and other irrelevant things are attempted to be removed; relative links are attempted to be converted into absolute links so that they can be used. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T1164201762"] = "The content is cleaned using an LLM agent: the main content is extracted, advertisements and other irrelevant things are attempted to be removed; relative links are attempted to be converted into absolute links so that they can be used." + +-- Fetch +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T1396322691"] = "Fetch" + +-- Please select a provider to use the cleanup agent. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T2035652317"] = "Please select a provider to use the cleanup agent." + +-- Please provide a URL to load the content from. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T2235427807"] = "Please provide a URL to load the content from." + +-- Loads the content from your URL. Does not work when the content is hidden behind a paywall. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T2672192696"] = "Loads the content from your URL. Does not work when the content is hidden behind a paywall." + +-- URL from which to load the content +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T2883163022"] = "URL from which to load the content" + +-- Read content from web? +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T2927391091"] = "Read content from web?" + +-- Cleanup content by using an LLM agent? +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T2939928117"] = "Cleanup content by using an LLM agent?" + +-- Hide web content options +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T3031774728"] = "Hide web content options" + +-- Please provide a valid HTTP or HTTPS URL. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T307442288"] = "Please provide a valid HTTP or HTTPS URL." + +-- No content cleaning +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T3588401674"] = "No content cleaning" + +-- Please provide a valid URL. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T3825586228"] = "Please provide a valid URL." + +-- Show web content options +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READWEBCONTENT::T4249712357"] = "Show web content options" + +-- Spellchecking is disabled +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1059411425"] = "Spellchecking is disabled" + +-- 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?" + +-- How often should we check for app updates? +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1364944735"] = "How often should we check for app updates?" + +-- Select preview features +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1439783084"] = "Select preview features" + +-- Select the desired behavior for the navigation bar. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1555038969"] = "Select the desired behavior for the navigation bar." + +-- Color theme +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1599198973"] = "Color theme" + +-- 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. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1666052109"] = "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." + +-- Select the language behavior for the app. The default is to use the system language. You might want to choose a language manually? +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T186780842"] = "Select the language behavior for the app. The default is to use the system language. You might want to choose a language manually?" + +-- Check for updates +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1890416390"] = "Check for updates" + +-- Which preview features would you like to enable? +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1898060643"] = "Which preview features would you like to enable?" + +-- Select the language for the app. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1907446663"] = "Select the language for the app." + +-- Language behavior +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2341504363"] = "Language behavior" + +-- Language +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2591284123"] = "Language" + +-- Save energy? +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T3100928009"] = "Save energy?" + +-- Spellchecking is enabled +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T3165555978"] = "Spellchecking is enabled" + +-- App Options +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T3577148634"] = "App Options" + +-- 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." + +-- Enable spellchecking? +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T3914529369"] = "Enable spellchecking?" + +-- Preselect one of your profiles? +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T4004501229"] = "Preselect one of your profiles?" + +-- When enabled, spellchecking will be active in all input fields. Depending on your operating system, errors may not be visually highlighted, but right-clicking may still offer possible corrections. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T4067492921"] = "When enabled, spellchecking will be active in all input fields. Depending on your operating system, errors may not be visually highlighted, but right-clicking may still offer possible corrections." + +-- Navigation bar behavior +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T602293588"] = "Navigation bar behavior" + +-- Choose the color theme that best suits for you. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T654667432"] = "Choose the color theme that best suits for you." + +-- Energy saving is enabled +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T71162186"] = "Energy saving is enabled" + +-- Energy saving is disabled +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T716338721"] = "Energy saving is disabled" + +-- Preview feature visibility +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T817101267"] = "Preview feature visibility" + +-- 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." + +-- Control how the LLM provider for loaded chats is selected and when assistant results are sent to chat. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCHAT::T172255919"] = "Control how the LLM provider for loaded chats is selected and when assistant results are sent to chat." + +-- Chat Options +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCHAT::T1757092713"] = "Chat Options" + +-- Shortcut to send input +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCHAT::T1773585398"] = "Shortcut to send input" + +-- Provider selection when creating new chats +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCHAT::T189306836"] = "Provider selection when creating new chats" + +-- Would you like to set one of your profiles as the default for chats? +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCHAT::T1933521846"] = "Would you like to set one of your profiles as the default for chats?" + +-- Apply default data source option when sending assistant results to chat +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCHAT::T2510376349"] = "Apply default data source option when sending assistant results to chat" + +-- Control how the LLM provider for added chats is selected. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCHAT::T263621180"] = "Control how the LLM provider for added chats is selected." + +-- Provider selection when loading a chat and sending assistant results to chat +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCHAT::T2868379953"] = "Provider selection when loading a chat and sending assistant results to chat" + +-- Show the latest message after loading? +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCHAT::T2913693228"] = "Show the latest message after loading?" + +-- Do you want to use any shortcut to send your input? +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCHAT::T2936560092"] = "Do you want to use any shortcut to send your input?" + +-- No chat options are preselected +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCHAT::T3383186996"] = "No chat options are preselected" + +-- First (oldest) message is shown, after loading a chat +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCHAT::T3507181366"] = "First (oldest) message is shown, after loading a chat" + +-- Preselect chat options? +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCHAT::T3728624759"] = "Preselect chat options?" + +-- Chat options are preselected +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCHAT::T3730599555"] = "Chat options are preselected" + +-- Latest message is shown, after loading a chat +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCHAT::T3755993611"] = "Latest message is shown, after loading a chat" + +-- Preselect one of your profiles? +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCHAT::T4004501229"] = "Preselect one of your profiles?" + +-- Do you want to apply the default data source options when sending assistant results to chat? +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCHAT::T4033153439"] = "Do you want to apply the default data source options when sending assistant results to chat?" + +-- When enabled, you can preselect chat options. This is might be useful when you prefer a specific provider. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCHAT::T477675197"] = "When enabled, you can preselect chat options. This is might be useful when you prefer a specific provider." + +-- You can set default data sources and options for new chats. You can change these settings later for each individual chat. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCHAT::T492357592"] = "You can set default data sources and options for new chats. You can change these settings later for each individual chat." + +-- When enabled, the latest message is shown after loading a chat. When disabled, the first (oldest) message is shown. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELCHAT::T582516016"] = "When enabled, the latest message is shown after loading a chat. When disabled, the first (oldest) message is shown." + +-- Edit Profile +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROFILES::T1143111468"] = "Edit Profile" + +-- Configure Profiles +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROFILES::T1352823555"] = "Configure Profiles" + +-- No profiles configured yet. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROFILES::T1433534732"] = "No profiles configured yet." + +-- Delete +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROFILES::T1469573738"] = "Delete" + +-- Your Profiles +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROFILES::T2378610256"] = "Your Profiles" + +-- Edit +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROFILES::T3267849393"] = "Edit" + +-- Profile Name +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROFILES::T3392578705"] = "Profile Name" + +-- Delete Profile +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROFILES::T3804515427"] = "Delete Profile" + +-- Actions +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROFILES::T3865031940"] = "Actions" + +-- Store personal data about yourself in various profiles so that the AIs know your personal context. This saves you from having to explain your context each time, for example, in every chat. When you have different roles, you can create a profile for each role. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROFILES::T4125557797"] = "Store personal data about yourself in various profiles so that the AIs know your personal context. This saves you from having to explain your context each time, for example, in every chat. When you have different roles, you can create a profile for each role." + +-- Add Profile +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROFILES::T4248067241"] = "Add Profile" + +-- Are you sure you want to delete the profile '{0}'? +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROFILES::T55364659"] = "Are you sure you want to delete the profile '{0}'?" + +-- Are you a project manager in a research facility? You might want to create a profile for your project management activities, one for your scientific work, and a profile for when you need to write program code. In these profiles, you can record how much experience you have or which methods you like or dislike using. Later, you can choose when and where you want to use each profile. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROFILES::T56359901"] = "Are you a project manager in a research facility? You might want to create a profile for your project management activities, one for your scientific work, and a profile for when you need to write program code. In these profiles, you can record how much experience you have or which methods you like or dislike using. Later, you can choose when and where you want to use each profile." + +-- Show provider's confidence level? +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1052533048"] = "Show provider's confidence level?" + +-- Delete +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1469573738"] = "Delete" + +-- When enabled, we show you the confidence level for the selected provider in the app. This helps you assess where you are sending your data at any time. Example: are you currently working with sensitive data? Then choose a particularly trustworthy provider, etc. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1505516304"] = "When enabled, we show you the confidence level for the selected provider in the app. This helps you assess where you are sending your data at any time. Example: are you currently working with sensitive data? Then choose a particularly trustworthy provider, etc." + +-- No, please hide the confidence level +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1628475119"] = "No, please hide the confidence level" + +-- Description +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1725856265"] = "Description" + +-- Add Provider +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1806589097"] = "Add Provider" + +-- Edit LLM Provider +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1868766523"] = "Edit LLM Provider" + +-- Are you sure you want to delete the provider '{0}'? +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T2031310917"] = "Are you sure you want to delete the provider '{0}'?" + +-- Do you want to always be able to recognize how trustworthy your LLM providers are? This way, you keep control over which provider you send your data to. You have two options for this: Either you choose a common schema, or you configure the trust levels for each LLM provider yourself. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T2082904277"] = "Do you want to always be able to recognize how trustworthy your LLM providers are? This way, you keep control over which provider you send your data to. You have two options for this: Either you choose a common schema, or you configure the trust levels for each LLM provider yourself." + +-- Model +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T2189814010"] = "Model" + +-- Choose the scheme that best suits you and your life. Do you trust any western provider? Or only providers from the USA or exclusively European providers? Then choose the appropriate scheme. Alternatively, you can assign the confidence levels to each provider yourself. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T2283885378"] = "Choose the scheme that best suits you and your life. Do you trust any western provider? Or only providers from the USA or exclusively European providers? Then choose the appropriate scheme. Alternatively, you can assign the confidence levels to each provider yourself." + +-- LLM Provider Confidence +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T2349972795"] = "LLM Provider Confidence" + +-- What we call a provider is the combination of an LLM provider such as OpenAI and a model like GPT-4o. You can configure as many providers as you want. This way, you can use the appropriate model for each task. As an LLM provider, you can also choose local providers. However, to use this app, you must configure at least one provider. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T2460361126"] = "What we call a provider is the combination of an LLM provider such as OpenAI and a model like GPT-4o. You can configure as many providers as you want. This way, you can use the appropriate model for each task. As an LLM provider, you can also choose local providers. However, to use this app, you must configure at least one provider." + +-- Confidence Level +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T2492230131"] = "Confidence Level" + +-- When enabled, you can enforce a minimum confidence level for all LLM providers. This way, you can ensure that only trustworthy providers are used. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T281063702"] = "When enabled, you can enforce a minimum confidence level for all LLM providers. This way, you can ensure that only trustworthy providers are used." + +-- Instance Name +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T2842060373"] = "Instance Name" + +-- No providers configured yet. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T2911731076"] = "No providers configured yet." + +-- Configure Providers +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3027859089"] = "Configure 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" + +-- Couldn't delete the provider '{0}'. The issue: {1}. We can ignore this issue and delete the provider anyway. Do you want to ignore it and delete this provider? +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3313715309"] = "Couldn't delete the provider '{0}'. The issue: {1}. We can ignore this issue and delete the provider anyway. Do you want to ignore it and delete this provider?" + +-- Add LLM Provider +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3346433704"] = "Add LLM Provider" + +-- LLM Provider +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3612415205"] = "LLM Provider" + +-- No, do not enforce a minimum confidence level +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3642102079"] = "No, do not enforce a minimum confidence level" + +-- Configured Providers +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3850871263"] = "Configured Providers" + +-- Actions +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3865031940"] = "Actions" + +-- Select a confidence scheme +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T4144206465"] = "Select a confidence scheme" + +-- Do you want to enforce an app-wide minimum confidence level? +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T4258968041"] = "Do you want to enforce an app-wide minimum confidence level?" + +-- Delete LLM Provider +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T4269256234"] = "Delete LLM Provider" + +-- Yes, enforce a minimum confidence level +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T458854917"] = "Yes, enforce a minimum confidence level" + +-- Not yet configured +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T48051324"] = "Not yet configured" + +-- Open Dashboard +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T78223861"] = "Open Dashboard" + +-- Yes, show me the confidence level +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T853225204"] = "Yes, show me the confidence level" + +-- Provider +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T900237532"] = "Provider" + +-- If and when should we delete your temporary chats? +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELWORKSPACES::T1014418451"] = "If and when should we delete your temporary chats?" + +-- Workspace display behavior +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELWORKSPACES::T2151409362"] = "Workspace display behavior" + +-- Workspace behavior +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELWORKSPACES::T2562846516"] = "Workspace behavior" + +-- How should we display your workspaces? +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELWORKSPACES::T3566924898"] = "How should we display your workspaces?" + +-- Should we store your chats? +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELWORKSPACES::T3942969162"] = "Should we store your chats?" + +-- Workspace Options +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELWORKSPACES::T476474348"] = "Workspace Options" + +-- Workspace maintenance +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELWORKSPACES::T49653413"] = "Workspace maintenance" + +-- Copy {0} to the clipboard +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TEXTINFOLINE::T2206391442"] = "Copy {0} to the clipboard" + +-- Copy {0} to the clipboard +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TEXTINFOLINES::T2206391442"] = "Copy {0} to the clipboard" + +-- Open the repository or website +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::THIRDPARTYCOMPONENT::T1392042694"] = "Open the repository or website" + +-- License: +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::THIRDPARTYCOMPONENT::T1908172666"] = "License:" + +-- You'll interact with the AI systems using your voice. To achieve this, we want to integrate voice input (speech-to-text) and output (text-to-speech). However, later on, it should also have a natural conversation flow, i.e., seamless conversation. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T1015366320"] = "You'll interact with the AI systems using your voice. To achieve this, we want to integrate voice input (speech-to-text) and output (text-to-speech). However, later on, it should also have a natural conversation flow, i.e., seamless conversation." + +-- We hope this vision excites you as much as it excites us. Together, let's build a powerful and flexible AI toolkit to support all your creative, professional, and everyday needs with MindWork AI Studio. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T1061000046"] = "We hope this vision excites you as much as it excites us. Together, let's build a powerful and flexible AI toolkit to support all your creative, professional, and everyday needs with MindWork AI Studio." + +-- Integration of enterprise data +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T1127694951"] = "Integration of enterprise data" + +-- Meet your needs +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T127032776"] = "Meet your needs" + +-- We're integrating a writing mode to help you create extensive works, like comprehensive project proposals, tenders, or your next fantasy novel. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T1457213518"] = "We're integrating a writing mode to help you create extensive works, like comprehensive project proposals, tenders, or your next fantasy novel." + +-- Email monitoring +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T1520989255"] = "Email monitoring" + +-- You'll be able to integrate your data into AI Studio, like your PDF or Office files, or your Markdown notes. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T1648606751"] = "You'll be able to integrate your data into AI Studio, like your PDF or Office files, or your Markdown notes." + +-- 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." + +-- 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." + +-- 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." + +-- Browser usage +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T2345974992"] = "Browser usage" + +-- Integrating your data +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T2416595938"] = "Integrating your data" + +-- Curious about the vision for MindWork AI Studio and what the future holds? We're here to address just that. Remember, this is a free, open-source project, meaning we can't guarantee when or if this vision will be fully realized. Our aim is to share our vision with you to help you decide whether this app is right for you. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T2491403346"] = "Curious about the vision for MindWork AI Studio and what the future holds? We're here to address just that. Remember, this is a free, open-source project, meaning we can't guarantee when or if this vision will be fully realized. Our aim is to share our vision with you to help you decide whether this app is right for you." + +-- Voice control +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T2827242540"] = "Voice control" + +-- Specific requirements +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T2868740431"] = "Specific requirements" + +-- We'll develop more assistants for everyday tasks. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T2899555955"] = "We'll develop more assistants for everyday tasks." + +-- We're working on offering AI Studio features in your browser via a plugin, allowing, e.g., for spell-checking or text rewriting directly in the browser. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T308543246"] = "We're working on offering AI Studio features in your browser via a plugin, allowing, e.g., for spell-checking or text rewriting directly in the browser." + +-- There will be an interface for AI Studio to create content in other apps. You could, for example, create blog posts directly on the target platform or add entries to an internal knowledge management tool. This requires development work by the tool developers. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T3290746961"] = "There will be an interface for AI Studio to create content in other apps. You could, for example, create blog posts directly on the target platform or add entries to an internal knowledge management tool. This requires development work by the tool developers." + +-- Want an assistant that suits your specific needs? We aim to offer a plugin architecture so organizations and enthusiasts can implement such ideas. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T3440464089"] = "Want an assistant that suits your specific needs? We aim to offer a plugin architecture so organizations and enthusiasts can implement such ideas." + +-- Writing mode +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T3640675146"] = "Writing mode" + +-- So, where are we headed, and how could the app evolve in the coming months and years? The following list outlines our ideas, though not in order of priority: +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T4106960135"] = "So, where are we headed, and how could the app evolve in the coming months and years? The following list outlines our ideas, though not in order of priority:" + +-- Content creation +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T428040679"] = "Content creation" + +-- Useful assistants +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T586430036"] = "Useful assistants" + +-- Are you sure you want to delete the chat '{0}' in the workspace '{1}'? +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1016188706"] = "Are you sure you want to delete the chat '{0}' in the workspace '{1}'?" + +-- Move chat +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1133040906"] = "Move chat" + +-- Delete +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1469573738"] = "Delete" + +-- Rename Workspace +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1474303418"] = "Rename Workspace" + +-- Rename Chat +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T156144855"] = "Rename Chat" + +-- Add workspace +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1586005241"] = "Add workspace" + +-- Add chat +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1874060138"] = "Add chat" + +-- Create Chat +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1939006681"] = "Create Chat" + +-- Please name your workspace: +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T201482774"] = "Please name your workspace:" + +-- Are you sure you want to load another chat? All unsaved changes will be lost. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2133593288"] = "Are you sure you want to load another chat? All unsaved changes will be lost." + +-- Are you sure you want to delete the workspace '{0}'? This will also delete {1} chat(s) in this workspace. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2151341762"] = "Are you sure you want to delete the workspace '{0}'? This will also delete {1} chat(s) in this workspace." + +-- Are you sure you want to create a another chat? All unsaved changes will be lost. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2237618267"] = "Are you sure you want to create a another chat? All unsaved changes will be lost." + +-- Delete Chat +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2244038752"] = "Delete Chat" + +-- Move to workspace +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T2509305748"] = "Move to workspace" + +-- Are you sure you want to delete the temporary chat '{0}'? +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3043761007"] = "Are you sure you want to delete the temporary chat '{0}'?" + +-- Move Chat to Workspace +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3045856778"] = "Move Chat to Workspace" + +-- Please enter a new or edit the name for your workspace '{0}': +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T323280982"] = "Please enter a new or edit the name for your workspace '{0}':" + +-- Rename +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3355849203"] = "Rename" + +-- Please enter a new or edit the name for your chat '{0}': +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3419791373"] = "Please enter a new or edit the name for your chat '{0}':" + +-- Load Chat +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3555709365"] = "Load Chat" + +-- Add Workspace +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T3672981145"] = "Add Workspace" + +-- Empty chat +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T4019509364"] = "Empty chat" + +-- Workspaces +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T4048389951"] = "Workspaces" + +-- Disappearing Chats +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T4201703117"] = "Disappearing Chats" + +-- Please select the workspace where you want to move the chat to. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T474393241"] = "Please select the workspace where you want to move the chat to." + +-- Delete Workspace +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T701874671"] = "Delete Workspace" + +-- There is no social event +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T1222800281"] = "There is no social event" + +-- Agenda options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T1249372829"] = "Agenda options are preselected" + +-- Preselect a duration? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T1404615656"] = "Preselect a duration?" + +-- Preselect the number of participants +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T1444356399"] = "Preselect the number of participants" + +-- Meeting is virtual +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T1446638309"] = "Meeting is virtual" + +-- Preselect a name? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T1471770981"] = "Preselect a name?" + +-- Preselect whether participants needs to arrive and depart +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T1648427207"] = "Preselect whether participants needs to arrive and depart" + +-- Preselect a start time? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T1901151023"] = "Preselect a start time?" + +-- Preselect a location? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T1908318849"] = "Preselect a location?" + +-- How many participants should be preselected? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T1998244307"] = "How many participants should be preselected?" + +-- Preselect whether the meeting is virtual +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T2084951012"] = "Preselect whether the meeting is virtual" + +-- Would you like to preselect one of your profiles? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T2221665527"] = "Would you like to preselect one of your profiles?" + +-- When enabled, you can preselect most agenda options. This is might be useful when you need to create similar agendas often. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T2373110543"] = "When enabled, you can preselect most agenda options. This is might be useful when you need to create similar agendas often." + +-- Preselect whether the participants should get to know each other +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T2519703500"] = "Preselect whether the participants should get to know each other" + +-- Which agenda language should be preselected? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T2801220321"] = "Which agenda language should be preselected?" + +-- Preselect another agenda language +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T2915422331"] = "Preselect another agenda language" + +-- Participants do not need to get to know each other +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T2949002251"] = "Participants do not need to get to know each other" + +-- There is a social event +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T296183299"] = "There is a social event" + +-- Participants should be actively involved +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T298324727"] = "Participants should be actively involved" + +-- Meeting is in person +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T3008159782"] = "Meeting is in person" + +-- Participants do not need to arrive and depart +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T3087504452"] = "Participants do not need to arrive and depart" + +-- Preselect whether there is a joint dinner +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T3175009548"] = "Preselect whether there is a joint dinner" + +-- Preselect an objective? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T3439476935"] = "Preselect an objective?" + +-- Preselect a moderator? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T3482798491"] = "Preselect a moderator?" + +-- Participants need to arrive and depart +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T3591032034"] = "Participants need to arrive and depart" + +-- Participants do not need to be actively involved +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T3679899885"] = "Participants do not need to be actively involved" + +-- Preselect the approx. lunch time +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T3709527588"] = "Preselect the approx. lunch time" + +-- Preselect a topic? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T3835166371"] = "Preselect a topic?" + +-- Preselect one of your profiles? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T4004501229"] = "Preselect one of your profiles?" + +-- Preselect the agenda language +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T4055846391"] = "Preselect the agenda language" + +-- No agenda options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T4094211586"] = "No agenda options are preselected" + +-- Participants should get to know each other +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T464127805"] = "Participants should get to know each other" + +-- Assistant: Agenda Planner Options +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T677962779"] = "Assistant: Agenda Planner Options" + +-- There is a joint dinner +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T707310400"] = "There is a joint dinner" + +-- Preselect the approx. break time +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T722113273"] = "Preselect the approx. break time" + +-- There is no joint dinner +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T768936730"] = "There is no joint dinner" + +-- Preselect agenda options? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T800921421"] = "Preselect agenda options?" + +-- Preselect whether there is a social event +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T816053055"] = "Preselect whether there is a social event" + +-- Preselect whether the participants should actively involved +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T817726429"] = "Preselect whether the participants should actively involved" + +-- Restrict to one bias a day? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBIAS::T1608129203"] = "Restrict to one bias a day?" + +-- Yes, you can only retrieve one bias per day +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBIAS::T1765683725"] = "Yes, you can only retrieve one bias per day" + +-- Reset +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBIAS::T180921696"] = "Reset" + +-- Would you like to preselect one of your profiles? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBIAS::T2221665527"] = "Would you like to preselect one of your profiles?" + +-- No restriction. You can retrieve as many biases as you want per day. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBIAS::T2305356277"] = "No restriction. You can retrieve as many biases as you want per day." + +-- Which language should be preselected? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBIAS::T2345162613"] = "Which language should be preselected?" + +-- Reset your bias-of-the-day statistics +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBIAS::T2350981714"] = "Reset your bias-of-the-day statistics" + +-- Preselect another language +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBIAS::T2382415529"] = "Preselect another language" + +-- Preselect the language +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBIAS::T2571465005"] = "Preselect the language" + +-- Close +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBIAS::T3448155331"] = "Close" + +-- No options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBIAS::T354528094"] = "No options are preselected" + +-- Assistant: Bias of the Day +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBIAS::T384887684"] = "Assistant: Bias of the Day" + +-- Options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBIAS::T3875604319"] = "Options are preselected" + +-- Preselect one of your profiles? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBIAS::T4004501229"] = "Preselect one of your profiles?" + +-- Are you sure you want to reset your bias-of-the-day statistics? The system will no longer remember which biases you already know. As a result, biases you are already familiar with may be addressed again. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBIAS::T405627382"] = "Are you sure you want to reset your bias-of-the-day statistics? The system will no longer remember which biases you already know. As a result, biases you are already familiar with may be addressed again." + +-- Assistant: Bias of the Day Options +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBIAS::T4235808594"] = "Assistant: Bias of the Day Options" + +-- Preselect options? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBIAS::T42672465"] = "Preselect options?" + +-- You have learned about {0} out of {1} biases. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBIAS::T679061561"] = "You have learned about {0} out of {1} biases." + +-- When enabled, you can preselect options. This is might be useful when you prefer a specific language or LLM model. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGASSISTANTBIAS::T711745239"] = "When enabled, you can preselect options. This is might be useful when you prefer a specific language or LLM model." + +-- Which programming language should be preselected for added contexts? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCODING::T1073540083"] = "Which programming language should be preselected for added contexts?" + +-- Compiler messages are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCODING::T1110902070"] = "Compiler messages are preselected" + +-- Preselect a programming language +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCODING::T2181567002"] = "Preselect a programming language" + +-- Would you like to preselect one of your profiles? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCODING::T2221665527"] = "Would you like to preselect one of your profiles?" + +-- When enabled, you can preselect the coding options. This is might be useful when you prefer a specific programming language or LLM model. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCODING::T2619641701"] = "When enabled, you can preselect the coding options. This is might be useful when you prefer a specific programming language or LLM model." + +-- Preselect coding options? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCODING::T2790579667"] = "Preselect coding options?" + +-- Preselect compiler messages? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCODING::T2970689954"] = "Preselect compiler messages?" + +-- No coding options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCODING::T3015105896"] = "No coding options are preselected" + +-- Coding options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCODING::T3567850751"] = "Coding options are preselected" + +-- Preselect one of your profiles? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCODING::T4004501229"] = "Preselect one of your profiles?" + +-- Preselect another programming language +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCODING::T4230412334"] = "Preselect another programming language" + +-- Compiler messages are not preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCODING::T516498299"] = "Compiler messages are not preselected" + +-- Assistant: Coding Options +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGCODING::T585868261"] = "Assistant: Coding Options" + +-- You might configure different data sources. A data source can include one file, all files in a directory, or data from your company. Later, you can incorporate these data sources as needed when the AI requires this data to complete a certain task. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T1084943026"] = "You might configure different data sources. A data source can include one file, all files in a directory, or data from your company. Later, you can incorporate these data sources as needed when the AI requires this data to complete a certain task." + +-- Are you sure you want to delete the data source '{0}' of type {1}? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T1096979935"] = "Are you sure you want to delete the data source '{0}' of type {1}?" + +-- Edit Local Directory Data Source +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T1215599168"] = "Edit Local Directory Data Source" + +-- Add Local Directory as Data Source +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T1454193397"] = "Add Local Directory as Data Source" + +-- Delete +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T1469573738"] = "Delete" + +-- Local File +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T1687345358"] = "Local File" + +-- Delete Data Source +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T1849107431"] = "Delete Data Source" + +-- Local Directory Data Source Information +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T2146756020"] = "Local Directory Data Source Information" + +-- Edit ERI v1 Data Source +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T221059217"] = "Edit ERI v1 Data Source" + +-- Edit Local File Data Source +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T2453292893"] = "Edit Local File Data Source" + +-- ERI v1 Data Source Information +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T26243729"] = "ERI v1 Data Source Information" + +-- Name +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T266367750"] = "Name" + +-- Embedding +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T2838542994"] = "Embedding" + +-- Edit +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T3267849393"] = "Edit" + +-- Add Data Source +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T3387511033"] = "Add Data Source" + +-- Unknown +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T3424652889"] = "Unknown" + +-- Close +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T3448155331"] = "Close" + +-- Add Local File as Data Source +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T3500365052"] = "Add Local File as Data Source" + +-- Type +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T3512062061"] = "Type" + +-- Local File Data Source Information +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T3525663993"] = "Local File Data Source Information" + +-- No data sources configured yet. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T3549650120"] = "No data sources configured yet." + +-- Actions +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T3865031940"] = "Actions" + +-- Configured Data Sources +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T543942217"] = "Configured Data Sources" + +-- Add ERI v1 Data Source +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T590005498"] = "Add ERI v1 Data Source" + +-- External Data (ERI-Server v1) +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T774473996"] = "External Data (ERI-Server v1)" + +-- Local Directory +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T926703547"] = "Local Directory" + +-- 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." + +-- Preselect ERI server options? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGERISERVER::T1664055662"] = "Preselect ERI server options?" + +-- No ERI server options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGERISERVER::T1793785587"] = "No ERI server options are preselected" + +-- Most ERI server options can be customized and saved directly in the ERI server assistant. For this, the ERI server assistant has an auto-save function. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGERISERVER::T2093534613"] = "Most ERI server options can be customized and saved directly in the ERI server assistant. For this, the ERI server assistant has an auto-save function." + +-- Would you like to preselect one of your profiles? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGERISERVER::T2221665527"] = "Would you like to preselect one of your profiles?" + +-- Close +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGERISERVER::T3448155331"] = "Close" + +-- Assistant: ERI Server Options +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGERISERVER::T3629372826"] = "Assistant: ERI Server Options" + +-- Preselect one of your profiles? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGERISERVER::T4004501229"] = "Preselect one of your profiles?" + +-- ERI server options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGERISERVER::T488190224"] = "ERI server options are preselected" + +-- Preselect the target language +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGGRAMMARSPELLING::T1417990312"] = "Preselect the target language" + +-- Preselect another target language +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGGRAMMARSPELLING::T1462295644"] = "Preselect another target language" + +-- Close +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGGRAMMARSPELLING::T3448155331"] = "Close" + +-- Which target language should be preselected? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGGRAMMARSPELLING::T3547337928"] = "Which target language should be preselected?" + +-- Assistant: Grammar & Spelling Checker Options +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGGRAMMARSPELLING::T886675455"] = "Assistant: Grammar & Spelling Checker Options" + +-- Preselect the target language +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGI18N::T1417990312"] = "Preselect the target language" + +-- Preselect another target language +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGI18N::T1462295644"] = "Preselect another target language" + +-- Assistant: Localization +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGI18N::T2573041664"] = "Assistant: Localization" + +-- Close +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGI18N::T3448155331"] = "Close" + +-- Which target language should be preselected? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGI18N::T3547337928"] = "Which target language should be preselected?" + +-- Preselect the icon source +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGICONFINDER::T1116652851"] = "Preselect the icon source" + +-- Assistant: Icon Finder Options +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGICONFINDER::T1570765862"] = "Assistant: Icon Finder Options" + +-- No icon options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGICONFINDER::T1694910115"] = "No icon options are preselected" + +-- Icon options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGICONFINDER::T1792507476"] = "Icon options are preselected" + +-- Close +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGICONFINDER::T3448155331"] = "Close" + +-- Preselect icon options? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGICONFINDER::T725252382"] = "Preselect icon options?" + +-- No job posting options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGJOBPOSTINGS::T1257718691"] = "No job posting options are preselected" + +-- Preselect some mandatory information about the job posting? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGJOBPOSTINGS::T1332068481"] = "Preselect some mandatory information about the job posting?" + +-- Preselect another target language +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGJOBPOSTINGS::T1462295644"] = "Preselect another target language" + +-- Job posting options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGJOBPOSTINGS::T1827578822"] = "Job posting options are preselected" + +-- Preselect the work location? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGJOBPOSTINGS::T1867962106"] = "Preselect the work location?" + +-- Preselect the language +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGJOBPOSTINGS::T2571465005"] = "Preselect the language" + +-- Preselect job posting options? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGJOBPOSTINGS::T2624983038"] = "Preselect job posting options?" + +-- Preselect the company name? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGJOBPOSTINGS::T2679442990"] = "Preselect the company name?" + +-- When enabled, you can preselect some job posting options. This is might be useful when you prefer a specific LLM model. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGJOBPOSTINGS::T2907036553"] = "When enabled, you can preselect some job posting options. This is might be useful when you prefer a specific LLM model." + +-- Preselect the job qualifications? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGJOBPOSTINGS::T3223375709"] = "Preselect the job qualifications?" + +-- Assistant: Job Posting Options +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGJOBPOSTINGS::T3307661496"] = "Assistant: Job Posting Options" + +-- Close +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGJOBPOSTINGS::T3448155331"] = "Close" + +-- Which target language should be preselected? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGJOBPOSTINGS::T3547337928"] = "Which target language should be preselected?" + +-- Preselect the job responsibilities? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGJOBPOSTINGS::T3788397013"] = "Preselect the job responsibilities?" + +-- Preselect the job description? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGJOBPOSTINGS::T3825475093"] = "Preselect the job description?" + +-- Content cleaner agent is preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T1013787967"] = "Content cleaner agent is preselected" + +-- Web content reader is shown +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T1030372436"] = "Web content reader is shown" + +-- When enabled, the web content reader is preselected. This is might be useful when you prefer to load legal content from the web very often. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T1507288278"] = "When enabled, the web content reader is preselected. This is might be useful when you prefer to load legal content from the web very often." + +-- Preselect legal check options? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T1563865738"] = "Preselect legal check options?" + +-- No legal check options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T1591931823"] = "No legal check options are preselected" + +-- When activated, the web content reader is hidden and cannot be used. As a result, the user interface becomes a bit easier to use. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T1633101895"] = "When activated, the web content reader is hidden and cannot be used. As a result, the user interface becomes a bit easier to use." + +-- Web content reader is not preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T1701127912"] = "Web content reader is not preselected" + +-- Content cleaner agent is not preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T1969816694"] = "Content cleaner agent is not preselected" + +-- Hide the web content reader? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T2090693677"] = "Hide the web content reader?" + +-- When enabled, you can preselect some legal check options. This is might be useful when you prefer a specific LLM model. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T2164667361"] = "When enabled, you can preselect some legal check options. This is might be useful when you prefer a specific LLM model." + +-- Would you like to preselect one of your profiles? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T2221665527"] = "Would you like to preselect one of your profiles?" + +-- Legal check options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T252916114"] = "Legal check options are preselected" + +-- When enabled, the content cleaner agent is preselected. This is might be useful when you prefer to clean up the legal content before translating it. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T2746583995"] = "When enabled, the content cleaner agent is preselected. This is might be useful when you prefer to clean up the legal content before translating it." + +-- Web content reader is hidden +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T2799795311"] = "Web content reader is hidden" + +-- Close +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T3448155331"] = "Close" + +-- Web content reader is preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T3641773985"] = "Web content reader is preselected" + +-- Preselect the content cleaner agent? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T3649428096"] = "Preselect the content cleaner agent?" + +-- Preselect one of your profiles? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T4004501229"] = "Preselect one of your profiles?" + +-- Assistant: Legal Check Options +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T4033382756"] = "Assistant: Legal Check Options" + +-- Preselect the web content reader? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGLEGALCHECK::T629158142"] = "Preselect the web content reader?" + +-- Would you like to preselect one of your profiles? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGMYTASKS::T2221665527"] = "Would you like to preselect one of your profiles?" + +-- Which language should be preselected? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGMYTASKS::T2345162613"] = "Which language should be preselected?" + +-- Preselect another language +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGMYTASKS::T2382415529"] = "Preselect another language" + +-- Preselect the language +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGMYTASKS::T2571465005"] = "Preselect the language" + +-- Close +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGMYTASKS::T3448155331"] = "Close" + +-- No options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGMYTASKS::T354528094"] = "No options are preselected" + +-- Assistant: My Tasks Options +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGMYTASKS::T3710380967"] = "Assistant: My Tasks Options" + +-- Options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGMYTASKS::T3875604319"] = "Options are preselected" + +-- Preselect one of your profiles? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGMYTASKS::T4004501229"] = "Preselect one of your profiles?" + +-- Preselect options? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGMYTASKS::T42672465"] = "Preselect options?" + +-- When enabled, you can preselect options. This is might be useful when you prefer a specific language or LLM model. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGMYTASKS::T711745239"] = "When enabled, you can preselect options. This is might be useful when you prefer a specific language or LLM model." + +-- Which writing style should be preselected? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGREWRITE::T1173034744"] = "Which writing style should be preselected?" + +-- Preselect the target language +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGREWRITE::T1417990312"] = "Preselect the target language" + +-- Preselect another target language +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGREWRITE::T1462295644"] = "Preselect another target language" + +-- Preselect a sentence structure +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGREWRITE::T1621537655"] = "Preselect a sentence structure" + +-- Assistant: Rewrite & Improve Text Options +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGREWRITE::T1995708818"] = "Assistant: Rewrite & Improve Text Options" + +-- Which voice should be preselected for the sentence structure? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGREWRITE::T2661599097"] = "Which voice should be preselected for the sentence structure?" + +-- Preselect a writing style +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGREWRITE::T28456020"] = "Preselect a writing style" + +-- Close +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGREWRITE::T3448155331"] = "Close" + +-- Which target language should be preselected? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGREWRITE::T3547337928"] = "Which target language should be preselected?" + +-- When enabled, you can preselect synonym options. This is might be useful when you prefer a specific language or LLM model. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSYNONYMS::T183953912"] = "When enabled, you can preselect synonym options. This is might be useful when you prefer a specific language or LLM model." + +-- No synonym options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSYNONYMS::T2183758387"] = "No synonym options are preselected" + +-- Which language should be preselected? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSYNONYMS::T2345162613"] = "Which language should be preselected?" + +-- Preselect another language +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSYNONYMS::T2382415529"] = "Preselect another language" + +-- Synonym options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSYNONYMS::T2390458990"] = "Synonym options are preselected" + +-- Preselect the language +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSYNONYMS::T2571465005"] = "Preselect the language" + +-- Close +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSYNONYMS::T3448155331"] = "Close" + +-- Assistant: Synonyms Options +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSYNONYMS::T3889117881"] = "Assistant: Synonyms Options" + +-- Preselect synonym options? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGSYNONYMS::T4170921846"] = "Preselect synonym options?" + +-- Content cleaner agent is preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTEXTSUMMARIZER::T1013787967"] = "Content cleaner agent is preselected" + +-- Web content reader is shown +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTEXTSUMMARIZER::T1030372436"] = "Web content reader is shown" + +-- Preselect the summarizer complexity +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTEXTSUMMARIZER::T104409170"] = "Preselect the summarizer complexity" + +-- Preselect summarizer options? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTEXTSUMMARIZER::T108151178"] = "Preselect summarizer options?" + +-- Preselect the target language +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTEXTSUMMARIZER::T1417990312"] = "Preselect the target language" + +-- Preselect another target language +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTEXTSUMMARIZER::T1462295644"] = "Preselect another target language" + +-- When activated, the web content reader is hidden and cannot be used. As a result, the user interface becomes a bit easier to use. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTEXTSUMMARIZER::T1633101895"] = "When activated, the web content reader is hidden and cannot be used. As a result, the user interface becomes a bit easier to use." + +-- Web content reader is not preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTEXTSUMMARIZER::T1701127912"] = "Web content reader is not preselected" + +-- Assistant: Text Summarizer Options +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTEXTSUMMARIZER::T1767527569"] = "Assistant: Text Summarizer Options" + +-- Content cleaner agent is not preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTEXTSUMMARIZER::T1969816694"] = "Content cleaner agent is not preselected" + +-- Hide the web content reader? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTEXTSUMMARIZER::T2090693677"] = "Hide the web content reader?" + +-- Summarizer options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTEXTSUMMARIZER::T2355441996"] = "Summarizer options are preselected" + +-- Web content reader is hidden +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTEXTSUMMARIZER::T2799795311"] = "Web content reader is hidden" + +-- No summarizer options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTEXTSUMMARIZER::T3215334223"] = "No summarizer options are preselected" + +-- When enabled, the web content reader is preselected. This is might be useful when you prefer to load content from the web very often. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTEXTSUMMARIZER::T3216157681"] = "When enabled, the web content reader is preselected. This is might be useful when you prefer to load content from the web very often." + +-- Close +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTEXTSUMMARIZER::T3448155331"] = "Close" + +-- Which target language should be preselected? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTEXTSUMMARIZER::T3547337928"] = "Which target language should be preselected?" + +-- Web content reader is preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTEXTSUMMARIZER::T3641773985"] = "Web content reader is preselected" + +-- Preselect the content cleaner agent? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTEXTSUMMARIZER::T3649428096"] = "Preselect the content cleaner agent?" + +-- When enabled, the content cleaner agent is preselected. This is might be useful when you prefer to clean up the content before summarize it. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTEXTSUMMARIZER::T3660434400"] = "When enabled, the content cleaner agent is preselected. This is might be useful when you prefer to clean up the content before summarize it." + +-- When enabled, you can preselect the text summarizer options. This is might be useful when you prefer a specific language, complexity, or LLM. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTEXTSUMMARIZER::T3820844575"] = "When enabled, you can preselect the text summarizer options. This is might be useful when you prefer a specific language, complexity, or LLM." + +-- Which summarizer complexity should be preselected? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTEXTSUMMARIZER::T408530182"] = "Which summarizer complexity should be preselected?" + +-- Preselect your expertise +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTEXTSUMMARIZER::T51139714"] = "Preselect your expertise" + +-- Preselect the web content reader? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTEXTSUMMARIZER::T629158142"] = "Preselect the web content reader?" + +-- Content cleaner agent is preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T1013787967"] = "Content cleaner agent is preselected" + +-- Assistant: Translator Options +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T1016384269"] = "Assistant: Translator Options" + +-- Web content reader is shown +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T1030372436"] = "Web content reader is shown" + +-- When enabled, you can preselect the translator options. This is might be useful when you prefer a specific target language or LLM model. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T1111006275"] = "When enabled, you can preselect the translator options. This is might be useful when you prefer a specific target language or LLM model." + +-- Preselect the target language +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T1417990312"] = "Preselect the target language" + +-- Preselect another target language +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T1462295644"] = "Preselect another target language" + +-- When activated, the web content reader is hidden and cannot be used. As a result, the user interface becomes a bit easier to use. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T1633101895"] = "When activated, the web content reader is hidden and cannot be used. As a result, the user interface becomes a bit easier to use." + +-- Web content reader is not preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T1701127912"] = "Web content reader is not preselected" + +-- Live translation is not preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T1825690873"] = "Live translation is not preselected" + +-- Content cleaner agent is not preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T1969816694"] = "Content cleaner agent is not preselected" + +-- Preselect translator options? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T1989346399"] = "Preselect translator options?" + +-- Hide the web content reader? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T2090693677"] = "Hide the web content reader?" + +-- Translator options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T2234531191"] = "Translator options are preselected" + +-- Live translation is preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T2435743076"] = "Live translation is preselected" + +-- Web content reader is hidden +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T2799795311"] = "Web content reader is hidden" + +-- No translator options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T2866358796"] = "No translator options are preselected" + +-- When enabled, the web content reader is preselected. This is might be useful when you prefer to load content from the web very often. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T3216157681"] = "When enabled, the web content reader is preselected. This is might be useful when you prefer to load content from the web very often." + +-- Close +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T3448155331"] = "Close" + +-- Which target language should be preselected? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T3547337928"] = "Which target language should be preselected?" + +-- Web content reader is preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T3641773985"] = "Web content reader is preselected" + +-- Preselect the content cleaner agent? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T3649428096"] = "Preselect the content cleaner agent?" + +-- Preselect the web content reader? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T629158142"] = "Preselect the web content reader?" + +-- How fast should the live translation react? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T884246296"] = "How fast should the live translation react?" + +-- When enabled, the content cleaner agent is preselected. This is might be useful when you prefer to clean up the content before translating it. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T894123480"] = "When enabled, the content cleaner agent is preselected. This is might be useful when you prefer to clean up the content before translating it." + +-- Preselect live translation? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T918172772"] = "Preselect live translation?" + +-- Which writing style should be preselected? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGWRITINGEMAILS::T1173034744"] = "Which writing style should be preselected?" + +-- Preselect a greeting? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGWRITINGEMAILS::T1254399201"] = "Preselect a greeting?" + +-- Preselect the target language +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGWRITINGEMAILS::T1417990312"] = "Preselect the target language" + +-- Preselect another target language +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGWRITINGEMAILS::T1462295644"] = "Preselect another target language" + +-- Assistant: Writing E-Mails Options +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGWRITINGEMAILS::T2021226503"] = "Assistant: Writing E-Mails Options" + +-- When enabled, you can preselect the e-mail options. This is might be useful when you prefer a specific language or LLM model. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGWRITINGEMAILS::T2116404483"] = "When enabled, you can preselect the e-mail options. This is might be useful when you prefer a specific language or LLM model." + +-- Preselect your name for the closing salutation? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGWRITINGEMAILS::T221974240"] = "Preselect your name for the closing salutation?" + +-- Would you like to preselect one of your profiles? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGWRITINGEMAILS::T2221665527"] = "Would you like to preselect one of your profiles?" + +-- Preselect a writing style +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGWRITINGEMAILS::T28456020"] = "Preselect a writing style" + +-- E-Mail options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGWRITINGEMAILS::T2985974420"] = "E-Mail options are preselected" + +-- No e-mail options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGWRITINGEMAILS::T3047605763"] = "No e-mail options are preselected" + +-- Close +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGWRITINGEMAILS::T3448155331"] = "Close" + +-- Which target language should be preselected? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGWRITINGEMAILS::T3547337928"] = "Which target language should be preselected?" + +-- Preselect e-mail options? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGWRITINGEMAILS::T3832719342"] = "Preselect e-mail options?" + +-- Preselect one of your profiles? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGWRITINGEMAILS::T4004501229"] = "Preselect one of your profiles?" + +-- Settings +UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T1258653480"] = "Settings" + +-- Home +UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T1391791790"] = "Home" + +-- About +UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T1491113694"] = "About" + +-- Are you sure you want to leave the chat page? All unsaved changes will be lost. +UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T1563130494"] = "Are you sure you want to leave the chat page? All unsaved changes will be lost." + +-- Assistants +UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T1614176092"] = "Assistants" + +-- Update +UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T1847791252"] = "Update" + +-- Leave Chat Page +UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T2124749705"] = "Leave Chat Page" + +-- Plugins +UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T2222816203"] = "Plugins" + +-- An update to version {0} is available. +UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T2800137365"] = "An update to version {0} is available." + +-- Please wait for the update to complete... +UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T2864211629"] = "Please wait for the update to complete..." + +-- Supporters +UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T2929332068"] = "Supporters" + +-- Writer +UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T2979224202"] = "Writer" + +-- Show details +UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T3692372066"] = "Show details" + +-- Chat +UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T578410699"] = "Chat" + +-- Startup log file +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T1019424746"] = "Startup log file" + +-- About MindWork AI Studio +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T1020427799"] = "About MindWork AI Studio" + +-- Browse AI Studio's source code on GitHub — we welcome your contributions. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T1107156991"] = "Browse AI Studio's source code on GitHub — we welcome your contributions." + +-- 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::ABOUT::T1388816916"] = "This library is used to read PDF files. This is necessary, e.g., for using PDFs as a data source for a chat." + +-- 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::ABOUT::T1421513382"] = "This library is used to extend the MudBlazor library. It provides additional components that are not part of the MudBlazor library." + +-- 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::ABOUT::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." + +-- Building on .NET, ASP.NET Core, and Blazor, MudBlazor is used as a library for designing and developing the user interface. It is a great project that significantly accelerates the development of advanced user interfaces with Blazor. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T1629800076"] = "Building on .NET, ASP.NET Core, and Blazor, MudBlazor is used as a library for designing and developing the user interface. It is a great project that significantly accelerates the development of advanced user interfaces with Blazor." + +-- AI Studio creates a log file at startup, in which events during startup are recorded. After startup, another log file is created that records all events that occur during the use of the app. This includes any errors that may occur. Depending on when an error occurs (at startup or during use), the contents of these log files can be helpful for troubleshooting. Sensitive information such as passwords is not included in the log files. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T1630237140"] = "AI Studio creates a log file at startup, in which events during startup are recorded. After startup, another log file is created that records all events that occur during the use of the app. This includes any errors that may occur. Depending on when an error occurs (at startup or during use), the contents of these log files can be helpful for troubleshooting. Sensitive information such as passwords is not included in the log files." + +-- This library is used to display the differences between two texts. This is necessary, e.g., for the grammar and spelling assistant. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T1772678682"] = "This library is used to display the differences between two texts. This is necessary, e.g., for the grammar and spelling assistant." + +-- By clicking on the respective path, the path is copied to the clipboard. You might open these files with a text editor to view their contents. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T1806897624"] = "By clicking on the respective path, the path is copied to the clipboard. You might open these files with a text editor to view their contents." + +-- Check for updates +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T1890416390"] = "Check for updates" + +-- Vision +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T1892426825"] = "Vision" + +-- 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::ABOUT::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." + +-- 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::ABOUT::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." + +-- This library is used to determine the file type of a file. This is necessary, e.g., when we want to stream a file. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T2173617769"] = "This library is used to determine the file type of a file. This is necessary, e.g., when we want to stream a file." + +-- For the secure communication between the user interface and the runtime, we need to create certificates. This Rust library is great for this purpose. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T2174764529"] = "For the secure communication between the user interface and the runtime, we need to create certificates. This Rust library is great for this purpose." + +-- 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::ABOUT::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." + +-- In order to use any LLM, each user must store their so-called token for each LLM provider. This token 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. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T228561878"] = "In order to use any LLM, each user must store their so-called token for each LLM provider. This token 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." + +-- The C# language is used for the implementation of the user interface and the backend. To implement the user interface with C#, the Blazor technology from ASP.NET Core is used. All these technologies are integrated into the .NET SDK. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T2329884315"] = "The C# language is used for the implementation of the user interface and the backend. To implement the user interface with C#, the Blazor technology from ASP.NET Core is used. All these technologies are integrated into the .NET SDK." + +-- This library is used to determine the language of the operating system. This is necessary to set the language of the user interface. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T2557014401"] = "This library is used to determine the language of the operating system. This is necessary to set the language of the user interface." + +-- Used Open Source Projects +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T2557066213"] = "Used Open Source Projects" + +-- Build time +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T260228112"] = "Build time" + +-- 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::ABOUT::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." + +-- Usage log file +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T2689995864"] = "Usage log file" + +-- Logbook +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T2706940196"] = "Logbook" + +-- This component is used to render Markdown text. This is important because the LLM often responds with Markdown-formatted text, allowing us to present it in a way that is easier to read. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T2726131107"] = "This component is used to render Markdown text. This is important because the LLM often responds with Markdown-formatted text, allowing us to present it in a way that is easier to read." + +-- 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. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T2777988282"] = "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." + +-- View our project roadmap and help shape AI Studio's future development. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T2829971158"] = "View our project roadmap and help shape AI Studio's future development." + +-- Used .NET runtime +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T2840227993"] = "Used .NET runtime" + +-- Explanation +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::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::ABOUT::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." + +-- Changelog +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T3017574265"] = "Changelog" + +-- Connect AI Studio to your organization's data with our External Retrieval Interface (ERI). +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T313276297"] = "Connect AI Studio to your organization's data with our External Retrieval Interface (ERI)." + +-- Have feature ideas? Submit suggestions for future AI Studio enhancements. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T3178730036"] = "Have feature ideas? Submit suggestions for future AI Studio enhancements." + +-- Discover MindWork AI's mission and vision on our official homepage. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T3294830584"] = "Discover MindWork AI's mission and vision on our official homepage." + +-- User-language provided by the OS +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T3334355246"] = "User-language provided by the OS" + +-- The following list shows the versions of the MindWork AI Studio, the used compilers, build time, etc.: +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T3405978777"] = "The following list shows the versions of the MindWork AI Studio, the used compilers, build time, etc.:" + +-- Used Rust compiler +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T3440211747"] = "Used Rust compiler" + +-- 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::ABOUT::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!" + +-- Motivation +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::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::ABOUT::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." + +-- 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. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T3908558992"] = "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." + +-- Versions +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T4010195468"] = "Versions" + +-- This library is used to create asynchronous streams in Rust. It allows us to work with streams of data that can be produced asynchronously, making it easier to handle events or data that arrive over time. We use this, e.g., to stream arbitrary data from the file system to the embedding system. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T4079152443"] = "This library is used to create asynchronous streams in Rust. It allows us to work with streams of data that can be produced asynchronously, making it easier to handle events or data that arrive over time. We use this, e.g., to stream arbitrary data from the file system to the embedding system." + +-- Community & Code +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T4158546761"] = "Community & Code" + +-- 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. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T4184485147"] = "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." + +-- When transferring sensitive data between Rust runtime and .NET app, we encrypt the data. We use some libraries from the Rust Crypto project for this purpose: cipher, aes, cbc, pbkdf2, hmac, and sha2. We are thankful for the great work of the Rust Crypto project. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T4229014037"] = "When transferring sensitive data between Rust runtime and .NET app, we encrypt the data. We use some libraries from the Rust Crypto project for this purpose: cipher, aes, cbc, pbkdf2, hmac, and sha2. We are thankful for the great work of the Rust Crypto project." + +-- This is a library providing the foundations for asynchronous programming in Rust. It includes key trait definitions like Stream, as well as utilities like join!, select!, and various futures combinator methods which enable expressive asynchronous control flow. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T566998575"] = "This is a library providing the foundations for asynchronous programming in Rust. It includes key trait definitions like Stream, as well as utilities like join!, select!, and various futures combinator methods which enable expressive asynchronous control flow." + +-- Used .NET SDK +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T585329785"] = "Used .NET SDK" + +-- Did you find a bug or are you experiencing issues? Report your concern here. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T639371534"] = "Did you find a bug or are you experiencing issues? Report your concern here." + +-- This Rust library is used to output the app's messages to the terminal. This is helpful during development and troubleshooting. This feature is initially invisible; when the app is started via the terminal, the messages become visible. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T64689067"] = "This Rust library is used to output the app's messages to the terminal. This is helpful during development and troubleshooting. This feature is initially invisible; when the app is started via the terminal, the messages become visible." + +-- For some data transfers, we need to encode the data in base64. This Rust library is great for this purpose. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T870640199"] = "For some data transfers, we need to encode the data in base64. This Rust library is great for this purpose." + +-- Get coding and debugging support from an LLM. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T1243850917"] = "Get coding and debugging support from an LLM." + +-- Legal Check +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T1348190638"] = "Legal Check" + +-- Coding +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T1617786407"] = "Coding" + +-- Analyze a text or an email for tasks you need to complete. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T1728590051"] = "Analyze a text or an email for tasks you need to complete." + +-- Text Summarizer +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T1907192403"] = "Text Summarizer" + +-- Check grammar and spelling of a given text. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T1934717573"] = "Check grammar and spelling of a given text." + +-- Translate text into another language. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T209791153"] = "Translate text into another language." + +-- Generate an e-mail for a given context. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T2383649630"] = "Generate an e-mail for a given context." + +-- Generate an agenda for a given meeting, seminar, etc. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T2406168562"] = "Generate an agenda for a given meeting, seminar, etc." + +-- Agenda Planner +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T2435638853"] = "Agenda Planner" + +-- Synonyms +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T2547582747"] = "Synonyms" + +-- Find synonyms for a given word or phrase. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T2712131461"] = "Find synonyms for a given word or phrase." + +-- Generate a job posting for a given job description. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T2831103254"] = "Generate a job posting for a given job description." + +-- My Tasks +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T3011450657"] = "My Tasks" + +-- Translate AI Studio text content into other languages +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T3181803840"] = "Translate AI Studio text content into other languages" + +-- Icon Finder +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T3693102312"] = "Icon Finder" + +-- Generate an ERI server to integrate business systems. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T3756213118"] = "Generate an ERI server to integrate business systems." + +-- Use an LLM to find an icon for a given context. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T3881504200"] = "Use an LLM to find an icon for a given context." + +-- Job Posting +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T3930052338"] = "Job Posting" + +-- Ask a question about a legal document. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T3970214537"] = "Ask a question about a legal document." + +-- ERI Server +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T4204533420"] = "ERI Server" + +-- Use an LLM to summarize a given text. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T502222021"] = "Use an LLM to summarize a given text." + +-- Translation +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T613888204"] = "Translation" + +-- Rewrite and improve a given text for a chosen style. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T722167136"] = "Rewrite and improve a given text for a chosen style." + +-- Bias of the Day +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T782102948"] = "Bias of the Day" + +-- Learn about one cognitive bias every day. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T878695986"] = "Learn about one cognitive bias every day." + +-- Localization +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T897888480"] = "Localization" + +-- Hide your workspaces +UI_TEXT_CONTENT["AISTUDIO::PAGES::CHAT::T2351468526"] = "Hide your workspaces" + +-- Disappearing Chat +UI_TEXT_CONTENT["AISTUDIO::PAGES::CHAT::T3046519404"] = "Disappearing Chat" + +-- Your workspaces +UI_TEXT_CONTENT["AISTUDIO::PAGES::CHAT::T3745240468"] = "Your workspaces" + +-- Chat in Workspace +UI_TEXT_CONTENT["AISTUDIO::PAGES::CHAT::T582100343"] = "Chat in Workspace" + +-- Unlike services like ChatGPT, which impose limits after intensive use, MindWork AI Studio offers unlimited usage through the providers API. +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T1009708591"] = "Unlike services like ChatGPT, which impose limits after intensive use, MindWork AI Studio offers unlimited usage through the providers API." + +-- Welcome to MindWork AI Studio! +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T1024253064"] = "Welcome to MindWork AI Studio!" + +-- Thank you for considering MindWork AI Studio for your AI needs. This app is designed to help you harness the power of Large Language Models (LLMs). Please note that this app doesn't come with an integrated LLM. Instead, you will need to bring an API key from a suitable provider. +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T1146553980"] = "Thank you for considering MindWork AI Studio for your AI needs. This app is designed to help you harness the power of Large Language Models (LLMs). Please note that this app doesn't come with an integrated LLM. Instead, you will need to bring an API key from a suitable provider." + +-- The app requires minimal storage for installation and operates with low memory usage. Additionally, it has a minimal impact on system resources, which is beneficial for battery life. +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T144565305"] = "The app requires minimal storage for installation and operates with low memory usage. Additionally, it has a minimal impact on system resources, which is beneficial for battery life." + +-- You only pay for what you use, which can be cheaper than monthly subscription services like ChatGPT Plus, especially if used infrequently. But beware, here be dragons: For extremely intensive usage, the API costs can be significantly higher. Unfortunately, providers currently do not offer a way to display current costs in the app. Therefore, check your account with the respective provider to see how your costs are developing. When available, use prepaid and set a cost limit. +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T149711988"] = "You only pay for what you use, which can be cheaper than monthly subscription services like ChatGPT Plus, especially if used infrequently. But beware, here be dragons: For extremely intensive usage, the API costs can be significantly higher. Unfortunately, providers currently do not offer a way to display current costs in the app. Therefore, check your account with the respective provider to see how your costs are developing. When available, use prepaid and set a cost limit." + +-- Assistants +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T1614176092"] = "Assistants" + +-- Unrestricted usage +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T1686815996"] = "Unrestricted usage" + +-- Vision +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T1892426825"] = "Vision" + +-- You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT4o, o1, etc.), Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), Hugging Face, and self-hosted models using 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. +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T2217921237"] = "You are not tied to any single provider. Instead, you might choose the provider that best suits your needs. Right now, we support OpenAI (GPT4o, o1, etc.), Mistral, Anthropic (Claude), Google Gemini, xAI (Grok), DeepSeek, Alibaba Cloud (Qwen), Hugging Face, and self-hosted models using 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." + +-- Let's get started +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T2331588413"] = "Let's get started" + +-- Last Changelog +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T2348849647"] = "Last Changelog" + +-- Choose the provider and model best suited for your current task. +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T2588488920"] = "Choose the provider and model best suited for your current task." + +-- Quick Start Guide +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T3002014720"] = "Quick Start Guide" + +-- 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. +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T3228075421"] = "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." + +-- We hope you enjoy using MindWork AI Studio to bring your AI projects to life! +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T3275341342"] = "We hope you enjoy using MindWork AI Studio to bring your AI projects to life!" + +-- Cost-effective +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T3341379752"] = "Cost-effective" + +-- Flexibility +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T3723223888"] = "Flexibility" + +-- Privacy +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T3959064551"] = "Privacy" + +-- You can control which providers receive your data using the provider confidence settings. For example, you can set different protection levels for writing emails compared to general chats, etc. Additionally, most providers guarantee that they won't use your data to train new AI systems. +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T457410099"] = "You can control which providers receive your data using the provider confidence settings. For example, you can set different protection levels for writing emails compared to general chats, etc. Additionally, most providers guarantee that they won't use your data to train new AI systems." + +-- Free of charge +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T617579208"] = "Free of charge" + +-- Independence +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T649448159"] = "Independence" + +-- No bloatware +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T858047957"] = "No bloatware" + +-- Here's what makes MindWork AI Studio stand out: +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T873851215"] = "Here's what makes MindWork AI Studio stand out:" + +-- The app is free to use, both for personal and commercial purposes. +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T91074375"] = "The app is free to use, both for personal and commercial purposes." + +-- Disable plugin +UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T1430375822"] = "Disable plugin" + +-- Internal Plugins +UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T158493184"] = "Internal Plugins" + +-- Disabled Plugins +UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T1724138133"] = "Disabled Plugins" + +-- Enable plugin +UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T2057806005"] = "Enable plugin" + +-- Plugins +UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T2222816203"] = "Plugins" + +-- Enabled Plugins +UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T2738444034"] = "Enabled Plugins" + +-- Actions +UI_TEXT_CONTENT["AISTUDIO::PAGES::PLUGINS::T3865031940"] = "Actions" + +-- Settings +UI_TEXT_CONTENT["AISTUDIO::PAGES::SETTINGS::T1258653480"] = "Settings" + +-- Thank you for being the first to contribute a one-time donation. +UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T1470916504"] = "Thank you for being the first to contribute a one-time donation." + +-- Thank you, Peer, for your courage in being the second person to support the project financially. +UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T1714878838"] = "Thank you, Peer, for your courage in being the second person to support the project financially." + +-- Individual Contributors +UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T1874835680"] = "Individual Contributors" + +-- Thanks, Nils, for taking the time to learn Rust and build the foundation for local retrieval. +UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T2355807535"] = "Thanks, Nils, for taking the time to learn Rust and build the foundation for local retrieval." + +-- The first 10 supporters who make a one-time contribution: +UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T2410456125"] = "The first 10 supporters who make a one-time contribution:" + +-- Supporters +UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T2929332068"] = "Supporters" + +-- Financial Support +UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T3061261435"] = "Financial Support" + +-- The first 10 supporters who make a monthly contribution: +UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T3364384944"] = "The first 10 supporters who make a monthly contribution:" + +-- Thank you, Richard, for being the first. +UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T3660718138"] = "Thank you, Richard, for being the first." + +-- Thanks Dominic for being the third supporter. +UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T3664780201"] = "Thanks Dominic for being the third supporter." + +-- Our Titans +UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T3805270964"] = "Our Titans" + +-- Moderation, Design, Wiki, and Documentation +UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T3821668394"] = "Moderation, Design, Wiki, and Documentation" + +-- Thank you, Peer, for familiarizing yourself with C#, providing excellent contributions like the Alibaba and Hugging Face providers, and revising the settings management. +UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T4106820759"] = "Thank you, Peer, for familiarizing yourself with C#, providing excellent contributions like the Alibaba and Hugging Face providers, and revising the settings management." + +-- Code Contributions +UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T4135925647"] = "Code Contributions" + +-- Become our first Titan +UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T414428338"] = "Become our first Titan" + +-- Become a contributor +UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T414604046"] = "Become a contributor" + +-- In this section, we highlight the titan supporters of MindWork AI Studio. Titans are prestigious companies that provide significant support to our mission. +UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T4270177642"] = "In this section, we highlight the titan supporters of MindWork AI Studio. Titans are prestigious companies that provide significant support to our mission." + +-- Thanks Luc for your build script contribution. +UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T432023389"] = "Thanks Luc for your build script contribution." + +-- For companies, sponsoring MindWork AI Studio is not only a way to support innovation but also a valuable opportunity for public relations and marketing. Your company's name and logo will be featured prominently, showcasing your commitment to using cutting-edge AI tools and enhancing your reputation as an innovative enterprise. +UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T68519158"] = "For companies, sponsoring MindWork AI Studio is not only a way to support innovation but also a valuable opportunity for public relations and marketing. Your company's name and logo will be featured prominently, showcasing your commitment to using cutting-edge AI tools and enhancing your reputation as an innovative enterprise." + +-- Thanks for your build script contribution. +UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T686206269"] = "Thanks for your build script contribution." + +-- Business Contributors +UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T838479287"] = "Business Contributors" + +-- Thank you very much, Kerstin, for taking care of creating the Wiki. +UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T991294232"] = "Thank you very much, Kerstin, for taking care of creating the Wiki." + +-- Write your text +UI_TEXT_CONTENT["AISTUDIO::PAGES::WRITER::T2220943334"] = "Write your text" + +-- Writer +UI_TEXT_CONTENT["AISTUDIO::PAGES::WRITER::T2979224202"] = "Writer" + +-- Suggestion +UI_TEXT_CONTENT["AISTUDIO::PAGES::WRITER::T3948127789"] = "Suggestion" + +-- Your stage directions +UI_TEXT_CONTENT["AISTUDIO::PAGES::WRITER::T779923726"] = "Your stage directions" diff --git a/app/MindWork AI Studio/Provider/Confidence.cs b/app/MindWork AI Studio/Provider/Confidence.cs index a49c2781..a63299e1 100644 --- a/app/MindWork AI Studio/Provider/Confidence.cs +++ b/app/MindWork AI Studio/Provider/Confidence.cs @@ -29,10 +29,7 @@ public sealed record Confidence public static readonly Confidence NONE = new() { Level = ConfidenceLevel.NONE, - Description = - """ - No provider selected. Please select a provider to get see its confidence level. - """, + Description = "No provider selected. Please select a provider to get see its confidence level.", }; public static readonly Confidence USA_HUB = new() diff --git a/app/MindWork AI Studio/Settings/SettingsManager.cs b/app/MindWork AI Studio/Settings/SettingsManager.cs index 1f1b543d..2984b94b 100644 --- a/app/MindWork AI Studio/Settings/SettingsManager.cs +++ b/app/MindWork AI Studio/Settings/SettingsManager.cs @@ -163,11 +163,17 @@ public sealed class SettingsManager(ILogger logger, RustService var languageCode = await this.rustService.ReadUserLanguage(); var languagePlugin = PluginFactory.RunningPlugins.FirstOrDefault(x => x is ILanguagePlugin langPlug && langPlug.IETFTag == languageCode); if (languagePlugin is null) + { + this.logger.LogWarning($"The language plugin for the language '{languageCode}' is not available."); return PluginFactory.BaseLanguage; - + } + if (languagePlugin is ILanguagePlugin langPlugin) + { + this.logger.LogDebug($"The used language plugin is {languagePlugin.Id} ({langPlugin.IETFTag})"); return langPlugin; - + } + this.logger.LogError("The language plugin is not a language plugin."); return PluginFactory.BaseLanguage; @@ -181,8 +187,11 @@ public sealed class SettingsManager(ILogger logger, RustService } if (plugin is ILanguagePlugin chosenLangPlugin) + { + this.logger.LogDebug($"The chosen language plugin is {plugin.Id} ({chosenLangPlugin.IETFTag})"); return chosenLangPlugin; - + } + this.logger.LogError("The chosen language plugin is not a language plugin."); return PluginFactory.BaseLanguage; } diff --git a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs index fd9329cc..1aedded6 100644 --- a/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs +++ b/app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs @@ -24,8 +24,7 @@ public static partial class PluginFactory /// - Check for forbidden plugins
/// - Creating a new instance of the allowed plugin
/// - Read the plugin metadata
- ///
- /// Loading a plugin does not mean to start the plugin, though. + /// - Start the plugin
/// public static async Task LoadAll(CancellationToken cancellationToken = default) { From 47b6a896851527e280e553c9a06b587b3d1cdd46 Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Sun, 27 Apr 2025 16:13:15 +0200 Subject: [PATCH 37/93] Add German localization (#430) --- README.md | 2 +- app/Build/Commands/CollectI18NKeysCommand.cs | 34 +- app/MindWork AI Studio.sln.DotSettings | 1 + .../Assistants/AssistantBase.razor | 10 +- .../Assistants/AssistantBase.razor.cs | 5 +- .../Assistants/I18N/AssistantI18N.razor.cs | 22 +- .../Assistants/I18N/allTexts.lua | 261 ++ .../Components/ChatComponent.razor | 8 +- .../Components/DataSourceSelection.razor | 55 +- .../Components/DataSourceSelection.razor.cs | 38 +- .../Components/MSGComponentBase.cs | 31 +- .../Components/MudTextList.razor | 4 +- .../Components/Vision.razor.cs | 27 +- .../Components/Workspaces.razor.cs | 18 +- .../Dialogs/ConfirmDialog.razor | 13 +- .../Dialogs/ConfirmDialog.razor.cs | 4 +- .../Dialogs/ProfileDialog.razor | 32 +- .../Dialogs/ProfileDialog.razor.cs | 20 +- .../Dialogs/ProviderDialog.razor | 40 +- .../Dialogs/ProviderDialog.razor.cs | 17 +- .../Dialogs/Settings/SettingsDialogBase.cs | 4 +- .../SettingsDialogDataSources.razor.cs | 4 +- .../Settings/SettingsDialogTranslation.razor | 2 +- .../Dialogs/SingleInputDialog.razor | 15 +- .../Dialogs/SingleInputDialog.razor.cs | 7 +- .../Dialogs/UpdateDialog.razor | 11 +- .../Dialogs/UpdateDialog.razor.cs | 7 +- .../Dialogs/WorkspaceSelectionDialog.razor | 13 +- .../Dialogs/WorkspaceSelectionDialog.razor.cs | 3 +- .../Layout/MainLayout.razor.cs | 4 + app/MindWork AI Studio/Pages/Assistants.razor | 18 +- app/MindWork AI Studio/Pages/Chat.razor | 2 +- app/MindWork AI Studio/Pages/Home.razor | 2 +- app/MindWork AI Studio/Pages/Home.razor.cs | 40 +- app/MindWork AI Studio/Pages/Supporters.razor | 2 +- .../contentHome.lua | 3 - .../plugin.lua | 2485 ++++++++++++++++- .../plugin.lua | 261 ++ .../Settings/SettingsManager.cs | 6 - .../Tools/PluginSystem/ILang.cs | 20 + .../Tools/PluginSystem/ILangExtensions.cs | 11 +- .../PluginSystem/PluginFactory.HotReload.cs | 18 +- .../PluginSystem/PluginFactory.Loading.cs | 75 +- .../wwwroot/changelog/v0.9.41.md | 1 + 44 files changed, 3394 insertions(+), 262 deletions(-) delete mode 100644 app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/contentHome.lua diff --git a/README.md b/README.md index d1955382..bcec5001 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Things we are currently working on: - [x] ~~Added hot-reload support for plugins ([PR #377](https://github.com/MindWorkAI/AI-Studio/pull/377), [PR #391](https://github.com/MindWorkAI/AI-Studio/pull/391))~~ - [ ] Add support for other languages (I18N) to AI Studio (~~[PR #381](https://github.com/MindWorkAI/AI-Studio/pull/381), [PR #400](https://github.com/MindWorkAI/AI-Studio/pull/400), [PR #404](https://github.com/MindWorkAI/AI-Studio/pull/404), [PR #429](https://github.com/MindWorkAI/AI-Studio/pull/429))~~ - [x] ~~Add an I18N assistant to translate all AI Studio texts to a certain language & culture ([PR #422](https://github.com/MindWorkAI/AI-Studio/pull/422))~~ - - [ ] Provide MindWork AI Studio in German ([#31](https://github.com/MindWorkAI/Planning/issues/31)) + - [ ] Provide MindWork AI Studio in German ([PR #430](https://github.com/MindWorkAI/AI-Studio/pull/430)) - [ ] Add configuration plugins, which allow pre-defining some LLM providers in organizations - [ ] Add an app store for plugins, showcasing community-contributed plugins from public GitHub and GitLab repositories. This will enable AI Studio users to discover, install, and update plugins directly within the platform. - [ ] Add assistant plugins diff --git a/app/Build/Commands/CollectI18NKeysCommand.cs b/app/Build/Commands/CollectI18NKeysCommand.cs index f7a533f9..3108a488 100644 --- a/app/Build/Commands/CollectI18NKeysCommand.cs +++ b/app/Build/Commands/CollectI18NKeysCommand.cs @@ -133,24 +133,48 @@ public sealed partial class CollectI18NKeysCommand private List FindAllTextTags(ReadOnlySpan fileContent) { - const string START_TAG = """ + const string START_TAG1 = """ T(" """; + const string START_TAG2 = """ + TB(" + """; + const string END_TAG = """ ") """; + (int Index, int Len) FindNextStart(ReadOnlySpan content) + { + var startIdx1 = content.IndexOf(START_TAG1); + var startIdx2 = content.IndexOf(START_TAG2); + + if (startIdx1 == -1 && startIdx2 == -1) + return (-1, 0); + + if (startIdx1 == -1) + return (startIdx2, START_TAG2.Length); + + if (startIdx2 == -1) + return (startIdx1, START_TAG1.Length); + + if (startIdx1 < startIdx2) + return (startIdx1, START_TAG1.Length); + + return (startIdx2, START_TAG2.Length); + } + var matches = new List(); - var startIdx = fileContent.IndexOf(START_TAG); + var startIdx = FindNextStart(fileContent); var content = fileContent; - while (startIdx > -1) + while (startIdx.Index > -1) { // // In some cases, after the initial " there follow more " characters. // We need to skip them: // - content = content[(startIdx + START_TAG.Length)..]; + content = content[(startIdx.Index + startIdx.Len)..]; while(content[0] == '"') content = content[1..]; @@ -163,7 +187,7 @@ public sealed partial class CollectI18NKeysCommand match = match[..^1]; matches.Add(match.ToString()); - startIdx = content.IndexOf(START_TAG); + startIdx = FindNextStart(content); } return matches; diff --git a/app/MindWork AI Studio.sln.DotSettings b/app/MindWork AI Studio.sln.DotSettings index f8edfd31..3eb9acc5 100644 --- a/app/MindWork AI Studio.sln.DotSettings +++ b/app/MindWork AI Studio.sln.DotSettings @@ -11,6 +11,7 @@ OS RAG RID + TB UI URL I18N diff --git a/app/MindWork AI Studio/Assistants/AssistantBase.razor b/app/MindWork AI Studio/Assistants/AssistantBase.razor index c27704c4..47714c22 100644 --- a/app/MindWork AI Studio/Assistants/AssistantBase.razor +++ b/app/MindWork AI Studio/Assistants/AssistantBase.razor @@ -34,7 +34,7 @@ @if (this.isProcessing && this.cancellationTokenSource is not null) { - + } @@ -80,7 +80,7 @@ { @if (this.ShowSendTo) { - + @foreach (var assistant in Enum.GetValues().Where(n => n.AllowSendTo()).OrderBy(n => n.Name().Length)) { @@ -110,7 +110,7 @@ break; case SendToButton sendToButton: - + @foreach (var assistant in Enum.GetValues().Where(n => n.AllowSendTo()).OrderBy(n => n.Name().Length)) { @@ -125,14 +125,14 @@ @if (this.ShowCopyResult) { - Copy result + @TB("Copy result") } @if (this.ShowReset) { - Reset + @TB("Reset") } diff --git a/app/MindWork AI Studio/Assistants/AssistantBase.razor.cs b/app/MindWork AI Studio/Assistants/AssistantBase.razor.cs index 877374ad..7abc5824 100644 --- a/app/MindWork AI Studio/Assistants/AssistantBase.razor.cs +++ b/app/MindWork AI Studio/Assistants/AssistantBase.razor.cs @@ -103,6 +103,8 @@ public abstract partial class AssistantBase : AssistantLowerBase wher protected override async Task OnInitializedAsync() { + await base.OnInitializedAsync(); + this.formChangeTimer.AutoReset = false; this.formChangeTimer.Elapsed += async (_, _) => { @@ -113,7 +115,6 @@ public abstract partial class AssistantBase : AssistantLowerBase wher this.MightPreselectValues(); this.providerSettings = this.SettingsManager.GetPreselectedProvider(this.Component); this.currentProfile = this.SettingsManager.GetPreselectedProfile(this.Component); - await base.OnInitializedAsync(); } protected override async Task OnParametersSetAsync() @@ -136,6 +137,8 @@ public abstract partial class AssistantBase : AssistantLowerBase wher #endregion + private string TB(string fallbackEN) => this.T(fallbackEN, typeof(AssistantBase).Namespace, nameof(AssistantBase)); + private string SubmitButtonStyle => this.SettingsManager.ConfigurationData.LLMProviders.ShowProviderConfidence ? this.providerSettings.UsedLLMProvider.GetConfidence(this.SettingsManager).StyleBorder(this.SettingsManager) : string.Empty; protected string? ValidatingProvider(AIStudio.Settings.Provider provider) diff --git a/app/MindWork AI Studio/Assistants/I18N/AssistantI18N.razor.cs b/app/MindWork AI Studio/Assistants/I18N/AssistantI18N.razor.cs index ee3a94cf..93284c3f 100644 --- a/app/MindWork AI Studio/Assistants/I18N/AssistantI18N.razor.cs +++ b/app/MindWork AI Studio/Assistants/I18N/AssistantI18N.razor.cs @@ -306,8 +306,23 @@ public partial class AssistantI18N : AssistantBaseCore if(this.cancellationTokenSource!.IsCancellationRequested) return; - // Phase 2: Create the Lua code - this.Phase2CreateLuaCode(); + // + // Phase 2: Create the Lua code. We want to use the base language + // for the comments, though: + // + var commentContent = new Dictionary(this.addedContent); + foreach (var keyValuePair in PluginFactory.BaseLanguage.Content) + { + if (this.cancellationTokenSource!.IsCancellationRequested) + break; + + if (this.removedContent.ContainsKey(keyValuePair.Key)) + continue; + + commentContent.TryAdd(keyValuePair.Key, keyValuePair.Value); + } + + this.Phase2CreateLuaCode(commentContent); } private async Task Phase1TranslateAddedContent() @@ -346,10 +361,9 @@ public partial class AssistantI18N : AssistantBaseCore } } - private void Phase2CreateLuaCode() + private void Phase2CreateLuaCode(IReadOnlyDictionary commentContent) { this.finalLuaCode.Clear(); - var commentContent = this.addedContent.Concat(PluginFactory.BaseLanguage.Content).ToDictionary(); LuaTable.Create(ref this.finalLuaCode, "UI_TEXT_CONTENT", this.localizedContent, commentContent, this.cancellationTokenSource!.Token); // Next, we must remove the `root::` prefix from the keys: diff --git a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua index 4791d157..f23e9219 100644 --- a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua +++ b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua @@ -46,9 +46,21 @@ LANG_NAME = "English (United States)" UI_TEXT_CONTENT = {} +-- Stop generation +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::ASSISTANTBASE::T1317408357"] = "Stop generation" + +-- Reset +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::ASSISTANTBASE::T180921696"] = "Reset" + -- Assistant - {0} UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::ASSISTANTBASE::T3043922"] = "Assistant - {0}" +-- Send to ... +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::ASSISTANTBASE::T4242312602"] = "Send to ..." + +-- Copy result +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::ASSISTANTBASE::T83711157"] = "Copy result" + -- 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." @@ -397,6 +409,12 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1133040906"] = "Move chat -- Are you sure you want to move this chat? All unsaved changes will be lost. UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1142475422"] = "Are you sure you want to move this chat? All unsaved changes will be lost." +-- Stop generation +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1317408357"] = "Stop generation" + +-- Save chat +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1516264254"] = "Save chat" + -- Type your input here... UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T1849313532"] = "Type your input here..." @@ -415,12 +433,18 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T3403290862"] = "The selec -- Select a provider first UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T3654197869"] = "Select a provider first" +-- Start temporary chat +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T4113970938"] = "Start temporary chat" + -- Please select the workspace where you want to move the chat to. UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T474393241"] = "Please select the workspace where you want to move the chat to." -- Move the chat to a workspace, or to another if it is already in one. UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T636393754"] = "Move the chat to a workspace, or to another if it is already in one." +-- Show your workspaces +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHATCOMPONENT::T733672375"] = "Show your workspaces" + -- Region UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIDENCEINFO::T1227782301"] = "Region" @@ -463,6 +487,69 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONPROVIDERSELECTION::T14699849 -- Use app default UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONPROVIDERSELECTION::T3672477670"] = "Use app default" +-- 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." + +-- Yes, let the AI validate & filter the retrieved data. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCESELECTION::T1309929755"] = "Yes, let the AI validate & filter the retrieved data." + +-- Data Source Selection +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCESELECTION::T15302104"] = "Data Source Selection" + +-- AI-Selected Data Sources +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCESELECTION::T168406579"] = "AI-Selected Data Sources" + +-- AI-based data validation +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCESELECTION::T1744745490"] = "AI-based data validation" + +-- Yes, I want to use data sources. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCESELECTION::T1975014927"] = "Yes, I want to use data sources." + +-- You haven't configured any data sources. To grant the AI access to your data, you need to add such a source. However, if you wish to use data from your device, you first have to set up a so-called embedding. This embedding is necessary so the AI can effectively search your data, find and retrieve the correct information required for each task. In addition to local data, you can also incorporate your company's data. To do so, your company must provide the data through an ERI (External Retrieval Interface). +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCESELECTION::T2113594442"] = "You haven't configured any data sources. To grant the AI access to your data, you need to add such a source. However, if you wish to use data from your device, you first have to set up a so-called embedding. This embedding is necessary so the AI can effectively search your data, find and retrieve the correct information required for each task. In addition to local data, you can also incorporate your company's data. To do so, your company must provide the data through an ERI (External Retrieval Interface)." + +-- Select the data you want to use here. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCESELECTION::T21181525"] = "Select the data you want to use here." + +-- Manage your data sources +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCESELECTION::T2149927097"] = "Manage your data sources" + +-- Select data +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCESELECTION::T274155039"] = "Select data" + +-- Read more about ERI +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCESELECTION::T3095532189"] = "Read more about ERI" + +-- AI-based data source selection +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCESELECTION::T3100256862"] = "AI-based data source selection" + +-- No, I don't want to use data sources. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCESELECTION::T3135725655"] = "No, I don't want to use data sources." + +-- Your data sources cannot be used with the LLM provider you selected due to data privacy, or they are currently unavailable. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCESELECTION::T3215374102"] = "Your data sources cannot be used with the LLM provider you selected due to data privacy, or they are currently unavailable." + +-- No, I manually decide which data source to use. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCESELECTION::T3440789294"] = "No, I manually decide which data source to use." + +-- Close +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCESELECTION::T3448155331"] = "Close" + +-- The AI evaluates each of your inputs to determine whether and which data sources are necessary. Currently, the AI has not selected any source. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCESELECTION::T3574254516"] = "The AI evaluates each of your inputs to determine whether and which data sources are necessary. Currently, the AI has not selected any source." + +-- No, use all data retrieved from the data sources. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCESELECTION::T3751463241"] = "No, use all data retrieved from the data sources." + +-- Are data sources enabled? +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCESELECTION::T396683085"] = "Are data sources enabled?" + +-- Manage Data Sources +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCESELECTION::T700666808"] = "Manage Data Sources" + +-- Available Data Sources +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCESELECTION::T86053874"] = "Available Data Sources" + -- Issues UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ISSUES::T3229841001"] = "Issues" @@ -1042,6 +1129,117 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T474393241"] = "Please select -- Delete Workspace UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T701874671"] = "Delete Workspace" +-- No +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CONFIRMDIALOG::T1642511898"] = "No" + +-- Yes +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CONFIRMDIALOG::T3013883440"] = "Yes" + +-- Tell the AI what you want it to do for you. What are your goals or are you trying to achieve? Like having the AI address you informally. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROFILEDIALOG::T1458195391"] = "Tell the AI what you want it to do for you. What are your goals or are you trying to achieve? Like having the AI address you informally." + +-- Update +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROFILEDIALOG::T1847791252"] = "Update" + +-- Tell the AI something about yourself. What is your profession? How experienced are you in this profession? Which technologies do you like? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROFILEDIALOG::T2119274961"] = "Tell the AI something about yourself. What is your profession? How experienced are you in this profession? Which technologies do you like?" + +-- What should the AI do for you? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROFILEDIALOG::T2261456575"] = "What should the AI do for you?" + +-- Please enter a profile name. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROFILEDIALOG::T2386844536"] = "Please enter a profile name." + +-- The text must not exceed 256 characters. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROFILEDIALOG::T2560188276"] = "The text must not exceed 256 characters." + +-- Add +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROFILEDIALOG::T2646845972"] = "Add" + +-- The profile name must not exceed 40 characters. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROFILEDIALOG::T3243902394"] = "The profile name must not exceed 40 characters." + +-- The text must not exceed 444 characters. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROFILEDIALOG::T3253349421"] = "The text must not exceed 444 characters." + +-- Profile Name +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROFILEDIALOG::T3392578705"] = "Profile Name" + +-- Please enter what the LLM should know about you and/or what actions it should take. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROFILEDIALOG::T3708405102"] = "Please enter what the LLM should know about you and/or what actions it should take." + +-- The name of the profile is mandatory. Each profile must have a unique name. Whether you provide information about yourself or only fill out the actions is up to you. Only one of these pieces is required. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROFILEDIALOG::T4061896123"] = "The name of the profile is mandatory. Each profile must have a unique name. Whether you provide information about yourself or only fill out the actions is up to you. Only one of these pieces is required." + +-- Store personal data about yourself in various profiles so that the AIs know your personal context. This saves you from having to explain your context each time, for example, in every chat. When you have different roles, you can create a profile for each role. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROFILEDIALOG::T4125557797"] = "Store personal data about yourself in various profiles so that the AIs know your personal context. This saves you from having to explain your context each time, for example, in every chat. When you have different roles, you can create a profile for each role." + +-- What should the AI know about you? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROFILEDIALOG::T4227846635"] = "What should the AI know about you?" + +-- Are you a project manager in a research facility? You might want to create a profile for your project management activities, one for your scientific work, and a profile for when you need to write program code. In these profiles, you can record how much experience you have or which methods you like or dislike using. Later, you can choose when and where you want to use each profile. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROFILEDIALOG::T56359901"] = "Are you a project manager in a research facility? You might want to create a profile for your project management activities, one for your scientific work, and a profile for when you need to write program code. In these profiles, you can record how much experience you have or which methods you like or dislike using. Later, you can choose when and where you want to use each profile." + +-- Cancel +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROFILEDIALOG::T900713019"] = "Cancel" + +-- The profile name must be unique; the chosen name is already in use. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROFILEDIALOG::T911748898"] = "The profile name must be unique; the chosen name is already in use." + +-- Hugging Face Inference Provider +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1085481431"] = "Hugging Face Inference Provider" + +-- Failed to store the API key in the operating system. The message was: {0}. Please try again. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1122745046"] = "Failed to store the API key in the operating system. The message was: {0}. Please try again." + +-- API Key +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1324664716"] = "API Key" + +-- Create account +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1356621346"] = "Create account" + +-- Load models +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T15352225"] = "Load models" + +-- Hostname +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1727440780"] = "Hostname" + +-- Update +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1847791252"] = "Update" + +-- Failed to load the API key from the operating system. The message was: {0}. You might ignore this message and provide the API key again. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1870831108"] = "Failed to load the API key from the operating system. The message was: {0}. You might ignore this message and provide the API key again." + +-- Please enter a model name. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T1936099896"] = "Please enter a model name." + +-- Model +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T2189814010"] = "Model" + +-- (Optional) API Key +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T2331453405"] = "(Optional) API Key" + +-- Add +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T2646845972"] = "Add" + +-- No models loaded or available. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T2810182573"] = "No models loaded or available." + +-- Instance Name +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T2842060373"] = "Instance Name" + +-- Show available models +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3763891899"] = "Show available models" + +-- Host +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T808120719"] = "Host" + +-- Provider +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T900237532"] = "Provider" + +-- Cancel +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T900713019"] = "Cancel" + -- There is no social event UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGAGENDA::T1222800281"] = "There is no social event" @@ -1270,6 +1468,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T145419 -- Delete UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T1469573738"] = "Delete" +-- External (ERI) +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T1652430727"] = "External (ERI)" + -- Local File UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T1687345358"] = "Local File" @@ -1291,6 +1492,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T262437 -- Name UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T266367750"] = "Name" +-- No valid embedding +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T2698203405"] = "No valid embedding" + -- Embedding UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T2838542994"] = "Embedding" @@ -1684,6 +1888,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T103037 -- When enabled, you can preselect the translator options. This is might be useful when you prefer a specific target language or LLM model. UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T1111006275"] = "When enabled, you can preselect the translator options. This is might be useful when you prefer a specific target language or LLM model." +-- milliseconds +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T1275514075"] = "milliseconds" + -- Preselect the target language UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGTRANSLATION::T1417990312"] = "Preselect the target language" @@ -1792,6 +1999,24 @@ 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?" +-- Chat name +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SINGLEINPUTDIALOG::T1746586282"] = "Chat name" + +-- Cancel +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SINGLEINPUTDIALOG::T900713019"] = "Cancel" + +-- Install now +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::UPDATEDIALOG::T2366359512"] = "Install now" + +-- Update from v{0} to v{1} +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::UPDATEDIALOG::T25417398"] = "Update from v{0} to v{1}" + +-- Install later +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::UPDATEDIALOG::T2936430090"] = "Install later" + +-- Cancel +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::WORKSPACESELECTIONDIALOG::T900713019"] = "Cancel" + -- Settings UI_TEXT_CONTENT["AISTUDIO::LAYOUT::MAINLAYOUT::T1258653480"] = "Settings" @@ -1993,9 +2218,21 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T870640199"] = "For some data transfers -- Get coding and debugging support from an LLM. UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T1243850917"] = "Get coding and debugging support from an LLM." +-- Business +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T131837803"] = "Business" + -- Legal Check UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T1348190638"] = "Legal Check" +-- General +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T1432485131"] = "General" + +-- Grammar & Spelling +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T1514925962"] = "Grammar & Spelling" + +-- Assistants +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T1614176092"] = "Assistants" + -- Coding UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T1617786407"] = "Coding" @@ -2026,15 +2263,27 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T2547582747"] = "Synonyms" -- Find synonyms for a given word or phrase. UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T2712131461"] = "Find synonyms for a given word or phrase." +-- AI Studio Development +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T2830810750"] = "AI Studio Development" + -- Generate a job posting for a given job description. UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T2831103254"] = "Generate a job posting for a given job description." -- My Tasks UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T3011450657"] = "My Tasks" +-- E-Mail +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T3026443472"] = "E-Mail" + -- Translate AI Studio text content into other languages UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T3181803840"] = "Translate AI Studio text content into other languages" +-- Software Engineering +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T3260960011"] = "Software Engineering" + +-- Rewrite & Improve +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T3309133329"] = "Rewrite & Improve" + -- Icon Finder UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T3693102312"] = "Icon Finder" @@ -2062,6 +2311,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T613888204"] = "Translation" -- Rewrite and improve a given text for a chosen style. UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T722167136"] = "Rewrite and improve a given text for a chosen style." +-- Learning +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T755590027"] = "Learning" + -- Bias of the Day UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T782102948"] = "Bias of the Day" @@ -2083,6 +2335,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::CHAT::T3745240468"] = "Your workspaces" -- Chat in Workspace UI_TEXT_CONTENT["AISTUDIO::PAGES::CHAT::T582100343"] = "Chat in Workspace" +-- Show your workspaces +UI_TEXT_CONTENT["AISTUDIO::PAGES::CHAT::T733672375"] = "Show your workspaces" + -- Unlike services like ChatGPT, which impose limits after intensive use, MindWork AI Studio offers unlimited usage through the providers API. UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T1009708591"] = "Unlike services like ChatGPT, which impose limits after intensive use, MindWork AI Studio offers unlimited usage through the providers API." @@ -2104,6 +2359,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T1614176092"] = "Assistants" -- Unrestricted usage UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T1686815996"] = "Unrestricted usage" +-- Introduction +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T1702902297"] = "Introduction" + -- Vision UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T1892426825"] = "Vision" @@ -2197,6 +2455,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T2410456125"] = "The first 10 supp -- Supporters UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T2929332068"] = "Supporters" +-- Content Contributors +UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T3060804484"] = "Content Contributors" + -- Financial Support UI_TEXT_CONTENT["AISTUDIO::PAGES::SUPPORTERS::T3061261435"] = "Financial Support" diff --git a/app/MindWork AI Studio/Components/ChatComponent.razor b/app/MindWork AI Studio/Components/ChatComponent.razor index fbcf1828..8973f9ac 100644 --- a/app/MindWork AI Studio/Components/ChatComponent.razor +++ b/app/MindWork AI Studio/Components/ChatComponent.razor @@ -58,19 +58,19 @@ this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is not WorkspaceStorageBehavior.DISABLE_WORKSPACES && this.SettingsManager.ConfigurationData.Workspace.DisplayBehavior is WorkspaceDisplayBehavior.TOGGLE_OVERLAY) { - + } @if (this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is WorkspaceStorageBehavior.STORE_CHATS_MANUALLY) { - + } - + @@ -102,7 +102,7 @@ @if (this.isStreaming && this.cancellationTokenSource is not null) { - + } diff --git a/app/MindWork AI Studio/Components/DataSourceSelection.razor b/app/MindWork AI Studio/Components/DataSourceSelection.razor index f99aa6fa..389e234f 100644 --- a/app/MindWork AI Studio/Components/DataSourceSelection.razor +++ b/app/MindWork AI Studio/Components/DataSourceSelection.razor @@ -1,9 +1,9 @@ @using AIStudio.Settings - +@inherits MSGComponentBase @if (this.SelectionMode is DataSourceSelectionMode.SELECTION_MODE) {
- + @if (this.PopoverTriggerMode is PopoverTriggerMode.ICON) { @@ -11,7 +11,7 @@ else { - Select data + @T("Select data") } @@ -22,9 +22,11 @@ - Data Source Selection + + @T("Data Source Selection") + - + @@ -40,57 +42,52 @@ else if (this.SettingsManager.ConfigurationData.DataSources.Count == 0) { - You haven't configured any data sources. To grant the AI access to your data, you need to - add such a source. However, if you wish to use data from your device, you first have to set up - a so-called embedding. This embedding is necessary so the AI can effectively search your data, - find and retrieve the correct information required for each task. In addition to local data, - you can also incorporate your company's data. To do so, your company must provide the data through - an ERI (External Retrieval Interface). + @T("You haven't configured any data sources. To grant the AI access to your data, you need to add such a source. However, if you wish to use data from your device, you first have to set up a so-called embedding. This embedding is necessary so the AI can effectively search your data, find and retrieve the correct information required for each task. In addition to local data, you can also incorporate your company's data. To do so, your company must provide the data through an ERI (External Retrieval Interface).") - Manage Data Sources + @T("Manage Data Sources") - Read more about ERI + @T("Read more about ERI") } else if (this.showDataSourceSelection) { - + @if (this.areDataSourcesEnabled) { - + @if (this.SettingsManager.ConfigurationData.AgentRetrievalContextValidation.EnableRetrievalContextValidation) { - + } @switch (this.aiBasedSourceSelection) { case true when this.availableDataSources.Count == 0: - Your data sources cannot be used with the LLM provider you selected due to data privacy, or they are currently unavailable. + @T("Your data sources cannot be used with the LLM provider you selected due to data privacy, or they are currently unavailable.") break; case true when this.DataSourcesAISelected.Count == 0: - The AI evaluates each of your inputs to determine whether and which data sources are necessary. Currently, the AI has not selected any source. + @T("The AI evaluates each of your inputs to determine whether and which data sources are necessary. Currently, the AI has not selected any source.") break; case false when this.availableDataSources.Count == 0: - Your data sources cannot be used with the LLM provider you selected due to data privacy, or they are currently unavailable. + @T("Your data sources cannot be used with the LLM provider you selected due to data privacy, or they are currently unavailable.") break; case false: - + @foreach (var source in this.availableDataSources) { @@ -104,7 +101,7 @@ case true: - + @foreach (var source in this.availableDataSources) { @@ -114,7 +111,7 @@ } - + @foreach (var source in this.DataSourcesAISelected) { @@ -141,7 +138,7 @@ - Close + @T("Close") @@ -152,7 +149,9 @@ else if (this.SelectionMode is DataSourceSelectionMode.CONFIGURATION_MODE) { - Data Source Selection + + @T("Data Source Selection") + @if (!string.IsNullOrWhiteSpace(this.ConfigurationHeaderMessage)) { @@ -161,12 +160,12 @@ else if (this.SelectionMode is DataSourceSelectionMode.CONFIGURATION_MODE) } - + @if (this.areDataSourcesEnabled) { - - - + + + @foreach (var source in this.availableDataSources) { diff --git a/app/MindWork AI Studio/Components/DataSourceSelection.razor.cs b/app/MindWork AI Studio/Components/DataSourceSelection.razor.cs index 22d4b0cb..5715d52f 100644 --- a/app/MindWork AI Studio/Components/DataSourceSelection.razor.cs +++ b/app/MindWork AI Studio/Components/DataSourceSelection.razor.cs @@ -9,7 +9,7 @@ using DialogOptions = AIStudio.Dialogs.DialogOptions; namespace AIStudio.Components; -public partial class DataSourceSelection : ComponentBase, IMessageBusReceiver, IDisposable +public partial class DataSourceSelection : MSGComponentBase { [Parameter] public DataSourceSelectionMode SelectionMode { get; set; } = DataSourceSelectionMode.SELECTION_MODE; @@ -38,12 +38,6 @@ public partial class DataSourceSelection : ComponentBase, IMessageBusReceiver, I [Parameter] public bool AutoSaveAppSettings { get; set; } - [Inject] - private SettingsManager SettingsManager { get; init; } = null!; - - [Inject] - private MessageBus MessageBus { get; init; } = null!; - [Inject] private DataSourceService DataSourceService { get; init; } = null!; @@ -63,8 +57,7 @@ public partial class DataSourceSelection : ComponentBase, IMessageBusReceiver, I protected override async Task OnInitializedAsync() { - this.MessageBus.RegisterComponent(this); - this.MessageBus.ApplyFilters(this, [], [ Event.COLOR_THEME_CHANGED, Event.RAG_AUTO_DATA_SOURCES_SELECTED ]); + this.ApplyFilters([], [ Event.RAG_AUTO_DATA_SOURCES_SELECTED ]); // // Load the settings: @@ -253,19 +246,12 @@ public partial class DataSourceSelection : ComponentBase, IMessageBusReceiver, I private void HideDataSourceSelection() => this.showDataSourceSelection = false; - #region Implementation of IMessageBusReceiver + #region Overrides of MSGComponentBase - public string ComponentName => nameof(ConfidenceInfo); - - public Task ProcessMessage(ComponentBase? sendingComponent, Event triggeredEvent, T? data) + protected override Task ProcessIncomingMessage(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default { switch (triggeredEvent) { - case Event.COLOR_THEME_CHANGED: - this.showDataSourceSelection = false; - this.StateHasChanged(); - break; - case Event.RAG_AUTO_DATA_SOURCES_SELECTED: if(data is IReadOnlyList aiSelectedDataSources) this.DataSourcesAISelected = aiSelectedDataSources; @@ -273,23 +259,9 @@ public partial class DataSourceSelection : ComponentBase, IMessageBusReceiver, I this.StateHasChanged(); break; } - + return Task.CompletedTask; } - public Task ProcessMessageWithResult(ComponentBase? sendingComponent, Event triggeredEvent, TPayload? data) - { - return Task.FromResult(default); - } - - #endregion - - #region Implementation of IDisposable - - public void Dispose() - { - this.MessageBus.Unregister(this); - } - #endregion } \ No newline at end of file diff --git a/app/MindWork AI Studio/Components/MSGComponentBase.cs b/app/MindWork AI Studio/Components/MSGComponentBase.cs index ad211203..739f6c68 100644 --- a/app/MindWork AI Studio/Components/MSGComponentBase.cs +++ b/app/MindWork AI Studio/Components/MSGComponentBase.cs @@ -35,6 +35,9 @@ public abstract class MSGComponentBase : ComponentBase, IDisposable, IMessageBus /// public string T(string fallbackEN) => this.GetText(this.Lang, fallbackEN); + + /// + public string T(string fallbackEN, string? typeNamespace, string? typeName) => this.GetText(this.Lang, fallbackEN, typeNamespace, typeName); #endregion @@ -73,20 +76,6 @@ public abstract class MSGComponentBase : ComponentBase, IDisposable, IMessageBus { return Task.FromResult(default); } - - protected virtual void DisposeResources() - { - } - - #region Implementation of IDisposable - - public void Dispose() - { - this.MessageBus.Unregister(this); - this.DisposeResources(); - } - - #endregion protected async Task SendMessage(Event triggeredEvent, T? data = default) { @@ -114,4 +103,18 @@ public abstract class MSGComponentBase : ComponentBase, IDisposable, IMessageBus this.MessageBus.ApplyFilters(this, filterComponents, eventsList.ToArray()); } + + protected virtual void DisposeResources() + { + } + + #region Implementation of IDisposable + + public void Dispose() + { + this.MessageBus.Unregister(this); + this.DisposeResources(); + } + + #endregion } \ No newline at end of file diff --git a/app/MindWork AI Studio/Components/MudTextList.razor b/app/MindWork AI Studio/Components/MudTextList.razor index f0ec6295..0b4a3fa3 100644 --- a/app/MindWork AI Studio/Components/MudTextList.razor +++ b/app/MindWork AI Studio/Components/MudTextList.razor @@ -2,7 +2,9 @@ @foreach(var item in this.Items) { - @item.Header: @item.Text + + @item.Header: @item.Text + } \ No newline at end of file diff --git a/app/MindWork AI Studio/Components/Vision.razor.cs b/app/MindWork AI Studio/Components/Vision.razor.cs index 73750d4a..b81c084a 100644 --- a/app/MindWork AI Studio/Components/Vision.razor.cs +++ b/app/MindWork AI Studio/Components/Vision.razor.cs @@ -1,10 +1,20 @@ +using Microsoft.AspNetCore.Components; + namespace AIStudio.Components; public partial class Vision : MSGComponentBase { - private readonly TextItem[] itemsVision; + private TextItem[] itemsVision = []; - public Vision() + #region Overrides of MSGComponentBase + + protected override async Task OnInitializedAsync() + { + await base.OnInitializedAsync(); + this.InitializeVisionItems(); + } + + private void InitializeVisionItems() { this.itemsVision = [ @@ -20,4 +30,17 @@ public partial class Vision : MSGComponentBase new(T("Browser usage"), T("We're working on offering AI Studio features in your browser via a plugin, allowing, e.g., for spell-checking or text rewriting directly in the browser.")), ]; } + + protected override async Task ProcessIncomingMessage(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default + { + switch (triggeredEvent) + { + case Event.PLUGINS_RELOADED: + this.InitializeVisionItems(); + await this.InvokeAsync(this.StateHasChanged); + break; + } + } + + #endregion } \ No newline at end of file diff --git a/app/MindWork AI Studio/Components/Workspaces.razor.cs b/app/MindWork AI Studio/Components/Workspaces.razor.cs index bcf95425..59e43144 100644 --- a/app/MindWork AI Studio/Components/Workspaces.razor.cs +++ b/app/MindWork AI Studio/Components/Workspaces.razor.cs @@ -39,6 +39,8 @@ public partial class Workspaces : MSGComponentBase protected override async Task OnInitializedAsync() { + await base.OnInitializedAsync(); + // // Notice: In order to get the server-based loading to work, we need to respect the following rules: // - We must have initial tree items @@ -46,7 +48,6 @@ public partial class Workspaces : MSGComponentBase // - When assigning the tree items to the MudTreeViewItem component, we must set the Value property to the value of the item // await this.LoadTreeItems(); - await base.OnInitializedAsync(); } #endregion @@ -504,4 +505,19 @@ public partial class Workspaces : MSGComponentBase await this.LoadChat(chatPath, switchToChat: true); await this.LoadTreeItems(); } + + #region Overrides of MSGComponentBase + + protected override async Task ProcessIncomingMessage(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default + { + switch (triggeredEvent) + { + case Event.PLUGINS_RELOADED: + await this.LoadTreeItems(); + await this.InvokeAsync(this.StateHasChanged); + break; + } + } + + #endregion } \ No newline at end of file diff --git a/app/MindWork AI Studio/Dialogs/ConfirmDialog.razor b/app/MindWork AI Studio/Dialogs/ConfirmDialog.razor index 3f757260..455ac41b 100644 --- a/app/MindWork AI Studio/Dialogs/ConfirmDialog.razor +++ b/app/MindWork AI Studio/Dialogs/ConfirmDialog.razor @@ -1,9 +1,16 @@ +@inherits MSGComponentBase - @this.Message + + @this.Message + - No - Yes + + @T("No") + + + @T("Yes") + \ No newline at end of file diff --git a/app/MindWork AI Studio/Dialogs/ConfirmDialog.razor.cs b/app/MindWork AI Studio/Dialogs/ConfirmDialog.razor.cs index 78fb9ad3..f022152e 100644 --- a/app/MindWork AI Studio/Dialogs/ConfirmDialog.razor.cs +++ b/app/MindWork AI Studio/Dialogs/ConfirmDialog.razor.cs @@ -1,3 +1,5 @@ +using AIStudio.Components; + using Microsoft.AspNetCore.Components; namespace AIStudio.Dialogs; @@ -5,7 +7,7 @@ namespace AIStudio.Dialogs; /// /// A confirmation dialog that can be used to ask the user for confirmation. /// -public partial class ConfirmDialog : ComponentBase +public partial class ConfirmDialog : MSGComponentBase { [CascadingParameter] private IMudDialogInstance MudDialog { get; set; } = null!; diff --git a/app/MindWork AI Studio/Dialogs/ProfileDialog.razor b/app/MindWork AI Studio/Dialogs/ProfileDialog.razor index fb44439a..b7440a40 100644 --- a/app/MindWork AI Studio/Dialogs/ProfileDialog.razor +++ b/app/MindWork AI Studio/Dialogs/ProfileDialog.razor @@ -1,29 +1,23 @@ +@inherits MSGComponentBase - Store personal data about yourself in various profiles so that the AIs know your personal context. - This saves you from having to explain your context each time, for example, in every chat. When you - have different roles, you can create a profile for each role. + @T("Store personal data about yourself in various profiles so that the AIs know your personal context. This saves you from having to explain your context each time, for example, in every chat. When you have different roles, you can create a profile for each role.") - Are you a project manager in a research facility? You might want to create a profile for your project - management activities, one for your scientific work, and a profile for when you need to write program - code. In these profiles, you can record how much experience you have or which methods you like or - dislike using. Later, you can choose when and where you want to use each profile. + @T("Are you a project manager in a research facility? You might want to create a profile for your project management activities, one for your scientific work, and a profile for when you need to write program code. In these profiles, you can record how much experience you have or which methods you like or dislike using. Later, you can choose when and where you want to use each profile.") - The name of the profile is mandatory. Each profile must have a unique name. Whether you provide - information about yourself or only fill out the actions is up to you. Only one of these pieces - is required. + @T("The name of the profile is mandatory. Each profile must have a unique name. Whether you provide information about yourself or only fill out the actions is up to you. Only one of these pieces is required.") @* ReSharper disable once CSharpWarnings::CS8974 *@ - Cancel + + @T("Cancel") + @if(this.IsEditing) { - @:Update + @T("Update") } else { - @:Add + @T("Add") } diff --git a/app/MindWork AI Studio/Dialogs/ProfileDialog.razor.cs b/app/MindWork AI Studio/Dialogs/ProfileDialog.razor.cs index 28b9b4b1..0b2a65a0 100644 --- a/app/MindWork AI Studio/Dialogs/ProfileDialog.razor.cs +++ b/app/MindWork AI Studio/Dialogs/ProfileDialog.razor.cs @@ -1,10 +1,11 @@ +using AIStudio.Components; using AIStudio.Settings; using Microsoft.AspNetCore.Components; namespace AIStudio.Dialogs; -public partial class ProfileDialog : ComponentBase +public partial class ProfileDialog : MSGComponentBase { [CascadingParameter] private IMudDialogInstance MudDialog { get; set; } = null!; @@ -45,9 +46,6 @@ public partial class ProfileDialog : ComponentBase [Parameter] public bool IsEditing { get; init; } - [Inject] - private SettingsManager SettingsManager { get; init; } = null!; - [Inject] private ILogger Logger { get; init; } = null!; @@ -129,10 +127,10 @@ public partial class ProfileDialog : ComponentBase private string? ValidateNeedToKnow(string text) { if (string.IsNullOrWhiteSpace(this.DataNeedToKnow) && string.IsNullOrWhiteSpace(this.DataActions)) - return "Please enter what the LLM should know about you and/or what actions it should take."; + return T("Please enter what the LLM should know about you and/or what actions it should take."); if(text.Length > 444) - return "The text must not exceed 444 characters."; + return T("The text must not exceed 444 characters."); return null; } @@ -140,10 +138,10 @@ public partial class ProfileDialog : ComponentBase private string? ValidateActions(string text) { if (string.IsNullOrWhiteSpace(this.DataNeedToKnow) && string.IsNullOrWhiteSpace(this.DataActions)) - return "Please enter what the LLM should know about you and/or what actions it should take."; + return T("Please enter what the LLM should know about you and/or what actions it should take."); if(text.Length > 256) - return "The text must not exceed 256 characters."; + return T("The text must not exceed 256 characters."); return null; } @@ -151,15 +149,15 @@ public partial class ProfileDialog : ComponentBase private string? ValidateName(string name) { if (string.IsNullOrWhiteSpace(name)) - return "Please enter a profile name."; + return T("Please enter a profile name."); if (name.Length > 40) - return "The profile name must not exceed 40 characters."; + return T("The profile name must not exceed 40 characters."); // The instance name must be unique: var lowerName = name.ToLowerInvariant(); if (lowerName != this.dataEditingPreviousName && this.UsedNames.Contains(lowerName)) - return "The profile name must be unique; the chosen name is already in use."; + return T("The profile name must be unique; the chosen name is already in use."); return null; } diff --git a/app/MindWork AI Studio/Dialogs/ProviderDialog.razor b/app/MindWork AI Studio/Dialogs/ProviderDialog.razor index 6e28d6fe..4b45637a 100644 --- a/app/MindWork AI Studio/Dialogs/ProviderDialog.razor +++ b/app/MindWork AI Studio/Dialogs/ProviderDialog.razor @@ -1,13 +1,13 @@ @using AIStudio.Provider @using AIStudio.Provider.HuggingFace @using AIStudio.Provider.SelfHosted - +@inherits MSGComponentBase @* ReSharper disable once CSharpWarnings::CS8974 *@ - + @foreach (LLMProviders provider in Enum.GetValues(typeof(LLMProviders))) { @@ -15,7 +15,9 @@ } - Create account + + @T("Create account") + @if (this.DataLLMProvider.IsAPIKeyNeeded(this.DataHost)) @@ -39,7 +41,7 @@ + @foreach (Host host in Enum.GetValues(typeof(Host))) { @@ -62,7 +64,7 @@ @if (this.DataLLMProvider.IsHFInstanceProviderNeeded()) { - + @foreach (HFInferenceProvider inferenceProvider in Enum.GetValues(typeof(HFInferenceProvider))) { @@ -71,7 +73,9 @@ } @* ReSharper disable Asp.Entity *@ - Please double-check if your model name matches the curl specifications provided by the inference provider. If it doesn't, you might get a Not Found error when trying to use the model. Here's a curl example. + + Please double-check if your model name matches the curl specifications provided by the inference provider. If it doesn't, you might get a Not Found error when trying to use the model. Here's a curl example. + @* ReSharper restore Asp.Entity *@ } @@ -80,12 +84,12 @@ @if (this.DataLLMProvider.IsLLMModelProvidedManually()) { - Show available models + @T("Show available models") - Load models + @T("Load models") @if(this.availableModels.Count is 0) { - No models loaded or available. + @T("No models loaded or available.") } else @@ -111,7 +115,9 @@ Adornment="Adornment.Start" Validation="@this.providerValidation.ValidatingModel"> @foreach (var model in this.availableModels) { - @model + + @model + } } @@ -123,7 +129,7 @@ - Cancel + + @T("Cancel") + @if(this.IsEditing) { - @:Update + @T("Update") } else { - @:Add + @T("Add") } diff --git a/app/MindWork AI Studio/Dialogs/ProviderDialog.razor.cs b/app/MindWork AI Studio/Dialogs/ProviderDialog.razor.cs index 5be79fa3..562e424e 100644 --- a/app/MindWork AI Studio/Dialogs/ProviderDialog.razor.cs +++ b/app/MindWork AI Studio/Dialogs/ProviderDialog.razor.cs @@ -1,6 +1,6 @@ +using AIStudio.Components; using AIStudio.Provider; using AIStudio.Provider.HuggingFace; -using AIStudio.Settings; using AIStudio.Tools.Services; using AIStudio.Tools.Validation; @@ -13,7 +13,7 @@ namespace AIStudio.Dialogs; /// /// The provider settings dialog. /// -public partial class ProviderDialog : ComponentBase, ISecretId +public partial class ProviderDialog : MSGComponentBase, ISecretId { [CascadingParameter] private IMudDialogInstance MudDialog { get; set; } = null!; @@ -78,9 +78,6 @@ public partial class ProviderDialog : ComponentBase, ISecretId [Parameter] public bool IsEditing { get; init; } - [Inject] - private SettingsManager SettingsManager { get; init; } = null!; - [Inject] private ILogger Logger { get; init; } = null!; @@ -182,7 +179,7 @@ public partial class ProviderDialog : ComponentBase, ISecretId this.dataAPIKey = string.Empty; if (this.DataLLMProvider is not LLMProviders.SELF_HOSTED) { - this.dataAPIKeyStorageIssue = $"Failed to load the API key from the operating system. The message was: {requestedSecret.Issue}. You might ignore this message and provide the API key again."; + this.dataAPIKeyStorageIssue = string.Format(T("Failed to load the API key from the operating system. The message was: {0}. You might ignore this message and provide the API key again."), requestedSecret.Issue); await this.form.Validate(); } } @@ -232,7 +229,7 @@ public partial class ProviderDialog : ComponentBase, ISecretId var storeResponse = await this.RustService.SetAPIKey(this, this.dataAPIKey); if (!storeResponse.Success) { - this.dataAPIKeyStorageIssue = $"Failed to store the API key in the operating system. The message was: {storeResponse.Issue}. Please try again."; + this.dataAPIKeyStorageIssue = string.Format(T("Failed to store the API key in the operating system. The message was: {0}. Please try again."), storeResponse.Issue); await this.form.Validate(); return; } @@ -244,7 +241,7 @@ public partial class ProviderDialog : ComponentBase, ISecretId private string? ValidateManuallyModel(string manuallyModel) { if ((this.DataLLMProvider is LLMProviders.FIREWORKS or LLMProviders.HUGGINGFACE) && string.IsNullOrWhiteSpace(manuallyModel)) - return "Please enter a model name."; + return T("Please enter a model name."); return null; } @@ -269,7 +266,7 @@ public partial class ProviderDialog : ComponentBase, ISecretId private string APIKeyText => this.DataLLMProvider switch { - LLMProviders.SELF_HOSTED => "(Optional) API Key", - _ => "API Key", + LLMProviders.SELF_HOSTED => T("(Optional) API Key"), + _ => T("API Key"), }; } \ No newline at end of file diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogBase.cs b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogBase.cs index 7fa14518..1dd94c1c 100644 --- a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogBase.cs +++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogBase.cs @@ -27,11 +27,13 @@ public abstract class SettingsDialogBase : MSGComponentBase /// protected override async Task OnInitializedAsync() { + await base.OnInitializedAsync(); + this.MudDialog.StateHasChanged(); + this.ApplyFilters([], [ Event.CONFIGURATION_CHANGED ]); this.UpdateProviders(); this.UpdateEmbeddingProviders(); - await base.OnInitializedAsync(); } #endregion diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogDataSources.razor.cs b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogDataSources.razor.cs index b35ce8f7..2c2eff67 100644 --- a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogDataSources.razor.cs +++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogDataSources.razor.cs @@ -12,13 +12,13 @@ public partial class SettingsDialogDataSources : SettingsDialogBase { var matchedEmbedding = this.SettingsManager.ConfigurationData.EmbeddingProviders.FirstOrDefault(x => x.Id == internalDataSource.EmbeddingId); if(matchedEmbedding == default) - return "T(No valid embedding)"; + return T("No valid embedding"); return matchedEmbedding.Name; } if(dataSource is IExternalDataSource) - return "T(External (ERI))"; + return T("External (ERI)"); return T("Unknown"); } diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogTranslation.razor b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogTranslation.razor index 259ddc52..f3db4a3c 100644 --- a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogTranslation.razor +++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogTranslation.razor @@ -8,7 +8,7 @@ - +