Compare commits

...
11 Commits
Author SHA1 Message Date
Thorsten Sommer ab7618097d Prepared release v26.1.1 (#624)
Build and Release / Read metadata (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg updater) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis updater) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage deb updater) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg updater) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis updater) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage deb updater) (push) Has been cancelled
Build and Release / Prepare & create release (push) Has been cancelled
Build and Release / Publish release (push) Has been cancelled
2026-01-11 17:00:33 +01:00
Thorsten Sommer 529986837e Implemented the transcription API (#623)
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage deb updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage deb updater) (push) Blocked by required conditions
Build and Release / Prepare & create release (push) Blocked by required conditions
Build and Release / Publish release (push) Blocked by required conditions
Build and Release / Read metadata (push) Waiting to run
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis updater) (push) Blocked by required conditions
2026-01-11 16:02:28 +01:00
Thorsten Sommer b94614b0ab Added transcription provider selection to app settings (#621)
Build and Release / Read metadata (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg updater) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis updater) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg updater) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis updater) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage deb updater) (push) Has been cancelled
Build and Release / Prepare & create release (push) Has been cancelled
Build and Release / Publish release (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage deb updater) (push) Has been cancelled
2026-01-09 15:49:44 +01:00
Thorsten Sommer 7110fb866f Added embedding provider config (#622) 2026-01-09 15:41:54 +01:00
Thorsten Sommer 30197cf507 Added transcription providers (#619)
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage deb updater) (push) Blocked by required conditions
Build and Release / Prepare & create release (push) Blocked by required conditions
Build and Release / Read metadata (push) Waiting to run
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage deb updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis updater) (push) Blocked by required conditions
Build and Release / Publish release (push) Blocked by required conditions
2026-01-09 12:45:21 +01:00
Thorsten Sommer f9a62febcb Fixed localization for profiles (#620)
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage deb updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage deb updater) (push) Blocked by required conditions
Build and Release / Prepare & create release (push) Blocked by required conditions
Build and Release / Publish release (push) Blocked by required conditions
Build and Release / Read metadata (push) Waiting to run
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg updater) (push) Blocked by required conditions
2026-01-09 09:52:31 +01:00
Thorsten Sommer 324ea9eb73 Added audio recording (#615)
Build and Release / Read metadata (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg updater) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis updater) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage deb updater) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg updater) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis updater) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage deb updater) (push) Has been cancelled
Build and Release / Prepare & create release (push) Has been cancelled
Build and Release / Publish release (push) Has been cancelled
2026-01-07 12:56:11 +01:00
Thorsten Sommer 724cf55293 Updated changelog (#618)
Build and Release / Read metadata (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage deb updater) (push) Has been cancelled
Build and Release / Prepare & create release (push) Has been cancelled
Build and Release / Publish release (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg updater) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis updater) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage deb updater) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg updater) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis updater) (push) Has been cancelled
2026-01-06 10:43:01 +01:00
Thorsten Sommer df4a769fdc Allow source code files for attachments (#617) 2026-01-06 10:35:16 +01:00
Thorsten Sommer 878e0f4dc5 Updated Google Gemini base URL (#616)
Build and Release / Read metadata (push) Waiting to run
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage deb updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis updater) (push) Blocked by required conditions
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage deb updater) (push) Blocked by required conditions
Build and Release / Prepare & create release (push) Blocked by required conditions
Build and Release / Publish release (push) Blocked by required conditions
2026-01-06 10:28:48 +01:00
Thorsten Sommer 47975870b4 Added file attachment support to chat templates (#614)
Build and Release / Read metadata (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-apple-darwin, osx-arm64, macos-latest, aarch64-apple-darwin, dmg updater) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-pc-windows-msvc.exe, win-arm64, windows-latest, aarch64-pc-windows-msvc, nsis updater) (push) Has been cancelled
Build and Release / Publish release (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-aarch64-unknown-linux-gnu, linux-arm64, ubuntu-22.04-arm, aarch64-unknown-linux-gnu, appimage deb updater) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-apple-darwin, osx-x64, macos-latest, x86_64-apple-darwin, dmg updater) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-pc-windows-msvc.exe, win-x64, windows-latest, x86_64-pc-windows-msvc, nsis updater) (push) Has been cancelled
Build and Release / Build app (${{ matrix.dotnet_runtime }}) (-x86_64-unknown-linux-gnu, linux-x64, ubuntu-22.04, x86_64-unknown-linux-gnu, appimage deb updater) (push) Has been cancelled
Build and Release / Prepare & create release (push) Has been cancelled
2026-01-01 16:47:15 +01:00
123 changed files with 3643 additions and 336 deletions

No files matched your search

+1 -1
View File
@@ -6,7 +6,7 @@ FSL-1.1-MIT
## Notice ## Notice
Copyright 2025 Thorsten Sommer Copyright 2026 Thorsten Sommer
## Terms and Conditions ## Terms and Conditions
+1 -2
View File
@@ -79,6 +79,7 @@ Since March 2025: We have started developing the plugin system. There will be la
</h3> </h3>
</summary> </summary>
- v26.1.1: Added the option to attach files, including images, to chat templates; added support for source code file attachments in chats and document analysis; added a preview feature for recording your own voice for transcription; fixed various bugs in provider dialogs and profile selection.
- v0.10.0: Added support for newer models like Mistral 3 & GPT 5.2, OpenRouter as LLM and embedding provider, the possibility to use file attachments in chats, and support for images as input. - v0.10.0: Added support for newer models like Mistral 3 & GPT 5.2, OpenRouter as LLM and embedding provider, the possibility to use file attachments in chats, and support for images as input.
- v0.9.51: Added support for [Perplexity](https://www.perplexity.ai/); citations added so that LLMs can provide source references (e.g., some OpenAI models, Perplexity); added support for OpenAI's Responses API so that all text LLMs from OpenAI now work in MindWork AI Studio, including Deep Research models; web searches are now possible (some OpenAI models, Perplexity). - v0.9.51: Added support for [Perplexity](https://www.perplexity.ai/); citations added so that LLMs can provide source references (e.g., some OpenAI models, Perplexity); added support for OpenAI's Responses API so that all text LLMs from OpenAI now work in MindWork AI Studio, including Deep Research models; web searches are now possible (some OpenAI models, Perplexity).
- v0.9.50: Added support for self-hosted LLMs using [vLLM](https://blog.vllm.ai/2023/06/20/vllm.html). - v0.9.50: Added support for self-hosted LLMs using [vLLM](https://blog.vllm.ai/2023/06/20/vllm.html).
@@ -90,8 +91,6 @@ Since March 2025: We have started developing the plugin system. There will be la
- 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.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.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.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.
</details> </details>
+6 -4
View File
@@ -3,8 +3,10 @@ namespace Build.Commands;
public enum PrepareAction public enum PrepareAction
{ {
NONE, NONE,
PATCH, BUILD,
MINOR, MONTH,
MAJOR, YEAR,
SET,
} }
+109 -49
View File
@@ -13,13 +13,32 @@ namespace Build.Commands;
public sealed partial class UpdateMetadataCommands public sealed partial class UpdateMetadataCommands
{ {
[Command("release", Description = "Prepare & build the next release")] [Command("release", Description = "Prepare & build the next release")]
public async Task Release(PrepareAction action) public async Task Release(
[Option("action", ['a'], Description = "The release action: patch, minor, or major")] PrepareAction action = PrepareAction.NONE,
[Option("version", ['v'], Description = "Set a specific version directly, e.g., 26.1.2")] string? version = null)
{ {
if(!Environment.IsWorkingDirectoryValid()) if(!Environment.IsWorkingDirectoryValid())
return; return;
// Validate parameters: either action or version must be specified, but not both:
if (action == PrepareAction.NONE && string.IsNullOrWhiteSpace(version))
{
Console.WriteLine("- Error: You must specify either --action (-a) or --version (-v).");
return;
}
if (action != PrepareAction.NONE && !string.IsNullOrWhiteSpace(version))
{
Console.WriteLine("- Error: You cannot specify both --action and --version. Please use only one.");
return;
}
// If version is specified, use SET action:
if (!string.IsNullOrWhiteSpace(version))
action = PrepareAction.SET;
// Prepare the metadata for the next release: // Prepare the metadata for the next release:
await this.PerformPrepare(action, true); await this.PerformPrepare(action, true, version);
// Build once to allow the Rust compiler to read the changed metadata // Build once to allow the Rust compiler to read the changed metadata
// and to update all .NET artifacts: // and to update all .NET artifacts:
@@ -53,11 +72,30 @@ public sealed partial class UpdateMetadataCommands
} }
[Command("prepare", Description = "Prepare the metadata for the next release")] [Command("prepare", Description = "Prepare the metadata for the next release")]
public async Task Prepare(PrepareAction action) public async Task Prepare(
[Option("action", ['a'], Description = "The release action: patch, minor, or major")] PrepareAction action = PrepareAction.NONE,
[Option("version", ['v'], Description = "Set a specific version directly, e.g., 26.1.2")] string? version = null)
{ {
if(!Environment.IsWorkingDirectoryValid()) if(!Environment.IsWorkingDirectoryValid())
return; return;
// Validate parameters: either action or version must be specified, but not both:
if (action == PrepareAction.NONE && string.IsNullOrWhiteSpace(version))
{
Console.WriteLine("- Error: You must specify either --action (-a) or --version (-v).");
return;
}
if (action != PrepareAction.NONE && !string.IsNullOrWhiteSpace(version))
{
Console.WriteLine("- Error: You cannot specify both --action and --version. Please use only one.");
return;
}
// If version is specified, use SET action:
if (!string.IsNullOrWhiteSpace(version))
action = PrepareAction.SET;
Console.WriteLine("=============================="); Console.WriteLine("==============================");
Console.Write("- Are you trying to prepare a new release? (y/n) "); Console.Write("- Are you trying to prepare a new release? (y/n) ");
var userAnswer = Console.ReadLine(); var userAnswer = Console.ReadLine();
@@ -66,18 +104,18 @@ public sealed partial class UpdateMetadataCommands
Console.WriteLine("- Please use the 'release' command instead"); Console.WriteLine("- Please use the 'release' command instead");
return; return;
} }
await this.PerformPrepare(action, false); await this.PerformPrepare(action, false, version);
} }
private async Task PerformPrepare(PrepareAction action, bool internalCall) private async Task PerformPrepare(PrepareAction action, bool internalCall, string? version = null)
{ {
if(internalCall) if(internalCall)
Console.WriteLine("=============================="); Console.WriteLine("==============================");
Console.WriteLine("- Prepare the metadata for the next release ..."); Console.WriteLine("- Prepare the metadata for the next release ...");
var appVersion = await this.UpdateAppVersion(action); var appVersion = await this.UpdateAppVersion(action, version);
if (!string.IsNullOrWhiteSpace(appVersion.VersionText)) if (!string.IsNullOrWhiteSpace(appVersion.VersionText))
{ {
var buildNumber = await this.IncreaseBuildNumber(); var buildNumber = await this.IncreaseBuildNumber();
@@ -239,17 +277,6 @@ public sealed partial class UpdateMetadataCommands
var pathChangelogs = Path.Combine(Environment.GetAIStudioDirectory(), "wwwroot", "changelog"); var pathChangelogs = Path.Combine(Environment.GetAIStudioDirectory(), "wwwroot", "changelog");
var nextBuildNumber = currentBuildNumber + 1; 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. // 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 // Thus, we check how many days this month has left. In the end, we want to predict the year and month
@@ -259,6 +286,19 @@ public sealed partial class UpdateMetadataCommands
var nextBuildYear = (DateTime.Today + TimeSpan.FromDays(7)).Year; var nextBuildYear = (DateTime.Today + TimeSpan.FromDays(7)).Year;
var nextBuildTimeString = $"{nextBuildYear}-{nextBuildMonth:00}-xx xx:xx UTC"; var nextBuildTimeString = $"{nextBuildYear}-{nextBuildMonth:00}-xx xx:xx UTC";
//
// We assume that most of the time, there will be patch releases:
//
// skipping the first 2 digits for major version
var nextBuildYearShort = nextBuildYear - 2000;
var nextMajor = nextBuildYearShort;
var nextMinor = nextBuildMonth;
var nextPatch = currentAppVersion.Major != nextBuildYearShort || currentAppVersion.Minor != nextBuildMonth ? 1 : currentAppVersion.Patch + 1;
var nextAppVersion = $"{nextMajor}.{nextMinor}.{nextPatch}";
var nextChangelogFilename = $"v{nextAppVersion}.md";
var nextChangelogFilePath = Path.Combine(pathChangelogs, nextChangelogFilename);
var changelogHeader = $""" var changelogHeader = $"""
# v{nextAppVersion}, build {nextBuildNumber} ({nextBuildTimeString}) # v{nextAppVersion}, build {nextBuildNumber} ({nextBuildTimeString})
@@ -355,49 +395,69 @@ public sealed partial class UpdateMetadataCommands
await File.WriteAllLinesAsync(pathMetadata, lines, Environment.UTF8_NO_BOM); await File.WriteAllLinesAsync(pathMetadata, lines, Environment.UTF8_NO_BOM);
} }
private async Task<AppVersion> UpdateAppVersion(PrepareAction action) private async Task<AppVersion> UpdateAppVersion(PrepareAction action, string? version = null)
{ {
const int APP_VERSION_INDEX = 0; const int APP_VERSION_INDEX = 0;
if (action == PrepareAction.NONE) if (action == PrepareAction.NONE)
{ {
Console.WriteLine("- No action specified. Skipping app version update."); Console.WriteLine("- No action specified. Skipping app version update.");
return new(string.Empty, 0, 0, 0); return new(string.Empty, 0, 0, 0);
} }
var pathMetadata = Environment.GetMetadataPath(); var pathMetadata = Environment.GetMetadataPath();
var lines = await File.ReadAllLinesAsync(pathMetadata, Encoding.UTF8); var lines = await File.ReadAllLinesAsync(pathMetadata, Encoding.UTF8);
var currentAppVersionLine = lines[APP_VERSION_INDEX].Trim(); var currentAppVersionLine = lines[APP_VERSION_INDEX].Trim();
var currentAppVersion = AppVersionRegex().Match(currentAppVersionLine);
var currentPatch = int.Parse(currentAppVersion.Groups["patch"].Value); int newMajor, newMinor, newPatch;
var currentMinor = int.Parse(currentAppVersion.Groups["minor"].Value); if (action == PrepareAction.SET && !string.IsNullOrWhiteSpace(version))
var currentMajor = int.Parse(currentAppVersion.Groups["major"].Value);
switch (action)
{ {
case PrepareAction.PATCH: // Parse the provided version string:
currentPatch++; var versionMatch = AppVersionRegex().Match(version);
break; if (!versionMatch.Success)
{
case PrepareAction.MINOR: Console.WriteLine($"- Error: Invalid version format '{version}'. Expected format: major.minor.patch (e.g., 26.1.2)");
currentPatch = 0; return new(string.Empty, 0, 0, 0);
currentMinor++; }
break;
newMajor = int.Parse(versionMatch.Groups["major"].Value);
case PrepareAction.MAJOR: newMinor = int.Parse(versionMatch.Groups["minor"].Value);
currentPatch = 0; newPatch = int.Parse(versionMatch.Groups["patch"].Value);
currentMinor = 0;
currentMajor++;
break;
} }
else
var updatedAppVersion = $"{currentMajor}.{currentMinor}.{currentPatch}"; {
// Parse current version and increment based on action:
var currentAppVersion = AppVersionRegex().Match(currentAppVersionLine);
newPatch = int.Parse(currentAppVersion.Groups["patch"].Value);
newMinor = int.Parse(currentAppVersion.Groups["minor"].Value);
newMajor = int.Parse(currentAppVersion.Groups["major"].Value);
switch (action)
{
case PrepareAction.BUILD:
newPatch++;
break;
case PrepareAction.MONTH:
newPatch = 1;
newMinor++;
break;
case PrepareAction.YEAR:
newPatch = 1;
newMinor = 1;
newMajor++;
break;
}
}
var updatedAppVersion = $"{newMajor}.{newMinor}.{newPatch}";
Console.WriteLine($"- Updating app version from '{currentAppVersionLine}' to '{updatedAppVersion}'."); Console.WriteLine($"- Updating app version from '{currentAppVersionLine}' to '{updatedAppVersion}'.");
lines[APP_VERSION_INDEX] = updatedAppVersion; lines[APP_VERSION_INDEX] = updatedAppVersion;
await File.WriteAllLinesAsync(pathMetadata, lines, Environment.UTF8_NO_BOM); await File.WriteAllLinesAsync(pathMetadata, lines, Environment.UTF8_NO_BOM);
return new(updatedAppVersion, currentMajor, currentMinor, currentPatch); return new(updatedAppVersion, newMajor, newMinor, newPatch);
} }
private async Task UpdateLicenceYear(string licenceFilePath) private async Task UpdateLicenceYear(string licenceFilePath)
+3
View File
@@ -6,6 +6,7 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=GWDG/@EntryIndexedValue">GWDG</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=GWDG/@EntryIndexedValue">GWDG</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HF/@EntryIndexedValue">HF</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=HF/@EntryIndexedValue">HF</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=IERI/@EntryIndexedValue">IERI</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=IERI/@EntryIndexedValue">IERI</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=IMIME/@EntryIndexedValue">IMIME</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=LLM/@EntryIndexedValue">LLM</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=LLM/@EntryIndexedValue">LLM</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=LM/@EntryIndexedValue">LM</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=LM/@EntryIndexedValue">LM</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=MSG/@EntryIndexedValue">MSG</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=MSG/@EntryIndexedValue">MSG</s:String>
@@ -18,10 +19,12 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=URL/@EntryIndexedValue">URL</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=URL/@EntryIndexedValue">URL</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=I18N/@EntryIndexedValue">I18N</s:String> <s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=I18N/@EntryIndexedValue">I18N</s:String>
<s:Boolean x:Key="/Default/UserDictionary/Words/=agentic/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=agentic/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=eri/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=groq/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=groq/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=gwdg/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=gwdg/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=huggingface/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=huggingface/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=ieri/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=ieri/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=mime/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=mwais/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=mwais/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=ollama/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=ollama/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=tauri_0027s/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary> <s:Boolean x:Key="/Default/UserDictionary/Words/=tauri_0027s/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
@@ -103,7 +103,7 @@ else
@T("Documents for the analysis") @T("Documents for the analysis")
</MudText> </MudText>
<AttachDocuments Name="Document Analysis Files" @bind-DocumentPaths="@this.loadedDocumentPaths" CatchAllDocuments="true" UseSmallForm="false" Provider="@this.providerSettings"/> <AttachDocuments Name="Document Analysis Files" Layer="@DropLayers.ASSISTANTS" @bind-DocumentPaths="@this.loadedDocumentPaths" CatchAllDocuments="true" UseSmallForm="false" Provider="@this.providerSettings"/>
</ExpansionPanel> </ExpansionPanel>
</MudExpansionPanels> </MudExpansionPanels>
@@ -2023,6 +2023,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1599198973"]
-- 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. -- 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." 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 a transcription provider for transcribing your voice. Without a selected provider, dictation and transcription features will be disabled.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1834486728"] = "Select a transcription provider for transcribing your voice. Without a selected provider, dictation and transcription features will be disabled."
-- Select the language behavior for the app. The default is to use the system language. You might want to choose a language manually? -- 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?" 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?"
@@ -2035,6 +2038,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1898060643"]
-- Select the language for the app. -- Select the language for the app.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1907446663"] = "Select the language for the app." UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1907446663"] = "Select the language for the app."
-- Disable dictation and transcription
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T215381891"] = "Disable dictation and transcription"
-- Language behavior -- Language behavior
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2341504363"] = "Language behavior" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2341504363"] = "Language behavior"
@@ -2065,6 +2071,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T4004501229"]
-- 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. -- 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." 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."
-- Select a transcription provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T4174666315"] = "Select a transcription provider"
-- Navigation bar behavior -- Navigation bar behavior
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T602293588"] = "Navigation bar behavior" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T602293588"] = "Navigation bar behavior"
@@ -2107,24 +2116,30 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T24199
-- Name -- Name
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T266367750"] = "Name" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T266367750"] = "Name"
-- Configured Embedding Providers
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T305753126"] = "Configured Embedding Providers"
-- This helps AI Studio understand and compare things in a way that's similar to how humans do. When you're working on something, AI Studio can automatically identify related documents and data by comparing their digital fingerprints. For instance, if you're writing about customer service, AI Studio can instantly find other documents in your data that discuss similar topics or experiences, even if they use different words. -- This helps AI Studio understand and compare things in a way that's similar to how humans do. When you're working on something, AI Studio can automatically identify related documents and data by comparing their digital fingerprints. For instance, if you're writing about customer service, AI Studio can instantly find other documents in your data that discuss similar topics or experiences, even if they use different words.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T3251217940"] = "This helps AI Studio understand and compare things in a way that's similar to how humans do. When you're working on something, AI Studio can automatically identify related documents and data by comparing their digital fingerprints. For instance, if you're writing about customer service, AI Studio can instantly find other documents in your data that discuss similar topics or experiences, even if they use different words." UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T3251217940"] = "This helps AI Studio understand and compare things in a way that's similar to how humans do. When you're working on something, AI Studio can automatically identify related documents and data by comparing their digital fingerprints. For instance, if you're writing about customer service, AI Studio can instantly find other documents in your data that discuss similar topics or experiences, even if they use different words."
-- Edit -- Edit
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T3267849393"] = "Edit" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T3267849393"] = "Edit"
-- Configured Embeddings
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T3526613453"] = "Configured Embeddings"
-- Actions -- Actions
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T3865031940"] = "Actions" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T3865031940"] = "Actions"
-- This embedding provider is managed by your organization.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T4062656589"] = "This embedding provider is managed by your organization."
-- No embeddings configured yet. -- No embeddings configured yet.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T4068015588"] = "No embeddings configured yet." UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T4068015588"] = "No embeddings configured yet."
-- Edit Embedding Provider -- Edit Embedding Provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T4264602229"] = "Edit Embedding Provider" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T4264602229"] = "Edit Embedding Provider"
-- Configure Embedding Providers
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T488419116"] = "Configure Embedding Providers"
-- Delete Embedding Provider -- Delete Embedding Provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T511304264"] = "Delete Embedding Provider" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T511304264"] = "Delete Embedding Provider"
@@ -2134,9 +2149,6 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T78223
-- Provider -- Provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T900237532"] = "Provider" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T900237532"] = "Provider"
-- Configure Embeddings
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T970042679"] = "Configure Embeddings"
-- Show provider's confidence level? -- Show provider's confidence level?
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1052533048"] = "Show provider's confidence level?" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1052533048"] = "Show provider's confidence level?"
@@ -2155,6 +2167,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T172585
-- Add Provider -- Add Provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1806589097"] = "Add Provider" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1806589097"] = "Add Provider"
-- Configure LLM Providers
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1810190350"] = "Configure LLM Providers"
-- Edit LLM Provider -- Edit LLM Provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1868766523"] = "Edit LLM Provider" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1868766523"] = "Edit LLM Provider"
@@ -2188,8 +2203,8 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T284206
-- No providers configured yet. -- No providers configured yet.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T2911731076"] = "No providers configured yet." UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T2911731076"] = "No providers configured yet."
-- Configure Providers -- Configured LLM Providers
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3027859089"] = "Configure Providers" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3019870540"] = "Configured LLM Providers"
-- as selected by provider -- as selected by provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3082210376"] = "as selected by provider" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3082210376"] = "as selected by provider"
@@ -2212,9 +2227,6 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T361241
-- No, do not enforce a minimum confidence level -- No, do not enforce a minimum confidence level
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3642102079"] = "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 -- Actions
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3865031940"] = "Actions" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3865031940"] = "Actions"
@@ -2242,6 +2254,57 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T853225
-- Provider -- Provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T900237532"] = "Provider" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T900237532"] = "Provider"
-- No transcription provider configured yet.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T1079350363"] = "No transcription provider configured yet."
-- Edit Transcription Provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T1317362918"] = "Edit Transcription Provider"
-- Delete
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T1469573738"] = "Delete"
-- Add transcription provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T1645238629"] = "Add transcription provider"
-- Add Transcription Provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T2066315685"] = "Add Transcription Provider"
-- Model
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T2189814010"] = "Model"
-- Name
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T266367750"] = "Name"
-- Edit
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T3267849393"] = "Edit"
-- Delete Transcription Provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T370103955"] = "Delete Transcription Provider"
-- Actions
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T3865031940"] = "Actions"
-- Configure Transcription Providers
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T4073110625"] = "Configure Transcription Providers"
-- Configured Transcription Providers
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T4210863523"] = "Configured Transcription Providers"
-- With the support of transcription models, MindWork AI Studio can convert human speech into text. This is useful, for example, when you need to dictate text. You can choose from dedicated transcription models, but not multimodal LLMs (large language models) that can handle both speech and text. The configuration of multimodal models is done in the 'Configure providers' section.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T584860404"] = "With the support of transcription models, MindWork AI Studio can convert human speech into text. This is useful, for example, when you need to dictate text. You can choose from dedicated transcription models, but not multimodal LLMs (large language models) that can handle both speech and text. The configuration of multimodal models is done in the 'Configure providers' section."
-- This transcription provider is managed by your organization.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T756131076"] = "This transcription provider is managed by your organization."
-- Open Dashboard
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T78223861"] = "Open Dashboard"
-- Are you sure you want to delete the transcription provider '{0}'?
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T789660305"] = "Are you sure you want to delete the transcription provider '{0}'?"
-- Provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T900237532"] = "Provider"
-- Copy {0} to the clipboard -- Copy {0} to the clipboard
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TEXTINFOLINE::T2206391442"] = "Copy {0} to the clipboard" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TEXTINFOLINE::T2206391442"] = "Copy {0} to the clipboard"
@@ -2323,6 +2386,33 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T428040679"] = "Content creation"
-- Useful assistants -- Useful assistants
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T586430036"] = "Useful assistants" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T586430036"] = "Useful assistants"
-- Failed to create the transcription provider.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VOICERECORDER::T1689988905"] = "Failed to create the transcription provider."
-- Stop recording and start transcription
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VOICERECORDER::T224155287"] = "Stop recording and start transcription"
-- Start recording your voice for a transcription
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VOICERECORDER::T2372624045"] = "Start recording your voice for a transcription"
-- Transcription in progress...
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VOICERECORDER::T2851219233"] = "Transcription in progress..."
-- The configured transcription provider was not found.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VOICERECORDER::T331613105"] = "The configured transcription provider was not found."
-- The configured transcription provider does not meet the minimum confidence level.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VOICERECORDER::T3834149033"] = "The configured transcription provider does not meet the minimum confidence level."
-- An error occurred during transcription.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VOICERECORDER::T588743762"] = "An error occurred during transcription."
-- No transcription provider is configured.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VOICERECORDER::T663630295"] = "No transcription provider is configured."
-- The transcription result is empty.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VOICERECORDER::T974954792"] = "The transcription result is empty."
-- Are you sure you want to delete the chat '{0}' in the workspace '{1}'? -- 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}'?" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1016188706"] = "Are you sure you want to delete the chat '{0}' in the workspace '{1}'?"
@@ -2443,6 +2533,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2147062613"] = "Profile
-- Add messages of an example conversation (user prompt followed by assistant prompt) to demonstrate the desired interaction pattern. These examples help the AI understand your expectations by showing it the correct format, style, and content of responses before it receives actual user inputs. -- Add messages of an example conversation (user prompt followed by assistant prompt) to demonstrate the desired interaction pattern. These examples help the AI understand your expectations by showing it the correct format, style, and content of responses before it receives actual user inputs.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2292424657"] = "Add messages of an example conversation (user prompt followed by assistant prompt) to demonstrate the desired interaction pattern. These examples help the AI understand your expectations by showing it the correct format, style, and content of responses before it receives actual user inputs." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2292424657"] = "Add messages of an example conversation (user prompt followed by assistant prompt) to demonstrate the desired interaction pattern. These examples help the AI understand your expectations by showing it the correct format, style, and content of responses before it receives actual user inputs."
-- File Attachments
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2294745309"] = "File Attachments"
-- Role -- Role
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2418769465"] = "Role" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2418769465"] = "Role"
@@ -2482,6 +2575,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3016903701"] = "The nam
-- Image content -- Image content
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3094908719"] = "Image content" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3094908719"] = "Image content"
-- You can attach files that will be automatically included when using this chat template. These files will be added to the first message sent in any chat using this template.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3108503534"] = "You can attach files that will be automatically included when using this chat template. These files will be added to the first message sent in any chat using this template."
-- Are you unsure which system prompt to use? You might start with the default system prompt that AI Studio uses for all chats. -- Are you unsure which system prompt to use? You might start with the default system prompt that AI Studio uses for all chats.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3127437308"] = "Are you unsure which system prompt to use? You might start with the default system prompt that AI Studio uses for all chats." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3127437308"] = "Are you unsure which system prompt to use? You might start with the default system prompt that AI Studio uses for all chats."
@@ -3094,9 +3190,6 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2189814010"] = "Mo
-- (Optional) API Key -- (Optional) API Key
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2331453405"] = "(Optional) API Key" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2331453405"] = "(Optional) API Key"
-- Currently, we cannot query the embedding models of self-hosted systems. Therefore, enter the model name manually.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2615586687"] = "Currently, we cannot query the embedding models of self-hosted systems. Therefore, enter the model name manually."
-- Add -- Add
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2646845972"] = "Add" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2646845972"] = "Add"
@@ -3106,6 +3199,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2810182573"] = "No
-- Instance Name -- Instance Name
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2842060373"] = "Instance Name" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2842060373"] = "Instance Name"
-- Currently, we cannot query the embedding models for the selected provider and/or host. Therefore, please enter the model name manually.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T290547799"] = "Currently, we cannot query the embedding models for the selected provider and/or host. Therefore, please enter the model name manually."
-- Model selection -- Model selection
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T416738168"] = "Model selection" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T416738168"] = "Model selection"
@@ -3316,6 +3412,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3361153305"] = "Show Expert
-- Show available models -- Show available models
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3763891899"] = "Show available models" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3763891899"] = "Show available models"
-- Currently, we cannot query the models for the selected provider and/or host. Therefore, please enter the model name manually.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T4116737656"] = "Currently, we cannot query the models for the selected provider and/or host. Therefore, please enter the model name manually."
-- Model selection -- Model selection
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T416738168"] = "Model selection" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T416738168"] = "Model selection"
@@ -4492,6 +4591,60 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SINGLEINPUTDIALOG::T4030229154"] = "Your Inp
-- Cancel -- Cancel
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SINGLEINPUTDIALOG::T900713019"] = "Cancel" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SINGLEINPUTDIALOG::T900713019"] = "Cancel"
-- Failed to store the API key in the operating system. The message was: {0}. Please try again.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::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::TRANSCRIPTIONPROVIDERDIALOG::T1324664716"] = "API Key"
-- Create account
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1356621346"] = "Create account"
-- Currently, we cannot query the transcription models for the selected provider and/or host. Therefore, please enter the model name manually.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1381635232"] = "Currently, we cannot query the transcription models for the selected provider and/or host. Therefore, please enter the model name manually."
-- Hostname
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1727440780"] = "Hostname"
-- Load
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1756340745"] = "Load"
-- Update
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::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::TRANSCRIPTIONPROVIDERDIALOG::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."
-- Model
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T2189814010"] = "Model"
-- (Optional) API Key
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T2331453405"] = "(Optional) API Key"
-- Add
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T2646845972"] = "Add"
-- No models loaded or available.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T2810182573"] = "No models loaded or available."
-- Instance Name
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T2842060373"] = "Instance Name"
-- Please enter a transcription model name.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T3703662664"] = "Please enter a transcription model name."
-- Model selection
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T416738168"] = "Model selection"
-- Host
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T808120719"] = "Host"
-- Provider
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T900237532"] = "Provider"
-- Cancel
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T900713019"] = "Cancel"
-- Install now -- Install now
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::UPDATEDIALOG::T2366359512"] = "Install now" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::UPDATEDIALOG::T2366359512"] = "Install now"
@@ -5194,6 +5347,12 @@ UI_TEXT_CONTENT["AISTUDIO::PROVIDER::LLMPROVIDERSEXTENSIONS::T3424652889"] = "Un
-- no model selected -- no model selected
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::MODEL::T2234274832"] = "no model selected" UI_TEXT_CONTENT["AISTUDIO::PROVIDER::MODEL::T2234274832"] = "no model selected"
-- Model as configured by whisper.cpp
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::SELFHOSTED::PROVIDERSELFHOSTED::T3313940770"] = "Model as configured by whisper.cpp"
-- Use no chat template
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CHATTEMPLATE::T4258819635"] = "Use no chat template"
-- Navigation never expands, but there are tooltips -- Navigation never expands, but there are tooltips
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T1095779033"] = "Navigation never expands, but there are tooltips" UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T1095779033"] = "Navigation never expands, but there are tooltips"
@@ -5365,6 +5524,9 @@ UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::PREVIEWFEATURESEXTENSIONS::T2708
-- Unknown preview feature -- Unknown preview feature
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::PREVIEWFEATURESEXTENSIONS::T2722827307"] = "Unknown preview feature" UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::PREVIEWFEATURESEXTENSIONS::T2722827307"] = "Unknown preview feature"
-- Transcription: Preview of our speech to text system where you can transcribe recordings and audio files into text
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::PREVIEWFEATURESEXTENSIONS::T714355911"] = "Transcription: Preview of our speech to text system where you can transcribe recordings and audio files into text"
-- Use no data sources, when sending an assistant result to a chat -- Use no data sources, when sending an assistant result to a chat
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::SENDTOCHATDATASOURCEBEHAVIOREXTENSIONS::T1223925477"] = "Use no data sources, when sending an assistant result to a chat" UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::SENDTOCHATDATASOURCEBEHAVIOREXTENSIONS::T1223925477"] = "Use no data sources, when sending an assistant result to a chat"
@@ -5929,6 +6091,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::RAG::RAGPROCESSES::AISRCSELWITHRETCTXVAL::T377
-- Executable Files -- Executable Files
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T2217313358"] = "Executable Files" UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T2217313358"] = "Executable Files"
-- All Source Code Files
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T2460199369"] = "All Source Code Files"
-- All Audio Files -- All Audio Files
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T2575722901"] = "All Audio Files" UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T2575722901"] = "All Audio Files"
@@ -95,7 +95,8 @@ public record FileAttachment(FileAttachmentType Type, string FileName, string Fi
// Check if it's an allowed document file (PDF, Text, or Office): // Check if it's an allowed document file (PDF, Text, or Office):
if (FileTypeFilter.PDF.FilterExtensions.Contains(extension) || if (FileTypeFilter.PDF.FilterExtensions.Contains(extension) ||
FileTypeFilter.Text.FilterExtensions.Contains(extension) || FileTypeFilter.Text.FilterExtensions.Contains(extension) ||
FileTypeFilter.AllOffice.FilterExtensions.Contains(extension)) FileTypeFilter.AllOffice.FilterExtensions.Contains(extension) ||
FileTypeFilter.AllSourceCode.FilterExtensions.Contains(extension))
return FileAttachmentType.DOCUMENT; return FileAttachmentType.DOCUMENT;
// All other file types are forbidden: // All other file types are forbidden:
@@ -1,3 +1,4 @@
using AIStudio.Tools.MIME;
using AIStudio.Tools.PluginSystem; using AIStudio.Tools.PluginSystem;
namespace AIStudio.Chat; namespace AIStudio.Chat;
@@ -6,7 +7,7 @@ public static class IImageSourceExtensions
{ {
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(IImageSourceExtensions).Namespace, nameof(IImageSourceExtensions)); private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(IImageSourceExtensions).Namespace, nameof(IImageSourceExtensions));
public static string DetermineMimeType(this IImageSource image) public static MIMEType DetermineMimeType(this IImageSource image)
{ {
switch (image.SourceType) switch (image.SourceType)
{ {
@@ -18,13 +19,11 @@ public static class IImageSourceExtensions
{ {
var mimeEnd = base64Data.IndexOf(';'); var mimeEnd = base64Data.IndexOf(';');
if (mimeEnd > 5) if (mimeEnd > 5)
{ return Builder.FromTextRepresentation(base64Data[5..mimeEnd]);
return base64Data[5..mimeEnd];
}
} }
// Fallback: // Fallback:
return "application/octet-stream"; return Builder.Create().UseApplication().UseSubtype(ApplicationSubtype.OCTET_STREAM).Build();
} }
case ContentImageSource.URL: case ContentImageSource.URL:
@@ -32,38 +31,36 @@ public static class IImageSourceExtensions
// Try to detect the mime type from the URL extension: // Try to detect the mime type from the URL extension:
var uri = new Uri(image.Source); var uri = new Uri(image.Source);
var extension = Path.GetExtension(uri.AbsolutePath).ToLowerInvariant(); var extension = Path.GetExtension(uri.AbsolutePath).ToLowerInvariant();
return extension switch return DeriveMIMETypeFromExtension(extension);
{
".png" => "image/png",
".jpg" or ".jpeg" => "image/jpeg",
".gif" => "image/gif",
".bmp" => "image/bmp",
".webp" => "image/webp",
_ => "application/octet-stream"
};
} }
case ContentImageSource.LOCAL_PATH: case ContentImageSource.LOCAL_PATH:
{ {
var extension = Path.GetExtension(image.Source).ToLowerInvariant(); var extension = Path.GetExtension(image.Source).ToLowerInvariant();
return extension switch return DeriveMIMETypeFromExtension(extension);
{
".png" => "image/png",
".jpg" or ".jpeg" => "image/jpeg",
".gif" => "image/gif",
".bmp" => "image/bmp",
".webp" => "image/webp",
_ => "application/octet-stream"
};
} }
default: default:
return "application/octet-stream"; return Builder.Create().UseApplication().UseSubtype(ApplicationSubtype.OCTET_STREAM).Build();
} }
} }
private static MIMEType DeriveMIMETypeFromExtension(string extension)
{
var imageBuilder = Builder.Create().UseImage();
return extension switch
{
".png" => imageBuilder.UseSubtype(ImageSubtype.PNG).Build(),
".jpg" or ".jpeg" => imageBuilder.UseSubtype(ImageSubtype.JPEG).Build(),
".gif" => imageBuilder.UseSubtype(ImageSubtype.GIF).Build(),
".webp" => imageBuilder.UseSubtype(ImageSubtype.WEBP).Build(),
".tiff" or ".tif" => imageBuilder.UseSubtype(ImageSubtype.TIFF).Build(),
".heic" or ".heif" => imageBuilder.UseSubtype(ImageSubtype.HEIC).Build(),
_ => Builder.Create().UseApplication().UseSubtype(ApplicationSubtype.OCTET_STREAM).Build()
};
}
/// <summary> /// <summary>
/// Read the image content as a base64 string. /// Read the image content as a base64 string.
/// </summary> /// </summary>
@@ -17,6 +17,18 @@ public partial class AttachDocuments : MSGComponentBase
[Parameter] [Parameter]
public string Name { get; set; } = string.Empty; public string Name { get; set; } = string.Empty;
/// <summary>
/// On which layer to register the drop area. Higher layers have priority over lower layers.
/// </summary>
[Parameter]
public int Layer { get; set; }
/// <summary>
/// When true, pause catching dropped files. Default is false.
/// </summary>
[Parameter]
public bool PauseCatchingDrops { get; set; }
[Parameter] [Parameter]
public HashSet<FileAttachment> DocumentPaths { get; set; } = []; public HashSet<FileAttachment> DocumentPaths { get; set; } = [];
@@ -36,6 +48,15 @@ public partial class AttachDocuments : MSGComponentBase
[Parameter] [Parameter]
public bool UseSmallForm { get; set; } public bool UseSmallForm { get; set; }
/// <summary>
/// When true, validate media file types before attaching. Default is true. That means that
/// the user cannot attach unsupported media file types when the provider or model does not
/// support them. Set it to false in order to disable this validation. This is useful for places
/// where the user might want to prepare a template.
/// </summary>
[Parameter]
public bool ValidateMediaFileTypes { get; set; } = true;
[Parameter] [Parameter]
public AIStudio.Settings.Provider? Provider { get; set; } public AIStudio.Settings.Provider? Provider { get; set; }
@@ -54,6 +75,7 @@ public partial class AttachDocuments : MSGComponentBase
private const Placement TOOLBAR_TOOLTIP_PLACEMENT = Placement.Top; private const Placement TOOLBAR_TOOLTIP_PLACEMENT = Placement.Top;
private static readonly string DROP_FILES_HERE_TEXT = TB("Drop files here to attach them."); private static readonly string DROP_FILES_HERE_TEXT = TB("Drop files here to attach them.");
private uint numDropAreasAboveThis;
private bool isComponentHovered; private bool isComponentHovered;
private bool isDraggingOver; private bool isDraggingOver;
@@ -61,7 +83,10 @@ public partial class AttachDocuments : MSGComponentBase
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
this.ApplyFilters([], [ Event.TAURI_EVENT_RECEIVED ]); this.ApplyFilters([], [ Event.TAURI_EVENT_RECEIVED, Event.REGISTER_FILE_DROP_AREA, Event.UNREGISTER_FILE_DROP_AREA ]);
// Register this drop area:
await this.MessageBus.SendMessage(this, Event.REGISTER_FILE_DROP_AREA, this.Layer);
await base.OnInitializedAsync(); await base.OnInitializedAsync();
} }
@@ -69,7 +94,35 @@ public partial class AttachDocuments : MSGComponentBase
{ {
switch (triggeredEvent) switch (triggeredEvent)
{ {
case Event.REGISTER_FILE_DROP_AREA when sendingComponent != this:
{
if(data is int layer && layer > this.Layer)
{
this.numDropAreasAboveThis++;
this.PauseCatchingDrops = true;
}
break;
}
case Event.UNREGISTER_FILE_DROP_AREA when sendingComponent != this:
{
if(data is int layer && layer > this.Layer)
{
if(this.numDropAreasAboveThis > 0)
this.numDropAreasAboveThis--;
if(this.numDropAreasAboveThis is 0)
this.PauseCatchingDrops = false;
}
break;
}
case Event.TAURI_EVENT_RECEIVED when data is TauriEvent { EventType: TauriEventType.FILE_DROP_HOVERED }: case Event.TAURI_EVENT_RECEIVED when data is TauriEvent { EventType: TauriEventType.FILE_DROP_HOVERED }:
if(this.PauseCatchingDrops)
return;
if(!this.isComponentHovered && !this.CatchAllDocuments) if(!this.isComponentHovered && !this.CatchAllDocuments)
{ {
this.Logger.LogDebug("Attach documents component '{Name}' is not hovered, ignoring file drop hovered event.", this.Name); this.Logger.LogDebug("Attach documents component '{Name}' is not hovered, ignoring file drop hovered event.", this.Name);
@@ -82,11 +135,17 @@ public partial class AttachDocuments : MSGComponentBase
break; break;
case Event.TAURI_EVENT_RECEIVED when data is TauriEvent { EventType: TauriEventType.FILE_DROP_CANCELED }: case Event.TAURI_EVENT_RECEIVED when data is TauriEvent { EventType: TauriEventType.FILE_DROP_CANCELED }:
if(this.PauseCatchingDrops)
return;
this.isDraggingOver = false; this.isDraggingOver = false;
this.StateHasChanged(); this.StateHasChanged();
break; break;
case Event.TAURI_EVENT_RECEIVED when data is TauriEvent { EventType: TauriEventType.WINDOW_NOT_FOCUSED }: case Event.TAURI_EVENT_RECEIVED when data is TauriEvent { EventType: TauriEventType.WINDOW_NOT_FOCUSED }:
if(this.PauseCatchingDrops)
return;
this.isDraggingOver = false; this.isDraggingOver = false;
this.isComponentHovered = false; this.isComponentHovered = false;
this.ClearDragClass(); this.ClearDragClass();
@@ -94,6 +153,9 @@ public partial class AttachDocuments : MSGComponentBase
break; break;
case Event.TAURI_EVENT_RECEIVED when data is TauriEvent { EventType: TauriEventType.FILE_DROP_DROPPED, Payload: var paths }: case Event.TAURI_EVENT_RECEIVED when data is TauriEvent { EventType: TauriEventType.FILE_DROP_DROPPED, Payload: var paths }:
if(this.PauseCatchingDrops)
return;
if(!this.isComponentHovered && !this.CatchAllDocuments) if(!this.isComponentHovered && !this.CatchAllDocuments)
{ {
this.Logger.LogDebug("Attach documents component '{Name}' is not hovered, ignoring file drop dropped event.", this.Name); this.Logger.LogDebug("Attach documents component '{Name}' is not hovered, ignoring file drop dropped event.", this.Name);
@@ -117,7 +179,7 @@ public partial class AttachDocuments : MSGComponentBase
foreach (var path in paths) foreach (var path in paths)
{ {
if(!await FileExtensionValidation.IsExtensionValidWithNotifyAsync(FileExtensionValidation.UseCase.ATTACHING_CONTENT, path, this.Provider)) if(!await FileExtensionValidation.IsExtensionValidWithNotifyAsync(FileExtensionValidation.UseCase.ATTACHING_CONTENT, path, this.ValidateMediaFileTypes, this.Provider))
continue; continue;
this.DocumentPaths.Add(FileAttachment.FromPath(path)); this.DocumentPaths.Add(FileAttachment.FromPath(path));
@@ -161,7 +223,7 @@ public partial class AttachDocuments : MSGComponentBase
if (!File.Exists(selectedFilePath)) if (!File.Exists(selectedFilePath))
continue; continue;
if (!await FileExtensionValidation.IsExtensionValidWithNotifyAsync(FileExtensionValidation.UseCase.ATTACHING_CONTENT, selectedFilePath, this.Provider)) if (!await FileExtensionValidation.IsExtensionValidWithNotifyAsync(FileExtensionValidation.UseCase.ATTACHING_CONTENT, selectedFilePath, this.ValidateMediaFileTypes, this.Provider))
continue; continue;
this.DocumentPaths.Add(FileAttachment.FromPath(selectedFilePath)); this.DocumentPaths.Add(FileAttachment.FromPath(selectedFilePath));
@@ -189,6 +251,9 @@ public partial class AttachDocuments : MSGComponentBase
private void OnMouseEnter(EventArgs _) private void OnMouseEnter(EventArgs _)
{ {
if(this.PauseCatchingDrops)
return;
this.Logger.LogDebug("Attach documents component '{Name}' is hovered.", this.Name); this.Logger.LogDebug("Attach documents component '{Name}' is hovered.", this.Name);
this.isComponentHovered = true; this.isComponentHovered = true;
this.SetDragClass(); this.SetDragClass();
@@ -197,6 +262,9 @@ public partial class AttachDocuments : MSGComponentBase
private void OnMouseLeave(EventArgs _) private void OnMouseLeave(EventArgs _)
{ {
if(this.PauseCatchingDrops)
return;
this.Logger.LogDebug("Attach documents component '{Name}' is no longer hovered.", this.Name); this.Logger.LogDebug("Attach documents component '{Name}' is no longer hovered.", this.Name);
this.isComponentHovered = false; this.isComponentHovered = false;
this.ClearDragClass(); this.ClearDragClass();
@@ -13,6 +13,7 @@ public partial class Changelog
public static readonly Log[] LOGS = public static readonly Log[] LOGS =
[ [
new (231, "v26.1.1, build 231 (2026-01-11 15:53 UTC)", "v26.1.1.md"),
new (230, "v0.10.0, build 230 (2025-12-31 14:04 UTC)", "v0.10.0.md"), new (230, "v0.10.0, build 230 (2025-12-31 14:04 UTC)", "v0.10.0.md"),
new (229, "v0.9.54, build 229 (2025-11-24 18:28 UTC)", "v0.9.54.md"), new (229, "v0.9.54, build 229 (2025-11-24 18:28 UTC)", "v0.9.54.md"),
new (228, "v0.9.53, build 228 (2025-11-14 13:14 UTC)", "v0.9.53.md"), new (228, "v0.9.53, build 228 (2025-11-14 13:14 UTC)", "v0.9.53.md"),
@@ -82,7 +82,7 @@
} }
<ChatTemplateSelection CanChatThreadBeUsedForTemplate="@this.CanThreadBeSaved" CurrentChatThread="@this.ChatThread" CurrentChatTemplate="@this.currentChatTemplate" CurrentChatTemplateChanged="@this.ChatTemplateWasChanged"/> <ChatTemplateSelection CanChatThreadBeUsedForTemplate="@this.CanThreadBeSaved" CurrentChatThread="@this.ChatThread" CurrentChatTemplate="@this.currentChatTemplate" CurrentChatTemplateChanged="@this.ChatTemplateWasChanged"/>
<AttachDocuments Name="File Attachments" @bind-DocumentPaths="@this.chatDocumentPaths" CatchAllDocuments="true" UseSmallForm="true" Provider="@this.Provider"/> <AttachDocuments Name="File Attachments" Layer="@DropLayers.PAGES" @bind-DocumentPaths="@this.chatDocumentPaths" CatchAllDocuments="true" UseSmallForm="true" Provider="@this.Provider"/>
@if (this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is WorkspaceStorageBehavior.STORE_CHATS_AUTOMATICALLY) @if (this.SettingsManager.ConfigurationData.Workspace.StorageBehavior is WorkspaceStorageBehavior.STORE_CHATS_AUTOMATICALLY)
{ {
@@ -79,7 +79,11 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
// Get the preselected chat template: // Get the preselected chat template:
this.currentChatTemplate = this.SettingsManager.GetPreselectedChatTemplate(Tools.Components.CHAT); this.currentChatTemplate = this.SettingsManager.GetPreselectedChatTemplate(Tools.Components.CHAT);
this.userInput = this.currentChatTemplate.PredefinedUserPrompt; this.userInput = this.currentChatTemplate.PredefinedUserPrompt;
// Apply template's file attachments, if any:
foreach (var attachment in this.currentChatTemplate.FileAttachments)
this.chatDocumentPaths.Add(attachment);
// //
// Check for deferred messages of the kind 'SEND_TO_CHAT', // Check for deferred messages of the kind 'SEND_TO_CHAT',
// aka the user sends an assistant result to the chat: // aka the user sends an assistant result to the chat:
@@ -328,7 +332,12 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
this.currentChatTemplate = chatTemplate; this.currentChatTemplate = chatTemplate;
if(!string.IsNullOrWhiteSpace(this.currentChatTemplate.PredefinedUserPrompt)) if(!string.IsNullOrWhiteSpace(this.currentChatTemplate.PredefinedUserPrompt))
this.userInput = this.currentChatTemplate.PredefinedUserPrompt; this.userInput = this.currentChatTemplate.PredefinedUserPrompt;
// Apply template's file attachments (replaces existing):
this.chatDocumentPaths.Clear();
foreach (var attachment in this.currentChatTemplate.FileAttachments)
this.chatDocumentPaths.Add(attachment);
if(this.ChatThread is null) if(this.ChatThread is null)
return; return;
@@ -677,9 +686,14 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
Blocks = this.currentChatTemplate == ChatTemplate.NO_CHAT_TEMPLATE ? [] : this.currentChatTemplate.ExampleConversation.Select(x => x.DeepClone()).ToList(), Blocks = this.currentChatTemplate == ChatTemplate.NO_CHAT_TEMPLATE ? [] : this.currentChatTemplate.ExampleConversation.Select(x => x.DeepClone()).ToList(),
}; };
} }
this.userInput = this.currentChatTemplate.PredefinedUserPrompt; this.userInput = this.currentChatTemplate.PredefinedUserPrompt;
// Apply template's file attachments:
this.chatDocumentPaths.Clear();
foreach (var attachment in this.currentChatTemplate.FileAttachments)
this.chatDocumentPaths.Add(attachment);
// Now, we have to reset the data source options as well: // Now, we have to reset the data source options as well:
this.ApplyStandardDataSourceOptions(); this.ApplyStandardDataSourceOptions();
@@ -7,7 +7,7 @@
@if (this.CurrentChatTemplate != ChatTemplate.NO_CHAT_TEMPLATE) @if (this.CurrentChatTemplate != ChatTemplate.NO_CHAT_TEMPLATE)
{ {
<MudButton IconSize="Size.Large" StartIcon="@Icons.Material.Filled.RateReview" IconColor="Color.Default"> <MudButton IconSize="Size.Large" StartIcon="@Icons.Material.Filled.RateReview" IconColor="Color.Default">
@this.CurrentChatTemplate.Name @this.CurrentChatTemplate.GetSafeName()
</MudButton> </MudButton>
} }
else else
@@ -6,7 +6,7 @@
@if (this.CurrentProfile != Profile.NO_PROFILE) @if (this.CurrentProfile != Profile.NO_PROFILE)
{ {
<MudButton IconSize="Size.Large" StartIcon="@Icons.Material.Filled.Person4" IconColor="Color.Default"> <MudButton IconSize="Size.Large" StartIcon="@Icons.Material.Filled.Person4" IconColor="Color.Default">
@this.CurrentProfile.Name @this.CurrentProfile.GetSafeName()
</MudButton> </MudButton>
} }
else else
@@ -15,12 +15,12 @@
} }
</ActivatorContent> </ActivatorContent>
<ChildContent> <ChildContent>
<MudMenuItem Icon="@Icons.Material.Filled.Settings" Label="@T("Manage your profiles")" OnClick="async () => await this.OpenSettingsDialog()" /> <MudMenuItem Icon="@Icons.Material.Filled.Settings" Label="@T("Manage your profiles")" OnClick="@(async () => await this.OpenSettingsDialog())" />
<MudDivider/> <MudDivider/>
@foreach (var profile in this.SettingsManager.ConfigurationData.Profiles.GetAllProfiles()) @foreach (var profile in this.SettingsManager.ConfigurationData.Profiles.GetAllProfiles())
{ {
<MudMenuItem Icon="@this.ProfileIcon(profile)" OnClick="() => this.SelectionChanged(profile)"> <MudMenuItem Icon="@this.ProfileIcon(profile)" OnClick="@(() => this.SelectionChanged(profile))">
@profile.Name @profile.GetSafeName()
</MudMenuItem> </MudMenuItem>
} }
</ChildContent> </ChildContent>
@@ -15,6 +15,6 @@
UserAttributes="@SPELLCHECK_ATTRIBUTES"/> UserAttributes="@SPELLCHECK_ATTRIBUTES"/>
<MudTooltip Text="@this.ToggleVisibilityTooltip"> <MudTooltip Text="@this.ToggleVisibilityTooltip">
<MudIconButton Icon="@this.InputTypeIcon" OnClick="() => this.ToggleVisibility()"/> <MudIconButton Icon="@this.InputTypeIcon" OnClick="@(() => this.ToggleVisibility())"/>
</MudTooltip> </MudTooltip>
</MudStack> </MudStack>
@@ -29,4 +29,9 @@
<ConfigurationProviderSelection Component="Components.APP_SETTINGS" Data="@this.AvailableLLMProvidersFunc()" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.PreselectedProvider = selectedValue)" HelpText="@(() => T("Would you like to set one provider as the default for the entire app? When you configure a different provider for an assistant, it will always take precedence."))"/> <ConfigurationProviderSelection Component="Components.APP_SETTINGS" Data="@this.AvailableLLMProvidersFunc()" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.PreselectedProvider = selectedValue)" HelpText="@(() => T("Would you like to set one provider as the default for the entire app? When you configure a different provider for an assistant, it will always take precedence."))"/>
<ConfigurationSelect OptionDescription="@T("Preselect one of your profiles?")" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.PreselectedProfile)" Data="@ConfigurationSelectDataFactory.GetProfilesData(this.SettingsManager.ConfigurationData.Profiles)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.PreselectedProfile = selectedValue)" OptionHelp="@T("Would you like to set one of your profiles as the default for the entire app? When you configure a different profile for an assistant, it will always take precedence.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.App, x => x.PreselectedProfile, out var meta) && meta.IsLocked"/> <ConfigurationSelect OptionDescription="@T("Preselect one of your profiles?")" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.PreselectedProfile)" Data="@ConfigurationSelectDataFactory.GetProfilesData(this.SettingsManager.ConfigurationData.Profiles)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.PreselectedProfile = selectedValue)" OptionHelp="@T("Would you like to set one of your profiles as the default for the entire app? When you configure a different profile for an assistant, it will always take precedence.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.App, x => x.PreselectedProfile, out var meta) && meta.IsLocked"/>
@if (PreviewFeatures.PRE_SPEECH_TO_TEXT_2026.IsEnabled(this.SettingsManager))
{
<ConfigurationSelect OptionDescription="@T("Select a transcription provider")" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.UseTranscriptionProvider)" Data="@this.GetFilteredTranscriptionProviders()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.UseTranscriptionProvider = selectedValue)" OptionHelp="@T("Select a transcription provider for transcribing your voice. Without a selected provider, dictation and transcription features will be disabled.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.App, x => x.UseTranscriptionProvider, out var meta) && meta.IsLocked"/>
}
</ExpansionPanel> </ExpansionPanel>
@@ -1,9 +1,23 @@
using AIStudio.Provider;
using AIStudio.Settings;
using AIStudio.Settings.DataModel; using AIStudio.Settings.DataModel;
namespace AIStudio.Components.Settings; namespace AIStudio.Components.Settings;
public partial class SettingsPanelApp : SettingsPanelBase public partial class SettingsPanelApp : SettingsPanelBase
{ {
private IEnumerable<ConfigurationSelectData<string>> GetFilteredTranscriptionProviders()
{
yield return new(T("Disable dictation and transcription"), string.Empty);
var minimumLevel = this.SettingsManager.GetMinimumConfidenceLevel(Tools.Components.APP_SETTINGS);
foreach (var provider in this.SettingsManager.ConfigurationData.TranscriptionProviders)
{
if (provider.UsedLLMProvider.GetConfidence(this.SettingsManager).Level >= minimumLevel)
yield return new(provider.Name, provider.Id);
}
}
private void UpdatePreviewFeatures(PreviewVisibility previewVisibility) private void UpdatePreviewFeatures(PreviewVisibility previewVisibility)
{ {
this.SettingsManager.ConfigurationData.App.PreviewVisibility = previewVisibility; this.SettingsManager.ConfigurationData.App.PreviewVisibility = previewVisibility;
@@ -4,10 +4,10 @@
@if (PreviewFeatures.PRE_RAG_2024.IsEnabled(this.SettingsManager)) @if (PreviewFeatures.PRE_RAG_2024.IsEnabled(this.SettingsManager))
{ {
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.IntegrationInstructions" HeaderText="@T("Configure Embeddings")"> <ExpansionPanel HeaderIcon="@Icons.Material.Filled.IntegrationInstructions" HeaderText="@T("Configure Embedding Providers")">
<PreviewPrototype ApplyInnerScrollingFix="true"/> <PreviewPrototype ApplyInnerScrollingFix="true"/>
<MudText Typo="Typo.h4" Class="mb-3"> <MudText Typo="Typo.h4" Class="mb-3">
@T("Configured Embeddings") @T("Configured Embedding Providers")
</MudText> </MudText>
<MudJustifiedText Typo="Typo.body1" Class="mb-3"> <MudJustifiedText Typo="Typo.body1" Class="mb-3">
@T("Embeddings are a way to represent words, sentences, entire documents, or even images and videos as digital fingerprints. Just like each person has a unique fingerprint, embedding models create unique digital patterns that capture the meaning and characteristics of the content they analyze. When two things are similar in meaning or content, their digital fingerprints will look very similar. For example, the fingerprints for 'happy' and 'joyful' would be more alike than those for 'happy' and 'sad'.") @T("Embeddings are a way to represent words, sentences, entire documents, or even images and videos as digital fingerprints. Just like each person has a unique fingerprint, embedding models create unique digital patterns that capture the meaning and characteristics of the content they analyze. When two things are similar in meaning or content, their digital fingerprints will look very similar. For example, the fingerprints for 'happy' and 'joyful' would be more alike than those for 'happy' and 'sad'.")
@@ -35,19 +35,28 @@
<MudTd>@context.Num</MudTd> <MudTd>@context.Num</MudTd>
<MudTd>@context.Name</MudTd> <MudTd>@context.Name</MudTd>
<MudTd>@context.UsedLLMProvider.ToName()</MudTd> <MudTd>@context.UsedLLMProvider.ToName()</MudTd>
<MudTd>@this.GetEmbeddingProviderModelName(context)</MudTd> <MudTd>@GetEmbeddingProviderModelName(context)</MudTd>
<MudTd> <MudTd>
<MudStack Row="true" Class="mb-2 mt-2" Wrap="Wrap.Wrap"> <MudStack Row="true" Class="mb-2 mt-2" Spacing="1" Wrap="Wrap.Wrap">
<MudTooltip Text="@T("Open Dashboard")"> @if (context.IsEnterpriseConfiguration)
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.OpenInBrowser" Href="@context.UsedLLMProvider.GetDashboardURL()" Target="_blank" Disabled="@(!context.UsedLLMProvider.HasDashboard())"/> {
</MudTooltip> <MudTooltip Text="@T("This embedding provider is managed by your organization.")">
<MudTooltip Text="@T("Edit")"> <MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Business" Disabled="true"/>
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Edit" OnClick="() => this.EditEmbeddingProvider(context)"/> </MudTooltip>
</MudTooltip> }
<MudTooltip Text="@T("Delete")"> else
<MudIconButton Color="Color.Error" Icon="@Icons.Material.Filled.Delete" OnClick="() => this.DeleteEmbeddingProvider(context)"/> {
</MudTooltip> <MudTooltip Text="@T("Open Dashboard")">
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.OpenInBrowser" Href="@context.UsedLLMProvider.GetDashboardURL()" Target="_blank" Disabled="@(!context.UsedLLMProvider.HasDashboard())"/>
</MudTooltip>
<MudTooltip Text="@T("Edit")">
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Edit" OnClick="@(() => this.EditEmbeddingProvider(context))"/>
</MudTooltip>
<MudTooltip Text="@T("Delete")">
<MudIconButton Color="Color.Error" Icon="@Icons.Material.Filled.Delete" OnClick="@(() => this.DeleteEmbeddingProvider(context))"/>
</MudTooltip>
}
</MudStack> </MudStack>
</MudTd> </MudTd>
</RowTemplate> </RowTemplate>
@@ -15,7 +15,7 @@ public partial class SettingsPanelEmbeddings : SettingsPanelBase
[Parameter] [Parameter]
public EventCallback<List<ConfigurationSelectData<string>>> AvailableEmbeddingProvidersChanged { get; set; } public EventCallback<List<ConfigurationSelectData<string>>> AvailableEmbeddingProvidersChanged { get; set; }
private string GetEmbeddingProviderModelName(EmbeddingProvider provider) private static string GetEmbeddingProviderModelName(EmbeddingProvider provider)
{ {
const int MAX_LENGTH = 36; const int MAX_LENGTH = 36;
var modelName = provider.Model.ToString(); var modelName = provider.Model.ToString();
@@ -100,13 +100,13 @@ public partial class SettingsPanelEmbeddings : SettingsPanelBase
if (dialogResult is null || dialogResult.Canceled) if (dialogResult is null || dialogResult.Canceled)
return; return;
var deleteSecretResponse = await this.RustService.DeleteAPIKey(provider); var deleteSecretResponse = await this.RustService.DeleteAPIKey(provider, SecretStoreType.EMBEDDING_PROVIDER);
if(deleteSecretResponse.Success) if(deleteSecretResponse.Success)
{ {
this.SettingsManager.ConfigurationData.EmbeddingProviders.Remove(provider); this.SettingsManager.ConfigurationData.EmbeddingProviders.Remove(provider);
await this.SettingsManager.StoreSettings(); await this.SettingsManager.StoreSettings();
} }
await this.UpdateEmbeddingProviders(); await this.UpdateEmbeddingProviders();
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED); await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
} }
@@ -3,9 +3,9 @@
@using AIStudio.Provider.SelfHosted @using AIStudio.Provider.SelfHosted
@inherits SettingsPanelBase @inherits SettingsPanelBase
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Layers" HeaderText="@T("Configure Providers")"> <ExpansionPanel HeaderIcon="@Icons.Material.Filled.Layers" HeaderText="@T("Configure LLM Providers")">
<MudText Typo="Typo.h4" Class="mb-3"> <MudText Typo="Typo.h4" Class="mb-3">
@T("Configured Providers") @T("Configured LLM Providers")
</MudText> </MudText>
<MudJustifiedText Typo="Typo.body1" Class="mb-3"> <MudJustifiedText Typo="Typo.body1" Class="mb-3">
@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.") @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.")
@@ -32,11 +32,11 @@
<MudTd> <MudTd>
@if (context.UsedLLMProvider is not LLMProviders.SELF_HOSTED) @if (context.UsedLLMProvider is not LLMProviders.SELF_HOSTED)
{ {
@this.GetLLMProviderModelName(context) @GetLLMProviderModelName(context)
} }
else if (context.UsedLLMProvider is LLMProviders.SELF_HOSTED && context.Host is not Host.LLAMACPP) else if (context.UsedLLMProvider is LLMProviders.SELF_HOSTED && context.Host is not Host.LLAMA_CPP)
{ {
@this.GetLLMProviderModelName(context) @GetLLMProviderModelName(context)
} }
else else
{ {
@@ -57,10 +57,10 @@
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.OpenInBrowser" Href="@context.UsedLLMProvider.GetDashboardURL()" Target="_blank" Disabled="@(!context.UsedLLMProvider.HasDashboard())"/> <MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.OpenInBrowser" Href="@context.UsedLLMProvider.GetDashboardURL()" Target="_blank" Disabled="@(!context.UsedLLMProvider.HasDashboard())"/>
</MudTooltip> </MudTooltip>
<MudTooltip Text="@T("Edit")"> <MudTooltip Text="@T("Edit")">
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Edit" OnClick="() => this.EditLLMProvider(context)"/> <MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Edit" OnClick="@(() => this.EditLLMProvider(context))"/>
</MudTooltip> </MudTooltip>
<MudTooltip Text="@T("Delete")"> <MudTooltip Text="@T("Delete")">
<MudIconButton Color="Color.Error" Icon="@Icons.Material.Filled.Delete" OnClick="() => this.DeleteLLMProvider(context)"/> <MudIconButton Color="Color.Error" Icon="@Icons.Material.Filled.Delete" OnClick="@(() => this.DeleteLLMProvider(context))"/>
</MudTooltip> </MudTooltip>
} }
</MudStack> </MudStack>
@@ -107,7 +107,7 @@ public partial class SettingsPanelProviders : SettingsPanelBase
if (dialogResult is null || dialogResult.Canceled) if (dialogResult is null || dialogResult.Canceled)
return; return;
var deleteSecretResponse = await this.RustService.DeleteAPIKey(provider); var deleteSecretResponse = await this.RustService.DeleteAPIKey(provider, SecretStoreType.LLM_PROVIDER);
if(deleteSecretResponse.Success) if(deleteSecretResponse.Success)
{ {
this.SettingsManager.ConfigurationData.Providers.Remove(provider); this.SettingsManager.ConfigurationData.Providers.Remove(provider);
@@ -134,7 +134,7 @@ public partial class SettingsPanelProviders : SettingsPanelBase
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED); await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
} }
private string GetLLMProviderModelName(AIStudio.Settings.Provider provider) private static string GetLLMProviderModelName(AIStudio.Settings.Provider provider)
{ {
const int MAX_LENGTH = 36; const int MAX_LENGTH = 36;
var modelName = provider.Model.ToString(); var modelName = provider.Model.ToString();
@@ -0,0 +1,73 @@
@using AIStudio.Provider
@using AIStudio.Settings.DataModel
@inherits SettingsPanelBase
@if (PreviewFeatures.PRE_SPEECH_TO_TEXT_2026.IsEnabled(this.SettingsManager))
{
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.VoiceChat" HeaderText="@T("Configure Transcription Providers")">
<PreviewPrototype ApplyInnerScrollingFix="true"/>
<MudText Typo="Typo.h4" Class="mb-3">
@T("Configured Transcription Providers")
</MudText>
<MudJustifiedText Typo="Typo.body1" Class="mb-3">
@T("With the support of transcription models, MindWork AI Studio can convert human speech into text. This is useful, for example, when you need to dictate text. You can choose from dedicated transcription models, but not multimodal LLMs (large language models) that can handle both speech and text. The configuration of multimodal models is done in the 'Configure providers' section.")
</MudJustifiedText>
<MudTable Items="@this.SettingsManager.ConfigurationData.TranscriptionProviders" Hover="@true" Class="border-dashed border rounded-lg">
<ColGroup>
<col style="width: 3em;"/>
<col style="width: 12em;"/>
<col style="width: 12em;"/>
<col/>
<col style="width: 16em;"/>
</ColGroup>
<HeaderContent>
<MudTh>#</MudTh>
<MudTh>@T("Name")</MudTh>
<MudTh>@T("Provider")</MudTh>
<MudTh>@T("Model")</MudTh>
<MudTh>@T("Actions")</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd>@context.Num</MudTd>
<MudTd>@context.Name</MudTd>
<MudTd>@context.UsedLLMProvider.ToName()</MudTd>
<MudTd>@GetTranscriptionProviderModelName(context)</MudTd>
<MudTd>
<MudStack Row="true" Class="mb-2 mt-2" Spacing="1" Wrap="Wrap.Wrap">
@if (context.IsEnterpriseConfiguration)
{
<MudTooltip Text="@T("This transcription provider is managed by your organization.")">
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Business" Disabled="true"/>
</MudTooltip>
}
else
{
<MudTooltip Text="@T("Open Dashboard")">
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.OpenInBrowser" Href="@context.UsedLLMProvider.GetDashboardURL()" Target="_blank" Disabled="@(!context.UsedLLMProvider.HasDashboard())"/>
</MudTooltip>
<MudTooltip Text="@T("Edit")">
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Edit" OnClick="@(() => this.EditTranscriptionProvider(context))"/>
</MudTooltip>
<MudTooltip Text="@T("Delete")">
<MudIconButton Color="Color.Error" Icon="@Icons.Material.Filled.Delete" OnClick="@(() => this.DeleteTranscriptionProvider(context))"/>
</MudTooltip>
}
</MudStack>
</MudTd>
</RowTemplate>
</MudTable>
@if (this.SettingsManager.ConfigurationData.TranscriptionProviders.Count == 0)
{
<MudText Typo="Typo.h6" Class="mt-3">
@T("No transcription provider configured yet.")
</MudText>
}
<MudButton Variant="Variant.Filled" Color="@Color.Primary" StartIcon="@Icons.Material.Filled.AddRoad" Class="mt-3 mb-6" OnClick="@this.AddTranscriptionProvider">
@T("Add transcription provider")
</MudButton>
</ExpansionPanel>
}
@@ -0,0 +1,122 @@
using AIStudio.Dialogs;
using AIStudio.Settings;
using Microsoft.AspNetCore.Components;
using DialogOptions = AIStudio.Dialogs.DialogOptions;
namespace AIStudio.Components.Settings;
public partial class SettingsPanelTranscription : SettingsPanelBase
{
[Parameter]
public List<ConfigurationSelectData<string>> AvailableTranscriptionProviders { get; set; } = new();
[Parameter]
public EventCallback<List<ConfigurationSelectData<string>>> AvailableTranscriptionProvidersChanged { get; set; }
private static string GetTranscriptionProviderModelName(TranscriptionProvider provider)
{
const int MAX_LENGTH = 36;
var modelName = provider.Model.ToString();
return modelName.Length > MAX_LENGTH ? "[...] " + modelName[^Math.Min(MAX_LENGTH, modelName.Length)..] : modelName;
}
#region Overrides of ComponentBase
protected override async Task OnInitializedAsync()
{
await this.UpdateTranscriptionProviders();
await base.OnInitializedAsync();
}
#endregion
private async Task AddTranscriptionProvider()
{
var dialogParameters = new DialogParameters<TranscriptionProviderDialog>
{
{ x => x.IsEditing, false },
};
var dialogReference = await this.DialogService.ShowAsync<TranscriptionProviderDialog>(T("Add Transcription Provider"), dialogParameters, DialogOptions.FULLSCREEN);
var dialogResult = await dialogReference.Result;
if (dialogResult is null || dialogResult.Canceled)
return;
var addedTranscription = (TranscriptionProvider)dialogResult.Data!;
addedTranscription = addedTranscription with { Num = this.SettingsManager.ConfigurationData.NextTranscriptionNum++ };
this.SettingsManager.ConfigurationData.TranscriptionProviders.Add(addedTranscription);
await this.UpdateTranscriptionProviders();
await this.SettingsManager.StoreSettings();
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
}
private async Task EditTranscriptionProvider(TranscriptionProvider transcriptionProvider)
{
var dialogParameters = new DialogParameters<TranscriptionProviderDialog>
{
{ x => x.DataNum, transcriptionProvider.Num },
{ x => x.DataId, transcriptionProvider.Id },
{ x => x.DataName, transcriptionProvider.Name },
{ x => x.DataLLMProvider, transcriptionProvider.UsedLLMProvider },
{ x => x.DataModel, transcriptionProvider.Model },
{ x => x.DataHostname, transcriptionProvider.Hostname },
{ x => x.IsSelfHosted, transcriptionProvider.IsSelfHosted },
{ x => x.IsEditing, true },
{ x => x.DataHost, transcriptionProvider.Host },
};
var dialogReference = await this.DialogService.ShowAsync<TranscriptionProviderDialog>(T("Edit Transcription Provider"), dialogParameters, DialogOptions.FULLSCREEN);
var dialogResult = await dialogReference.Result;
if (dialogResult is null || dialogResult.Canceled)
return;
var editedTranscriptionProvider = (TranscriptionProvider)dialogResult.Data!;
// Set the provider number if it's not set. This is important for providers
// added before we started saving the provider number.
if(editedTranscriptionProvider.Num == 0)
editedTranscriptionProvider = editedTranscriptionProvider with { Num = this.SettingsManager.ConfigurationData.NextTranscriptionNum++ };
this.SettingsManager.ConfigurationData.TranscriptionProviders[this.SettingsManager.ConfigurationData.TranscriptionProviders.IndexOf(transcriptionProvider)] = editedTranscriptionProvider;
await this.UpdateTranscriptionProviders();
await this.SettingsManager.StoreSettings();
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
}
private async Task DeleteTranscriptionProvider(TranscriptionProvider provider)
{
var dialogParameters = new DialogParameters<ConfirmDialog>
{
{ x => x.Message, string.Format(T("Are you sure you want to delete the transcription provider '{0}'?"), provider.Name) },
};
var dialogReference = await this.DialogService.ShowAsync<ConfirmDialog>(T("Delete Transcription Provider"), dialogParameters, DialogOptions.FULLSCREEN);
var dialogResult = await dialogReference.Result;
if (dialogResult is null || dialogResult.Canceled)
return;
var deleteSecretResponse = await this.RustService.DeleteAPIKey(provider, SecretStoreType.TRANSCRIPTION_PROVIDER);
if(deleteSecretResponse.Success)
{
this.SettingsManager.ConfigurationData.TranscriptionProviders.Remove(provider);
await this.SettingsManager.StoreSettings();
}
await this.UpdateTranscriptionProviders();
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
}
private async Task UpdateTranscriptionProviders()
{
this.AvailableTranscriptionProviders.Clear();
foreach (var provider in this.SettingsManager.ConfigurationData.TranscriptionProviders)
this.AvailableTranscriptionProviders.Add(new (provider.Name, provider.Id));
await this.AvailableTranscriptionProvidersChanged.InvokeAsync(this.AvailableTranscriptionProviders);
}
}
@@ -0,0 +1,23 @@
@using AIStudio.Settings.DataModel
@namespace AIStudio.Components
@inherits MSGComponentBase
@if (PreviewFeatures.PRE_SPEECH_TO_TEXT_2026.IsEnabled(this.SettingsManager) && !string.IsNullOrWhiteSpace(this.SettingsManager.ConfigurationData.App.UseTranscriptionProvider))
{
<MudTooltip Text="@this.Tooltip">
@if (this.isTranscribing)
{
<MudProgressCircular Size="Size.Small" Indeterminate="true" Color="Color.Primary"/>
}
else
{
<MudToggleIconButton Toggled="@this.isRecording"
ToggledChanged="@this.OnRecordingToggled"
Icon="@Icons.Material.Filled.Mic"
ToggledIcon="@Icons.Material.Filled.Stop"
Color="Color.Primary"
ToggledColor="Color.Error"/>
}
</MudTooltip>
}
@@ -0,0 +1,321 @@
using AIStudio.Provider;
using AIStudio.Tools.MIME;
using AIStudio.Tools.Services;
using Microsoft.AspNetCore.Components;
namespace AIStudio.Components;
public partial class VoiceRecorder : MSGComponentBase
{
[Inject]
private ILogger<VoiceRecorder> Logger { get; init; } = null!;
[Inject]
private IJSRuntime JsRuntime { get; init; } = null!;
[Inject]
private RustService RustService { get; init; } = null!;
[Inject]
private ISnackbar Snackbar { get; init; } = null!;
private uint numReceivedChunks;
private bool isRecording;
private bool isTranscribing;
private FileStream? currentRecordingStream;
private string? currentRecordingPath;
private string? currentRecordingMimeType;
private string? finalRecordingPath;
private DotNetObjectReference<VoiceRecorder>? dotNetReference;
private string Tooltip => this.isTranscribing
? T("Transcription in progress...")
: this.isRecording
? T("Stop recording and start transcription")
: T("Start recording your voice for a transcription");
private async Task OnRecordingToggled(bool toggled)
{
if (toggled)
{
var mimeTypes = GetPreferredMimeTypes(
Builder.Create().UseAudio().UseSubtype(AudioSubtype.OGG).Build(),
Builder.Create().UseAudio().UseSubtype(AudioSubtype.AAC).Build(),
Builder.Create().UseAudio().UseSubtype(AudioSubtype.MP3).Build(),
Builder.Create().UseAudio().UseSubtype(AudioSubtype.AIFF).Build(),
Builder.Create().UseAudio().UseSubtype(AudioSubtype.WAV).Build(),
Builder.Create().UseAudio().UseSubtype(AudioSubtype.FLAC).Build()
);
this.Logger.LogInformation("Starting audio recording with preferred MIME types: '{PreferredMimeTypes}'.", string.Join<MIMEType>(", ", mimeTypes));
// Create a DotNetObjectReference to pass to JavaScript:
this.dotNetReference = DotNetObjectReference.Create(this);
// Initialize the file stream for writing chunks:
await this.InitializeRecordingStream();
var mimeTypeStrings = mimeTypes.ToStringArray();
var actualMimeType = await this.JsRuntime.InvokeAsync<string>("audioRecorder.start", this.dotNetReference, mimeTypeStrings);
// Store the MIME type for later use:
this.currentRecordingMimeType = actualMimeType;
this.Logger.LogInformation("Audio recording started with MIME type: '{ActualMimeType}'.", actualMimeType);
this.isRecording = true;
}
else
{
var result = await this.JsRuntime.InvokeAsync<AudioRecordingResult>("audioRecorder.stop");
if (result.ChangedMimeType)
this.Logger.LogWarning("The recorded audio MIME type was changed to '{ResultMimeType}'.", result.MimeType);
// Close and finalize the recording stream:
await this.FinalizeRecordingStream();
this.isRecording = false;
this.StateHasChanged();
// Start transcription if we have a recording and a configured provider:
if (this.finalRecordingPath is not null)
await this.TranscribeRecordingAsync();
}
}
private static MIMEType[] GetPreferredMimeTypes(params MIMEType[] mimeTypes)
{
// Default list if no parameters provided:
if (mimeTypes.Length is 0)
{
var audioBuilder = Builder.Create().UseAudio();
return
[
audioBuilder.UseSubtype(AudioSubtype.WEBM).Build(),
audioBuilder.UseSubtype(AudioSubtype.OGG).Build(),
audioBuilder.UseSubtype(AudioSubtype.MP4).Build(),
audioBuilder.UseSubtype(AudioSubtype.MPEG).Build(),
];
}
return mimeTypes;
}
private async Task InitializeRecordingStream()
{
this.numReceivedChunks = 0;
var dataDirectory = await this.RustService.GetDataDirectory();
var recordingDirectory = Path.Combine(dataDirectory, "audioRecordings");
if (!Directory.Exists(recordingDirectory))
Directory.CreateDirectory(recordingDirectory);
var fileName = $"recording_{DateTime.UtcNow:yyyyMMdd_HHmmss}.audio";
this.currentRecordingPath = Path.Combine(recordingDirectory, fileName);
this.currentRecordingStream = new FileStream(this.currentRecordingPath, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: 8192, useAsync: true);
this.Logger.LogInformation("Initialized audio recording stream: '{RecordingPath}'.", this.currentRecordingPath);
}
[JSInvokable]
public async Task OnAudioChunkReceived(byte[] chunkBytes)
{
if (this.currentRecordingStream is null)
{
this.Logger.LogWarning("Received audio chunk but no recording stream is active.");
return;
}
try
{
this.numReceivedChunks++;
await this.currentRecordingStream.WriteAsync(chunkBytes);
await this.currentRecordingStream.FlushAsync();
this.Logger.LogDebug("Wrote {ByteCount} bytes to recording stream.", chunkBytes.Length);
}
catch (Exception ex)
{
this.Logger.LogError(ex, "Error writing audio chunk to stream.");
}
}
private async Task FinalizeRecordingStream()
{
this.finalRecordingPath = null;
if (this.currentRecordingStream is not null)
{
await this.currentRecordingStream.FlushAsync();
await this.currentRecordingStream.DisposeAsync();
this.currentRecordingStream = null;
// Rename the file with the correct extension based on MIME type:
if (this.currentRecordingPath is not null && this.currentRecordingMimeType is not null)
{
var extension = GetFileExtension(this.currentRecordingMimeType);
var newPath = Path.ChangeExtension(this.currentRecordingPath, extension);
if (File.Exists(this.currentRecordingPath))
{
File.Move(this.currentRecordingPath, newPath, overwrite: true);
this.finalRecordingPath = newPath;
this.Logger.LogInformation("Finalized audio recording over {NumChunks} streamed audio chunks to the file '{RecordingPath}'.", this.numReceivedChunks, newPath);
}
}
}
this.currentRecordingPath = null;
this.currentRecordingMimeType = null;
// Dispose the .NET reference:
this.dotNetReference?.Dispose();
this.dotNetReference = null;
}
private static string GetFileExtension(string mimeType)
{
var baseMimeType = mimeType.Split(';')[0].Trim().ToLowerInvariant();
return baseMimeType switch
{
"audio/webm" => ".webm",
"audio/ogg" => ".ogg",
"audio/mp4" => ".m4a",
"audio/mpeg" => ".mp3",
"audio/wav" => ".wav",
"audio/x-wav" => ".wav",
_ => ".audio" // Fallback
};
}
private async Task TranscribeRecordingAsync()
{
if (this.finalRecordingPath is null)
return;
this.isTranscribing = true;
this.StateHasChanged();
try
{
// Get the configured transcription provider ID:
var transcriptionProviderId = this.SettingsManager.ConfigurationData.App.UseTranscriptionProvider;
if (string.IsNullOrWhiteSpace(transcriptionProviderId))
{
this.Logger.LogWarning("No transcription provider is configured.");
await this.MessageBus.SendError(new(Icons.Material.Filled.VoiceChat, this.T("No transcription provider is configured.")));
return;
}
// Find the transcription provider in the list of configured providers:
var transcriptionProviderSettings = this.SettingsManager.ConfigurationData.TranscriptionProviders
.FirstOrDefault(x => x.Id == transcriptionProviderId);
if (transcriptionProviderSettings is null)
{
this.Logger.LogWarning("The configured transcription provider with ID '{ProviderId}' was not found.", transcriptionProviderId);
await this.MessageBus.SendError(new(Icons.Material.Filled.VoiceChat, this.T("The configured transcription provider was not found.")));
return;
}
// Check the confidence level:
var minimumLevel = this.SettingsManager.GetMinimumConfidenceLevel(Tools.Components.NONE);
var providerConfidence = transcriptionProviderSettings.UsedLLMProvider.GetConfidence(this.SettingsManager);
if (providerConfidence.Level < minimumLevel)
{
this.Logger.LogWarning(
"The configured transcription provider '{ProviderName}' has a confidence level of '{ProviderLevel}', which is below the minimum required level of '{MinimumLevel}'.",
transcriptionProviderSettings.Name,
providerConfidence.Level,
minimumLevel);
await this.MessageBus.SendError(new(Icons.Material.Filled.VoiceChat, this.T("The configured transcription provider does not meet the minimum confidence level.")));
return;
}
// Create the provider instance:
var provider = transcriptionProviderSettings.CreateProvider();
if (provider.Provider is LLMProviders.NONE)
{
this.Logger.LogError("Failed to create the transcription provider instance.");
await this.MessageBus.SendError(new(Icons.Material.Filled.VoiceChat, this.T("Failed to create the transcription provider.")));
return;
}
// Call the transcription API:
this.Logger.LogInformation("Starting transcription with provider '{ProviderName}' and model '{ModelName}'.", transcriptionProviderSettings.Name, transcriptionProviderSettings.Model.DisplayName);
var transcribedText = await provider.TranscribeAudioAsync(transcriptionProviderSettings.Model, this.finalRecordingPath, this.SettingsManager);
if (string.IsNullOrWhiteSpace(transcribedText))
{
this.Logger.LogWarning("The transcription result is empty.");
await this.MessageBus.SendWarning(new(Icons.Material.Filled.VoiceChat, this.T("The transcription result is empty.")));
return;
}
// Remove trailing and leading whitespace:
transcribedText = transcribedText.Trim();
// Replace line breaks with spaces:
transcribedText = transcribedText.Replace("\r", " ").Replace("\n", " ");
// Replace two spaces with a single space:
transcribedText = transcribedText.Replace(" ", " ");
this.Logger.LogInformation("Transcription completed successfully. Result length: {Length} characters.", transcribedText.Length);
// Play the transcription done sound effect:
await this.JsRuntime.InvokeVoidAsync("playSound", "/sounds/transcription_done.ogg");
// Copy the transcribed text to the clipboard:
await this.RustService.CopyText2Clipboard(this.Snackbar, transcribedText);
// Delete the recording file:
try
{
if (File.Exists(this.finalRecordingPath))
{
File.Delete(this.finalRecordingPath);
this.Logger.LogInformation("Deleted the recording file '{RecordingPath}'.", this.finalRecordingPath);
}
}
catch (Exception ex)
{
this.Logger.LogError(ex, "Failed to delete the recording file '{RecordingPath}'.", this.finalRecordingPath);
}
}
catch (Exception ex)
{
this.Logger.LogError(ex, "An error occurred during transcription.");
await this.MessageBus.SendError(new(Icons.Material.Filled.VoiceChat, this.T("An error occurred during transcription.")));
}
finally
{
this.finalRecordingPath = null;
this.isTranscribing = false;
this.StateHasChanged();
}
}
private sealed class AudioRecordingResult
{
public string MimeType { get; init; } = string.Empty;
public bool ChangedMimeType { get; init; }
}
#region Overrides of MSGComponentBase
protected override void DisposeResources()
{
// Clean up recording resources if still active:
if (this.currentRecordingStream is not null)
{
this.currentRecordingStream.Dispose();
this.currentRecordingStream = null;
}
this.dotNetReference?.Dispose();
this.dotNetReference = null;
base.DisposeResources();
}
#endregion
}
@@ -77,7 +77,22 @@
UserAttributes="@SPELLCHECK_ATTRIBUTES" UserAttributes="@SPELLCHECK_ATTRIBUTES"
HelperText="@T("Tell the AI your predefined user input.")" HelperText="@T("Tell the AI your predefined user input.")"
/> />
<MudText Typo="Typo.h6" Class="mb-3 mt-6">
@T("File Attachments")
</MudText>
<MudJustifiedText Class="mb-3" Typo="Typo.body1">
@T("You can attach files that will be automatically included when using this chat template. These files will be added to the first message sent in any chat using this template.")
</MudJustifiedText>
<AttachDocuments
Name="ChatTemplateFileAttachments"
Layer="@DropLayers.DIALOGS"
@bind-DocumentPaths="@this.fileAttachments"
UseSmallForm="false"
CatchAllDocuments="true"
ValidateMediaFileTypes="false"
/>
<MudText Typo="Typo.h6" Class="mb-3 mt-6"> <MudText Typo="Typo.h6" Class="mb-3 mt-6">
@T("Profile Usage") @T("Profile Usage")
</MudText> </MudText>
@@ -50,7 +50,10 @@ public partial class ChatTemplateDialog : MSGComponentBase
[Parameter] [Parameter]
public IReadOnlyCollection<ContentBlock> ExampleConversation { get; init; } = []; public IReadOnlyCollection<ContentBlock> ExampleConversation { get; init; } = [];
[Parameter] [Parameter]
public IReadOnlyCollection<FileAttachment> FileAttachments { get; init; } = [];
[Parameter]
public bool AllowProfileUsage { get; set; } = true; public bool AllowProfileUsage { get; set; } = true;
[Parameter] [Parameter]
@@ -71,6 +74,7 @@ public partial class ChatTemplateDialog : MSGComponentBase
private bool dataIsValid; private bool dataIsValid;
private List<ContentBlock> dataExampleConversation = []; private List<ContentBlock> dataExampleConversation = [];
private HashSet<FileAttachment> fileAttachments = [];
private string[] dataIssues = []; private string[] dataIssues = [];
private string dataEditingPreviousName = string.Empty; private string dataEditingPreviousName = string.Empty;
private bool isInlineEditOnGoing; private bool isInlineEditOnGoing;
@@ -95,6 +99,7 @@ public partial class ChatTemplateDialog : MSGComponentBase
{ {
this.dataEditingPreviousName = this.DataName.ToLowerInvariant(); this.dataEditingPreviousName = this.DataName.ToLowerInvariant();
this.dataExampleConversation = this.ExampleConversation.Select(n => n.DeepClone()).ToList(); this.dataExampleConversation = this.ExampleConversation.Select(n => n.DeepClone()).ToList();
this.fileAttachments = [..this.FileAttachments];
} }
if (this.CreateFromExistingChatThread && this.ExistingChatThread is not null) if (this.CreateFromExistingChatThread && this.ExistingChatThread is not null)
@@ -128,6 +133,7 @@ public partial class ChatTemplateDialog : MSGComponentBase
SystemPrompt = this.DataSystemPrompt, SystemPrompt = this.DataSystemPrompt,
PredefinedUserPrompt = this.PredefinedUserPrompt, PredefinedUserPrompt = this.PredefinedUserPrompt,
ExampleConversation = this.dataExampleConversation, ExampleConversation = this.dataExampleConversation,
FileAttachments = [..this.fileAttachments],
AllowProfileUsage = this.AllowProfileUsage, AllowProfileUsage = this.AllowProfileUsage,
EnterpriseConfigurationPluginId = Guid.Empty, EnterpriseConfigurationPluginId = Guid.Empty,
@@ -96,7 +96,7 @@ public partial class DataSourceLocalDirectoryDialog : MSGComponentBase
#endregion #endregion
private bool SelectedCloudEmbedding => !this.SettingsManager.ConfigurationData.EmbeddingProviders.FirstOrDefault(x => x.Id == this.dataEmbeddingId).IsSelfHosted; private bool SelectedCloudEmbedding => !this.SettingsManager.ConfigurationData.EmbeddingProviders.FirstOrDefault(x => x.Id == this.dataEmbeddingId)?.IsSelfHosted ?? false;
private DataSourceLocalDirectory CreateDataSource() => new() private DataSourceLocalDirectory CreateDataSource() => new()
{ {
@@ -27,7 +27,7 @@ public partial class DataSourceLocalDirectoryInfoDialog : MSGComponentBase, IAsy
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
this.embeddingProvider = this.SettingsManager.ConfigurationData.EmbeddingProviders.FirstOrDefault(x => x.Id == this.DataSource.EmbeddingId); this.embeddingProvider = this.SettingsManager.ConfigurationData.EmbeddingProviders.FirstOrDefault(x => x.Id == this.DataSource.EmbeddingId) ?? EmbeddingProvider.NONE;
this.directoryInfo = new DirectoryInfo(this.DataSource.Path); this.directoryInfo = new DirectoryInfo(this.DataSource.Path);
if (this.directoryInfo.Exists) if (this.directoryInfo.Exists)
@@ -46,7 +46,7 @@ public partial class DataSourceLocalDirectoryInfoDialog : MSGComponentBase, IAsy
private readonly CancellationTokenSource cts = new(); private readonly CancellationTokenSource cts = new();
private EmbeddingProvider embeddingProvider; private EmbeddingProvider embeddingProvider = EmbeddingProvider.NONE;
private DirectoryInfo directoryInfo = null!; private DirectoryInfo directoryInfo = null!;
private long directorySizeBytes; private long directorySizeBytes;
private long directorySizeNumFiles; private long directorySizeNumFiles;
@@ -96,7 +96,7 @@ public partial class DataSourceLocalFileDialog : MSGComponentBase
#endregion #endregion
private bool SelectedCloudEmbedding => !this.SettingsManager.ConfigurationData.EmbeddingProviders.FirstOrDefault(x => x.Id == this.dataEmbeddingId).IsSelfHosted; private bool SelectedCloudEmbedding => !this.SettingsManager.ConfigurationData.EmbeddingProviders.FirstOrDefault(x => x.Id == this.dataEmbeddingId)?.IsSelfHosted ?? false;
private DataSourceLocalFile CreateDataSource() => new() private DataSourceLocalFile CreateDataSource() => new()
{ {
@@ -18,14 +18,14 @@ public partial class DataSourceLocalFileInfoDialog : MSGComponentBase
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
this.embeddingProvider = this.SettingsManager.ConfigurationData.EmbeddingProviders.FirstOrDefault(x => x.Id == this.DataSource.EmbeddingId); this.embeddingProvider = this.SettingsManager.ConfigurationData.EmbeddingProviders.FirstOrDefault(x => x.Id == this.DataSource.EmbeddingId) ?? EmbeddingProvider.NONE;
this.fileInfo = new FileInfo(this.DataSource.FilePath); this.fileInfo = new FileInfo(this.DataSource.FilePath);
await base.OnInitializedAsync(); await base.OnInitializedAsync();
} }
#endregion #endregion
private EmbeddingProvider embeddingProvider; private EmbeddingProvider embeddingProvider = EmbeddingProvider.NONE;
private FileInfo fileInfo = null!; private FileInfo fileInfo = null!;
private bool IsCloudEmbedding => !this.embeddingProvider.IsSelfHosted; private bool IsCloudEmbedding => !this.embeddingProvider.IsSelfHosted;
@@ -10,7 +10,7 @@
<MudSelect @bind-Value="@this.DataLLMProvider" Label="@T("Provider")" Class="mb-3" OpenIcon="@Icons.Material.Filled.AccountBalance" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.providerValidation.ValidatingProvider"> <MudSelect @bind-Value="@this.DataLLMProvider" Label="@T("Provider")" Class="mb-3" OpenIcon="@Icons.Material.Filled.AccountBalance" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.providerValidation.ValidatingProvider">
@foreach (LLMProviders provider in Enum.GetValues(typeof(LLMProviders))) @foreach (LLMProviders provider in Enum.GetValues(typeof(LLMProviders)))
{ {
if (provider.ProvideEmbeddings() || provider is LLMProviders.NONE) if (provider.ProvideEmbeddingAPI() || provider is LLMProviders.NONE)
{ {
<MudSelectItem Value="@provider"> <MudSelectItem Value="@provider">
@provider.ToName() @provider.ToName()
@@ -25,7 +25,7 @@
@if (this.DataLLMProvider.IsAPIKeyNeeded(this.DataHost)) @if (this.DataLLMProvider.IsAPIKeyNeeded(this.DataHost))
{ {
<SecretInputField @bind-Secret="@this.dataAPIKey" Label="@this.APIKeyText" Validation="@this.providerValidation.ValidatingAPIKey"/> <SecretInputField Secret="@this.dataAPIKey" SecretChanged="@this.OnAPIKeyChanged" Label="@this.APIKeyText" Validation="@this.providerValidation.ValidatingAPIKey"/>
} }
@if (this.DataLLMProvider.IsHostnameNeeded()) @if (this.DataLLMProvider.IsHostnameNeeded())
@@ -47,7 +47,7 @@
<MudSelect @bind-Value="@this.DataHost" Label="@T("Host")" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.providerValidation.ValidatingHost"> <MudSelect @bind-Value="@this.DataHost" Label="@T("Host")" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.providerValidation.ValidatingHost">
@foreach (Host host in Enum.GetValues(typeof(Host))) @foreach (Host host in Enum.GetValues(typeof(Host)))
{ {
if (host.AreEmbeddingsSupported()) if (host.IsEmbeddingSupported())
{ {
<MudSelectItem Value="@host"> <MudSelectItem Value="@host">
@host.Name() @host.Name()
@@ -71,12 +71,12 @@
AdornmentColor="Color.Info" AdornmentColor="Color.Info"
Validation="@this.ValidateManuallyModel" Validation="@this.ValidateManuallyModel"
UserAttributes="@SPELLCHECK_ATTRIBUTES" UserAttributes="@SPELLCHECK_ATTRIBUTES"
HelperText="@T("Currently, we cannot query the embedding models of self-hosted systems. Therefore, enter the model name manually.")" HelperText="@T("Currently, we cannot query the embedding models for the selected provider and/or host. Therefore, please enter the model name manually.")"
/> />
} }
else else
{ {
<MudButton Disabled="@(!this.DataLLMProvider.CanLoadModels(this.DataHost, this.dataAPIKey))" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.Refresh" OnClick="this.ReloadModels"> <MudButton Disabled="@(!this.DataLLMProvider.CanLoadModels(this.DataHost, this.dataAPIKey))" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.Refresh" OnClick="@this.ReloadModels">
@T("Load") @T("Load")
</MudButton> </MudButton>
@if(this.availableModels.Count is 0) @if(this.availableModels.Count is 0)
@@ -129,6 +129,8 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
IsSelfHosted = this.DataLLMProvider is LLMProviders.SELF_HOSTED, IsSelfHosted = this.DataLLMProvider is LLMProviders.SELF_HOSTED,
Hostname = cleanedHostname.EndsWith('/') ? cleanedHostname[..^1] : cleanedHostname, Hostname = cleanedHostname.EndsWith('/') ? cleanedHostname[..^1] : cleanedHostname,
Host = this.DataHost, Host = this.DataHost,
IsEnterpriseConfiguration = false,
EnterpriseConfigurationPluginId = Guid.Empty,
}; };
} }
@@ -136,6 +138,9 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
// Call the base initialization first so that the I18N is ready:
await base.OnInitializedAsync();
// Configure the spellchecking for the instance name input: // Configure the spellchecking for the instance name input:
this.SettingsManager.InjectSpellchecking(SPELLCHECK_ATTRIBUTES); this.SettingsManager.InjectSpellchecking(SPELLCHECK_ATTRIBUTES);
@@ -162,7 +167,7 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
} }
// Load the API key: // Load the API key:
var requestedSecret = await this.RustService.GetAPIKey(this, isTrying: this.DataLLMProvider is LLMProviders.SELF_HOSTED); var requestedSecret = await this.RustService.GetAPIKey(this, SecretStoreType.EMBEDDING_PROVIDER, isTrying: this.DataLLMProvider is LLMProviders.SELF_HOSTED);
if (requestedSecret.Success) if (requestedSecret.Success)
this.dataAPIKey = await requestedSecret.Secret.Decrypt(this.encryption); this.dataAPIKey = await requestedSecret.Secret.Decrypt(this.encryption);
else else
@@ -177,8 +182,6 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
await this.ReloadModels(); await this.ReloadModels();
} }
await base.OnInitializedAsync();
} }
protected override async Task OnAfterRenderAsync(bool firstRender) protected override async Task OnAfterRenderAsync(bool firstRender)
@@ -195,7 +198,7 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
#region Implementation of ISecretId #region Implementation of ISecretId
public string SecretId => this.DataId; public string SecretId => this.DataLLMProvider.ToName();
public string SecretName => this.DataName; public string SecretName => this.DataName;
@@ -216,7 +219,7 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
if (!string.IsNullOrWhiteSpace(this.dataAPIKey)) if (!string.IsNullOrWhiteSpace(this.dataAPIKey))
{ {
// Store the API key in the OS secure storage: // Store the API key in the OS secure storage:
var storeResponse = await this.RustService.SetAPIKey(this, this.dataAPIKey); var storeResponse = await this.RustService.SetAPIKey(this, this.dataAPIKey, SecretStoreType.EMBEDDING_PROVIDER);
if (!storeResponse.Success) if (!storeResponse.Success)
{ {
this.dataAPIKeyStorageIssue = string.Format(T("Failed to store the API key in the operating system. The message was: {0}. Please try again."), storeResponse.Issue); this.dataAPIKeyStorageIssue = string.Format(T("Failed to store the API key in the operating system. The message was: {0}. Please try again."), storeResponse.Issue);
@@ -237,6 +240,16 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
} }
private void Cancel() => this.MudDialog.Cancel(); private void Cancel() => this.MudDialog.Cancel();
private async Task OnAPIKeyChanged(string apiKey)
{
this.dataAPIKey = apiKey;
if (!string.IsNullOrWhiteSpace(this.dataAPIKeyStorageIssue))
{
this.dataAPIKeyStorageIssue = string.Empty;
await this.form.Validate();
}
}
private async Task ReloadModels() private async Task ReloadModels()
{ {
@@ -22,7 +22,7 @@
@if (this.DataLLMProvider.IsAPIKeyNeeded(this.DataHost)) @if (this.DataLLMProvider.IsAPIKeyNeeded(this.DataHost))
{ {
<SecretInputField @bind-Secret="@this.dataAPIKey" Label="@this.APIKeyText" Validation="@this.providerValidation.ValidatingAPIKey"/> <SecretInputField Secret="@this.dataAPIKey" SecretChanged="@this.OnAPIKeyChanged" Label="@this.APIKeyText" Validation="@this.providerValidation.ValidatingAPIKey"/>
} }
@if (this.DataLLMProvider.IsHostnameNeeded()) @if (this.DataLLMProvider.IsHostnameNeeded())
@@ -44,9 +44,12 @@
<MudSelect @bind-Value="@this.DataHost" Label="@T("Host")" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.providerValidation.ValidatingHost"> <MudSelect @bind-Value="@this.DataHost" Label="@T("Host")" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.providerValidation.ValidatingHost">
@foreach (Host host in Enum.GetValues(typeof(Host))) @foreach (Host host in Enum.GetValues(typeof(Host)))
{ {
<MudSelectItem Value="@host"> @if (host.IsChatSupported())
@host.Name() {
</MudSelectItem> <MudSelectItem Value="@host">
@host.Name()
</MudSelectItem>
}
} }
</MudSelect> </MudSelect>
} }
@@ -84,11 +87,12 @@
AdornmentColor="Color.Info" AdornmentColor="Color.Info"
Validation="@this.ValidateManuallyModel" Validation="@this.ValidateManuallyModel"
UserAttributes="@SPELLCHECK_ATTRIBUTES" UserAttributes="@SPELLCHECK_ATTRIBUTES"
HelperText="@T("Currently, we cannot query the models for the selected provider and/or host. Therefore, please enter the model name manually.")"
/> />
} }
else else
{ {
<MudButton Disabled="@(!this.DataLLMProvider.CanLoadModels(this.DataHost, this.dataAPIKey))" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.Refresh" OnClick="this.ReloadModels"> <MudButton Disabled="@(!this.DataLLMProvider.CanLoadModels(this.DataHost, this.dataAPIKey))" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.Refresh" OnClick="@this.ReloadModels">
@T("Load models") @T("Load models")
</MudButton> </MudButton>
@if(this.availableModels.Count is 0) @if(this.availableModels.Count is 0)
@@ -147,6 +147,9 @@ public partial class ProviderDialog : MSGComponentBase, ISecretId
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
// Call the base initialization first so that the I18N is ready:
await base.OnInitializedAsync();
// Configure the spellchecking for the instance name input: // Configure the spellchecking for the instance name input:
this.SettingsManager.InjectSpellchecking(SPELLCHECK_ATTRIBUTES); this.SettingsManager.InjectSpellchecking(SPELLCHECK_ATTRIBUTES);
@@ -177,7 +180,7 @@ public partial class ProviderDialog : MSGComponentBase, ISecretId
} }
// Load the API key: // Load the API key:
var requestedSecret = await this.RustService.GetAPIKey(this, isTrying: this.DataLLMProvider is LLMProviders.SELF_HOSTED); var requestedSecret = await this.RustService.GetAPIKey(this, SecretStoreType.LLM_PROVIDER, isTrying: this.DataLLMProvider is LLMProviders.SELF_HOSTED);
if (requestedSecret.Success) if (requestedSecret.Success)
this.dataAPIKey = await requestedSecret.Secret.Decrypt(this.encryption); this.dataAPIKey = await requestedSecret.Secret.Decrypt(this.encryption);
else else
@@ -192,8 +195,6 @@ public partial class ProviderDialog : MSGComponentBase, ISecretId
await this.ReloadModels(); await this.ReloadModels();
} }
await base.OnInitializedAsync();
} }
protected override async Task OnAfterRenderAsync(bool firstRender) protected override async Task OnAfterRenderAsync(bool firstRender)
@@ -232,7 +233,7 @@ public partial class ProviderDialog : MSGComponentBase, ISecretId
if (!string.IsNullOrWhiteSpace(this.dataAPIKey)) if (!string.IsNullOrWhiteSpace(this.dataAPIKey))
{ {
// Store the API key in the OS secure storage: // Store the API key in the OS secure storage:
var storeResponse = await this.RustService.SetAPIKey(this, this.dataAPIKey); var storeResponse = await this.RustService.SetAPIKey(this, this.dataAPIKey, SecretStoreType.LLM_PROVIDER);
if (!storeResponse.Success) if (!storeResponse.Success)
{ {
this.dataAPIKeyStorageIssue = string.Format(T("Failed to store the API key in the operating system. The message was: {0}. Please try again."), storeResponse.Issue); this.dataAPIKeyStorageIssue = string.Format(T("Failed to store the API key in the operating system. The message was: {0}. Please try again."), storeResponse.Issue);
@@ -253,6 +254,16 @@ public partial class ProviderDialog : MSGComponentBase, ISecretId
} }
private void Cancel() => this.MudDialog.Cancel(); private void Cancel() => this.MudDialog.Cancel();
private async Task OnAPIKeyChanged(string apiKey)
{
this.dataAPIKey = apiKey;
if (!string.IsNullOrWhiteSpace(this.dataAPIKeyStorageIssue))
{
this.dataAPIKeyStorageIssue = string.Empty;
await this.form.Validate();
}
}
private async Task ReloadModels() private async Task ReloadModels()
{ {
@@ -41,10 +41,10 @@
{ {
<MudStack Row="true" Class="mb-2 mt-2" Wrap="Wrap.Wrap"> <MudStack Row="true" Class="mb-2 mt-2" Wrap="Wrap.Wrap">
<MudTooltip Text="@T("Edit")"> <MudTooltip Text="@T("Edit")">
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Edit" OnClick="() => this.EditChatTemplate(context)"/> <MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Edit" OnClick="@(() => this.EditChatTemplate(context))"/>
</MudTooltip> </MudTooltip>
<MudTooltip Text="@T("Delete")"> <MudTooltip Text="@T("Delete")">
<MudIconButton Color="Color.Error" Icon="@Icons.Material.Filled.Delete" OnClick="() => this.DeleteChatTemplate(context)"/> <MudIconButton Color="Color.Error" Icon="@Icons.Material.Filled.Delete" OnClick="@(() => this.DeleteChatTemplate(context))"/>
</MudTooltip> </MudTooltip>
</MudStack> </MudStack>
} }
@@ -65,6 +65,7 @@ public partial class SettingsDialogChatTemplate : SettingsDialogBase
{ x => x.PredefinedUserPrompt, chatTemplate.PredefinedUserPrompt }, { x => x.PredefinedUserPrompt, chatTemplate.PredefinedUserPrompt },
{ x => x.IsEditing, true }, { x => x.IsEditing, true },
{ x => x.ExampleConversation, chatTemplate.ExampleConversation }, { x => x.ExampleConversation, chatTemplate.ExampleConversation },
{ x => x.FileAttachments, chatTemplate.FileAttachments },
{ x => x.AllowProfileUsage, chatTemplate.AllowProfileUsage }, { x => x.AllowProfileUsage, chatTemplate.AllowProfileUsage },
}; };
@@ -0,0 +1,140 @@
@using AIStudio.Provider
@using AIStudio.Provider.SelfHosted
@inherits MSGComponentBase
<MudDialog>
<DialogContent>
<MudForm @ref="@this.form" @bind-IsValid="@this.dataIsValid" @bind-Errors="@this.dataIssues">
<MudStack Row="@true" AlignItems="AlignItems.Center">
@* ReSharper disable once CSharpWarnings::CS8974 *@
<MudSelect @bind-Value="@this.DataLLMProvider" Label="@T("Provider")" Class="mb-3" OpenIcon="@Icons.Material.Filled.AccountBalance" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.providerValidation.ValidatingProvider">
@foreach (LLMProviders provider in Enum.GetValues(typeof(LLMProviders)))
{
if (provider.ProvideTranscriptionAPI() || provider is LLMProviders.NONE)
{
<MudSelectItem Value="@provider">
@provider.ToName()
</MudSelectItem>
}
}
</MudSelect>
<MudButton Disabled="@(!this.DataLLMProvider.ShowRegisterButton())" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.OpenInBrowser" Href="@this.DataLLMProvider.GetCreationURL()" Target="_blank">
@T("Create account")
</MudButton>
</MudStack>
@if (this.DataLLMProvider.IsAPIKeyNeeded(this.DataHost))
{
<SecretInputField Secret="@this.dataAPIKey" SecretChanged="@this.OnAPIKeyChanged" Label="@this.APIKeyText" Validation="@this.providerValidation.ValidatingAPIKey"/>
}
@if (this.DataLLMProvider.IsHostnameNeeded())
{
<MudTextField
T="string"
@bind-Text="@this.DataHostname"
Label="@T("Hostname")"
Class="mb-3"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Dns"
AdornmentColor="Color.Info"
Validation="@this.providerValidation.ValidatingHostname"
UserAttributes="@SPELLCHECK_ATTRIBUTES"/>
}
@if (this.DataLLMProvider.IsHostNeeded())
{
<MudSelect @bind-Value="@this.DataHost" Label="@T("Host")" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.providerValidation.ValidatingHost">
@foreach (Host host in Enum.GetValues(typeof(Host)))
{
if (host.IsTranscriptionSupported())
{
<MudSelectItem Value="@host">
@host.Name()
</MudSelectItem>
}
}
</MudSelect>
}
<MudField FullWidth="true" Label="@T("Model selection")" Variant="Variant.Outlined" Class="mb-3">
<MudStack Row="@true" AlignItems="AlignItems.Center" StretchItems="StretchItems.End">
@if (this.DataLLMProvider.IsTranscriptionModelProvidedManually(this.DataHost))
{
<MudTextField
T="string"
@bind-Text="@this.dataManuallyModel"
Label="@T("Model")"
Class="mb-3"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Dns"
AdornmentColor="Color.Info"
Validation="@this.ValidateManuallyModel"
UserAttributes="@SPELLCHECK_ATTRIBUTES"
HelperText="@T("Currently, we cannot query the transcription models for the selected provider and/or host. Therefore, please enter the model name manually.")"
/>
}
else
{
<MudButton Disabled="@(!this.DataLLMProvider.CanLoadModels(this.DataHost, this.dataAPIKey))" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.Refresh" OnClick="@this.ReloadModels">
@T("Load")
</MudButton>
@if(this.availableModels.Count is 0)
{
<MudText Typo="Typo.body1">
@T("No models loaded or available.")
</MudText>
}
else
{
<MudSelect Disabled="@this.IsNoneProvider" @bind-Value="@this.DataModel" Label="@T("Model")"
OpenIcon="@Icons.Material.Filled.FaceRetouchingNatural"
AdornmentColor="Color.Info" Adornment="Adornment.Start"
Validation="@this.providerValidation.ValidatingModel">
@foreach (var model in this.availableModels)
{
<MudSelectItem Value="@model">
@model
</MudSelectItem>
}
</MudSelect>
}
}
</MudStack>
</MudField>
@* ReSharper disable once CSharpWarnings::CS8974 *@
<MudTextField
T="string"
@bind-Text="@this.DataName"
Label="@T("Instance Name")"
Class="mb-3"
MaxLength="40"
Counter="40"
Immediate="@true"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Lightbulb"
AdornmentColor="Color.Info"
Validation="@this.providerValidation.ValidatingInstanceName"
UserAttributes="@SPELLCHECK_ATTRIBUTES"
/>
</MudForm>
<Issues IssuesData="@this.dataIssues"/>
</DialogContent>
<DialogActions>
<MudButton OnClick="@this.Cancel" Variant="Variant.Filled">
@T("Cancel")
</MudButton>
<MudButton OnClick="@this.Store" Variant="Variant.Filled" Color="Color.Primary">
@if(this.IsEditing)
{
@T("Update")
}
else
{
@T("Add")
}
</MudButton>
</DialogActions>
</MudDialog>
@@ -0,0 +1,285 @@
using AIStudio.Components;
using AIStudio.Provider;
using AIStudio.Settings;
using AIStudio.Tools.Services;
using AIStudio.Tools.Validation;
using Microsoft.AspNetCore.Components;
using Host = AIStudio.Provider.SelfHosted.Host;
namespace AIStudio.Dialogs;
public partial class TranscriptionProviderDialog : MSGComponentBase, ISecretId
{
[CascadingParameter]
private IMudDialogInstance MudDialog { get; set; } = null!;
/// <summary>
/// The transcription provider's number in the list.
/// </summary>
[Parameter]
public uint DataNum { get; set; }
/// <summary>
/// The transcription provider's ID.
/// </summary>
[Parameter]
public string DataId { get; set; } = Guid.NewGuid().ToString();
/// <summary>
/// The user chosen name.
/// </summary>
[Parameter]
public string DataName { get; set; } = string.Empty;
/// <summary>
/// The chosen hostname for self-hosted providers.
/// </summary>
[Parameter]
public string DataHostname { get; set; } = string.Empty;
/// <summary>
/// The host to use, e.g., llama.cpp.
/// </summary>
[Parameter]
public Host DataHost { get; set; } = Host.NONE;
/// <summary>
/// Is this provider self-hosted?
/// </summary>
[Parameter]
public bool IsSelfHosted { get; set; }
/// <summary>
/// The provider to use.
/// </summary>
[Parameter]
public LLMProviders DataLLMProvider { get; set; } = LLMProviders.NONE;
/// <summary>
/// The transcription model to use.
/// </summary>
[Parameter]
public Model DataModel { get; set; }
/// <summary>
/// Should the dialog be in editing mode?
/// </summary>
[Parameter]
public bool IsEditing { get; init; }
[Inject]
private RustService RustService { get; init; } = null!;
private static readonly Dictionary<string, object?> SPELLCHECK_ATTRIBUTES = new();
/// <summary>
/// The list of used instance names. We need this to check for uniqueness.
/// </summary>
private List<string> UsedInstanceNames { get; set; } = [];
private bool dataIsValid;
private string[] dataIssues = [];
private string dataAPIKey = string.Empty;
private string dataManuallyModel = string.Empty;
private string dataAPIKeyStorageIssue = string.Empty;
private string dataEditingPreviousInstanceName = string.Empty;
// We get the form reference from Blazor code to validate it manually:
private MudForm form = null!;
private readonly List<Model> availableModels = new();
private readonly Encryption encryption = Program.ENCRYPTION;
private readonly ProviderValidation providerValidation;
public TranscriptionProviderDialog()
{
this.providerValidation = new()
{
GetProvider = () => this.DataLLMProvider,
GetAPIKeyStorageIssue = () => this.dataAPIKeyStorageIssue,
GetPreviousInstanceName = () => this.dataEditingPreviousInstanceName,
GetUsedInstanceNames = () => this.UsedInstanceNames,
GetHost = () => this.DataHost,
};
}
private TranscriptionProvider CreateTranscriptionProviderSettings()
{
var cleanedHostname = this.DataHostname.Trim();
Model model = default;
if(this.DataLLMProvider is LLMProviders.SELF_HOSTED)
{
switch (this.DataHost)
{
case Host.OLLAMA:
model = new Model(this.dataManuallyModel, null);
break;
case Host.VLLM:
case Host.LM_STUDIO:
case Host.WHISPER_CPP:
model = this.DataModel;
break;
}
}
else
model = this.DataModel;
return new()
{
Num = this.DataNum,
Id = this.DataId,
Name = this.DataName,
UsedLLMProvider = this.DataLLMProvider,
Model = model,
IsSelfHosted = this.DataLLMProvider is LLMProviders.SELF_HOSTED,
Hostname = cleanedHostname.EndsWith('/') ? cleanedHostname[..^1] : cleanedHostname,
Host = this.DataHost,
IsEnterpriseConfiguration = false,
EnterpriseConfigurationPluginId = Guid.Empty,
};
}
#region Overrides of ComponentBase
protected override async Task OnInitializedAsync()
{
// Call the base initialization first so that the I18N is ready:
await base.OnInitializedAsync();
// Configure the spellchecking for the instance name input:
this.SettingsManager.InjectSpellchecking(SPELLCHECK_ATTRIBUTES);
// Load the used instance names:
this.UsedInstanceNames = this.SettingsManager.ConfigurationData.TranscriptionProviders.Select(x => x.Name.ToLowerInvariant()).ToList();
// When editing, we need to load the data:
if(this.IsEditing)
{
this.dataEditingPreviousInstanceName = this.DataName.ToLowerInvariant();
// When using self-hosted models, we must copy the model name:
if (this.DataLLMProvider is LLMProviders.SELF_HOSTED)
this.dataManuallyModel = this.DataModel.Id;
//
// We cannot load the API key for self-hosted providers:
//
if (this.DataLLMProvider is LLMProviders.SELF_HOSTED && this.DataHost is not Host.OLLAMA)
{
await this.ReloadModels();
await base.OnInitializedAsync();
return;
}
// Load the API key:
var requestedSecret = await this.RustService.GetAPIKey(this, SecretStoreType.TRANSCRIPTION_PROVIDER, isTrying: this.DataLLMProvider is LLMProviders.SELF_HOSTED);
if (requestedSecret.Success)
this.dataAPIKey = await requestedSecret.Secret.Decrypt(this.encryption);
else
{
this.dataAPIKey = string.Empty;
if (this.DataLLMProvider is not LLMProviders.SELF_HOSTED)
{
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();
}
}
await this.ReloadModels();
}
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
// Reset the validation when not editing and on the first render.
// We don't want to show validation errors when the user opens the dialog.
if(!this.IsEditing && firstRender)
this.form.ResetValidation();
await base.OnAfterRenderAsync(firstRender);
}
#endregion
#region Implementation of ISecretId
public string SecretId => this.DataLLMProvider.ToName();
public string SecretName => this.DataName;
#endregion
private async Task Store()
{
await this.form.Validate();
this.dataAPIKeyStorageIssue = string.Empty;
// When the data is not valid, we don't store it:
if (!this.dataIsValid)
return;
// Use the data model to store the provider.
// We just return this data to the parent component:
var addedProviderSettings = this.CreateTranscriptionProviderSettings();
if (!string.IsNullOrWhiteSpace(this.dataAPIKey))
{
// Store the API key in the OS secure storage:
var storeResponse = await this.RustService.SetAPIKey(this, this.dataAPIKey, SecretStoreType.TRANSCRIPTION_PROVIDER);
if (!storeResponse.Success)
{
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;
}
}
this.MudDialog.Close(DialogResult.Ok(addedProviderSettings));
}
private string? ValidateManuallyModel(string manuallyModel)
{
if (this.DataLLMProvider is LLMProviders.SELF_HOSTED && string.IsNullOrWhiteSpace(manuallyModel))
return T("Please enter a transcription model name.");
return null;
}
private void Cancel() => this.MudDialog.Cancel();
private async Task OnAPIKeyChanged(string apiKey)
{
this.dataAPIKey = apiKey;
if (!string.IsNullOrWhiteSpace(this.dataAPIKeyStorageIssue))
{
this.dataAPIKeyStorageIssue = string.Empty;
await this.form.Validate();
}
}
private async Task ReloadModels()
{
var currentTranscriptionProviderSettings = this.CreateTranscriptionProviderSettings();
var provider = currentTranscriptionProviderSettings.CreateProvider();
if(provider is NoProvider)
return;
var models = await provider.GetTranscriptionModels(this.dataAPIKey);
// Order descending by ID means that the newest models probably come first:
var orderedModels = models.OrderByDescending(n => n.Id);
this.availableModels.Clear();
this.availableModels.AddRange(orderedModels);
}
private string APIKeyText => this.DataLLMProvider switch
{
LLMProviders.SELF_HOSTED => T("(Optional) API Key"),
_ => T("API Key"),
};
private bool IsNoneProvider => this.DataLLMProvider is LLMProviders.NONE;
}
+19 -1
View File
@@ -1,4 +1,6 @@
@using AIStudio.Settings.DataModel @using AIStudio.Settings.DataModel
@using AIStudio.Components
@using Microsoft.AspNetCore.Components.Routing @using Microsoft.AspNetCore.Components.Routing
@using MudBlazor @using MudBlazor
@@ -20,12 +22,20 @@
</MudNavLink> </MudNavLink>
} }
</MudNavMenu> </MudNavMenu>
<MudSpacer/>
<MudStack AlignItems="AlignItems.Center">
<MudToolBar WrapContent="true">
<VoiceRecorder />
</MudToolBar>
</MudStack>
</MudDrawer> </MudDrawer>
</MudDrawerContainer> </MudDrawerContainer>
} }
else else
{ {
<MudPaper Width="4em" Class="mud-height-full absolute"> <MudPaper Width="4em" Class="mud-height-full absolute" Style="display: flex; flex-direction: column;">
<MudNavMenu> <MudNavMenu>
@foreach (var navBarItem in this.navItems) @foreach (var navBarItem in this.navItems)
{ {
@@ -41,6 +51,14 @@
} }
} }
</MudNavMenu> </MudNavMenu>
<MudSpacer/>
<MudStack AlignItems="AlignItems.Center">
<MudToolBar WrapContent="true">
<VoiceRecorder />
</MudToolBar>
</MudStack>
</MudPaper> </MudPaper>
} }
} }
@@ -341,7 +341,7 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan
await this.MessageBus.SendMessage<bool>(this, Event.COLOR_THEME_CHANGED); await this.MessageBus.SendMessage<bool>(this, Event.COLOR_THEME_CHANGED);
this.StateHasChanged(); this.StateHasChanged();
} }
#region Implementation of IDisposable #region Implementation of IDisposable
public void Dispose() public void Dispose()
@@ -52,7 +52,7 @@
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="9.0.11" /> <PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="9.0.11" />
<PackageReference Include="MudBlazor" Version="8.15.0" /> <PackageReference Include="MudBlazor" Version="8.15.0" />
<PackageReference Include="MudBlazor.Markdown" Version="8.11.0" /> <PackageReference Include="MudBlazor.Markdown" Version="8.11.0" />
<PackageReference Include="ReverseMarkdown" Version="4.7.1" /> <PackageReference Include="ReverseMarkdown" Version="5.0.0" />
<PackageReference Include="LuaCSharp" Version="0.5.1" /> <PackageReference Include="LuaCSharp" Version="0.5.1" />
</ItemGroup> </ItemGroup>
@@ -190,7 +190,7 @@ public partial class Information : MSGComponentBase
## Notice ## Notice
Copyright 2025 Thorsten Sommer Copyright 2026 Thorsten Sommer
## Terms and Conditions ## Terms and Conditions
+10 -5
View File
@@ -12,18 +12,23 @@
@if (PreviewFeatures.PRE_RAG_2024.IsEnabled(this.SettingsManager)) @if (PreviewFeatures.PRE_RAG_2024.IsEnabled(this.SettingsManager))
{ {
<SettingsPanelEmbeddings AvailableLLMProvidersFunc="() => this.availableLLMProviders" @bind-AvailableEmbeddingProviders="@this.availableEmbeddingProviders"/> <SettingsPanelEmbeddings AvailableLLMProvidersFunc="@(() => this.availableLLMProviders)" @bind-AvailableEmbeddingProviders="@this.availableEmbeddingProviders"/>
} }
<SettingsPanelApp AvailableLLMProvidersFunc="() => this.availableLLMProviders"/> @if (PreviewFeatures.PRE_SPEECH_TO_TEXT_2026.IsEnabled(this.SettingsManager))
{
<SettingsPanelTranscription AvailableLLMProvidersFunc="@(() => this.availableLLMProviders)" @bind-AvailableTranscriptionProviders="@this.availableTranscriptionProviders"/>
}
<SettingsPanelApp AvailableLLMProvidersFunc="@(() => this.availableLLMProviders)"/>
@if (PreviewFeatures.PRE_RAG_2024.IsEnabled(this.SettingsManager)) @if (PreviewFeatures.PRE_RAG_2024.IsEnabled(this.SettingsManager))
{ {
<SettingsPanelAgentDataSourceSelection AvailableLLMProvidersFunc="() => this.availableLLMProviders"/> <SettingsPanelAgentDataSourceSelection AvailableLLMProvidersFunc="@(() => this.availableLLMProviders)"/>
<SettingsPanelAgentRetrievalContextValidation AvailableLLMProvidersFunc="() => this.availableLLMProviders"/> <SettingsPanelAgentRetrievalContextValidation AvailableLLMProvidersFunc="@(() => this.availableLLMProviders)"/>
} }
<SettingsPanelAgentContentCleaner AvailableLLMProvidersFunc="() => this.availableLLMProviders"/> <SettingsPanelAgentContentCleaner AvailableLLMProvidersFunc="@(() => this.availableLLMProviders)"/>
</MudExpansionPanels> </MudExpansionPanels>
</InnerScrolling> </InnerScrolling>
</div> </div>
@@ -9,6 +9,7 @@ public partial class Settings : MSGComponentBase
{ {
private List<ConfigurationSelectData<string>> availableLLMProviders = new(); private List<ConfigurationSelectData<string>> availableLLMProviders = new();
private List<ConfigurationSelectData<string>> availableEmbeddingProviders = new(); private List<ConfigurationSelectData<string>> availableEmbeddingProviders = new();
private List<ConfigurationSelectData<string>> availableTranscriptionProviders = new();
#region Overrides of ComponentBase #region Overrides of ComponentBase
@@ -53,11 +53,11 @@ CONFIG["LLM_PROVIDERS"][#CONFIG["LLM_PROVIDERS"]+1] = {
["Id"] = "00000000-0000-0000-0000-000000000000", ["Id"] = "00000000-0000-0000-0000-000000000000",
["InstanceName"] = "<user-friendly name for the combination of server and model>", ["InstanceName"] = "<user-friendly name for the combination of server and model>",
["UsedLLMProvider"] = "SELF_HOSTED", ["UsedLLMProvider"] = "SELF_HOSTED",
-- Allowed values for Host are: LM_STUDIO, LLAMACPP, OLLAMA, and VLLM -- Allowed values for Host are: LM_STUDIO, LLAMACPP, OLLAMA, and VLLM
["Host"] = "OLLAMA", ["Host"] = "OLLAMA",
["Hostname"] = "<https address of the server>", ["Hostname"] = "<https address of the server>",
-- Optional: Additional parameters for the API. -- Optional: Additional parameters for the API.
-- Please refer to the documentation of the selected host for details. -- Please refer to the documentation of the selected host for details.
-- Might be something like ... \"temperature\": 0.5 ... for one parameter. -- Might be something like ... \"temperature\": 0.5 ... for one parameter.
@@ -70,6 +70,42 @@ CONFIG["LLM_PROVIDERS"][#CONFIG["LLM_PROVIDERS"]+1] = {
} }
} }
-- Transcription providers for voice-to-text functionality:
CONFIG["TRANSCRIPTION_PROVIDERS"] = {}
-- An example of a transcription provider configuration:
-- CONFIG["TRANSCRIPTION_PROVIDERS"][#CONFIG["TRANSCRIPTION_PROVIDERS"]+1] = {
-- ["Id"] = "00000000-0000-0000-0000-000000000000",
-- ["Name"] = "<user-friendly name for the transcription provider>",
-- ["UsedLLMProvider"] = "SELF_HOSTED",
--
-- -- Allowed values for Host are: LM_STUDIO, LLAMACPP, OLLAMA, VLLM, and WHISPER_CPP
-- ["Host"] = "WHISPER_CPP",
-- ["Hostname"] = "<https address of the server>",
-- ["Model"] = {
-- ["Id"] = "<the model ID>",
-- ["DisplayName"] = "<user-friendly name of the model>",
-- }
-- }
-- Embedding providers for local RAG (Retrieval-Augmented Generation) functionality:
CONFIG["EMBEDDING_PROVIDERS"] = {}
-- An example of an embedding provider configuration:
-- CONFIG["EMBEDDING_PROVIDERS"][#CONFIG["EMBEDDING_PROVIDERS"]+1] = {
-- ["Id"] = "00000000-0000-0000-0000-000000000000",
-- ["Name"] = "<user-friendly name for the embedding provider>",
-- ["UsedLLMProvider"] = "SELF_HOSTED",
--
-- -- Allowed values for Host are: LM_STUDIO, LLAMACPP, OLLAMA, and VLLM
-- ["Host"] = "OLLAMA",
-- ["Hostname"] = "<https address of the server>",
-- ["Model"] = {
-- ["Id"] = "<the model ID, e.g., nomic-embed-text>",
-- ["DisplayName"] = "<user-friendly name of the model>",
-- }
-- }
CONFIG["SETTINGS"] = {} CONFIG["SETTINGS"] = {}
-- Configure the update check interval: -- Configure the update check interval:
@@ -101,6 +137,12 @@ CONFIG["SETTINGS"] = {}
-- Please note: using an empty string ("") will lock the preselected profile selection, even though no valid preselected profile is found. -- Please note: using an empty string ("") will lock the preselected profile selection, even though no valid preselected profile is found.
-- CONFIG["SETTINGS"]["DataApp.PreselectedProfile"] = "00000000-0000-0000-0000-000000000000" -- CONFIG["SETTINGS"]["DataApp.PreselectedProfile"] = "00000000-0000-0000-0000-000000000000"
-- Configure the transcription provider for voice-to-text functionality.
-- It must be one of the transcription provider IDs defined in CONFIG["TRANSCRIPTION_PROVIDERS"].
-- Without a selected transcription provider, dictation and transcription features will be disabled.
-- Please note: using an empty string ("") will lock the selection and disable dictation/transcription.
-- CONFIG["SETTINGS"]["DataApp.UseTranscriptionProvider"] = "00000000-0000-0000-0000-000000000000"
-- Example chat templates for this configuration: -- Example chat templates for this configuration:
CONFIG["CHAT_TEMPLATES"] = {} CONFIG["CHAT_TEMPLATES"] = {}
@@ -125,6 +167,33 @@ CONFIG["CHAT_TEMPLATES"][#CONFIG["CHAT_TEMPLATES"]+1] = {
} }
} }
-- An example chat template with file attachments:
-- This template automatically attaches specified files when the user selects it.
CONFIG["CHAT_TEMPLATES"][#CONFIG["CHAT_TEMPLATES"]+1] = {
["Id"] = "00000000-0000-0000-0000-000000000001",
["Name"] = "Document Analysis Template",
["SystemPrompt"] = "You are an expert document analyst. Please analyze the attached documents and provide insights.",
["PredefinedUserPrompt"] = "Please analyze the attached company guidelines and summarize the key points.",
["AllowProfileUsage"] = true,
-- Optional: Pre-attach files that will be automatically included when using this template.
-- These files will be loaded when the user selects this chat template.
-- Note: File paths must be absolute paths and accessible to all users.
["FileAttachments"] = {
"G:\\Company\\Documents\\Guidelines.pdf",
"G:\\Company\\Documents\\CompanyPolicies.docx"
},
["ExampleConversation"] = {
{
["Role"] = "USER",
["Content"] = "I have attached the company documents for analysis."
},
{
["Role"] = "AI",
["Content"] = "Thank you. I'll analyze the documents and provide a comprehensive summary."
}
}
}
-- Profiles for this configuration: -- Profiles for this configuration:
CONFIG["PROFILES"] = {} CONFIG["PROFILES"] = {}
@@ -2025,6 +2025,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1599198973"]
-- 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. -- 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"] = "Möchten Sie eines ihrer Profile als Standard für die gesamte App festlegen? Wenn Sie einem Assistenten ein anderes Profil zuweisen, hat dieses immer Vorrang." UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1666052109"] = "Möchten Sie eines ihrer Profile als Standard für die gesamte App festlegen? Wenn Sie einem Assistenten ein anderes Profil zuweisen, hat dieses immer Vorrang."
-- Select a transcription provider for transcribing your voice. Without a selected provider, dictation and transcription features will be disabled.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1834486728"] = "Wählen Sie für die Transkription Ihrer Stimme einen Anbieter für Transkriptionen aus. Ohne einen ausgewählten Anbieter wird die Diktier- und Transkriptions-Funktion deaktiviert."
-- Select the language behavior for the app. The default is to use the system language. You might want to choose a language manually? -- 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"] = "Wählen Sie das Sprachverhalten für die App aus. Standardmäßig wird die Systemsprache verwendet. Möchten Sie die Sprache manuell einstellen?" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T186780842"] = "Wählen Sie das Sprachverhalten für die App aus. Standardmäßig wird die Systemsprache verwendet. Möchten Sie die Sprache manuell einstellen?"
@@ -2037,6 +2040,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1898060643"]
-- Select the language for the app. -- Select the language for the app.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1907446663"] = "Wählen Sie die Sprache für die App aus." UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1907446663"] = "Wählen Sie die Sprache für die App aus."
-- Disable dictation and transcription
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T215381891"] = "Diktieren und Transkribieren deaktivieren"
-- Language behavior -- Language behavior
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2341504363"] = "Sprachverhalten" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2341504363"] = "Sprachverhalten"
@@ -2067,6 +2073,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T4004501229"]
-- 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. -- 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"] = "Wenn aktiviert, ist die Rechtschreibprüfung in allen Eingabefeldern aktiv. Je nach Betriebssystem werden Fehler möglicherweise nicht visuell hervorgehoben, aber ein Rechtsklick kann dennoch Korrekturvorschläge anzeigen." UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T4067492921"] = "Wenn aktiviert, ist die Rechtschreibprüfung in allen Eingabefeldern aktiv. Je nach Betriebssystem werden Fehler möglicherweise nicht visuell hervorgehoben, aber ein Rechtsklick kann dennoch Korrekturvorschläge anzeigen."
-- Select a transcription provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T4174666315"] = "Wählen Sie einen Transkriptionsanbieter aus"
-- Navigation bar behavior -- Navigation bar behavior
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T602293588"] = "Verhalten der Navigationsleiste" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T602293588"] = "Verhalten der Navigationsleiste"
@@ -2109,24 +2118,30 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T24199
-- Name -- Name
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T266367750"] = "Name" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T266367750"] = "Name"
-- Configured Embedding Providers
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T305753126"] = "Konfigurierte Anbieter für Einbettungen"
-- This helps AI Studio understand and compare things in a way that's similar to how humans do. When you're working on something, AI Studio can automatically identify related documents and data by comparing their digital fingerprints. For instance, if you're writing about customer service, AI Studio can instantly find other documents in your data that discuss similar topics or experiences, even if they use different words. -- This helps AI Studio understand and compare things in a way that's similar to how humans do. When you're working on something, AI Studio can automatically identify related documents and data by comparing their digital fingerprints. For instance, if you're writing about customer service, AI Studio can instantly find other documents in your data that discuss similar topics or experiences, even if they use different words.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T3251217940"] = "Dies hilft AI Studio, Dinge auf eine Art und Weise zu verstehen und zu vergleichen, die der menschlichen Denkweise ähnelt. Wenn Sie an etwas arbeiten, kann AI Studio automatisch verwandte Dokumente und Daten erkennen, indem es ihre digitalen Fingerabdrücke vergleicht. Wenn Sie zum Beispiel über Kundenservice schreiben, kann AI Studio sofort andere Dokumente in ihren Daten finden, die über ähnliche Themen oder Erfahrungen sprechen – selbst wenn sie andere Begriffe verwenden." UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T3251217940"] = "Dies hilft AI Studio, Dinge auf eine Art und Weise zu verstehen und zu vergleichen, die der menschlichen Denkweise ähnelt. Wenn Sie an etwas arbeiten, kann AI Studio automatisch verwandte Dokumente und Daten erkennen, indem es ihre digitalen Fingerabdrücke vergleicht. Wenn Sie zum Beispiel über Kundenservice schreiben, kann AI Studio sofort andere Dokumente in ihren Daten finden, die über ähnliche Themen oder Erfahrungen sprechen – selbst wenn sie andere Begriffe verwenden."
-- Edit -- Edit
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T3267849393"] = "Bearbeiten" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T3267849393"] = "Bearbeiten"
-- Configured Embeddings
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T3526613453"] = "Konfigurierte Einbettungen"
-- Actions -- Actions
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T3865031940"] = "Aktionen" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T3865031940"] = "Aktionen"
-- This embedding provider is managed by your organization.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T4062656589"] = "Dieser Einbettungsanbieter wird von Ihrer Organisation verwaltet."
-- No embeddings configured yet. -- No embeddings configured yet.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T4068015588"] = "Es wurden bislang keine Einbettungen konfiguriert." UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T4068015588"] = "Es wurden bislang keine Einbettungen konfiguriert."
-- Edit Embedding Provider -- Edit Embedding Provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T4264602229"] = "Einbettungsanbieter bearbeiten" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T4264602229"] = "Einbettungsanbieter bearbeiten"
-- Configure Embedding Providers
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T488419116"] = "Anbieter für Einbettungen konfigurieren"
-- Delete Embedding Provider -- Delete Embedding Provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T511304264"] = "Einbettungsanbieter löschen" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T511304264"] = "Einbettungsanbieter löschen"
@@ -2136,9 +2151,6 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T78223
-- Provider -- Provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T900237532"] = "Anbieter" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T900237532"] = "Anbieter"
-- Configure Embeddings
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T970042679"] = "Einbettungen konfigurieren"
-- Show provider's confidence level? -- Show provider's confidence level?
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1052533048"] = "Anzeigen, wie sicher sich der Anbieter ist?" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1052533048"] = "Anzeigen, wie sicher sich der Anbieter ist?"
@@ -2157,6 +2169,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T172585
-- Add Provider -- Add Provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1806589097"] = "Anbieter hinzufügen" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1806589097"] = "Anbieter hinzufügen"
-- Configure LLM Providers
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1810190350"] = "Anbieter für LLM konfigurieren"
-- Edit LLM Provider -- Edit LLM Provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1868766523"] = "LLM-Anbieter bearbeiten" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1868766523"] = "LLM-Anbieter bearbeiten"
@@ -2190,8 +2205,8 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T284206
-- No providers configured yet. -- No providers configured yet.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T2911731076"] = "Noch keine Anbieter konfiguriert." UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T2911731076"] = "Noch keine Anbieter konfiguriert."
-- Configure Providers -- Configured LLM Providers
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3027859089"] = "Anbieter konfigurieren" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3019870540"] = "Konfigurierte Anbieter für LLM"
-- as selected by provider -- as selected by provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3082210376"] = "wie vom Anbieter ausgewählt" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3082210376"] = "wie vom Anbieter ausgewählt"
@@ -2214,9 +2229,6 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T361241
-- No, do not enforce a minimum confidence level -- No, do not enforce a minimum confidence level
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3642102079"] = "Nein, kein Mindestvertrauensniveau erzwingen" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3642102079"] = "Nein, kein Mindestvertrauensniveau erzwingen"
-- Configured Providers
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3850871263"] = "Konfigurierte Anbieter"
-- Actions -- Actions
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3865031940"] = "Aktionen" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3865031940"] = "Aktionen"
@@ -2244,6 +2256,57 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T853225
-- Provider -- Provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T900237532"] = "Anbieter" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T900237532"] = "Anbieter"
-- No transcription provider configured yet.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T1079350363"] = "Es ist bisher kein Anbieter für Transkriptionen konfiguriert."
-- Edit Transcription Provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T1317362918"] = "Anbieter für Transkriptionen bearbeiten"
-- Delete
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T1469573738"] = "Löschen"
-- Add transcription provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T1645238629"] = "Anbieter für Transkriptionen hinzufügen"
-- Add Transcription Provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T2066315685"] = "Anbieter für Transkriptionen hinzufügen"
-- Model
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T2189814010"] = "Modell"
-- Name
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T266367750"] = "Name"
-- Edit
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T3267849393"] = "Bearbeiten"
-- Delete Transcription Provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T370103955"] = "Anbieter für Transkriptionen löschen"
-- Actions
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T3865031940"] = "Aktionen"
-- Configure Transcription Providers
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T4073110625"] = "Anbieter für Transkriptionen konfigurieren"
-- Configured Transcription Providers
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T4210863523"] = "Konfigurierte Anbieter für Transkriptionen"
-- This transcription provider is managed by your organization.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T756131076"] = "Dieser Anbieter für Transkriptionen wird von Ihrer Organisation verwaltet."
-- Open Dashboard
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T78223861"] = "Dashboard öffnen"
-- Are you sure you want to delete the transcription provider '{0}'?
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T789660305"] = "Möchten Sie den Anbieter für Transkriptionen „{0}“ wirklich löschen?"
-- With the support of transcription models, MindWork AI Studio can convert human speech into text. This is useful, for example, when you need to dictate text. You can choose from dedicated transcription models, but not multimodal LLMs (large language models) that can handle both speech and text. The configuration of multimodal models is done in the \"Configure providers\" section.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T584860404"] = "Mit Unterstützung von Modellen für Transkriptionen kann MindWork AI Studio menschliche Sprache in Text umwandeln. Das ist zum Beispiel hilfreich, wenn Sie Texte diktieren möchten. Sie können aus speziellen Modellen für Transkriptionen wählen, jedoch nicht aus multimodalen LLMs (Large Language Models), die sowohl Sprache als auch Text verarbeiten können. Die Einrichtung multimodaler Modelle erfolgt im Abschnitt „Anbieter für LLM konfigurieren“."
-- Provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T900237532"] = "Anbieter"
-- Copy {0} to the clipboard -- Copy {0} to the clipboard
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TEXTINFOLINE::T2206391442"] = "Kopiere {0} in die Zwischenablage" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TEXTINFOLINE::T2206391442"] = "Kopiere {0} in die Zwischenablage"
@@ -2325,6 +2388,33 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T428040679"] = "Erstellung von In
-- Useful assistants -- Useful assistants
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T586430036"] = "Nützliche Assistenten" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T586430036"] = "Nützliche Assistenten"
-- Failed to create the transcription provider.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VOICERECORDER::T1689988905"] = "Der Anbieter für die Transkription konnte nicht erstellt werden."
-- Stop recording and start transcription
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VOICERECORDER::T224155287"] = "Aufnahme beenden und Transkription starten"
-- Start recording your voice for a transcription
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VOICERECORDER::T2372624045"] = "Beginnen Sie mit der Aufnahme Ihrer Stimme für eine Transkription"
-- Transcription in progress...
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VOICERECORDER::T2851219233"] = "Transkription läuft …"
-- The configured transcription provider was not found.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VOICERECORDER::T331613105"] = "Der konfigurierte Anbieter für die Transkription wurde nicht gefunden."
-- The configured transcription provider does not meet the minimum confidence level.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VOICERECORDER::T3834149033"] = "Der konfigurierte Anbieter für die Transkription erfüllt nicht das erforderliche Mindestmaß an Vertrauenswürdigkeit."
-- An error occurred during transcription.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VOICERECORDER::T588743762"] = "Während der Transkription ist ein Fehler aufgetreten."
-- No transcription provider is configured.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VOICERECORDER::T663630295"] = "Es ist kein Anbieter für die Transkription konfiguriert."
-- The transcription result is empty.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VOICERECORDER::T974954792"] = "Das Ergebnis der Transkription ist leer."
-- Are you sure you want to delete the chat '{0}' in the workspace '{1}'? -- Are you sure you want to delete the chat '{0}' in the workspace '{1}'?
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1016188706"] = "Möchten Sie den Chat „{0}“ im Arbeitsbereich „{1}“ wirklich löschen?" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1016188706"] = "Möchten Sie den Chat „{0}“ im Arbeitsbereich „{1}“ wirklich löschen?"
@@ -2445,6 +2535,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2147062613"] = "Profiln
-- Add messages of an example conversation (user prompt followed by assistant prompt) to demonstrate the desired interaction pattern. These examples help the AI understand your expectations by showing it the correct format, style, and content of responses before it receives actual user inputs. -- Add messages of an example conversation (user prompt followed by assistant prompt) to demonstrate the desired interaction pattern. These examples help the AI understand your expectations by showing it the correct format, style, and content of responses before it receives actual user inputs.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2292424657"] = "Fügen Sie Nachrichten einer Beispiel-Konversation hinzu (Nutzereingabe, gefolgt von einer Antwort des Assistenten), um das gewünschte Interaktionsmuster zu demonstrieren. Diese Beispiele helfen der KI, ihre Erwartungen zu verstehen, indem Sie das korrekte Format, den Stil und den Inhalt von Antworten zeigen, bevor tatsächliche Nutzereingaben erfolgen." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2292424657"] = "Fügen Sie Nachrichten einer Beispiel-Konversation hinzu (Nutzereingabe, gefolgt von einer Antwort des Assistenten), um das gewünschte Interaktionsmuster zu demonstrieren. Diese Beispiele helfen der KI, ihre Erwartungen zu verstehen, indem Sie das korrekte Format, den Stil und den Inhalt von Antworten zeigen, bevor tatsächliche Nutzereingaben erfolgen."
-- File Attachments
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2294745309"] = "Dateianhänge"
-- Role -- Role
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2418769465"] = "Rolle" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2418769465"] = "Rolle"
@@ -2484,6 +2577,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3016903701"] = "Der Nam
-- Image content -- Image content
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3094908719"] = "Bildinhalt" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3094908719"] = "Bildinhalt"
-- You can attach files that will be automatically included when using this chat template. These files will be added to the first message sent in any chat using this template.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3108503534"] = "Sie können Dateien anhängen, die bei Verwendung dieser Chat-Vorlage automatisch einbezogen werden. Diese Dateien werden der ersten Nachricht hinzugefügt, die in einem Chat gesendet wird, der diese Vorlage verwendet."
-- Are you unsure which system prompt to use? You might start with the default system prompt that AI Studio uses for all chats. -- Are you unsure which system prompt to use? You might start with the default system prompt that AI Studio uses for all chats.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3127437308"] = "Sind Sie unsicher, welchen System-Prompt Sie verwenden sollen? Sie können mit dem Standard-System-Prompt beginnen, den AI Studio für alle Chats verwendet." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3127437308"] = "Sind Sie unsicher, welchen System-Prompt Sie verwenden sollen? Sie können mit dem Standard-System-Prompt beginnen, den AI Studio für alle Chats verwendet."
@@ -3096,9 +3192,6 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2189814010"] = "Mo
-- (Optional) API Key -- (Optional) API Key
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2331453405"] = "(Optional) API-Schlüssel" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2331453405"] = "(Optional) API-Schlüssel"
-- Currently, we cannot query the embedding models of self-hosted systems. Therefore, enter the model name manually.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2615586687"] = "Derzeit können wir die Einbettungs-Modelle von selbst gehosteten Systemen nicht abfragen. Bitte geben Sie daher den Modellnamen manuell ein."
-- Add -- Add
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2646845972"] = "Hinzufügen" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2646845972"] = "Hinzufügen"
@@ -3108,6 +3201,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2810182573"] = "Ke
-- Instance Name -- Instance Name
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2842060373"] = "Instanzname" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2842060373"] = "Instanzname"
-- Currently, we cannot query the embedding models for the selected provider and/or host. Therefore, please enter the model name manually.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T290547799"] = "Derzeit können wir die Einbettungs-Modelle für den ausgewählten Anbieter und/oder Host nicht abfragen. Bitte geben Sie daher den Modellnamen manuell ein."
-- Model selection -- Model selection
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T416738168"] = "Modellauswahl" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T416738168"] = "Modellauswahl"
@@ -3318,6 +3414,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3361153305"] = "Experten-Ei
-- Show available models -- Show available models
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3763891899"] = "Verfügbare Modelle anzeigen" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3763891899"] = "Verfügbare Modelle anzeigen"
-- Currently, we cannot query the models for the selected provider and/or host. Therefore, please enter the model name manually.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T4116737656"] = "Derzeit können wir die Modelle für den ausgewählten Anbieter und/oder Host nicht abfragen. Bitte geben Sie daher den Modellnamen manuell ein."
-- Model selection -- Model selection
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T416738168"] = "Modellauswahl" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T416738168"] = "Modellauswahl"
@@ -4494,6 +4593,60 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SINGLEINPUTDIALOG::T4030229154"] = "Ihre Ein
-- Cancel -- Cancel
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SINGLEINPUTDIALOG::T900713019"] = "Abbrechen" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SINGLEINPUTDIALOG::T900713019"] = "Abbrechen"
-- Failed to store the API key in the operating system. The message was: {0}. Please try again.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1122745046"] = "Der API-Schlüssel konnte nicht im Betriebssystem gespeichert werden. Die Meldung lautete: '{0}'. Bitte versuchen Sie es erneut."
-- API Key
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1324664716"] = "API-Schlüssel"
-- Create account
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1356621346"] = "Konto erstellen"
-- Currently, we cannot query the transcription models for the selected provider and/or host. Therefore, please enter the model name manually.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1381635232"] = "Derzeit können wir die Modelle für Transkriptionen für den ausgewählten Anbieter und/oder Host nicht abfragen. Bitte geben Sie daher den Modellnamen manuell ein."
-- Hostname
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1727440780"] = "Hostname"
-- Load
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1756340745"] = "Laden"
-- Update
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1847791252"] = "Aktualisieren"
-- 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::TRANSCRIPTIONPROVIDERDIALOG::T1870831108"] = "Der API-Schlüssel konnte nicht aus dem Betriebssystem geladen werden. Die Meldung lautete: '{0}'. Sie können diese Meldung ignorieren und den API-Schlüssel erneut eingeben."
-- Model
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T2189814010"] = "Modell"
-- (Optional) API Key
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T2331453405"] = "(Optional) API-Schlüssel"
-- Add
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T2646845972"] = "Hinzufügen"
-- No models loaded or available.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T2810182573"] = "Keine Modelle geladen oder verfügbar."
-- Instance Name
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T2842060373"] = "Instanzname"
-- Please enter a transcription model name.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T3703662664"] = "Bitte geben Sie den Namen eines Transkriptionsmodells ein."
-- Model selection
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T416738168"] = "Modellauswahl"
-- Host
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T808120719"] = "Host"
-- Provider
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T900237532"] = "Anbieter"
-- Cancel
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T900713019"] = "Abbrechen"
-- Install now -- Install now
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::UPDATEDIALOG::T2366359512"] = "Jetzt installieren" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::UPDATEDIALOG::T2366359512"] = "Jetzt installieren"
@@ -5196,6 +5349,9 @@ UI_TEXT_CONTENT["AISTUDIO::PROVIDER::LLMPROVIDERSEXTENSIONS::T3424652889"] = "Un
-- no model selected -- no model selected
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::MODEL::T2234274832"] = "Kein Modell ausgewählt" UI_TEXT_CONTENT["AISTUDIO::PROVIDER::MODEL::T2234274832"] = "Kein Modell ausgewählt"
-- Model as configured by whisper.cpp
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::SELFHOSTED::PROVIDERSELFHOSTED::T3313940770"] = "Modell wie in whisper.cpp konfiguriert"
-- Use no chat template -- Use no chat template
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CHATTEMPLATE::T4258819635"] = "Keine Chat-Vorlage verwenden" UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CHATTEMPLATE::T4258819635"] = "Keine Chat-Vorlage verwenden"
@@ -5370,6 +5526,9 @@ UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::PREVIEWFEATURESEXTENSIONS::T2708
-- Unknown preview feature -- Unknown preview feature
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::PREVIEWFEATURESEXTENSIONS::T2722827307"] = "Unbekannte Vorschau-Funktion" UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::PREVIEWFEATURESEXTENSIONS::T2722827307"] = "Unbekannte Vorschau-Funktion"
-- Transcription: Preview of our speech to text system where you can transcribe recordings and audio files into text
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::PREVIEWFEATURESEXTENSIONS::T714355911"] = "Transkription: Vorschau unseres Sprache-zu-Text-Systems, mit dem Sie Aufnahmen und Audiodateien in Text transkribieren können"
-- Use no data sources, when sending an assistant result to a chat -- Use no data sources, when sending an assistant result to a chat
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::SENDTOCHATDATASOURCEBEHAVIOREXTENSIONS::T1223925477"] = "Keine Datenquellen vorauswählen, wenn ein Ergebnis von einem Assistenten an einen neuen Chat gesendet wird" UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::SENDTOCHATDATASOURCEBEHAVIOREXTENSIONS::T1223925477"] = "Keine Datenquellen vorauswählen, wenn ein Ergebnis von einem Assistenten an einen neuen Chat gesendet wird"
@@ -5934,6 +6093,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::RAG::RAGPROCESSES::AISRCSELWITHRETCTXVAL::T377
-- Executable Files -- Executable Files
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T2217313358"] = "Ausführbare Dateien" UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T2217313358"] = "Ausführbare Dateien"
-- All Source Code Files
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T2460199369"] = "Alle Quellcodedateien"
-- All Audio Files -- All Audio Files
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T2575722901"] = "Alle Audiodateien" UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T2575722901"] = "Alle Audiodateien"
@@ -2025,6 +2025,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1599198973"]
-- 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. -- 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." 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 a transcription provider for transcribing your voice. Without a selected provider, dictation and transcription features will be disabled.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1834486728"] = "Select a transcription provider for transcribing your voice. Without a selected provider, dictation and transcription features will be disabled."
-- Select the language behavior for the app. The default is to use the system language. You might want to choose a language manually? -- 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?" 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?"
@@ -2037,6 +2040,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1898060643"]
-- Select the language for the app. -- Select the language for the app.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1907446663"] = "Select the language for the app." UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1907446663"] = "Select the language for the app."
-- Disable dictation and transcription
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T215381891"] = "Disable dictation and transcription"
-- Language behavior -- Language behavior
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2341504363"] = "Language behavior" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2341504363"] = "Language behavior"
@@ -2067,6 +2073,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T4004501229"]
-- 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. -- 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." 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."
-- Select a transcription provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T4174666315"] = "Select a transcription provider"
-- Navigation bar behavior -- Navigation bar behavior
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T602293588"] = "Navigation bar behavior" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T602293588"] = "Navigation bar behavior"
@@ -2109,24 +2118,30 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T24199
-- Name -- Name
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T266367750"] = "Name" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T266367750"] = "Name"
-- Configured Embedding Providers
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T305753126"] = "Configured Embedding Providers"
-- This helps AI Studio understand and compare things in a way that's similar to how humans do. When you're working on something, AI Studio can automatically identify related documents and data by comparing their digital fingerprints. For instance, if you're writing about customer service, AI Studio can instantly find other documents in your data that discuss similar topics or experiences, even if they use different words. -- This helps AI Studio understand and compare things in a way that's similar to how humans do. When you're working on something, AI Studio can automatically identify related documents and data by comparing their digital fingerprints. For instance, if you're writing about customer service, AI Studio can instantly find other documents in your data that discuss similar topics or experiences, even if they use different words.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T3251217940"] = "This helps AI Studio understand and compare things in a way that's similar to how humans do. When you're working on something, AI Studio can automatically identify related documents and data by comparing their digital fingerprints. For instance, if you're writing about customer service, AI Studio can instantly find other documents in your data that discuss similar topics or experiences, even if they use different words." UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T3251217940"] = "This helps AI Studio understand and compare things in a way that's similar to how humans do. When you're working on something, AI Studio can automatically identify related documents and data by comparing their digital fingerprints. For instance, if you're writing about customer service, AI Studio can instantly find other documents in your data that discuss similar topics or experiences, even if they use different words."
-- Edit -- Edit
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T3267849393"] = "Edit" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T3267849393"] = "Edit"
-- Configured Embeddings
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T3526613453"] = "Configured Embeddings"
-- Actions -- Actions
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T3865031940"] = "Actions" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T3865031940"] = "Actions"
-- This embedding provider is managed by your organization.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T4062656589"] = "This embedding provider is managed by your organization."
-- No embeddings configured yet. -- No embeddings configured yet.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T4068015588"] = "No embeddings configured yet." UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T4068015588"] = "No embeddings configured yet."
-- Edit Embedding Provider -- Edit Embedding Provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T4264602229"] = "Edit Embedding Provider" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T4264602229"] = "Edit Embedding Provider"
-- Configure Embedding Providers
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T488419116"] = "Configure Embedding Providers"
-- Delete Embedding Provider -- Delete Embedding Provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T511304264"] = "Delete Embedding Provider" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T511304264"] = "Delete Embedding Provider"
@@ -2136,9 +2151,6 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T78223
-- Provider -- Provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T900237532"] = "Provider" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T900237532"] = "Provider"
-- Configure Embeddings
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T970042679"] = "Configure Embeddings"
-- Show provider's confidence level? -- Show provider's confidence level?
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1052533048"] = "Show provider's confidence level?" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1052533048"] = "Show provider's confidence level?"
@@ -2157,6 +2169,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T172585
-- Add Provider -- Add Provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1806589097"] = "Add Provider" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1806589097"] = "Add Provider"
-- Configure LLM Providers
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1810190350"] = "Configure LLM Providers"
-- Edit LLM Provider -- Edit LLM Provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1868766523"] = "Edit LLM Provider" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1868766523"] = "Edit LLM Provider"
@@ -2190,8 +2205,8 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T284206
-- No providers configured yet. -- No providers configured yet.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T2911731076"] = "No providers configured yet." UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T2911731076"] = "No providers configured yet."
-- Configure Providers -- Configured LLM Providers
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3027859089"] = "Configure Providers" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3019870540"] = "Configured LLM Providers"
-- as selected by provider -- as selected by provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3082210376"] = "as selected by provider" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3082210376"] = "as selected by provider"
@@ -2214,9 +2229,6 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T361241
-- No, do not enforce a minimum confidence level -- No, do not enforce a minimum confidence level
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3642102079"] = "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 -- Actions
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3865031940"] = "Actions" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3865031940"] = "Actions"
@@ -2244,6 +2256,57 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T853225
-- Provider -- Provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T900237532"] = "Provider" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T900237532"] = "Provider"
-- No transcription provider configured yet.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T1079350363"] = "No transcription provider configured yet."
-- Edit Transcription Provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T1317362918"] = "Edit Transcription Provider"
-- Delete
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T1469573738"] = "Delete"
-- Add transcription provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T1645238629"] = "Add transcription provider"
-- Add Transcription Provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T2066315685"] = "Add Transcription Provider"
-- Model
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T2189814010"] = "Model"
-- Name
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T266367750"] = "Name"
-- Edit
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T3267849393"] = "Edit"
-- Delete Transcription Provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T370103955"] = "Delete Transcription Provider"
-- Actions
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T3865031940"] = "Actions"
-- Configure Transcription Providers
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T4073110625"] = "Configure Transcription Providers"
-- Configured Transcription Providers
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T4210863523"] = "Configured Transcription Providers"
-- With the support of transcription models, MindWork AI Studio can convert human speech into text. This is useful, for example, when you need to dictate text. You can choose from dedicated transcription models, but not multimodal LLMs (large language models) that can handle both speech and text. The configuration of multimodal models is done in the 'Configure providers' section.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T584860404"] = "With the support of transcription models, MindWork AI Studio can convert human speech into text. This is useful, for example, when you need to dictate text. You can choose from dedicated transcription models, but not multimodal LLMs (large language models) that can handle both speech and text. The configuration of multimodal models is done in the 'Configure LLM providers' section."
-- This transcription provider is managed by your organization.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T756131076"] = "This transcription provider is managed by your organization."
-- Open Dashboard
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T78223861"] = "Open Dashboard"
-- Are you sure you want to delete the transcription provider '{0}'?
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T789660305"] = "Are you sure you want to delete the transcription provider '{0}'?"
-- Provider
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T900237532"] = "Provider"
-- Copy {0} to the clipboard -- Copy {0} to the clipboard
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TEXTINFOLINE::T2206391442"] = "Copy {0} to the clipboard" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TEXTINFOLINE::T2206391442"] = "Copy {0} to the clipboard"
@@ -2325,6 +2388,33 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T428040679"] = "Content creation"
-- Useful assistants -- Useful assistants
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T586430036"] = "Useful assistants" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T586430036"] = "Useful assistants"
-- Failed to create the transcription provider.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VOICERECORDER::T1689988905"] = "Failed to create the transcription provider."
-- Stop recording and start transcription
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VOICERECORDER::T224155287"] = "Stop recording and start transcription"
-- Start recording your voice for a transcription
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VOICERECORDER::T2372624045"] = "Start recording your voice for a transcription"
-- Transcription in progress...
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VOICERECORDER::T2851219233"] = "Transcription in progress..."
-- The configured transcription provider was not found.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VOICERECORDER::T331613105"] = "The configured transcription provider was not found."
-- The configured transcription provider does not meet the minimum confidence level.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VOICERECORDER::T3834149033"] = "The configured transcription provider does not meet the minimum confidence level."
-- An error occurred during transcription.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VOICERECORDER::T588743762"] = "An error occurred during transcription."
-- No transcription provider is configured.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VOICERECORDER::T663630295"] = "No transcription provider is configured."
-- The transcription result is empty.
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VOICERECORDER::T974954792"] = "The transcription result is empty."
-- Are you sure you want to delete the chat '{0}' in the workspace '{1}'? -- 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}'?" UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1016188706"] = "Are you sure you want to delete the chat '{0}' in the workspace '{1}'?"
@@ -2445,6 +2535,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2147062613"] = "Profile
-- Add messages of an example conversation (user prompt followed by assistant prompt) to demonstrate the desired interaction pattern. These examples help the AI understand your expectations by showing it the correct format, style, and content of responses before it receives actual user inputs. -- Add messages of an example conversation (user prompt followed by assistant prompt) to demonstrate the desired interaction pattern. These examples help the AI understand your expectations by showing it the correct format, style, and content of responses before it receives actual user inputs.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2292424657"] = "Add messages of an example conversation (user prompt followed by assistant prompt) to demonstrate the desired interaction pattern. These examples help the AI understand your expectations by showing it the correct format, style, and content of responses before it receives actual user inputs." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2292424657"] = "Add messages of an example conversation (user prompt followed by assistant prompt) to demonstrate the desired interaction pattern. These examples help the AI understand your expectations by showing it the correct format, style, and content of responses before it receives actual user inputs."
-- File Attachments
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2294745309"] = "File Attachments"
-- Role -- Role
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2418769465"] = "Role" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2418769465"] = "Role"
@@ -2484,6 +2577,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3016903701"] = "The nam
-- Image content -- Image content
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3094908719"] = "Image content" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3094908719"] = "Image content"
-- You can attach files that will be automatically included when using this chat template. These files will be added to the first message sent in any chat using this template.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3108503534"] = "You can attach files that will be automatically included when using this chat template. These files will be added to the first message sent in any chat using this template."
-- Are you unsure which system prompt to use? You might start with the default system prompt that AI Studio uses for all chats. -- Are you unsure which system prompt to use? You might start with the default system prompt that AI Studio uses for all chats.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3127437308"] = "Are you unsure which system prompt to use? You might start with the default system prompt that AI Studio uses for all chats." UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3127437308"] = "Are you unsure which system prompt to use? You might start with the default system prompt that AI Studio uses for all chats."
@@ -3096,9 +3192,6 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2189814010"] = "Mo
-- (Optional) API Key -- (Optional) API Key
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2331453405"] = "(Optional) API Key" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2331453405"] = "(Optional) API Key"
-- Currently, we cannot query the embedding models of self-hosted systems. Therefore, enter the model name manually.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2615586687"] = "Currently, we cannot query the embedding models of self-hosted systems. Therefore, enter the model name manually."
-- Add -- Add
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2646845972"] = "Add" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2646845972"] = "Add"
@@ -3108,6 +3201,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2810182573"] = "No
-- Instance Name -- Instance Name
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2842060373"] = "Instance Name" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2842060373"] = "Instance Name"
-- Currently, we cannot query the embedding models for the selected provider and/or host. Therefore, please enter the model name manually.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T290547799"] = "Currently, we cannot query the embedding models for the selected provider and/or host. Therefore, please enter the model name manually."
-- Model selection -- Model selection
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T416738168"] = "Model selection" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T416738168"] = "Model selection"
@@ -3318,6 +3414,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3361153305"] = "Show Expert
-- Show available models -- Show available models
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3763891899"] = "Show available models" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3763891899"] = "Show available models"
-- Currently, we cannot query the models for the selected provider and/or host. Therefore, please enter the model name manually.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T4116737656"] = "Currently, we cannot query the models for the selected provider and/or host. Therefore, please enter the model name manually."
-- Model selection -- Model selection
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T416738168"] = "Model selection" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T416738168"] = "Model selection"
@@ -4494,6 +4593,60 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SINGLEINPUTDIALOG::T4030229154"] = "Your Inp
-- Cancel -- Cancel
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SINGLEINPUTDIALOG::T900713019"] = "Cancel" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SINGLEINPUTDIALOG::T900713019"] = "Cancel"
-- Failed to store the API key in the operating system. The message was: {0}. Please try again.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::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::TRANSCRIPTIONPROVIDERDIALOG::T1324664716"] = "API Key"
-- Create account
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1356621346"] = "Create account"
-- Currently, we cannot query the transcription models for the selected provider and/or host. Therefore, please enter the model name manually.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1381635232"] = "Currently, we cannot query the transcription models for the selected provider and/or host. Therefore, please enter the model name manually."
-- Hostname
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1727440780"] = "Hostname"
-- Load
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T1756340745"] = "Load"
-- Update
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::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::TRANSCRIPTIONPROVIDERDIALOG::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."
-- Model
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T2189814010"] = "Model"
-- (Optional) API Key
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T2331453405"] = "(Optional) API Key"
-- Add
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T2646845972"] = "Add"
-- No models loaded or available.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T2810182573"] = "No models loaded or available."
-- Instance Name
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T2842060373"] = "Instance Name"
-- Please enter a transcription model name.
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T3703662664"] = "Please enter a transcription model name."
-- Model selection
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T416738168"] = "Model selection"
-- Host
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T808120719"] = "Host"
-- Provider
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T900237532"] = "Provider"
-- Cancel
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T900713019"] = "Cancel"
-- Install now -- Install now
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::UPDATEDIALOG::T2366359512"] = "Install now" UI_TEXT_CONTENT["AISTUDIO::DIALOGS::UPDATEDIALOG::T2366359512"] = "Install now"
@@ -5196,6 +5349,9 @@ UI_TEXT_CONTENT["AISTUDIO::PROVIDER::LLMPROVIDERSEXTENSIONS::T3424652889"] = "Un
-- no model selected -- no model selected
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::MODEL::T2234274832"] = "no model selected" UI_TEXT_CONTENT["AISTUDIO::PROVIDER::MODEL::T2234274832"] = "no model selected"
-- Model as configured by whisper.cpp
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::SELFHOSTED::PROVIDERSELFHOSTED::T3313940770"] = "Model as configured by whisper.cpp"
-- Use no chat template -- Use no chat template
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CHATTEMPLATE::T4258819635"] = "Use no chat template" UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CHATTEMPLATE::T4258819635"] = "Use no chat template"
@@ -5370,6 +5526,9 @@ UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::PREVIEWFEATURESEXTENSIONS::T2708
-- Unknown preview feature -- Unknown preview feature
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::PREVIEWFEATURESEXTENSIONS::T2722827307"] = "Unknown preview feature" UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::PREVIEWFEATURESEXTENSIONS::T2722827307"] = "Unknown preview feature"
-- Transcription: Preview of our speech to text system where you can transcribe recordings and audio files into text
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::PREVIEWFEATURESEXTENSIONS::T714355911"] = "Transcription: Preview of our speech to text system where you can transcribe recordings and audio files into text"
-- Use no data sources, when sending an assistant result to a chat -- Use no data sources, when sending an assistant result to a chat
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::SENDTOCHATDATASOURCEBEHAVIOREXTENSIONS::T1223925477"] = "Use no data sources, when sending an assistant result to a chat" UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::SENDTOCHATDATASOURCEBEHAVIOREXTENSIONS::T1223925477"] = "Use no data sources, when sending an assistant result to a chat"
@@ -5934,6 +6093,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::RAG::RAGPROCESSES::AISRCSELWITHRETCTXVAL::T377
-- Executable Files -- Executable Files
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T2217313358"] = "Executable Files" UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T2217313358"] = "Executable Files"
-- All Source Code Files
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T2460199369"] = "All Source Code Files"
-- All Audio Files -- All Audio Files
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T2575722901"] = "All Audio Files" UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T2575722901"] = "All Audio Files"
-1
View File
@@ -83,7 +83,6 @@ internal sealed class Program
} }
var builder = WebApplication.CreateBuilder(); var builder = WebApplication.CreateBuilder();
builder.WebHost.ConfigureKestrel(kestrelServerOptions => builder.WebHost.ConfigureKestrel(kestrelServerOptions =>
{ {
kestrelServerOptions.ConfigureEndpointDefaults(listenOptions => kestrelServerOptions.ConfigureEndpointDefaults(listenOptions =>
@@ -25,7 +25,7 @@ public sealed class ProviderAlibabaCloud() : BaseProvider(LLMProviders.ALIBABA_C
public override async IAsyncEnumerable<ContentStreamChunk> StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default) public override async IAsyncEnumerable<ContentStreamChunk> StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
{ {
// Get the API key: // Get the API key:
var requestedSecret = await RUST_SERVICE.GetAPIKey(this); var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.LLM_PROVIDER);
if(!requestedSecret.Success) if(!requestedSecret.Success)
yield break; yield break;
@@ -80,6 +80,12 @@ public sealed class ProviderAlibabaCloud() : BaseProvider(LLMProviders.ALIBABA_C
yield break; yield break;
} }
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
/// <inheritdoc />
public override Task<string> TranscribeAudioAsync(Model transcriptionModel, string audioFilePath, SettingsManager settingsManager, CancellationToken token = default)
{
return Task.FromResult(string.Empty);
}
/// <inheritdoc /> /// <inheritdoc />
public override Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) public override Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
@@ -111,7 +117,7 @@ public sealed class ProviderAlibabaCloud() : BaseProvider(LLMProviders.ALIBABA_C
new Model("qwen2.5-vl-3b-instruct", "Qwen2.5-VL 3b"), 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); return this.LoadModels(["q"], SecretStoreType.LLM_PROVIDER, token, apiKeyProvisional).ContinueWith(t => t.Result.Concat(additionalModels).OrderBy(x => x.Id).AsEnumerable(), token);
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -129,18 +135,27 @@ public sealed class ProviderAlibabaCloud() : BaseProvider(LLMProviders.ALIBABA_C
new Model("text-embedding-v3", "text-embedding-v3"), 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); return this.LoadModels(["text-embedding-"], SecretStoreType.EMBEDDING_PROVIDER, token, apiKeyProvisional).ContinueWith(t => t.Result.Concat(additionalModels).OrderBy(x => x.Id).AsEnumerable(), token);
} }
#region Overrides of BaseProvider
/// <inheritdoc />
public override Task<IEnumerable<Model>> GetTranscriptionModels(string? apiKeyProvisional = null, CancellationToken token = default)
{
return Task.FromResult(Enumerable.Empty<Model>());
}
#endregion
#endregion #endregion
private async Task<IEnumerable<Model>> LoadModels(string[] prefixes, CancellationToken token, string? apiKeyProvisional = null) private async Task<IEnumerable<Model>> LoadModels(string[] prefixes, SecretStoreType storeType, CancellationToken token, string? apiKeyProvisional = null)
{ {
var secretKey = apiKeyProvisional switch var secretKey = apiKeyProvisional switch
{ {
not null => apiKeyProvisional, not null => apiKeyProvisional,
_ => await RUST_SERVICE.GetAPIKey(this) switch _ => await RUST_SERVICE.GetAPIKey(this, storeType) switch
{ {
{ Success: true } result => await result.Secret.Decrypt(ENCRYPTION), { Success: true } result => await result.Secret.Decrypt(ENCRYPTION),
_ => null, _ => null,
@@ -23,7 +23,7 @@ public sealed class ProviderAnthropic() : BaseProvider(LLMProviders.ANTHROPIC, "
public override async IAsyncEnumerable<ContentStreamChunk> StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default) public override async IAsyncEnumerable<ContentStreamChunk> StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
{ {
// Get the API key: // Get the API key:
var requestedSecret = await RUST_SERVICE.GetAPIKey(this); var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.LLM_PROVIDER);
if(!requestedSecret.Success) if(!requestedSecret.Success)
yield break; yield break;
@@ -107,6 +107,12 @@ public sealed class ProviderAnthropic() : BaseProvider(LLMProviders.ANTHROPIC, "
yield break; yield break;
} }
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
/// <inheritdoc />
public override Task<string> TranscribeAudioAsync(Model transcriptionModel, string audioFilePath, SettingsManager settingsManager, CancellationToken token = default)
{
return Task.FromResult(string.Empty);
}
/// <inheritdoc /> /// <inheritdoc />
public override Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) public override Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
@@ -121,7 +127,7 @@ public sealed class ProviderAnthropic() : BaseProvider(LLMProviders.ANTHROPIC, "
new Model("claude-3-opus-latest", "Claude 3 Opus (Latest)"), new Model("claude-3-opus-latest", "Claude 3 Opus (Latest)"),
}; };
return this.LoadModels(token, apiKeyProvisional).ContinueWith(t => t.Result.Concat(additionalModels).OrderBy(x => x.Id).AsEnumerable(), token); return this.LoadModels(SecretStoreType.LLM_PROVIDER, token, apiKeyProvisional).ContinueWith(t => t.Result.Concat(additionalModels).OrderBy(x => x.Id).AsEnumerable(), token);
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -136,14 +142,20 @@ public sealed class ProviderAnthropic() : BaseProvider(LLMProviders.ANTHROPIC, "
return Task.FromResult(Enumerable.Empty<Model>()); return Task.FromResult(Enumerable.Empty<Model>());
} }
/// <inheritdoc />
public override Task<IEnumerable<Model>> GetTranscriptionModels(string? apiKeyProvisional = null, CancellationToken token = default)
{
return Task.FromResult(Enumerable.Empty<Model>());
}
#endregion #endregion
private async Task<IEnumerable<Model>> LoadModels(CancellationToken token, string? apiKeyProvisional = null) private async Task<IEnumerable<Model>> LoadModels(SecretStoreType storeType, CancellationToken token, string? apiKeyProvisional = null)
{ {
var secretKey = apiKeyProvisional switch var secretKey = apiKeyProvisional switch
{ {
not null => apiKeyProvisional, not null => apiKeyProvisional,
_ => await RUST_SERVICE.GetAPIKey(this) switch _ => await RUST_SERVICE.GetAPIKey(this, storeType) switch
{ {
{ Success: true } result => await result.Secret.Decrypt(ENCRYPTION), { Success: true } result => await result.Secret.Decrypt(ENCRYPTION),
_ => null, _ => null,
@@ -1,4 +1,5 @@
using System.Net; using System.Net;
using System.Net.Http.Headers;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Text.Json; using System.Text.Json;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
@@ -6,10 +7,15 @@ using System.Text.Json.Serialization;
using AIStudio.Chat; using AIStudio.Chat;
using AIStudio.Provider.Anthropic; using AIStudio.Provider.Anthropic;
using AIStudio.Provider.OpenAI; using AIStudio.Provider.OpenAI;
using AIStudio.Provider.SelfHosted;
using AIStudio.Settings; using AIStudio.Settings;
using AIStudio.Tools.MIME;
using AIStudio.Tools.PluginSystem; using AIStudio.Tools.PluginSystem;
using AIStudio.Tools.Rust;
using AIStudio.Tools.Services; using AIStudio.Tools.Services;
using Host = AIStudio.Provider.SelfHosted.Host;
namespace AIStudio.Provider; namespace AIStudio.Provider;
/// <summary> /// <summary>
@@ -89,6 +95,9 @@ public abstract class BaseProvider : IProvider, ISecretId
/// <inheritdoc /> /// <inheritdoc />
public abstract IAsyncEnumerable<ImageURL> StreamImageCompletion(Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, CancellationToken token = default); public abstract IAsyncEnumerable<ImageURL> StreamImageCompletion(Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, CancellationToken token = default);
/// <inheritdoc />
public abstract Task<string> TranscribeAudioAsync(Model transcriptionModel, string audioFilePath, SettingsManager settingsManager, CancellationToken token = default);
/// <inheritdoc /> /// <inheritdoc />
public abstract Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default); public abstract Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default);
@@ -97,6 +106,9 @@ public abstract class BaseProvider : IProvider, ISecretId
/// <inheritdoc /> /// <inheritdoc />
public abstract Task<IEnumerable<Model>> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default); public abstract Task<IEnumerable<Model>> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default);
/// <inheritdoc />
public abstract Task<IEnumerable<Model>> GetTranscriptionModels(string? apiKeyProvisional = null, CancellationToken token = default);
#endregion #endregion
@@ -533,6 +545,78 @@ public abstract class BaseProvider : IProvider, ISecretId
streamReader.Dispose(); streamReader.Dispose();
} }
protected async Task<string> PerformStandardTranscriptionRequest(RequestedSecret requestedSecret, Model transcriptionModel, string audioFilePath, Host host = Host.NONE, CancellationToken token = default)
{
try
{
using var form = new MultipartFormDataContent();
var mimeType = Builder.FromFilename(audioFilePath);
await using var fileStream = File.OpenRead(audioFilePath);
using var fileContent = new StreamContent(fileStream);
fileContent.Headers.ContentType = new MediaTypeHeaderValue(mimeType);
form.Add(fileContent, "file", Path.GetFileName(audioFilePath));
form.Add(new StringContent(transcriptionModel.Id), "model");
using var request = new HttpRequestMessage(HttpMethod.Post, host.TranscriptionURL());
request.Content = form;
// Handle the authorization header based on the provider:
switch (this.Provider)
{
case LLMProviders.SELF_HOSTED:
if(requestedSecret.Success)
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await requestedSecret.Secret.Decrypt(ENCRYPTION));
break;
case LLMProviders.FIREWORKS:
if(!requestedSecret.Success)
{
this.logger.LogError("No valid API key available for transcription request.");
return string.Empty;
}
request.Headers.Add("Authorization", await requestedSecret.Secret.Decrypt(ENCRYPTION));
break;
default:
if(!requestedSecret.Success)
{
this.logger.LogError("No valid API key available for transcription request.");
return string.Empty;
}
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await requestedSecret.Secret.Decrypt(ENCRYPTION));
break;
}
using var response = await this.httpClient.SendAsync(request, token);
var responseBody = response.Content.ReadAsStringAsync(token).Result;
if (!response.IsSuccessStatusCode)
{
this.logger.LogError("Transcription request failed with status code {ResponseStatusCode} and body: '{ResponseBody}'.", response.StatusCode, responseBody);
return string.Empty;
}
var transcriptionResponse = JsonSerializer.Deserialize<TranscriptionResponse>(responseBody, JSON_SERIALIZER_OPTIONS);
if(transcriptionResponse is null)
{
this.logger.LogError("Was not able to deserialize the transcription response.");
return string.Empty;
}
return transcriptionResponse.Text;
}
catch (Exception e)
{
this.logger.LogError("Failed to perform transcription request: '{Message}'.", e.Message);
return string.Empty;
}
}
/// <summary> /// <summary>
/// Parse and convert API parameters from a provided JSON string into a dictionary, /// Parse and convert API parameters from a provided JSON string into a dictionary,
/// optionally merging additional parameters and removing specific keys. /// optionally merging additional parameters and removing specific keys.
@@ -25,7 +25,7 @@ public sealed class ProviderDeepSeek() : BaseProvider(LLMProviders.DEEP_SEEK, "h
public override async IAsyncEnumerable<ContentStreamChunk> StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default) public override async IAsyncEnumerable<ContentStreamChunk> StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
{ {
// Get the API key: // Get the API key:
var requestedSecret = await RUST_SERVICE.GetAPIKey(this); var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.LLM_PROVIDER);
if(!requestedSecret.Success) if(!requestedSecret.Success)
yield break; yield break;
@@ -80,11 +80,17 @@ public sealed class ProviderDeepSeek() : BaseProvider(LLMProviders.DEEP_SEEK, "h
yield break; yield break;
} }
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
/// <inheritdoc />
public override Task<string> TranscribeAudioAsync(Model transcriptionModel, string audioFilePath, SettingsManager settingsManager, CancellationToken token = default)
{
return Task.FromResult(string.Empty);
}
/// <inheritdoc /> /// <inheritdoc />
public override Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) public override Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
{ {
return this.LoadModels(token, apiKeyProvisional); return this.LoadModels(SecretStoreType.LLM_PROVIDER, token, apiKeyProvisional);
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -99,15 +105,20 @@ public sealed class ProviderDeepSeek() : BaseProvider(LLMProviders.DEEP_SEEK, "h
return Task.FromResult(Enumerable.Empty<Model>()); return Task.FromResult(Enumerable.Empty<Model>());
} }
/// <inheritdoc />
public override Task<IEnumerable<Model>> GetTranscriptionModels(string? apiKeyProvisional = null, CancellationToken token = default)
{
return Task.FromResult(Enumerable.Empty<Model>());
}
#endregion #endregion
private async Task<IEnumerable<Model>> LoadModels(CancellationToken token, string? apiKeyProvisional = null) private async Task<IEnumerable<Model>> LoadModels(SecretStoreType storeType, CancellationToken token, string? apiKeyProvisional = null)
{ {
var secretKey = apiKeyProvisional switch var secretKey = apiKeyProvisional switch
{ {
not null => apiKeyProvisional, not null => apiKeyProvisional,
_ => await RUST_SERVICE.GetAPIKey(this) switch _ => await RUST_SERVICE.GetAPIKey(this, storeType) switch
{ {
{ Success: true } result => await result.Secret.Decrypt(ENCRYPTION), { Success: true } result => await result.Secret.Decrypt(ENCRYPTION),
_ => null, _ => null,
@@ -25,7 +25,7 @@ public class ProviderFireworks() : BaseProvider(LLMProviders.FIREWORKS, "https:/
public override async IAsyncEnumerable<ContentStreamChunk> StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default) public override async IAsyncEnumerable<ContentStreamChunk> StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
{ {
// Get the API key: // Get the API key:
var requestedSecret = await RUST_SERVICE.GetAPIKey(this); var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.LLM_PROVIDER);
if(!requestedSecret.Success) if(!requestedSecret.Success)
yield break; yield break;
@@ -81,6 +81,13 @@ public class ProviderFireworks() : BaseProvider(LLMProviders.FIREWORKS, "https:/
yield break; yield break;
} }
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
/// <inheritdoc />
public override async Task<string> TranscribeAudioAsync(Model transcriptionModel, string audioFilePath, SettingsManager settingsManager, CancellationToken token = default)
{
var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.TRANSCRIPTION_PROVIDER);
return await this.PerformStandardTranscriptionRequest(requestedSecret, transcriptionModel, audioFilePath, token: token);
}
/// <inheritdoc /> /// <inheritdoc />
public override Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) public override Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
@@ -100,5 +107,17 @@ public class ProviderFireworks() : BaseProvider(LLMProviders.FIREWORKS, "https:/
return Task.FromResult(Enumerable.Empty<Model>()); return Task.FromResult(Enumerable.Empty<Model>());
} }
/// <inheritdoc />
public override Task<IEnumerable<Model>> GetTranscriptionModels(string? apiKeyProvisional = null, CancellationToken token = default)
{
// Source: https://docs.fireworks.ai/api-reference/audio-transcriptions#param-model
return Task.FromResult<IEnumerable<Model>>(
new List<Model>
{
new("whisper-v3", "Whisper v3"),
// new("whisper-v3-turbo", "Whisper v3 Turbo"), // does not work
});
}
#endregion #endregion
} }
@@ -25,7 +25,7 @@ public sealed class ProviderGWDG() : BaseProvider(LLMProviders.GWDG, "https://ch
public override async IAsyncEnumerable<ContentStreamChunk> StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default) public override async IAsyncEnumerable<ContentStreamChunk> StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
{ {
// Get the API key: // Get the API key:
var requestedSecret = await RUST_SERVICE.GetAPIKey(this); var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.LLM_PROVIDER);
if(!requestedSecret.Success) if(!requestedSecret.Success)
yield break; yield break;
@@ -80,11 +80,18 @@ public sealed class ProviderGWDG() : BaseProvider(LLMProviders.GWDG, "https://ch
yield break; yield break;
} }
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
/// <inheritdoc />
public override async Task<string> TranscribeAudioAsync(Model transcriptionModel, string audioFilePath, SettingsManager settingsManager, CancellationToken token = default)
{
var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.TRANSCRIPTION_PROVIDER);
return await this.PerformStandardTranscriptionRequest(requestedSecret, transcriptionModel, audioFilePath, token: token);
}
/// <inheritdoc /> /// <inheritdoc />
public override async Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) public override async Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
{ {
var models = await this.LoadModels(token, apiKeyProvisional); var models = await this.LoadModels(SecretStoreType.LLM_PROVIDER, token, apiKeyProvisional);
return models.Where(model => !model.Id.StartsWith("e5-mistral-7b-instruct", StringComparison.InvariantCultureIgnoreCase)); return models.Where(model => !model.Id.StartsWith("e5-mistral-7b-instruct", StringComparison.InvariantCultureIgnoreCase));
} }
@@ -97,18 +104,29 @@ public sealed class ProviderGWDG() : BaseProvider(LLMProviders.GWDG, "https://ch
/// <inheritdoc /> /// <inheritdoc />
public override async Task<IEnumerable<Model>> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default) public override async Task<IEnumerable<Model>> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default)
{ {
var models = await this.LoadModels(token, apiKeyProvisional); var models = await this.LoadModels(SecretStoreType.EMBEDDING_PROVIDER, token, apiKeyProvisional);
return models.Where(model => model.Id.StartsWith("e5-", StringComparison.InvariantCultureIgnoreCase)); return models.Where(model => model.Id.StartsWith("e5-", StringComparison.InvariantCultureIgnoreCase));
} }
/// <inheritdoc />
public override Task<IEnumerable<Model>> GetTranscriptionModels(string? apiKeyProvisional = null, CancellationToken token = default)
{
// Source: https://docs.hpc.gwdg.de/services/saia/index.html#voice-to-text
return Task.FromResult<IEnumerable<Model>>(
new List<Model>
{
new("whisper-large-v2", "Whisper v2 Large"),
});
}
#endregion #endregion
private async Task<IEnumerable<Model>> LoadModels(CancellationToken token, string? apiKeyProvisional = null) private async Task<IEnumerable<Model>> LoadModels(SecretStoreType storeType, CancellationToken token, string? apiKeyProvisional = null)
{ {
var secretKey = apiKeyProvisional switch var secretKey = apiKeyProvisional switch
{ {
not null => apiKeyProvisional, not null => apiKeyProvisional,
_ => await RUST_SERVICE.GetAPIKey(this) switch _ => await RUST_SERVICE.GetAPIKey(this, storeType) switch
{ {
{ Success: true } result => await result.Secret.Decrypt(ENCRYPTION), { Success: true } result => await result.Secret.Decrypt(ENCRYPTION),
_ => null, _ => null,
@@ -9,7 +9,7 @@ using AIStudio.Settings;
namespace AIStudio.Provider.Google; namespace AIStudio.Provider.Google;
public class ProviderGoogle() : BaseProvider(LLMProviders.GOOGLE, "https://generativelanguage.googleapis.com/v1beta/", LOGGER) public class ProviderGoogle() : BaseProvider(LLMProviders.GOOGLE, "https://generativelanguage.googleapis.com/v1beta/openai/", LOGGER)
{ {
private static readonly ILogger<ProviderGoogle> LOGGER = Program.LOGGER_FACTORY.CreateLogger<ProviderGoogle>(); private static readonly ILogger<ProviderGoogle> LOGGER = Program.LOGGER_FACTORY.CreateLogger<ProviderGoogle>();
@@ -25,7 +25,7 @@ public class ProviderGoogle() : BaseProvider(LLMProviders.GOOGLE, "https://gener
public override async IAsyncEnumerable<ContentStreamChunk> StreamChatCompletion(Provider.Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default) public override async IAsyncEnumerable<ContentStreamChunk> StreamChatCompletion(Provider.Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
{ {
// Get the API key: // Get the API key:
var requestedSecret = await RUST_SERVICE.GetAPIKey(this); var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.LLM_PROVIDER);
if(!requestedSecret.Success) if(!requestedSecret.Success)
yield break; yield break;
@@ -82,10 +82,16 @@ public class ProviderGoogle() : BaseProvider(LLMProviders.GOOGLE, "https://gener
} }
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
/// <inheritdoc />
public override Task<string> TranscribeAudioAsync(Provider.Model transcriptionModel, string audioFilePath, SettingsManager settingsManager, CancellationToken token = default)
{
return Task.FromResult(string.Empty);
}
/// <inheritdoc /> /// <inheritdoc />
public override async Task<IEnumerable<Provider.Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) public override async Task<IEnumerable<Provider.Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
{ {
var modelResponse = await this.LoadModels(token, apiKeyProvisional); var modelResponse = await this.LoadModels(SecretStoreType.LLM_PROVIDER, token, apiKeyProvisional);
if(modelResponse == default) if(modelResponse == default)
return []; return [];
@@ -102,7 +108,7 @@ public class ProviderGoogle() : BaseProvider(LLMProviders.GOOGLE, "https://gener
public override async Task<IEnumerable<Provider.Model>> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default) public override async Task<IEnumerable<Provider.Model>> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default)
{ {
var modelResponse = await this.LoadModels(token, apiKeyProvisional); var modelResponse = await this.LoadModels(SecretStoreType.EMBEDDING_PROVIDER, token, apiKeyProvisional);
if(modelResponse == default) if(modelResponse == default)
return []; return [];
@@ -112,15 +118,20 @@ public class ProviderGoogle() : BaseProvider(LLMProviders.GOOGLE, "https://gener
.Select(n => new Provider.Model(n.Name.Replace("models/", string.Empty), n.DisplayName)); .Select(n => new Provider.Model(n.Name.Replace("models/", string.Empty), n.DisplayName));
} }
/// <inheritdoc />
public override Task<IEnumerable<Provider.Model>> GetTranscriptionModels(string? apiKeyProvisional = null, CancellationToken token = default)
{
return Task.FromResult(Enumerable.Empty<Provider.Model>());
}
#endregion #endregion
private async Task<ModelsResponse> LoadModels(CancellationToken token, string? apiKeyProvisional = null) private async Task<ModelsResponse> LoadModels(SecretStoreType storeType, CancellationToken token, string? apiKeyProvisional = null)
{ {
var secretKey = apiKeyProvisional switch var secretKey = apiKeyProvisional switch
{ {
not null => apiKeyProvisional, not null => apiKeyProvisional,
_ => await RUST_SERVICE.GetAPIKey(this) switch _ => await RUST_SERVICE.GetAPIKey(this, storeType) switch
{ {
{ Success: true } result => await result.Secret.Decrypt(ENCRYPTION), { Success: true } result => await result.Secret.Decrypt(ENCRYPTION),
_ => null, _ => null,
@@ -25,7 +25,7 @@ public class ProviderGroq() : BaseProvider(LLMProviders.GROQ, "https://api.groq.
public override async IAsyncEnumerable<ContentStreamChunk> StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default) public override async IAsyncEnumerable<ContentStreamChunk> StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
{ {
// Get the API key: // Get the API key:
var requestedSecret = await RUST_SERVICE.GetAPIKey(this); var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.LLM_PROVIDER);
if(!requestedSecret.Success) if(!requestedSecret.Success)
yield break; yield break;
@@ -81,17 +81,23 @@ public class ProviderGroq() : BaseProvider(LLMProviders.GROQ, "https://api.groq.
yield break; yield break;
} }
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
/// <inheritdoc />
public override Task<string> TranscribeAudioAsync(Model transcriptionModel, string audioFilePath, SettingsManager settingsManager, CancellationToken token = default)
{
return Task.FromResult(string.Empty);
}
/// <inheritdoc /> /// <inheritdoc />
public override Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) public override Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
{ {
return this.LoadModels(token, apiKeyProvisional); return this.LoadModels(SecretStoreType.LLM_PROVIDER, token, apiKeyProvisional);
} }
/// <inheritdoc /> /// <inheritdoc />
public override Task<IEnumerable<Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default) public override Task<IEnumerable<Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default)
{ {
return Task.FromResult<IEnumerable<Model>>(Array.Empty<Model>()); return Task.FromResult<IEnumerable<Model>>([]);
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -100,14 +106,20 @@ public class ProviderGroq() : BaseProvider(LLMProviders.GROQ, "https://api.groq.
return Task.FromResult(Enumerable.Empty<Model>()); return Task.FromResult(Enumerable.Empty<Model>());
} }
/// <inheritdoc />
public override Task<IEnumerable<Model>> GetTranscriptionModels(string? apiKeyProvisional = null, CancellationToken token = default)
{
return Task.FromResult(Enumerable.Empty<Model>());
}
#endregion #endregion
private async Task<IEnumerable<Model>> LoadModels(CancellationToken token, string? apiKeyProvisional = null) private async Task<IEnumerable<Model>> LoadModels(SecretStoreType storeType, CancellationToken token, string? apiKeyProvisional = null)
{ {
var secretKey = apiKeyProvisional switch var secretKey = apiKeyProvisional switch
{ {
not null => apiKeyProvisional, not null => apiKeyProvisional,
_ => await RUST_SERVICE.GetAPIKey(this) switch _ => await RUST_SERVICE.GetAPIKey(this, storeType) switch
{ {
{ Success: true } result => await result.Secret.Decrypt(ENCRYPTION), { Success: true } result => await result.Secret.Decrypt(ENCRYPTION),
_ => null, _ => null,
@@ -25,7 +25,7 @@ public sealed class ProviderHelmholtz() : BaseProvider(LLMProviders.HELMHOLTZ, "
public override async IAsyncEnumerable<ContentStreamChunk> StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default) public override async IAsyncEnumerable<ContentStreamChunk> StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
{ {
// Get the API key: // Get the API key:
var requestedSecret = await RUST_SERVICE.GetAPIKey(this); var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.LLM_PROVIDER);
if(!requestedSecret.Success) if(!requestedSecret.Success)
yield break; yield break;
@@ -80,11 +80,17 @@ public sealed class ProviderHelmholtz() : BaseProvider(LLMProviders.HELMHOLTZ, "
yield break; yield break;
} }
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
/// <inheritdoc />
public override Task<string> TranscribeAudioAsync(Model transcriptionModel, string audioFilePath, SettingsManager settingsManager, CancellationToken token = default)
{
return Task.FromResult(string.Empty);
}
/// <inheritdoc /> /// <inheritdoc />
public override async Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) public override async Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
{ {
var models = await this.LoadModels(token, apiKeyProvisional); var models = await this.LoadModels(SecretStoreType.LLM_PROVIDER, token, apiKeyProvisional);
return models.Where(model => !model.Id.StartsWith("text-", StringComparison.InvariantCultureIgnoreCase) && return models.Where(model => !model.Id.StartsWith("text-", StringComparison.InvariantCultureIgnoreCase) &&
!model.Id.StartsWith("alias-embedding", StringComparison.InvariantCultureIgnoreCase)); !model.Id.StartsWith("alias-embedding", StringComparison.InvariantCultureIgnoreCase));
} }
@@ -98,21 +104,27 @@ public sealed class ProviderHelmholtz() : BaseProvider(LLMProviders.HELMHOLTZ, "
/// <inheritdoc /> /// <inheritdoc />
public override async Task<IEnumerable<Model>> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default) public override async Task<IEnumerable<Model>> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default)
{ {
var models = await this.LoadModels(token, apiKeyProvisional); var models = await this.LoadModels(SecretStoreType.EMBEDDING_PROVIDER, token, apiKeyProvisional);
return models.Where(model => return models.Where(model =>
model.Id.StartsWith("alias-embedding", StringComparison.InvariantCultureIgnoreCase) || model.Id.StartsWith("alias-embedding", StringComparison.InvariantCultureIgnoreCase) ||
model.Id.StartsWith("text-", StringComparison.InvariantCultureIgnoreCase) || model.Id.StartsWith("text-", StringComparison.InvariantCultureIgnoreCase) ||
model.Id.Contains("gritlm", StringComparison.InvariantCultureIgnoreCase)); model.Id.Contains("gritlm", StringComparison.InvariantCultureIgnoreCase));
} }
/// <inheritdoc />
public override Task<IEnumerable<Model>> GetTranscriptionModels(string? apiKeyProvisional = null, CancellationToken token = default)
{
return Task.FromResult(Enumerable.Empty<Model>());
}
#endregion #endregion
private async Task<IEnumerable<Model>> LoadModels(CancellationToken token, string? apiKeyProvisional = null) private async Task<IEnumerable<Model>> LoadModels(SecretStoreType storeType, CancellationToken token, string? apiKeyProvisional = null)
{ {
var secretKey = apiKeyProvisional switch var secretKey = apiKeyProvisional switch
{ {
not null => apiKeyProvisional, not null => apiKeyProvisional,
_ => await RUST_SERVICE.GetAPIKey(this) switch _ => await RUST_SERVICE.GetAPIKey(this, storeType) switch
{ {
{ Success: true } result => await result.Secret.Decrypt(ENCRYPTION), { Success: true } result => await result.Secret.Decrypt(ENCRYPTION),
_ => null, _ => null,
@@ -30,7 +30,7 @@ public sealed class ProviderHuggingFace : BaseProvider
public override async IAsyncEnumerable<ContentStreamChunk> StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default) public override async IAsyncEnumerable<ContentStreamChunk> StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
{ {
// Get the API key: // Get the API key:
var requestedSecret = await RUST_SERVICE.GetAPIKey(this); var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.LLM_PROVIDER);
if(!requestedSecret.Success) if(!requestedSecret.Success)
yield break; yield break;
@@ -85,6 +85,12 @@ public sealed class ProviderHuggingFace : BaseProvider
yield break; yield break;
} }
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
/// <inheritdoc />
public override Task<string> TranscribeAudioAsync(Model transcriptionModel, string audioFilePath, SettingsManager settingsManager, CancellationToken token = default)
{
return Task.FromResult(string.Empty);
}
/// <inheritdoc /> /// <inheritdoc />
public override Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) public override Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
@@ -104,5 +110,11 @@ public sealed class ProviderHuggingFace : BaseProvider
return Task.FromResult(Enumerable.Empty<Model>()); return Task.FromResult(Enumerable.Empty<Model>());
} }
/// <inheritdoc />
public override Task<IEnumerable<Model>> GetTranscriptionModels(string? apiKeyProvisional = null, CancellationToken token = default)
{
return Task.FromResult(Enumerable.Empty<Model>());
}
#endregion #endregion
} }
@@ -50,6 +50,16 @@ public interface IProvider
/// <returns>The image completion stream.</returns> /// <returns>The image completion stream.</returns>
public IAsyncEnumerable<ImageURL> StreamImageCompletion(Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, CancellationToken token = default); public IAsyncEnumerable<ImageURL> StreamImageCompletion(Model imageModel, string promptPositive, string promptNegative = FilterOperator.String.Empty, ImageURL referenceImageURL = default, CancellationToken token = default);
/// <summary>
/// Transcribe an audio file.
/// </summary>
/// <param name="transcriptionModel">The model to use for transcription.</param>
/// <param name="audioFilePath">The audio file path.</param>
/// <param name="settingsManager">The settings manager instance to use.</param>
/// <param name="token">The cancellation token.</param>
/// <returns>>The transcription result.</returns>
public Task<string> TranscribeAudioAsync(Model transcriptionModel, string audioFilePath, SettingsManager settingsManager, CancellationToken token = default);
/// <summary> /// <summary>
/// Load all possible text models that can be used with this provider. /// Load all possible text models that can be used with this provider.
/// </summary> /// </summary>
@@ -74,4 +84,11 @@ public interface IProvider
/// <returns>The list of embedding models.</returns> /// <returns>The list of embedding models.</returns>
public Task<IEnumerable<Model>> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default); public Task<IEnumerable<Model>> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default);
/// <summary>
/// Load all possible transcription models that can be used with this provider.
/// </summary>
/// <param name="apiKeyProvisional">The provisional API key to use. Useful when the user is adding a new provider. When null, the stored API key is used.</param>
/// <param name="token">>The cancellation token.</param>
/// <returns>>The list of transcription models.</returns>
public Task<IEnumerable<Model>> GetTranscriptionModels(string? apiKeyProvisional = null, CancellationToken token = default);
} }
@@ -110,7 +110,7 @@ public static class LLMProvidersExtensions
/// </summary> /// </summary>
/// <param name="llmProvider">The provider to check.</param> /// <param name="llmProvider">The provider to check.</param>
/// <returns>True if the provider supports embeddings; otherwise, false.</returns> /// <returns>True if the provider supports embeddings; otherwise, false.</returns>
public static bool ProvideEmbeddings(this LLMProviders llmProvider) => llmProvider switch public static bool ProvideEmbeddingAPI(this LLMProviders llmProvider) => llmProvider switch
{ {
// //
// Providers that support embeddings: // Providers that support embeddings:
@@ -141,6 +141,43 @@ public static class LLMProvidersExtensions
_ => false, _ => false,
}; };
public static bool ProvideTranscriptionAPI(this LLMProviders llmProvider) => llmProvider switch
{
//
// Providers that support transcription:
//
LLMProviders.OPEN_AI => true,
LLMProviders.MISTRAL => true,
LLMProviders.FIREWORKS => true,
LLMProviders.GWDG => true,
//
// Providers that support transcription but provide no OpenAI-compatible API yet:
//
LLMProviders.ALIBABA_CLOUD => false,
LLMProviders.GOOGLE => false,
//
// Providers that do not support transcription:
//
LLMProviders.OPEN_ROUTER => false,
LLMProviders.GROQ => false,
LLMProviders.ANTHROPIC => false,
LLMProviders.X => false,
LLMProviders.DEEP_SEEK => false,
LLMProviders.HUGGINGFACE => false,
LLMProviders.PERPLEXITY => false,
LLMProviders.HELMHOLTZ => false,
//
// Self-hosted providers are treated as a special case anyway.
//
LLMProviders.SELF_HOSTED => true,
_ => false,
};
/// <summary> /// <summary>
/// Creates a new provider instance based on the provider value. /// Creates a new provider instance based on the provider value.
@@ -162,6 +199,16 @@ public static class LLMProvidersExtensions
return embeddingProviderSettings.UsedLLMProvider.CreateProvider(embeddingProviderSettings.Name, embeddingProviderSettings.Host, embeddingProviderSettings.Hostname, embeddingProviderSettings.Model, HFInferenceProvider.NONE); return embeddingProviderSettings.UsedLLMProvider.CreateProvider(embeddingProviderSettings.Name, embeddingProviderSettings.Host, embeddingProviderSettings.Hostname, embeddingProviderSettings.Model, HFInferenceProvider.NONE);
} }
/// <summary>
/// Creates a new provider instance based on the speech provider value.
/// </summary>
/// <param name="transcriptionProviderSettings">The speech provider settings.</param>
/// <returns>The provider instance.</returns>
public static IProvider CreateProvider(this TranscriptionProvider transcriptionProviderSettings)
{
return transcriptionProviderSettings.UsedLLMProvider.CreateProvider(transcriptionProviderSettings.Name, transcriptionProviderSettings.Host, transcriptionProviderSettings.Hostname, transcriptionProviderSettings.Model, HFInferenceProvider.NONE);
}
private static IProvider CreateProvider(this LLMProviders provider, string instanceName, Host host, string hostname, Model model, HFInferenceProvider inferenceProvider, string expertProviderApiParameter = "") private static IProvider CreateProvider(this LLMProviders provider, string instanceName, Host host, string hostname, Model model, HFInferenceProvider inferenceProvider, string expertProviderApiParameter = "")
{ {
try try
@@ -274,6 +321,11 @@ public static class LLMProvidersExtensions
LLMProviders.SELF_HOSTED => host is not Host.LM_STUDIO, LLMProviders.SELF_HOSTED => host is not Host.LM_STUDIO,
_ => false, _ => false,
}; };
public static bool IsTranscriptionModelProvidedManually(this LLMProviders provider, Host host) => provider switch
{
_ => false,
};
public static bool IsHostNeeded(this LLMProviders provider) => provider switch public static bool IsHostNeeded(this LLMProviders provider) => provider switch
{ {
@@ -338,13 +390,14 @@ public static class LLMProvidersExtensions
switch (host) switch (host)
{ {
case Host.NONE: case Host.NONE:
case Host.LLAMACPP: case Host.LLAMA_CPP:
default: default:
return false; return false;
case Host.OLLAMA: case Host.OLLAMA:
case Host.LM_STUDIO: case Host.LM_STUDIO:
case Host.VLLM: case Host.VLLM:
case Host.WHISPER_CPP:
return true; return true;
} }
} }
@@ -23,7 +23,7 @@ public sealed class ProviderMistral() : BaseProvider(LLMProviders.MISTRAL, "http
public override async IAsyncEnumerable<ContentStreamChunk> StreamChatCompletion(Provider.Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default) public override async IAsyncEnumerable<ContentStreamChunk> StreamChatCompletion(Provider.Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
{ {
// Get the API key: // Get the API key:
var requestedSecret = await RUST_SERVICE.GetAPIKey(this); var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.LLM_PROVIDER);
if(!requestedSecret.Success) if(!requestedSecret.Success)
yield break; yield break;
@@ -81,11 +81,18 @@ public sealed class ProviderMistral() : BaseProvider(LLMProviders.MISTRAL, "http
yield break; yield break;
} }
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
/// <inheritdoc />
public override async Task<string> TranscribeAudioAsync(Provider.Model transcriptionModel, string audioFilePath, SettingsManager settingsManager, CancellationToken token = default)
{
var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.TRANSCRIPTION_PROVIDER);
return await this.PerformStandardTranscriptionRequest(requestedSecret, transcriptionModel, audioFilePath, token: token);
}
/// <inheritdoc /> /// <inheritdoc />
public override async Task<IEnumerable<Provider.Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) public override async Task<IEnumerable<Provider.Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
{ {
var modelResponse = await this.LoadModelList(apiKeyProvisional, token); var modelResponse = await this.LoadModelList(SecretStoreType.LLM_PROVIDER, apiKeyProvisional, token);
if(modelResponse == default) if(modelResponse == default)
return []; return [];
@@ -99,7 +106,7 @@ public sealed class ProviderMistral() : BaseProvider(LLMProviders.MISTRAL, "http
/// <inheritdoc /> /// <inheritdoc />
public override async Task<IEnumerable<Provider.Model>> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default) public override async Task<IEnumerable<Provider.Model>> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default)
{ {
var modelResponse = await this.LoadModelList(apiKeyProvisional, token); var modelResponse = await this.LoadModelList(SecretStoreType.EMBEDDING_PROVIDER, apiKeyProvisional, token);
if(modelResponse == default) if(modelResponse == default)
return []; return [];
@@ -113,14 +120,25 @@ public sealed class ProviderMistral() : BaseProvider(LLMProviders.MISTRAL, "http
return Task.FromResult(Enumerable.Empty<Provider.Model>()); return Task.FromResult(Enumerable.Empty<Provider.Model>());
} }
/// <inheritdoc />
public override Task<IEnumerable<Provider.Model>> GetTranscriptionModels(string? apiKeyProvisional = null, CancellationToken token = default)
{
// Source: https://docs.mistral.ai/capabilities/audio_transcription
return Task.FromResult<IEnumerable<Provider.Model>>(
new List<Provider.Model>
{
new("voxtral-mini-latest", "Voxtral Mini Latest"),
});
}
#endregion #endregion
private async Task<ModelsResponse> LoadModelList(string? apiKeyProvisional, CancellationToken token) private async Task<ModelsResponse> LoadModelList(SecretStoreType storeType, string? apiKeyProvisional, CancellationToken token)
{ {
var secretKey = apiKeyProvisional switch var secretKey = apiKeyProvisional switch
{ {
not null => apiKeyProvisional, not null => apiKeyProvisional,
_ => await RUST_SERVICE.GetAPIKey(this) switch _ => await RUST_SERVICE.GetAPIKey(this, storeType) switch
{ {
{ Success: true } result => await result.Secret.Decrypt(ENCRYPTION), { Success: true } result => await result.Secret.Decrypt(ENCRYPTION),
_ => null, _ => null,
@@ -23,6 +23,8 @@ public class NoProvider : IProvider
public Task<IEnumerable<Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default) => Task.FromResult<IEnumerable<Model>>([]); public Task<IEnumerable<Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default) => Task.FromResult<IEnumerable<Model>>([]);
public Task<IEnumerable<Model>> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default) => Task.FromResult<IEnumerable<Model>>([]); public Task<IEnumerable<Model>> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default) => Task.FromResult<IEnumerable<Model>>([]);
public Task<IEnumerable<Model>> GetTranscriptionModels(string? apiKeyProvisional = null, CancellationToken token = default) => Task.FromResult<IEnumerable<Model>>([]);
public async IAsyncEnumerable<ContentStreamChunk> StreamChatCompletion(Model chatModel, ChatThread chatChatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default) public async IAsyncEnumerable<ContentStreamChunk> StreamChatCompletion(Model chatModel, ChatThread chatChatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
{ {
@@ -36,6 +38,8 @@ public class NoProvider : IProvider
yield break; yield break;
} }
public Task<string> TranscribeAudioAsync(Model transcriptionModel, string audioFilePath, SettingsManager settingsManager, CancellationToken token = default) => Task.FromResult(string.Empty);
public IReadOnlyCollection<Capability> GetModelCapabilities(Model model) => [ Capability.NONE ]; public IReadOnlyCollection<Capability> GetModelCapabilities(Model model) => [ Capability.NONE ];
#endregion #endregion
@@ -27,7 +27,7 @@ public sealed class ProviderOpenAI() : BaseProvider(LLMProviders.OPEN_AI, "https
public override async IAsyncEnumerable<ContentStreamChunk> StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default) public override async IAsyncEnumerable<ContentStreamChunk> StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
{ {
// Get the API key: // Get the API key:
var requestedSecret = await RUST_SERVICE.GetAPIKey(this); var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.LLM_PROVIDER);
if(!requestedSecret.Success) if(!requestedSecret.Success)
yield break; yield break;
@@ -217,11 +217,18 @@ public sealed class ProviderOpenAI() : BaseProvider(LLMProviders.OPEN_AI, "https
} }
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
/// <inheritdoc />
public override async Task<string> TranscribeAudioAsync(Model transcriptionModel, string audioFilePath, SettingsManager settingsManager, CancellationToken token = default)
{
var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.TRANSCRIPTION_PROVIDER);
return await this.PerformStandardTranscriptionRequest(requestedSecret, transcriptionModel, audioFilePath, token: token);
}
/// <inheritdoc /> /// <inheritdoc />
public override async Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) public override async Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
{ {
var models = await this.LoadModels(["chatgpt-", "gpt-", "o1-", "o3-", "o4-"], token, apiKeyProvisional); var models = await this.LoadModels(SecretStoreType.LLM_PROVIDER, ["chatgpt-", "gpt-", "o1-", "o3-", "o4-"], token, apiKeyProvisional);
return models.Where(model => !model.Id.Contains("image", StringComparison.OrdinalIgnoreCase) && return models.Where(model => !model.Id.Contains("image", StringComparison.OrdinalIgnoreCase) &&
!model.Id.Contains("realtime", StringComparison.OrdinalIgnoreCase) && !model.Id.Contains("realtime", StringComparison.OrdinalIgnoreCase) &&
!model.Id.Contains("audio", StringComparison.OrdinalIgnoreCase) && !model.Id.Contains("audio", StringComparison.OrdinalIgnoreCase) &&
@@ -232,23 +239,31 @@ public sealed class ProviderOpenAI() : BaseProvider(LLMProviders.OPEN_AI, "https
/// <inheritdoc /> /// <inheritdoc />
public override Task<IEnumerable<Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default) public override Task<IEnumerable<Model>> GetImageModels(string? apiKeyProvisional = null, CancellationToken token = default)
{ {
return this.LoadModels(["dall-e-", "gpt-image"], token, apiKeyProvisional); return this.LoadModels(SecretStoreType.IMAGE_PROVIDER, ["dall-e-", "gpt-image"], token, apiKeyProvisional);
} }
/// <inheritdoc /> /// <inheritdoc />
public override Task<IEnumerable<Model>> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default) public override Task<IEnumerable<Model>> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default)
{ {
return this.LoadModels(["text-embedding-"], token, apiKeyProvisional); return this.LoadModels(SecretStoreType.EMBEDDING_PROVIDER, ["text-embedding-"], token, apiKeyProvisional);
}
/// <inheritdoc />
public override async Task<IEnumerable<Model>> GetTranscriptionModels(string? apiKeyProvisional = null, CancellationToken token = default)
{
var models = await this.LoadModels(SecretStoreType.TRANSCRIPTION_PROVIDER, ["whisper-", "gpt-"], token, apiKeyProvisional);
return models.Where(model => model.Id.StartsWith("whisper-", StringComparison.InvariantCultureIgnoreCase) ||
model.Id.Contains("-transcribe", StringComparison.InvariantCultureIgnoreCase));
} }
#endregion #endregion
private async Task<IEnumerable<Model>> LoadModels(string[] prefixes, CancellationToken token, string? apiKeyProvisional = null) private async Task<IEnumerable<Model>> LoadModels(SecretStoreType storeType, string[] prefixes, CancellationToken token, string? apiKeyProvisional = null)
{ {
var secretKey = apiKeyProvisional switch var secretKey = apiKeyProvisional switch
{ {
not null => apiKeyProvisional, not null => apiKeyProvisional,
_ => await RUST_SERVICE.GetAPIKey(this) switch _ => await RUST_SERVICE.GetAPIKey(this, storeType) switch
{ {
{ Success: true } result => await result.Secret.Decrypt(ENCRYPTION), { Success: true } result => await result.Secret.Decrypt(ENCRYPTION),
_ => null, _ => null,
@@ -28,7 +28,7 @@ public sealed class ProviderOpenRouter() : BaseProvider(LLMProviders.OPEN_ROUTER
public override async IAsyncEnumerable<ContentStreamChunk> StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default) public override async IAsyncEnumerable<ContentStreamChunk> StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
{ {
// Get the API key: // Get the API key:
var requestedSecret = await RUST_SERVICE.GetAPIKey(this); var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.LLM_PROVIDER);
if(!requestedSecret.Success) if(!requestedSecret.Success)
yield break; yield break;
@@ -88,11 +88,17 @@ public sealed class ProviderOpenRouter() : BaseProvider(LLMProviders.OPEN_ROUTER
yield break; yield break;
} }
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
/// <inheritdoc />
public override Task<string> TranscribeAudioAsync(Model transcriptionModel, string audioFilePath, SettingsManager settingsManager, CancellationToken token = default)
{
return Task.FromResult(string.Empty);
}
/// <inheritdoc /> /// <inheritdoc />
public override Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) public override Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
{ {
return this.LoadModels(token, apiKeyProvisional); return this.LoadModels(SecretStoreType.LLM_PROVIDER, token, apiKeyProvisional);
} }
/// <inheritdoc /> /// <inheritdoc />
@@ -106,15 +112,21 @@ public sealed class ProviderOpenRouter() : BaseProvider(LLMProviders.OPEN_ROUTER
{ {
return this.LoadEmbeddingModels(token, apiKeyProvisional); return this.LoadEmbeddingModels(token, apiKeyProvisional);
} }
/// <inheritdoc />
public override Task<IEnumerable<Model>> GetTranscriptionModels(string? apiKeyProvisional = null, CancellationToken token = default)
{
return Task.FromResult(Enumerable.Empty<Model>());
}
#endregion #endregion
private async Task<IEnumerable<Model>> LoadModels(CancellationToken token, string? apiKeyProvisional = null) private async Task<IEnumerable<Model>> LoadModels(SecretStoreType storeType, CancellationToken token, string? apiKeyProvisional = null)
{ {
var secretKey = apiKeyProvisional switch var secretKey = apiKeyProvisional switch
{ {
not null => apiKeyProvisional, not null => apiKeyProvisional,
_ => await RUST_SERVICE.GetAPIKey(this) switch _ => await RUST_SERVICE.GetAPIKey(this, storeType) switch
{ {
{ Success: true } result => await result.Secret.Decrypt(ENCRYPTION), { Success: true } result => await result.Secret.Decrypt(ENCRYPTION),
_ => null, _ => null,
@@ -156,7 +168,7 @@ public sealed class ProviderOpenRouter() : BaseProvider(LLMProviders.OPEN_ROUTER
var secretKey = apiKeyProvisional switch var secretKey = apiKeyProvisional switch
{ {
not null => apiKeyProvisional, not null => apiKeyProvisional,
_ => await RUST_SERVICE.GetAPIKey(this) switch _ => await RUST_SERVICE.GetAPIKey(this, SecretStoreType.EMBEDDING_PROVIDER) switch
{ {
{ Success: true } result => await result.Secret.Decrypt(ENCRYPTION), { Success: true } result => await result.Secret.Decrypt(ENCRYPTION),
_ => null, _ => null,
@@ -34,7 +34,7 @@ public sealed class ProviderPerplexity() : BaseProvider(LLMProviders.PERPLEXITY,
public override async IAsyncEnumerable<ContentStreamChunk> StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default) public override async IAsyncEnumerable<ContentStreamChunk> StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
{ {
// Get the API key: // Get the API key:
var requestedSecret = await RUST_SERVICE.GetAPIKey(this); var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.LLM_PROVIDER);
if(!requestedSecret.Success) if(!requestedSecret.Success)
yield break; yield break;
@@ -88,6 +88,12 @@ public sealed class ProviderPerplexity() : BaseProvider(LLMProviders.PERPLEXITY,
yield break; yield break;
} }
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
/// <inheritdoc />
public override Task<string> TranscribeAudioAsync(Model transcriptionModel, string audioFilePath, SettingsManager settingsManager, CancellationToken token = default)
{
return Task.FromResult(string.Empty);
}
/// <inheritdoc /> /// <inheritdoc />
public override Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) public override Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
@@ -107,6 +113,12 @@ public sealed class ProviderPerplexity() : BaseProvider(LLMProviders.PERPLEXITY,
return Task.FromResult(Enumerable.Empty<Model>()); return Task.FromResult(Enumerable.Empty<Model>());
} }
/// <inheritdoc />
public override Task<IEnumerable<Model>> GetTranscriptionModels(string? apiKeyProvisional = null, CancellationToken token = default)
{
return Task.FromResult(Enumerable.Empty<Model>());
}
#endregion #endregion
private Task<IEnumerable<Model>> LoadModels() => Task.FromResult<IEnumerable<Model>>(KNOWN_MODELS); private Task<IEnumerable<Model>> LoadModels() => Task.FromResult<IEnumerable<Model>>(KNOWN_MODELS);
@@ -5,7 +5,8 @@ public enum Host
NONE, NONE,
LM_STUDIO, LM_STUDIO,
LLAMACPP, LLAMA_CPP,
WHISPER_CPP,
OLLAMA, OLLAMA,
VLLM, VLLM,
} }
@@ -7,7 +7,8 @@ public static class HostExtensions
Host.NONE => "None", Host.NONE => "None",
Host.LM_STUDIO => "LM Studio", Host.LM_STUDIO => "LM Studio",
Host.LLAMACPP => "llama.cpp", Host.LLAMA_CPP => "llama.cpp",
Host.WHISPER_CPP => "whisper.cpp",
Host.OLLAMA => "ollama", Host.OLLAMA => "ollama",
Host.VLLM => "vLLM", Host.VLLM => "vLLM",
@@ -24,7 +25,28 @@ public static class HostExtensions
_ => "chat/completions", _ => "chat/completions",
}; };
public static bool AreEmbeddingsSupported(this Host host) public static string TranscriptionURL(this Host host) => host switch
{
_ => "audio/transcriptions",
};
public static bool IsChatSupported(this Host host)
{
switch (host)
{
case Host.WHISPER_CPP:
return false;
default:
case Host.OLLAMA:
case Host.VLLM:
case Host.LM_STUDIO:
case Host.LLAMA_CPP:
return true;
}
}
public static bool IsEmbeddingSupported(this Host host)
{ {
switch (host) switch (host)
{ {
@@ -34,7 +56,23 @@ public static class HostExtensions
return true; return true;
default: default:
case Host.LLAMACPP: case Host.LLAMA_CPP:
return false;
}
}
public static bool IsTranscriptionSupported(this Host host)
{
switch (host)
{
case Host.OLLAMA:
case Host.VLLM:
case Host.WHISPER_CPP:
return true;
default:
case Host.LM_STUDIO:
case Host.LLAMA_CPP:
return false; return false;
} }
} }
@@ -6,12 +6,15 @@ using System.Text.Json;
using AIStudio.Chat; using AIStudio.Chat;
using AIStudio.Provider.OpenAI; using AIStudio.Provider.OpenAI;
using AIStudio.Settings; using AIStudio.Settings;
using AIStudio.Tools.PluginSystem;
namespace AIStudio.Provider.SelfHosted; namespace AIStudio.Provider.SelfHosted;
public sealed class ProviderSelfHosted(Host host, string hostname) : BaseProvider(LLMProviders.SELF_HOSTED, $"{hostname}{host.BaseURL()}", LOGGER) public sealed class ProviderSelfHosted(Host host, string hostname) : BaseProvider(LLMProviders.SELF_HOSTED, $"{hostname}{host.BaseURL()}", LOGGER)
{ {
private static readonly ILogger<ProviderSelfHosted> LOGGER = Program.LOGGER_FACTORY.CreateLogger<ProviderSelfHosted>(); private static readonly ILogger<ProviderSelfHosted> LOGGER = Program.LOGGER_FACTORY.CreateLogger<ProviderSelfHosted>();
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(ProviderSelfHosted).Namespace, nameof(ProviderSelfHosted));
#region Implementation of IProvider #region Implementation of IProvider
@@ -23,7 +26,7 @@ public sealed class ProviderSelfHosted(Host host, string hostname) : BaseProvide
public override async IAsyncEnumerable<ContentStreamChunk> StreamChatCompletion(Provider.Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default) public override async IAsyncEnumerable<ContentStreamChunk> StreamChatCompletion(Provider.Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
{ {
// Get the API key: // Get the API key:
var requestedSecret = await RUST_SERVICE.GetAPIKey(this, isTrying: true); var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.LLM_PROVIDER, isTrying: true);
// Prepare the system prompt: // Prepare the system prompt:
var systemPrompt = new TextMessage var systemPrompt = new TextMessage
@@ -85,13 +88,20 @@ public sealed class ProviderSelfHosted(Host host, string hostname) : BaseProvide
} }
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
/// <inheritdoc />
public override async Task<string> TranscribeAudioAsync(Provider.Model transcriptionModel, string audioFilePath, SettingsManager settingsManager, CancellationToken token = default)
{
var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.TRANSCRIPTION_PROVIDER, isTrying: true);
return await this.PerformStandardTranscriptionRequest(requestedSecret, transcriptionModel, audioFilePath, host, token);
}
public override async Task<IEnumerable<Provider.Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) public override async Task<IEnumerable<Provider.Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
{ {
try try
{ {
switch (host) switch (host)
{ {
case Host.LLAMACPP: case Host.LLAMA_CPP:
// Right now, llama.cpp only supports one model. // Right now, llama.cpp only supports one model.
// There is no API to list the model(s). // There is no API to list the model(s).
return [ new Provider.Model("as configured by llama.cpp", null) ]; return [ new Provider.Model("as configured by llama.cpp", null) ];
@@ -99,7 +109,7 @@ public sealed class ProviderSelfHosted(Host host, string hostname) : BaseProvide
case Host.LM_STUDIO: case Host.LM_STUDIO:
case Host.OLLAMA: case Host.OLLAMA:
case Host.VLLM: case Host.VLLM:
return await this.LoadModels(["embed"], [], token, apiKeyProvisional); return await this.LoadModels( SecretStoreType.LLM_PROVIDER, ["embed"], [], token, apiKeyProvisional);
} }
return []; return [];
@@ -126,7 +136,7 @@ public sealed class ProviderSelfHosted(Host host, string hostname) : BaseProvide
case Host.LM_STUDIO: case Host.LM_STUDIO:
case Host.OLLAMA: case Host.OLLAMA:
case Host.VLLM: case Host.VLLM:
return await this.LoadModels([], ["embed"], token, apiKeyProvisional); return await this.LoadModels( SecretStoreType.EMBEDDING_PROVIDER, [], ["embed"], token, apiKeyProvisional);
} }
return []; return [];
@@ -138,14 +148,43 @@ public sealed class ProviderSelfHosted(Host host, string hostname) : BaseProvide
} }
} }
/// <inheritdoc />
public override Task<IEnumerable<Provider.Model>> GetTranscriptionModels(string? apiKeyProvisional = null, CancellationToken token = default)
{
try
{
switch (host)
{
case Host.WHISPER_CPP:
return Task.FromResult<IEnumerable<Provider.Model>>(
new List<Provider.Model>
{
new("loaded-model", TB("Model as configured by whisper.cpp")),
});
case Host.OLLAMA:
case Host.VLLM:
return this.LoadModels(SecretStoreType.TRANSCRIPTION_PROVIDER, [], [], token, apiKeyProvisional);
default:
return Task.FromResult(Enumerable.Empty<Provider.Model>());
}
}
catch (Exception e)
{
LOGGER.LogError(e, "Failed to load transcription models from self-hosted provider.");
return Task.FromResult(Enumerable.Empty<Provider.Model>());
}
}
#endregion #endregion
private async Task<IEnumerable<Provider.Model>> LoadModels(string[] ignorePhrases, string[] filterPhrases, CancellationToken token, string? apiKeyProvisional = null) private async Task<IEnumerable<Provider.Model>> LoadModels(SecretStoreType storeType, string[] ignorePhrases, string[] filterPhrases, CancellationToken token, string? apiKeyProvisional = null)
{ {
var secretKey = apiKeyProvisional switch var secretKey = apiKeyProvisional switch
{ {
not null => apiKeyProvisional, not null => apiKeyProvisional,
_ => await RUST_SERVICE.GetAPIKey(this, isTrying: true) switch _ => await RUST_SERVICE.GetAPIKey(this, storeType, isTrying: true) switch
{ {
{ Success: true } result => await result.Secret.Decrypt(ENCRYPTION), { Success: true } result => await result.Secret.Decrypt(ENCRYPTION),
_ => null, _ => null,
@@ -0,0 +1,3 @@
namespace AIStudio.Provider;
public sealed record TranscriptionResponse(string Text);
+17 -5
View File
@@ -25,7 +25,7 @@ public sealed class ProviderX() : BaseProvider(LLMProviders.X, "https://api.x.ai
public override async IAsyncEnumerable<ContentStreamChunk> StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default) public override async IAsyncEnumerable<ContentStreamChunk> StreamChatCompletion(Model chatModel, ChatThread chatThread, SettingsManager settingsManager, [EnumeratorCancellation] CancellationToken token = default)
{ {
// Get the API key: // Get the API key:
var requestedSecret = await RUST_SERVICE.GetAPIKey(this); var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.LLM_PROVIDER);
if(!requestedSecret.Success) if(!requestedSecret.Success)
yield break; yield break;
@@ -81,11 +81,17 @@ public sealed class ProviderX() : BaseProvider(LLMProviders.X, "https://api.x.ai
yield break; yield break;
} }
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously #pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
/// <inheritdoc />
public override Task<string> TranscribeAudioAsync(Model transcriptionModel, string audioFilePath, SettingsManager settingsManager, CancellationToken token = default)
{
return Task.FromResult(string.Empty);
}
/// <inheritdoc /> /// <inheritdoc />
public override async Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) public override async Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
{ {
var models = await this.LoadModels(["grok-"], token, apiKeyProvisional); var models = await this.LoadModels(SecretStoreType.LLM_PROVIDER, ["grok-"], token, apiKeyProvisional);
return models.Where(n => !n.Id.Contains("-image", StringComparison.OrdinalIgnoreCase)); return models.Where(n => !n.Id.Contains("-image", StringComparison.OrdinalIgnoreCase));
} }
@@ -101,14 +107,20 @@ public sealed class ProviderX() : BaseProvider(LLMProviders.X, "https://api.x.ai
return Task.FromResult<IEnumerable<Model>>([]); return Task.FromResult<IEnumerable<Model>>([]);
} }
/// <inheritdoc />
public override Task<IEnumerable<Model>> GetTranscriptionModels(string? apiKeyProvisional = null, CancellationToken token = default)
{
return Task.FromResult(Enumerable.Empty<Model>());
}
#endregion #endregion
private async Task<IEnumerable<Model>> LoadModels(string[] prefixes, CancellationToken token, string? apiKeyProvisional = null) private async Task<IEnumerable<Model>> LoadModels(SecretStoreType storeType, string[] prefixes, CancellationToken token, string? apiKeyProvisional = null)
{ {
var secretKey = apiKeyProvisional switch var secretKey = apiKeyProvisional switch
{ {
not null => apiKeyProvisional, not null => apiKeyProvisional,
_ => await RUST_SERVICE.GetAPIKey(this) switch _ => await RUST_SERVICE.GetAPIKey(this, storeType) switch
{ {
{ Success: true } result => await result.Secret.Decrypt(ENCRYPTION), { Success: true } result => await result.Secret.Decrypt(ENCRYPTION),
_ => null, _ => null,
@@ -12,13 +12,12 @@ public record ChatTemplate(
string SystemPrompt, string SystemPrompt,
string PredefinedUserPrompt, string PredefinedUserPrompt,
List<ContentBlock> ExampleConversation, List<ContentBlock> ExampleConversation,
List<FileAttachment> FileAttachments,
bool AllowProfileUsage, bool AllowProfileUsage,
bool IsEnterpriseConfiguration = false, bool IsEnterpriseConfiguration = false,
Guid EnterpriseConfigurationPluginId = default) : ConfigurationBaseObject Guid EnterpriseConfigurationPluginId = default) : ConfigurationBaseObject
{ {
private const string USE_NO_CHAT_TEMPLATE_TEXT = "Use no chat template"; public ChatTemplate() : this(0, Guid.Empty.ToString(), string.Empty, string.Empty, string.Empty, [], [], false)
public ChatTemplate() : this(0, Guid.Empty.ToString(), string.Empty, string.Empty, string.Empty, [], false)
{ {
} }
@@ -28,12 +27,13 @@ public record ChatTemplate(
public static readonly ChatTemplate NO_CHAT_TEMPLATE = new() public static readonly ChatTemplate NO_CHAT_TEMPLATE = new()
{ {
Name = TB(USE_NO_CHAT_TEMPLATE_TEXT), // Cannot be localized due to being a static readonly field Name = TB("Use no chat template"), // Cannot be localized due to being a static readonly field
SystemPrompt = string.Empty, SystemPrompt = string.Empty,
PredefinedUserPrompt = string.Empty, PredefinedUserPrompt = string.Empty,
Id = Guid.Empty.ToString(), Id = Guid.Empty.ToString(),
Num = uint.MaxValue, Num = uint.MaxValue,
ExampleConversation = [], ExampleConversation = [],
FileAttachments = [],
AllowProfileUsage = true, AllowProfileUsage = true,
EnterpriseConfigurationPluginId = Guid.Empty, EnterpriseConfigurationPluginId = Guid.Empty,
IsEnterpriseConfiguration = false, IsEnterpriseConfiguration = false,
@@ -61,7 +61,7 @@ public record ChatTemplate(
public string GetSafeName() public string GetSafeName()
{ {
if(this == NO_CHAT_TEMPLATE) if(this == NO_CHAT_TEMPLATE)
return TB(USE_NO_CHAT_TEMPLATE_TEXT); return TB("Use no chat template");
return this.Name; return this.Name;
} }
@@ -102,7 +102,9 @@ public record ChatTemplate(
var allowProfileUsage = false; var allowProfileUsage = false;
if (table.TryGetValue("AllowProfileUsage", out var allowProfileValue) && allowProfileValue.TryRead<bool>(out var allow)) if (table.TryGetValue("AllowProfileUsage", out var allowProfileValue) && allowProfileValue.TryRead<bool>(out var allow))
allowProfileUsage = allow; allowProfileUsage = allow;
var fileAttachments = ParseFileAttachments(idx, table);
template = new ChatTemplate template = new ChatTemplate
{ {
Num = 0, Num = 0,
@@ -111,6 +113,7 @@ public record ChatTemplate(
SystemPrompt = systemPrompt, SystemPrompt = systemPrompt,
PredefinedUserPrompt = predefinedUserPrompt, PredefinedUserPrompt = predefinedUserPrompt,
ExampleConversation = ParseExampleConversation(idx, table), ExampleConversation = ParseExampleConversation(idx, table),
FileAttachments = fileAttachments,
AllowProfileUsage = allowProfileUsage, AllowProfileUsage = allowProfileUsage,
IsEnterpriseConfiguration = true, IsEnterpriseConfiguration = true,
EnterpriseConfigurationPluginId = configPluginId, EnterpriseConfigurationPluginId = configPluginId,
@@ -165,4 +168,26 @@ public record ChatTemplate(
return exampleConversation; return exampleConversation;
} }
private static List<FileAttachment> ParseFileAttachments(int idx, LuaTable table)
{
var fileAttachments = new List<FileAttachment>();
if (!table.TryGetValue("FileAttachments", out var fileAttValue) || !fileAttValue.TryRead<LuaTable>(out var fileAttTable))
return fileAttachments;
var numAttachments = fileAttTable.ArrayLength;
for (var attachmentNum = 1; attachmentNum <= numAttachments; attachmentNum++)
{
var attachmentValue = fileAttTable[attachmentNum];
if (!attachmentValue.TryRead<string>(out var filePath))
{
LOGGER.LogWarning("The FileAttachments entry {AttachmentNum} in chat template {IdxChatTemplate} is not a valid string.", attachmentNum, idx);
continue;
}
fileAttachments.Add(FileAttachment.FromPath(filePath));
}
return fileAttachments;
}
} }
@@ -203,6 +203,12 @@ public static class ConfigurationSelectDataFactory
foreach (var profile in profiles.GetAllProfiles()) foreach (var profile in profiles.GetAllProfiles())
yield return new(profile.Name, profile.Id); yield return new(profile.Name, profile.Id);
} }
public static IEnumerable<ConfigurationSelectData<string>> GetTranscriptionProvidersData(IEnumerable<TranscriptionProvider> transcriptionProviders)
{
foreach (var provider in transcriptionProviders)
yield return new(provider.Name, provider.Id);
}
public static IEnumerable<ConfigurationSelectData<string>> GetChatTemplatesData(IEnumerable<ChatTemplate> chatTemplates) public static IEnumerable<ConfigurationSelectData<string>> GetChatTemplatesData(IEnumerable<ChatTemplate> chatTemplates)
{ {
@@ -25,6 +25,11 @@ public sealed class Data
/// A collection of embedding providers configured. /// A collection of embedding providers configured.
/// </summary> /// </summary>
public List<EmbeddingProvider> EmbeddingProviders { get; init; } = []; public List<EmbeddingProvider> EmbeddingProviders { get; init; } = [];
/// <summary>
/// A collection of speech providers configured.
/// </summary>
public List<TranscriptionProvider> TranscriptionProviders { get; init; } = [];
/// <summary> /// <summary>
/// A collection of data sources configured. /// A collection of data sources configured.
@@ -52,9 +57,14 @@ public sealed class Data
public uint NextProviderNum { get; set; } = 1; public uint NextProviderNum { get; set; } = 1;
/// <summary> /// <summary>
/// The next embedding number to use. /// The next embedding provider number to use.
/// </summary> /// </summary>
public uint NextEmbeddingNum { get; set; } = 1; public uint NextEmbeddingNum { get; set; } = 1;
/// <summary>
/// The next transcription provider number to use.
/// </summary>
public uint NextTranscriptionNum { get; set; } = 1;
/// <summary> /// <summary>
/// The next data source number to use. /// The next data source number to use.
@@ -77,6 +77,11 @@ public sealed class DataApp(Expression<Func<Data, DataApp>>? configSelection = n
/// </summary> /// </summary>
public string PreselectedChatTemplate { get; set; } = string.Empty; public string PreselectedChatTemplate { get; set; } = string.Empty;
/// <summary>
/// Which transcription provider should be used?
/// </summary>
public string UseTranscriptionProvider { get; set; } = ManagedConfiguration.Register(configSelection, n => n.UseTranscriptionProvider, string.Empty);
/// <summary> /// <summary>
/// Should the user be allowed to add providers? /// Should the user be allowed to add providers?
/// </summary> /// </summary>
@@ -12,4 +12,5 @@ public enum PreviewFeatures
PRE_PLUGINS_2025, PRE_PLUGINS_2025,
PRE_READ_PDF_2025, PRE_READ_PDF_2025,
PRE_DOCUMENT_ANALYSIS_2025, PRE_DOCUMENT_ANALYSIS_2025,
PRE_SPEECH_TO_TEXT_2026,
} }
@@ -14,6 +14,7 @@ public static class PreviewFeaturesExtensions
PreviewFeatures.PRE_PLUGINS_2025 => TB("Plugins: Preview of our plugin system where you can extend the functionality of the app"), PreviewFeatures.PRE_PLUGINS_2025 => TB("Plugins: Preview of our plugin system where you can extend the functionality of the app"),
PreviewFeatures.PRE_READ_PDF_2025 => TB("Read PDF: Preview of our PDF reading system where you can read and extract text from PDF files"), PreviewFeatures.PRE_READ_PDF_2025 => TB("Read PDF: Preview of our PDF reading system where you can read and extract text from PDF files"),
PreviewFeatures.PRE_DOCUMENT_ANALYSIS_2025 => TB("Document Analysis: Preview of our document analysis system where you can analyze and extract information from documents"), PreviewFeatures.PRE_DOCUMENT_ANALYSIS_2025 => TB("Document Analysis: Preview of our document analysis system where you can analyze and extract information from documents"),
PreviewFeatures.PRE_SPEECH_TO_TEXT_2026 => TB("Transcription: Preview of our speech to text system where you can transcribe recordings and audio files into text"),
_ => TB("Unknown preview feature") _ => TB("Unknown preview feature")
}; };
@@ -21,6 +21,7 @@ public static class PreviewVisibilityExtensions
{ {
features.Add(PreviewFeatures.PRE_RAG_2024); features.Add(PreviewFeatures.PRE_RAG_2024);
features.Add(PreviewFeatures.PRE_DOCUMENT_ANALYSIS_2025); features.Add(PreviewFeatures.PRE_DOCUMENT_ANALYSIS_2025);
features.Add(PreviewFeatures.PRE_SPEECH_TO_TEXT_2026);
} }
if (visibility >= PreviewVisibility.EXPERIMENTAL) if (visibility >= PreviewVisibility.EXPERIMENTAL)
@@ -1,32 +1,134 @@
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using AIStudio.Provider; using AIStudio.Provider;
using AIStudio.Tools.PluginSystem;
using Lua;
using Host = AIStudio.Provider.SelfHosted.Host; using Host = AIStudio.Provider.SelfHosted.Host;
namespace AIStudio.Settings; namespace AIStudio.Settings;
public readonly record struct EmbeddingProvider( public sealed record EmbeddingProvider(
uint Num, uint Num,
string Id, string Id,
string Name, string Name,
LLMProviders UsedLLMProvider, LLMProviders UsedLLMProvider,
Model Model, Model Model,
bool IsSelfHosted = false, bool IsSelfHosted = false,
bool IsEnterpriseConfiguration = false,
Guid EnterpriseConfigurationPluginId = default,
string Hostname = "http://localhost:1234", string Hostname = "http://localhost:1234",
Host Host = Host.NONE) : ISecretId Host Host = Host.NONE) : ConfigurationBaseObject, ISecretId
{ {
private static readonly ILogger<EmbeddingProvider> LOGGER = Program.LOGGER_FACTORY.CreateLogger<EmbeddingProvider>();
public static readonly EmbeddingProvider NONE = new();
public EmbeddingProvider() : this(
0,
Guid.Empty.ToString(),
string.Empty,
LLMProviders.NONE,
default,
false,
false,
Guid.Empty)
{
}
public override string ToString() => this.Name; public override string ToString() => this.Name;
#region Implementation of ISecretId #region Implementation of ISecretId
/// <inheritdoc /> /// <inheritdoc />
[JsonIgnore] [JsonIgnore]
public string SecretId => this.Id; public string SecretId => this.Id;
/// <inheritdoc /> /// <inheritdoc />
[JsonIgnore] [JsonIgnore]
public string SecretName => this.Name; public string SecretName => this.Name;
#endregion #endregion
public static bool TryParseEmbeddingProviderTable(int idx, LuaTable table, Guid configPluginId, out ConfigurationBaseObject provider)
{
provider = NONE;
if (!table.TryGetValue("Id", out var idValue) || !idValue.TryRead<string>(out var idText) || !Guid.TryParse(idText, out var id))
{
LOGGER.LogWarning($"The configured embedding provider {idx} does not contain a valid ID. The ID must be a valid GUID.");
return false;
}
if (!table.TryGetValue("Name", out var nameValue) || !nameValue.TryRead<string>(out var name))
{
LOGGER.LogWarning($"The configured embedding provider {idx} does not contain a valid name.");
return false;
}
if (!table.TryGetValue("UsedLLMProvider", out var usedLLMProviderValue) || !usedLLMProviderValue.TryRead<string>(out var usedLLMProviderText) || !Enum.TryParse<LLMProviders>(usedLLMProviderText, true, out var usedLLMProvider))
{
LOGGER.LogWarning($"The configured embedding provider {idx} does not contain a valid LLM provider enum value.");
return false;
}
if (!table.TryGetValue("Host", out var hostValue) || !hostValue.TryRead<string>(out var hostText) || !Enum.TryParse<Host>(hostText, true, out var host))
{
LOGGER.LogWarning($"The configured embedding provider {idx} does not contain a valid host enum value.");
return false;
}
if (!table.TryGetValue("Hostname", out var hostnameValue) || !hostnameValue.TryRead<string>(out var hostname))
{
LOGGER.LogWarning($"The configured embedding provider {idx} does not contain a valid hostname.");
return false;
}
if (!table.TryGetValue("Model", out var modelValue) || !modelValue.TryRead<LuaTable>(out var modelTable))
{
LOGGER.LogWarning($"The configured embedding provider {idx} does not contain a valid model table.");
return false;
}
if (!TryReadModelTable(idx, modelTable, out var model))
{
LOGGER.LogWarning($"The configured embedding provider {idx} does not contain a valid model configuration.");
return false;
}
provider = new EmbeddingProvider
{
Num = 0,
Id = id.ToString(),
Name = name,
UsedLLMProvider = usedLLMProvider,
Model = model,
IsSelfHosted = usedLLMProvider is LLMProviders.SELF_HOSTED,
IsEnterpriseConfiguration = true,
EnterpriseConfigurationPluginId = configPluginId,
Hostname = hostname,
Host = host,
};
return true;
}
private static bool TryReadModelTable(int idx, LuaTable table, out Model model)
{
model = default;
if (!table.TryGetValue("Id", out var idValue) || !idValue.TryRead<string>(out var id))
{
LOGGER.LogWarning($"The configured embedding provider {idx} does not contain a valid model ID.");
return false;
}
if (!table.TryGetValue("DisplayName", out var displayNameValue) || !displayNameValue.TryRead<string>(out var displayName))
{
LOGGER.LogWarning($"The configured embedding provider {idx} does not contain a valid model display name.");
return false;
}
model = new(id, displayName);
return true;
}
} }
+18 -1
View File
@@ -22,7 +22,7 @@ public record Profile(
public static readonly Profile NO_PROFILE = new() public static readonly Profile NO_PROFILE = new()
{ {
Name = TB("Use no profile"), Name = TB("Use no profile"), // Cannot be localized due to being a static readonly field
NeedToKnow = string.Empty, NeedToKnow = string.Empty,
Actions = string.Empty, Actions = string.Empty,
Id = Guid.Empty.ToString(), Id = Guid.Empty.ToString(),
@@ -39,6 +39,23 @@ public record Profile(
#endregion #endregion
/// <summary>
/// Gets the name of this profile. If it is the NO_PROFILE, it returns a localized string.
/// </summary>
/// <remarks>
/// Why not using the Name property directly? Because the Name property of NO_PROFILE cannot be
/// localized because it is a static readonly field. So we need this method to return a localized
/// string instead.
/// </remarks>
/// <returns>The name of this profile.</returns>
public string GetSafeName()
{
if(this == NO_PROFILE)
return TB("Use no profile");
return this.Name;
}
public string ToSystemPrompt() public string ToSystemPrompt()
{ {
if(this.Num == uint.MaxValue) if(this.Num == uint.MaxValue)
@@ -263,7 +263,7 @@ public sealed class SettingsManager
if (preselection != Profile.NO_PROFILE) if (preselection != Profile.NO_PROFILE)
return preselection; return preselection;
preselection = this.ConfigurationData.Profiles.FirstOrDefault(x => x.Id.Equals(this.ConfigurationData.App.PreselectedProfile, StringComparison.InvariantCultureIgnoreCase)); preselection = this.ConfigurationData.Profiles.FirstOrDefault(x => x.Id.Equals(this.ConfigurationData.App.PreselectedProfile, StringComparison.OrdinalIgnoreCase));
return preselection ?? Profile.NO_PROFILE; return preselection ?? Profile.NO_PROFILE;
} }
@@ -273,7 +273,7 @@ public sealed class SettingsManager
if (preselection != ChatTemplate.NO_CHAT_TEMPLATE) if (preselection != ChatTemplate.NO_CHAT_TEMPLATE)
return preselection; return preselection;
preselection = this.ConfigurationData.ChatTemplates.FirstOrDefault(x => x.Id.Equals(this.ConfigurationData.App.PreselectedChatTemplate, StringComparison.InvariantCultureIgnoreCase)); preselection = this.ConfigurationData.ChatTemplates.FirstOrDefault(x => x.Id.Equals(this.ConfigurationData.App.PreselectedChatTemplate, StringComparison.OrdinalIgnoreCase));
return preselection ?? ChatTemplate.NO_CHAT_TEMPLATE; return preselection ?? ChatTemplate.NO_CHAT_TEMPLATE;
} }
@@ -0,0 +1,134 @@
using System.Text.Json.Serialization;
using AIStudio.Provider;
using AIStudio.Tools.PluginSystem;
using Lua;
using Host = AIStudio.Provider.SelfHosted.Host;
namespace AIStudio.Settings;
public sealed record TranscriptionProvider(
uint Num,
string Id,
string Name,
LLMProviders UsedLLMProvider,
Model Model,
bool IsSelfHosted = false,
bool IsEnterpriseConfiguration = false,
Guid EnterpriseConfigurationPluginId = default,
string Hostname = "http://localhost:1234",
Host Host = Host.NONE) : ConfigurationBaseObject, ISecretId
{
private static readonly ILogger<TranscriptionProvider> LOGGER = Program.LOGGER_FACTORY.CreateLogger<TranscriptionProvider>();
public static readonly TranscriptionProvider NONE = new();
public TranscriptionProvider() : this(
0,
Guid.Empty.ToString(),
string.Empty,
LLMProviders.NONE,
default,
false,
false,
Guid.Empty)
{
}
public override string ToString() => this.Name;
#region Implementation of ISecretId
/// <inheritdoc />
[JsonIgnore]
public string SecretId => this.Id;
/// <inheritdoc />
[JsonIgnore]
public string SecretName => this.Name;
#endregion
public static bool TryParseTranscriptionProviderTable(int idx, LuaTable table, Guid configPluginId, out ConfigurationBaseObject provider)
{
provider = NONE;
if (!table.TryGetValue("Id", out var idValue) || !idValue.TryRead<string>(out var idText) || !Guid.TryParse(idText, out var id))
{
LOGGER.LogWarning($"The configured transcription provider {idx} does not contain a valid ID. The ID must be a valid GUID.");
return false;
}
if (!table.TryGetValue("Name", out var nameValue) || !nameValue.TryRead<string>(out var name))
{
LOGGER.LogWarning($"The configured transcription provider {idx} does not contain a valid name.");
return false;
}
if (!table.TryGetValue("UsedLLMProvider", out var usedLLMProviderValue) || !usedLLMProviderValue.TryRead<string>(out var usedLLMProviderText) || !Enum.TryParse<LLMProviders>(usedLLMProviderText, true, out var usedLLMProvider))
{
LOGGER.LogWarning($"The configured transcription provider {idx} does not contain a valid LLM provider enum value.");
return false;
}
if (!table.TryGetValue("Host", out var hostValue) || !hostValue.TryRead<string>(out var hostText) || !Enum.TryParse<Host>(hostText, true, out var host))
{
LOGGER.LogWarning($"The configured transcription provider {idx} does not contain a valid host enum value.");
return false;
}
if (!table.TryGetValue("Hostname", out var hostnameValue) || !hostnameValue.TryRead<string>(out var hostname))
{
LOGGER.LogWarning($"The configured transcription provider {idx} does not contain a valid hostname.");
return false;
}
if (!table.TryGetValue("Model", out var modelValue) || !modelValue.TryRead<LuaTable>(out var modelTable))
{
LOGGER.LogWarning($"The configured transcription provider {idx} does not contain a valid model table.");
return false;
}
if (!TryReadModelTable(idx, modelTable, out var model))
{
LOGGER.LogWarning($"The configured transcription provider {idx} does not contain a valid model configuration.");
return false;
}
provider = new TranscriptionProvider
{
Num = 0,
Id = id.ToString(),
Name = name,
UsedLLMProvider = usedLLMProvider,
Model = model,
IsSelfHosted = usedLLMProvider is LLMProviders.SELF_HOSTED,
IsEnterpriseConfiguration = true,
EnterpriseConfigurationPluginId = configPluginId,
Hostname = hostname,
Host = host,
};
return true;
}
private static bool TryReadModelTable(int idx, LuaTable table, out Model model)
{
model = default;
if (!table.TryGetValue("Id", out var idValue) || !idValue.TryRead<string>(out var id))
{
LOGGER.LogWarning($"The configured transcription provider {idx} does not contain a valid model ID.");
return false;
}
if (!table.TryGetValue("DisplayName", out var displayNameValue) || !displayNameValue.TryRead<string>(out var displayName))
{
LOGGER.LogWarning($"The configured transcription provider {idx} does not contain a valid model display name.");
return false;
}
model = new(id, displayName);
return true;
}
}
@@ -0,0 +1,11 @@
namespace AIStudio.Tools;
public static class DropLayers
{
public const int ROOT = 0;
public const int PAGES = 10;
public const int ASSISTANTS = 20;
public const int DIALOGS = 100;
}
+4
View File
@@ -34,6 +34,10 @@ public enum Event
// RAG events: // RAG events:
RAG_AUTO_DATA_SOURCES_SELECTED, RAG_AUTO_DATA_SOURCES_SELECTED,
// File attachment events:
REGISTER_FILE_DROP_AREA,
UNREGISTER_FILE_DROP_AREA,
// Send events: // Send events:
SEND_TO_GRAMMAR_SPELLING_ASSISTANT, SEND_TO_GRAMMAR_SPELLING_ASSISTANT,
SEND_TO_ICON_FINDER_ASSISTANT, SEND_TO_ICON_FINDER_ASSISTANT,
@@ -0,0 +1,67 @@
namespace AIStudio.Tools.MIME;
public class ApplicationBuilder : ISubtype
{
private const BaseType BASE_TYPE = BaseType.APPLICATION;
private ApplicationBuilder()
{
}
public static ApplicationBuilder Create() => new();
private ApplicationSubtype subtype;
public ApplicationBuilder UseSubtype(string subType)
{
this.subtype = subType.ToLowerInvariant() switch
{
"vnd.ms-excel" => ApplicationSubtype.EXCEL_OLD,
"vnd.ms-word" => ApplicationSubtype.WORD_OLD,
"vnd.ms-powerpoint" => ApplicationSubtype.POWERPOINT_OLD,
"vnd.openxmlformats-officedocument.spreadsheetml.sheet" => ApplicationSubtype.EXCEL,
"vnd.openxmlformats-officedocument.wordprocessingml.document" => ApplicationSubtype.WORD,
"vnd.openxmlformats-officedocument.presentationml.presentation" => ApplicationSubtype.POWERPOINT,
"octet-stream" => ApplicationSubtype.OCTET_STREAM,
"json" => ApplicationSubtype.JSON,
"xml" => ApplicationSubtype.XML,
"pdf" => ApplicationSubtype.PDF,
"zip" => ApplicationSubtype.ZIP,
"x-www-form-urlencoded" => ApplicationSubtype.X_WWW_FORM_URLENCODED,
_ => throw new ArgumentOutOfRangeException(nameof(subType), "Unsupported MIME application subtype.")
};
return this;
}
public ApplicationBuilder UseSubtype(ApplicationSubtype subType)
{
this.subtype = subType;
return this;
}
#region Implementation of IMIMESubtype
public MIMEType Build() => new()
{
Type = this,
TextRepresentation = this.subtype switch
{
ApplicationSubtype.EXCEL_OLD => $"{BASE_TYPE}/vnd.ms-excel".ToLowerInvariant(),
ApplicationSubtype.WORD_OLD => $"{BASE_TYPE}/vnd.ms-word".ToLowerInvariant(),
ApplicationSubtype.POWERPOINT_OLD => $"{BASE_TYPE}/vnd.ms-powerpoint".ToLowerInvariant(),
ApplicationSubtype.EXCEL => $"{BASE_TYPE}/vnd.openxmlformats-officedocument.spreadsheetml.sheet".ToLowerInvariant(),
ApplicationSubtype.WORD => $"{BASE_TYPE}/vnd.openxmlformats-officedocument.wordprocessingml.document".ToLowerInvariant(),
ApplicationSubtype.POWERPOINT => $"{BASE_TYPE}/vnd.openxmlformats-officedocument.presentationml.presentation".ToLowerInvariant(),
_ => $"{BASE_TYPE}/{this.subtype}".ToLowerInvariant()
}
};
#endregion
}
@@ -0,0 +1,21 @@
namespace AIStudio.Tools.MIME;
public enum ApplicationSubtype
{
OCTET_STREAM,
JSON,
XML,
PDF,
ZIP,
X_WWW_FORM_URLENCODED,
WORD_OLD,
WORD,
EXCEL_OLD,
EXCEL,
POWERPOINT_OLD,
POWERPOINT,
}
@@ -0,0 +1,51 @@
namespace AIStudio.Tools.MIME;
public class AudioBuilder : ISubtype
{
private const BaseType BASE_TYPE = BaseType.AUDIO;
private AudioBuilder()
{
}
public static AudioBuilder Create() => new();
private AudioSubtype subtype;
public AudioBuilder UseSubtype(string subType)
{
this.subtype = subType.ToLowerInvariant() switch
{
"mpeg" => AudioSubtype.MPEG,
"wav" => AudioSubtype.WAV,
"ogg" => AudioSubtype.OGG,
"aac" => AudioSubtype.AAC,
"flac" => AudioSubtype.FLAC,
"webm" => AudioSubtype.WEBM,
"mp4" => AudioSubtype.MP4,
"mp3" => AudioSubtype.MP3,
"m4a" => AudioSubtype.M4A,
"aiff" => AudioSubtype.AIFF,
_ => throw new ArgumentException("Unsupported MIME audio subtype.", nameof(subType))
};
return this;
}
public AudioBuilder UseSubtype(AudioSubtype subType)
{
this.subtype = subType;
return this;
}
#region Implementation of IMIMESubtype
public MIMEType Build() => new()
{
Type = this,
TextRepresentation = $"{BASE_TYPE}/{this.subtype}".ToLowerInvariant()
};
#endregion
}
@@ -0,0 +1,16 @@
namespace AIStudio.Tools.MIME;
public enum AudioSubtype
{
WAV,
MP3,
OGG,
AAC,
FLAC,
// ReSharper disable once InconsistentNaming
M4A,
MPEG,
MP4,
WEBM,
AIFF
}
@@ -0,0 +1,10 @@
namespace AIStudio.Tools.MIME;
public enum BaseType
{
APPLICATION,
AUDIO,
IMAGE,
VIDEO,
TEXT,
}
@@ -0,0 +1,120 @@
namespace AIStudio.Tools.MIME;
public class Builder
{
private Builder()
{
}
public static Builder Create() => new();
public static MIMEType FromFilename(string filenameOrPath)
{
var extension = Path.GetExtension(filenameOrPath);
if (string.IsNullOrEmpty(extension))
throw new ArgumentException("Filename or path does not have a valid extension.", nameof(filenameOrPath));
extension = extension.TrimStart('.').ToLowerInvariant();
var builder = Create();
return extension switch
{
// Application types
"pdf" => builder.UseApplication().UseSubtype(ApplicationSubtype.PDF).Build(),
"zip" => builder.UseApplication().UseSubtype(ApplicationSubtype.ZIP).Build(),
"doc" => builder.UseApplication().UseSubtype(ApplicationSubtype.WORD_OLD).Build(),
"docx" => builder.UseApplication().UseSubtype(ApplicationSubtype.WORD).Build(),
"xls" => builder.UseApplication().UseSubtype(ApplicationSubtype.EXCEL_OLD).Build(),
"xlsx" => builder.UseApplication().UseSubtype(ApplicationSubtype.EXCEL).Build(),
"ppt" => builder.UseApplication().UseSubtype(ApplicationSubtype.POWERPOINT_OLD).Build(),
"pptx" => builder.UseApplication().UseSubtype(ApplicationSubtype.POWERPOINT).Build(),
"json" => builder.UseApplication().UseSubtype(ApplicationSubtype.JSON).Build(),
"xml" => builder.UseApplication().UseSubtype(ApplicationSubtype.XML).Build(),
// Text types
"txt" => builder.UseText().UseSubtype(TextSubtype.PLAIN).Build(),
"html" or "htm" => builder.UseText().UseSubtype(TextSubtype.HTML).Build(),
"css" => builder.UseText().UseSubtype(TextSubtype.CSS).Build(),
"csv" => builder.UseText().UseSubtype(TextSubtype.CSV).Build(),
"js" => builder.UseText().UseSubtype(TextSubtype.JAVASCRIPT).Build(),
"md" or "markdown" => builder.UseText().UseSubtype(TextSubtype.MARKDOWN).Build(),
// Audio types
"wav" => builder.UseAudio().UseSubtype(AudioSubtype.WAV).Build(),
"mp3" => builder.UseAudio().UseSubtype(AudioSubtype.MP3).Build(),
"ogg" => builder.UseAudio().UseSubtype(AudioSubtype.OGG).Build(),
"aac" => builder.UseAudio().UseSubtype(AudioSubtype.AAC).Build(),
"flac" => builder.UseAudio().UseSubtype(AudioSubtype.FLAC).Build(),
"m4a" => builder.UseAudio().UseSubtype(AudioSubtype.M4A).Build(),
"aiff" or "aif" => builder.UseAudio().UseSubtype(AudioSubtype.AIFF).Build(),
"mpga" => builder.UseAudio().UseSubtype(AudioSubtype.MPEG).Build(),
"webm" => builder.UseAudio().UseSubtype(AudioSubtype.WEBM).Build(),
// Image types
"jpg" or "jpeg" => builder.UseImage().UseSubtype(ImageSubtype.JPEG).Build(),
"png" => builder.UseImage().UseSubtype(ImageSubtype.PNG).Build(),
"gif" => builder.UseImage().UseSubtype(ImageSubtype.GIF).Build(),
"tiff" or "tif" => builder.UseImage().UseSubtype(ImageSubtype.TIFF).Build(),
"webp" => builder.UseImage().UseSubtype(ImageSubtype.WEBP).Build(),
"svg" => builder.UseImage().UseSubtype(ImageSubtype.SVG).Build(),
"heic" => builder.UseImage().UseSubtype(ImageSubtype.HEIC).Build(),
// Video types
"mp4" => builder.UseVideo().UseSubtype(VideoSubtype.MP4).Build(),
"avi" => builder.UseVideo().UseSubtype(VideoSubtype.AVI).Build(),
"mov" => builder.UseVideo().UseSubtype(VideoSubtype.MOV).Build(),
"mkv" => builder.UseVideo().UseSubtype(VideoSubtype.MKV).Build(),
"mpeg" or "mpg" => builder.UseVideo().UseSubtype(VideoSubtype.MPEG).Build(),
_ => throw new ArgumentException($"Unsupported file extension: '.{extension}'.", nameof(filenameOrPath))
};
}
public static MIMEType FromTextRepresentation(string textRepresentation)
{
var parts = textRepresentation.Split('/');
if (parts.Length != 2)
throw new ArgumentException("Invalid MIME type format.", nameof(textRepresentation));
var baseType = parts[0].ToLowerInvariant();
var subType = parts[1].ToLowerInvariant();
var builder = Create();
switch (baseType)
{
case "application":
var appBuilder = builder.UseApplication();
return appBuilder.UseSubtype(subType).Build();
case "text":
var textBuilder = builder.UseText();
return textBuilder.UseSubtype(subType).Build();
case "audio":
var audioBuilder = builder.UseAudio();
return audioBuilder.UseSubtype(subType).Build();
case "image":
var imageBuilder = builder.UseImage();
return imageBuilder.UseSubtype(subType).Build();
case "video":
var videoBuilder = builder.UseVideo();
return videoBuilder.UseSubtype(subType).Build();
default:
throw new ArgumentException("Unsupported base type.", nameof(textRepresentation));
}
}
public ApplicationBuilder UseApplication() => ApplicationBuilder.Create();
public TextBuilder UseText() => TextBuilder.Create();
public AudioBuilder UseAudio() => AudioBuilder.Create();
public ImageBuilder UseImage() => ImageBuilder.Create();
public VideoBuilder UseVideo() => VideoBuilder.Create();
}
@@ -0,0 +1,6 @@
namespace AIStudio.Tools.MIME;
public interface ISubtype
{
public MIMEType Build();
}
@@ -0,0 +1,48 @@
namespace AIStudio.Tools.MIME;
public class ImageBuilder : ISubtype
{
private const BaseType BASE_TYPE = BaseType.IMAGE;
private ImageBuilder()
{
}
public static ImageBuilder Create() => new();
private ImageSubtype subtype;
public ImageBuilder UseSubtype(string subType)
{
this.subtype = subType.ToLowerInvariant() switch
{
"jpeg" or "jpg" => ImageSubtype.JPEG,
"png" => ImageSubtype.PNG,
"gif" => ImageSubtype.GIF,
"webp" => ImageSubtype.WEBP,
"tiff" or "tif" => ImageSubtype.TIFF,
"svg+xml" or "svg" => ImageSubtype.SVG,
"heic" => ImageSubtype.HEIC,
_ => throw new ArgumentException("Unsupported MIME image subtype.", nameof(subType))
};
return this;
}
public ImageBuilder UseSubtype(ImageSubtype subType)
{
this.subtype = subType;
return this;
}
#region Implementation of IMIMESubtype
public MIMEType Build() => new()
{
Type = this,
TextRepresentation = $"{BASE_TYPE}/{this.subtype}".ToLowerInvariant()
};
#endregion
}
@@ -0,0 +1,12 @@
namespace AIStudio.Tools.MIME;
public enum ImageSubtype
{
JPEG,
PNG,
GIF,
TIFF,
WEBP,
SVG,
HEIC,
}
@@ -0,0 +1,16 @@
namespace AIStudio.Tools.MIME;
public record MIMEType
{
public required ISubtype Type { get; init; }
public required string TextRepresentation { get; init; }
#region Overrides of Object
public override string ToString() => this.TextRepresentation;
#endregion
public static implicit operator string(MIMEType mimeType) => mimeType.TextRepresentation;
}
@@ -0,0 +1,15 @@
namespace AIStudio.Tools.MIME;
public static class MIMETypeExtensions
{
public static string[] ToStringArray(this MIMEType[] mimeTypes)
{
var result = new string[mimeTypes.Length];
for (var i = 0; i < mimeTypes.Length; i++)
{
result[i] = mimeTypes[i];
}
return result;
}
}
@@ -0,0 +1,49 @@
namespace AIStudio.Tools.MIME;
public class TextBuilder : ISubtype
{
private const BaseType BASE_TYPE = BaseType.TEXT;
private TextBuilder()
{
}
public static TextBuilder Create() => new();
private TextSubtype subtype;
public TextBuilder UseSubtype(string subType)
{
this.subtype = subType.ToLowerInvariant() switch
{
"plain" => TextSubtype.PLAIN,
"html" => TextSubtype.HTML,
"css" => TextSubtype.CSS,
"csv" => TextSubtype.CSV,
"javascript" => TextSubtype.JAVASCRIPT,
"xml" => TextSubtype.XML,
"markdown" => TextSubtype.MARKDOWN,
"json" => TextSubtype.JSON,
_ => throw new ArgumentException("Unsupported MIME text subtype.", nameof(subType))
};
return this;
}
public TextBuilder UseSubtype(TextSubtype subType)
{
this.subtype = subType;
return this;
}
#region Implementation of IMIMESubtype
public MIMEType Build() => new()
{
Type = this,
TextRepresentation = $"{BASE_TYPE}/{this.subtype}".ToLowerInvariant()
};
#endregion
}
@@ -0,0 +1,13 @@
namespace AIStudio.Tools.MIME;
public enum TextSubtype
{
PLAIN,
HTML,
CSS,
CSV,
JAVASCRIPT,
XML,
JSON,
MARKDOWN,
}
@@ -0,0 +1,46 @@
namespace AIStudio.Tools.MIME;
public class VideoBuilder : ISubtype
{
private const BaseType BASE_TYPE = BaseType.VIDEO;
private VideoBuilder()
{
}
public static VideoBuilder Create() => new();
private VideoSubtype subtype;
public VideoBuilder UseSubtype(string subType)
{
this.subtype = subType.ToLowerInvariant() switch
{
"mp4" => VideoSubtype.MP4,
"webm" => VideoSubtype.WEBM,
"avi" => VideoSubtype.AVI,
"mov" => VideoSubtype.MOV,
"mkv" => VideoSubtype.MKV,
_ => throw new ArgumentException("Unsupported MIME video subtype.", nameof(subType))
};
return this;
}
public VideoBuilder UseSubtype(VideoSubtype subType)
{
this.subtype = subType;
return this;
}
#region Implementation of IMIMESubtype
public MIMEType Build() => new()
{
Type = this,
TextRepresentation = $"{BASE_TYPE}/{this.subtype}".ToLowerInvariant()
};
#endregion
}
@@ -0,0 +1,11 @@
namespace AIStudio.Tools.MIME;
public enum VideoSubtype
{
MP4,
AVI,
MOV,
MKV,
WEBM,
MPEG,
}
Loaded 100 of 123 files, more files were not shown because too many files have changed in this diff. Show more