mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-10-20 08:58:00 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
09f93c65c9 | ||
|
|
37293e4a7c | ||
|
|
8e85473b28 | ||
|
|
251b7973fa | ||
|
|
c9e1ae641f | ||
|
|
530b5f6cf8 | ||
|
|
3763b1e4a4 | ||
|
|
127778df42 | ||
|
|
c9f037bb2c | ||
|
|
f84ea772b4 | ||
|
|
a1f69c8dad | ||
|
|
cc3560fdd2 | ||
|
|
85af3ed651 | ||
|
|
3abc2bf0f1 | ||
|
|
0a504276d9 | ||
|
|
5f18cbe2bf | ||
|
|
eb39d130b9 | ||
|
|
4f8266f255 | ||
|
|
ab7618097d | ||
|
|
529986837e | ||
|
|
b94614b0ab | ||
|
|
7110fb866f | ||
|
|
30197cf507 | ||
|
|
f9a62febcb | ||
|
|
324ea9eb73 | ||
|
|
724cf55293 | ||
|
|
df4a769fdc | ||
|
|
878e0f4dc5 | ||
|
|
47975870b4 |
No files matched your search
@@ -13,7 +13,7 @@ MindWork AI Studio is a cross-platform desktop application for interacting with
|
||||
- **Providers:** Multi-provider architecture supporting OpenAI, Anthropic, Google, Mistral, Perplexity, self-hosted models, and others
|
||||
- **Plugin System:** Lua-based plugin system for language packs, configuration, and future assistant plugins
|
||||
|
||||
## Building and Running
|
||||
## Building
|
||||
|
||||
### Prerequisites
|
||||
- .NET 9 SDK
|
||||
@@ -22,39 +22,16 @@ MindWork AI Studio is a cross-platform desktop application for interacting with
|
||||
- Tauri prerequisites (platform-specific dependencies)
|
||||
- **Note:** Development on Linux is discouraged due to complex Tauri dependencies that vary by distribution
|
||||
|
||||
### One-Time Setup
|
||||
### Build
|
||||
```bash
|
||||
cd app/Build
|
||||
dotnet run build
|
||||
```
|
||||
This builds the .NET app as a Tauri "sidecar" binary, which is required even for development.
|
||||
|
||||
### Development Workflow
|
||||
Run these commands in separate terminals:
|
||||
|
||||
**Terminal 1 - Start Rust runtime:**
|
||||
```bash
|
||||
cd runtime
|
||||
cargo tauri dev --no-watch
|
||||
```
|
||||
|
||||
**Terminal 2 - Start .NET app:**
|
||||
```bash
|
||||
cd "app/MindWork AI Studio"
|
||||
dotnet run
|
||||
```
|
||||
|
||||
The app will start in the Tauri window. Hot reload is supported for .NET code changes.
|
||||
|
||||
### Building for Production
|
||||
```bash
|
||||
cd app/Build
|
||||
dotnet run build
|
||||
```
|
||||
Creates a release build for the current platform and architecture. Output is in `runtime/target/release/`.
|
||||
|
||||
### Running Tests
|
||||
Currently no automated test suite exists in the repository.
|
||||
Currently, no automated test suite exists in the repository.
|
||||
|
||||
## Architecture Details
|
||||
|
||||
@@ -125,6 +102,14 @@ Plugins can configure:
|
||||
- Preview features visibility
|
||||
- Preselected profiles
|
||||
- Chat templates
|
||||
- etc.
|
||||
|
||||
When adding configuration options, update:
|
||||
- `app/MindWork AI Studio/Tools/PluginSystem/PluginConfiguration.cs`: In method `TryProcessConfiguration` register new options.
|
||||
- `app/MindWork AI Studio/Tools/PluginSystem/PluginFactory.Loading.cs`: In method `LoadAll` check for leftover configuration.
|
||||
- The corresponding data class in `app/MindWork AI Studio/Settings/DataModel/` to call `ManagedConfiguration.Register(...)`, when adding config options (in contrast to complex config. objects)
|
||||
- `app/MindWork AI Studio/Tools/PluginSystem/PluginConfigurationObject.cs` for parsing logic of complex configuration objects.
|
||||
- `app/MindWork AI Studio/Plugins/configuration/plugin.lua` to document the new configuration option.
|
||||
|
||||
## RAG (Retrieval-Augmented Generation)
|
||||
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ FSL-1.1-MIT
|
||||
|
||||
## Notice
|
||||
|
||||
Copyright 2025 Thorsten Sommer
|
||||
Copyright 2026 Thorsten Sommer
|
||||
|
||||
## Terms and Conditions
|
||||
|
||||
|
||||
@@ -79,6 +79,7 @@ Since March 2025: We have started developing the plugin system. There will be la
|
||||
</h3>
|
||||
</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.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).
|
||||
@@ -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.29: Added agents to support the RAG process (selecting the best data sources & validating retrieved data as part of the augmentation process)
|
||||
- v0.9.26+: Added RAG for external data sources using our [ERI interface](https://mindworkai.org/#eri---external-retrieval-interface) as a preview feature.
|
||||
- v0.9.25: Added [xAI](https://x.ai/) as a new provider. xAI provides their Grok models for generating content.
|
||||
- v0.9.23: Added support for OpenAI `o` models (`o1`, `o1-mini`, `o3`, etc.); added also an [ERI](https://github.com/MindWorkAI/ERI) server coding assistant as a preview feature behind the RAG feature flag. Your own ERI server can be used to gain access to, e.g., your enterprise data from within AI Studio.
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
@@ -69,7 +69,10 @@ public sealed partial class CollectI18NKeysCommand
|
||||
|
||||
var ns = this.DetermineNamespace(filePath);
|
||||
var fileInfo = new FileInfo(filePath);
|
||||
var name = fileInfo.Name.Replace(fileInfo.Extension, string.Empty).Replace(".razor", string.Empty);
|
||||
|
||||
var name = this.DetermineTypeName(filePath)
|
||||
?? fileInfo.Name.Replace(fileInfo.Extension, string.Empty).Replace(".razor", string.Empty);
|
||||
|
||||
var langNamespace = $"{ns}.{name}".ToUpperInvariant();
|
||||
foreach (var match in matches)
|
||||
{
|
||||
@@ -236,6 +239,14 @@ public sealed partial class CollectI18NKeysCommand
|
||||
Console.WriteLine($"- Error: The file '{filePath}' is neither a C# nor a Razor file. We can't determine the namespace.");
|
||||
return null;
|
||||
}
|
||||
|
||||
private string? DetermineTypeName(string filePath)
|
||||
{
|
||||
if (!filePath.EndsWith(".cs", StringComparison.OrdinalIgnoreCase))
|
||||
return null;
|
||||
|
||||
return this.ReadPartialTypeNameFromCSharp(filePath);
|
||||
}
|
||||
|
||||
private string? ReadNamespaceFromCSharp(string filePath)
|
||||
{
|
||||
@@ -254,6 +265,24 @@ public sealed partial class CollectI18NKeysCommand
|
||||
var match = matches[0];
|
||||
return match.Groups[1].Value;
|
||||
}
|
||||
|
||||
private string? ReadPartialTypeNameFromCSharp(string filePath)
|
||||
{
|
||||
var content = File.ReadAllText(filePath, Encoding.UTF8);
|
||||
var matches = CSharpPartialTypeRegex().Matches(content);
|
||||
|
||||
if (matches.Count == 0)
|
||||
return null;
|
||||
|
||||
if (matches.Count > 1)
|
||||
{
|
||||
Console.WriteLine($"The file '{filePath}' contains multiple partial type declarations. This scenario is not supported.");
|
||||
return null;
|
||||
}
|
||||
|
||||
var match = matches[0];
|
||||
return match.Groups[1].Value;
|
||||
}
|
||||
|
||||
private string? ReadNamespaceFromRazor(string filePath)
|
||||
{
|
||||
@@ -278,4 +307,7 @@ public sealed partial class CollectI18NKeysCommand
|
||||
|
||||
[GeneratedRegex("""namespace\s+([a-zA-Z0-9_.]+)""")]
|
||||
private static partial Regex CSharpNamespaceRegex();
|
||||
}
|
||||
|
||||
[GeneratedRegex("""\bpartial\s+(?:class|struct|interface|record(?:\s+(?:class|struct))?)\s+([A-Za-z_][A-Za-z0-9_]*)""")]
|
||||
private static partial Regex CSharpPartialTypeRegex();
|
||||
}
|
||||
@@ -3,8 +3,10 @@ namespace Build.Commands;
|
||||
public enum PrepareAction
|
||||
{
|
||||
NONE,
|
||||
|
||||
PATCH,
|
||||
MINOR,
|
||||
MAJOR,
|
||||
|
||||
BUILD,
|
||||
MONTH,
|
||||
YEAR,
|
||||
|
||||
SET,
|
||||
}
|
||||
@@ -13,13 +13,32 @@ namespace Build.Commands;
|
||||
public sealed partial class UpdateMetadataCommands
|
||||
{
|
||||
[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())
|
||||
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:
|
||||
await this.PerformPrepare(action, true);
|
||||
await this.PerformPrepare(action, true, version);
|
||||
|
||||
// Build once to allow the Rust compiler to read the changed metadata
|
||||
// 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")]
|
||||
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())
|
||||
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.Write("- Are you trying to prepare a new release? (y/n) ");
|
||||
var userAnswer = Console.ReadLine();
|
||||
@@ -66,18 +104,18 @@ public sealed partial class UpdateMetadataCommands
|
||||
Console.WriteLine("- Please use the 'release' command instead");
|
||||
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)
|
||||
Console.WriteLine("==============================");
|
||||
|
||||
|
||||
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))
|
||||
{
|
||||
var buildNumber = await this.IncreaseBuildNumber();
|
||||
@@ -239,17 +277,6 @@ public sealed partial class UpdateMetadataCommands
|
||||
var pathChangelogs = Path.Combine(Environment.GetAIStudioDirectory(), "wwwroot", "changelog");
|
||||
var nextBuildNumber = currentBuildNumber + 1;
|
||||
|
||||
//
|
||||
// We assume that most of the time, there will be patch releases:
|
||||
//
|
||||
var nextMajor = currentAppVersion.Major;
|
||||
var nextMinor = currentAppVersion.Minor;
|
||||
var nextPatch = currentAppVersion.Patch + 1;
|
||||
|
||||
var nextAppVersion = $"{nextMajor}.{nextMinor}.{nextPatch}";
|
||||
var nextChangelogFilename = $"v{nextAppVersion}.md";
|
||||
var nextChangelogFilePath = Path.Combine(pathChangelogs, nextChangelogFilename);
|
||||
|
||||
//
|
||||
// Regarding the next build time: We assume that the next release will take place in one week from now.
|
||||
// Thus, we check how many days this month has left. In the end, we want to predict the year and month
|
||||
@@ -259,6 +286,19 @@ public sealed partial class UpdateMetadataCommands
|
||||
var nextBuildYear = (DateTime.Today + TimeSpan.FromDays(7)).Year;
|
||||
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 = $"""
|
||||
# v{nextAppVersion}, build {nextBuildNumber} ({nextBuildTimeString})
|
||||
|
||||
@@ -355,49 +395,69 @@ public sealed partial class UpdateMetadataCommands
|
||||
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;
|
||||
|
||||
|
||||
if (action == PrepareAction.NONE)
|
||||
{
|
||||
Console.WriteLine("- No action specified. Skipping app version update.");
|
||||
return new(string.Empty, 0, 0, 0);
|
||||
}
|
||||
|
||||
|
||||
var pathMetadata = Environment.GetMetadataPath();
|
||||
var lines = await File.ReadAllLinesAsync(pathMetadata, Encoding.UTF8);
|
||||
var currentAppVersionLine = lines[APP_VERSION_INDEX].Trim();
|
||||
var currentAppVersion = AppVersionRegex().Match(currentAppVersionLine);
|
||||
var currentPatch = int.Parse(currentAppVersion.Groups["patch"].Value);
|
||||
var currentMinor = int.Parse(currentAppVersion.Groups["minor"].Value);
|
||||
var currentMajor = int.Parse(currentAppVersion.Groups["major"].Value);
|
||||
|
||||
switch (action)
|
||||
|
||||
int newMajor, newMinor, newPatch;
|
||||
if (action == PrepareAction.SET && !string.IsNullOrWhiteSpace(version))
|
||||
{
|
||||
case PrepareAction.PATCH:
|
||||
currentPatch++;
|
||||
break;
|
||||
|
||||
case PrepareAction.MINOR:
|
||||
currentPatch = 0;
|
||||
currentMinor++;
|
||||
break;
|
||||
|
||||
case PrepareAction.MAJOR:
|
||||
currentPatch = 0;
|
||||
currentMinor = 0;
|
||||
currentMajor++;
|
||||
break;
|
||||
// Parse the provided version string:
|
||||
var versionMatch = AppVersionRegex().Match(version);
|
||||
if (!versionMatch.Success)
|
||||
{
|
||||
Console.WriteLine($"- Error: Invalid version format '{version}'. Expected format: major.minor.patch (e.g., 26.1.2)");
|
||||
return new(string.Empty, 0, 0, 0);
|
||||
}
|
||||
|
||||
newMajor = int.Parse(versionMatch.Groups["major"].Value);
|
||||
newMinor = int.Parse(versionMatch.Groups["minor"].Value);
|
||||
newPatch = int.Parse(versionMatch.Groups["patch"].Value);
|
||||
}
|
||||
|
||||
var updatedAppVersion = $"{currentMajor}.{currentMinor}.{currentPatch}";
|
||||
else
|
||||
{
|
||||
// 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}'.");
|
||||
|
||||
|
||||
lines[APP_VERSION_INDEX] = updatedAppVersion;
|
||||
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)
|
||||
|
||||
@@ -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/=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/=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/=LM/@EntryIndexedValue">LM</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/=I18N/@EntryIndexedValue">I18N</s:String>
|
||||
<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/=gwdg/@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/=mime/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=mwais/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=ollama/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=tauri_0027s/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
@@ -27,6 +27,7 @@
|
||||
<script src="system/MudBlazor.Markdown/MudBlazor.Markdown.min.js"></script>
|
||||
<script src="system/CodeBeam.MudBlazor.Extensions/MudExtensions.min.js"></script>
|
||||
<script src="app.js"></script>
|
||||
<script src="audio.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -190,6 +190,7 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
|
||||
{
|
||||
this.chatThread = new()
|
||||
{
|
||||
IncludeDateTime = false,
|
||||
SelectedProvider = this.providerSettings.Id,
|
||||
SelectedProfile = this.AllowProfiles ? this.currentProfile.Id : Profile.NO_PROFILE.Id,
|
||||
SystemPrompt = this.SystemPrompt,
|
||||
@@ -205,6 +206,7 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
|
||||
var chatId = Guid.NewGuid();
|
||||
this.chatThread = new()
|
||||
{
|
||||
IncludeDateTime = false,
|
||||
SelectedProvider = this.providerSettings.Id,
|
||||
SelectedProfile = this.AllowProfiles ? this.currentProfile.Id : Profile.NO_PROFILE.Id,
|
||||
SystemPrompt = this.SystemPrompt,
|
||||
|
||||
+53
-44
@@ -42,50 +42,59 @@ else
|
||||
</MudStack>
|
||||
|
||||
<MudExpansionPanels Class="mb-3 mt-6" MultiExpansion="@false">
|
||||
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Policy" HeaderText="@(T("Policy definition") + $": {this.selectedPolicy?.PolicyName}")" IsExpanded="@(!this.selectedPolicy?.IsProtected ?? true)">
|
||||
<MudText Typo="Typo.h5" Class="mb-1">
|
||||
@T("Common settings")
|
||||
</MudText>
|
||||
|
||||
<MudTextField T="string" Disabled="@this.IsNoPolicySelectedOrProtected" @bind-Text="@this.policyName" Validation="@this.ValidatePolicyName" Immediate="@true" Label="@T("Policy name")" HelperText="@T("Please give your policy a name that provides information about the intended purpose. The name will be displayed to users in AI Studio.")" Counter="60" MaxLength="60" Variant="Variant.Outlined" Margin="Margin.Normal" UserAttributes="@USER_INPUT_ATTRIBUTES" Class="mb-3" OnKeyUp="() => this.PolicyNameWasChanged()"/>
|
||||
|
||||
<MudTextField T="string" Disabled="@this.IsNoPolicySelectedOrProtected" @bind-Text="@this.policyDescription" Validation="@this.ValidatePolicyDescription" Immediate="@true" Label="@T("Policy description")" HelperText="@T("Please provide a brief description of your policy. Describe or explain what your policy does. This description will be shown to users in AI Studio.")" Counter="512" MaxLength="512" Variant="Variant.Outlined" Margin="Margin.Normal" Lines="3" AutoGrow="@true" MaxLines="6" UserAttributes="@USER_INPUT_ATTRIBUTES" Class="mb-3"/>
|
||||
|
||||
<MudTextSwitch Disabled="@(this.IsNoPolicySelected || (this.selectedPolicy?.IsManaged ?? true))" Label="@T("Would you like to protect this policy so that you cannot accidentally edit or delete it?")" Value="@this.policyIsProtected" ValueChanged="async state => await this.PolicyProtectionWasChanged(state)" LabelOn="@T("Yes, protect this policy")" LabelOff="@T("No, the policy can be edited")" />
|
||||
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Policy" HeaderText="@(T("Policy definition") + $": {this.selectedPolicy?.PolicyName}")" IsExpanded="@this.policyDefinitionExpanded" ExpandedChanged="@this.PolicyDefinitionExpandedChanged">
|
||||
@if (!this.policyDefinitionExpanded)
|
||||
{
|
||||
<MudJustifiedText Typo="Typo.body1" Class="mb-1">
|
||||
@T("Expand this section to view and edit the policy definition.")
|
||||
</MudJustifiedText>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudText Typo="Typo.h5" Class="mb-1">
|
||||
@T("Common settings")
|
||||
</MudText>
|
||||
|
||||
<MudTextField T="string" Disabled="@this.IsNoPolicySelectedOrProtected" @bind-Text="@this.policyName" Validation="@this.ValidatePolicyName" Immediate="@true" Label="@T("Policy name")" HelperText="@T("Please give your policy a name that provides information about the intended purpose. The name will be displayed to users in AI Studio.")" Counter="60" MaxLength="60" Variant="Variant.Outlined" Margin="Margin.Normal" UserAttributes="@USER_INPUT_ATTRIBUTES" Class="mb-3" OnKeyUp="@(() => this.PolicyNameWasChanged())"/>
|
||||
|
||||
<MudTextField T="string" Disabled="@this.IsNoPolicySelectedOrProtected" @bind-Text="@this.policyDescription" Validation="@this.ValidatePolicyDescription" Immediate="@true" Label="@T("Policy description")" HelperText="@T("Please provide a brief description of your policy. Describe or explain what your policy does. This description will be shown to users in AI Studio.")" Counter="512" MaxLength="512" Variant="Variant.Outlined" Margin="Margin.Normal" Lines="3" AutoGrow="@true" MaxLines="6" UserAttributes="@USER_INPUT_ATTRIBUTES" Class="mb-3"/>
|
||||
|
||||
<MudTextSwitch Disabled="@(this.IsNoPolicySelected || (this.selectedPolicy?.IsManaged ?? true))" Label="@T("Would you like to protect this policy so that you cannot accidentally edit or delete it?")" Value="@this.policyIsProtected" ValueChanged="async state => await this.PolicyProtectionWasChanged(state)" LabelOn="@T("Yes, protect this policy")" LabelOff="@T("No, the policy can be edited")" />
|
||||
|
||||
<MudText Typo="Typo.h5" Class="mt-6 mb-1">
|
||||
@T("Analysis and output rules")
|
||||
</MudText>
|
||||
|
||||
<MudJustifiedText Typo="Typo.body1" Class="mt-3">
|
||||
@T("Use the analysis and output rules to define how the AI evaluates your documents and formats the results.")
|
||||
</MudJustifiedText>
|
||||
|
||||
<MudJustifiedText Typo="Typo.body1" Class="mt-3">
|
||||
@T("The analysis rules specify what the AI should pay particular attention to while reviewing the documents you provide, and which aspects it should highlight or save. For example, if you want to extract the potential of green hydrogen for agriculture from a variety of general publications, you can explicitly define this in the analysis rules.")
|
||||
</MudJustifiedText>
|
||||
|
||||
<MudTextField T="string" Disabled="@this.IsNoPolicySelectedOrProtected" @bind-Text="@this.policyAnalysisRules" Validation="@this.ValidateAnalysisRules" Immediate="@true" Label="@T("Analysis rules")" HelperText="@T("Please provide a description of your analysis rules. This rules will be used to instruct the AI on how to analyze the documents.")" Variant="Variant.Outlined" Margin="Margin.Normal" Lines="5" AutoGrow="@true" MaxLines="26" UserAttributes="@USER_INPUT_ATTRIBUTES" Class="mb-3"/>
|
||||
|
||||
<ReadFileContent Text="@T("Load analysis rules from document")" @bind-FileContent="@this.policyAnalysisRules"/>
|
||||
|
||||
<MudJustifiedText Typo="Typo.body1" Class="mt-3">
|
||||
@T("After the AI has processed all documents, it needs your instructions on how the result should be formatted. Would you like a structured list with keywords or a continuous text? Should the output include emojis or be written in formal business language? You can specify all these preferences in the output rules. There, you can also predefine a desired structure—for example, by using Markdown formatting to define headings, paragraphs, or bullet points.")
|
||||
</MudJustifiedText>
|
||||
|
||||
<MudTextField T="string" Disabled="@this.IsNoPolicySelectedOrProtected" @bind-Text="@this.policyOutputRules" Validation="@this.ValidateOutputRules" Immediate="@true" Label="@T("Output rules")" HelperText="@T("Please provide a description of your output rules. This rules will be used to instruct the AI on how to format the output of the analysis.")" Variant="Variant.Outlined" Margin="Margin.Normal" Lines="5" AutoGrow="@true" MaxLines="26" UserAttributes="@USER_INPUT_ATTRIBUTES" Class="mb-3"/>
|
||||
|
||||
<ReadFileContent Text="@T("Load output rules from document")" @bind-FileContent="@this.policyOutputRules"/>
|
||||
|
||||
<MudText Typo="Typo.h5" Class="mt-6 mb-1">
|
||||
@T("Preparation for enterprise distribution")
|
||||
</MudText>
|
||||
|
||||
<MudTooltip Text="@T("Not implemented yet.")">
|
||||
<MudButton StartIcon="@Icons.Material.Filled.FileDownload" Disabled="true" Variant="Variant.Filled" Color="Color.Primary" OnClick="@this.ExportPolicyAsConfiguration">
|
||||
@T("Export policy as configuration section")
|
||||
</MudButton>
|
||||
</MudTooltip>
|
||||
<MudText Typo="Typo.h5" Class="mt-6 mb-1">
|
||||
@T("Analysis and output rules")
|
||||
</MudText>
|
||||
|
||||
<MudJustifiedText Typo="Typo.body1" Class="mt-3">
|
||||
@T("Use the analysis and output rules to define how the AI evaluates your documents and formats the results.")
|
||||
</MudJustifiedText>
|
||||
|
||||
<MudJustifiedText Typo="Typo.body1" Class="mt-3">
|
||||
@T("The analysis rules specify what the AI should pay particular attention to while reviewing the documents you provide, and which aspects it should highlight or save. For example, if you want to extract the potential of green hydrogen for agriculture from a variety of general publications, you can explicitly define this in the analysis rules.")
|
||||
</MudJustifiedText>
|
||||
|
||||
<MudTextField T="string" Disabled="@this.IsNoPolicySelectedOrProtected" @bind-Text="@this.policyAnalysisRules" Validation="@this.ValidateAnalysisRules" Immediate="@true" Label="@T("Analysis rules")" HelperText="@T("Please provide a description of your analysis rules. This rules will be used to instruct the AI on how to analyze the documents.")" Variant="Variant.Outlined" Margin="Margin.Normal" Lines="5" AutoGrow="@true" MaxLines="26" UserAttributes="@USER_INPUT_ATTRIBUTES" Class="mb-3"/>
|
||||
|
||||
<ReadFileContent Text="@T("Load analysis rules from document")" @bind-FileContent="@this.policyAnalysisRules"/>
|
||||
|
||||
<MudJustifiedText Typo="Typo.body1" Class="mt-3">
|
||||
@T("After the AI has processed all documents, it needs your instructions on how the result should be formatted. Would you like a structured list with keywords or a continuous text? Should the output include emojis or be written in formal business language? You can specify all these preferences in the output rules. There, you can also predefine a desired structure—for example, by using Markdown formatting to define headings, paragraphs, or bullet points.")
|
||||
</MudJustifiedText>
|
||||
|
||||
<MudTextField T="string" Disabled="@this.IsNoPolicySelectedOrProtected" @bind-Text="@this.policyOutputRules" Validation="@this.ValidateOutputRules" Immediate="@true" Label="@T("Output rules")" HelperText="@T("Please provide a description of your output rules. This rules will be used to instruct the AI on how to format the output of the analysis.")" Variant="Variant.Outlined" Margin="Margin.Normal" Lines="5" AutoGrow="@true" MaxLines="26" UserAttributes="@USER_INPUT_ATTRIBUTES" Class="mb-3"/>
|
||||
|
||||
<ReadFileContent Text="@T("Load output rules from document")" @bind-FileContent="@this.policyOutputRules"/>
|
||||
|
||||
<MudText Typo="Typo.h5" Class="mt-6 mb-1">
|
||||
@T("Preparation for enterprise distribution")
|
||||
</MudText>
|
||||
|
||||
<MudTooltip Text="@T("Not implemented yet.")">
|
||||
<MudButton StartIcon="@Icons.Material.Filled.FileDownload" Disabled="true" Variant="Variant.Filled" Color="Color.Primary" OnClick="@this.ExportPolicyAsConfiguration">
|
||||
@T("Export policy as configuration section")
|
||||
</MudButton>
|
||||
</MudTooltip>
|
||||
}
|
||||
</ExpansionPanel>
|
||||
|
||||
<MudDivider Style="height: 0.25ch; margin: 1rem 0;" Class="mt-6" />
|
||||
@@ -103,7 +112,7 @@ else
|
||||
@T("Documents for the analysis")
|
||||
</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>
|
||||
</MudExpansionPanels>
|
||||
|
||||
+53
-4
@@ -118,10 +118,47 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore<SettingsDialo
|
||||
|
||||
protected override bool SubmitDisabled => (this.IsNoPolicySelected || this.loadedDocumentPaths.Count==0);
|
||||
|
||||
protected override ChatThread ConvertToChatThread => (this.chatThread ?? new()) with
|
||||
protected override ChatThread ConvertToChatThread
|
||||
{
|
||||
SystemPrompt = SystemPrompts.DEFAULT,
|
||||
};
|
||||
get
|
||||
{
|
||||
if (this.chatThread is null || this.chatThread.Blocks.Count < 2)
|
||||
{
|
||||
return new ChatThread
|
||||
{
|
||||
SystemPrompt = SystemPrompts.DEFAULT
|
||||
};
|
||||
}
|
||||
|
||||
return new ChatThread
|
||||
{
|
||||
ChatId = Guid.NewGuid(),
|
||||
Name = string.Format(T("{0} - Document Analysis Session"), this.selectedPolicy?.PolicyName ?? T("Empty")),
|
||||
SystemPrompt = SystemPrompts.DEFAULT,
|
||||
Blocks =
|
||||
[
|
||||
// Replace the first "user block" (here, it was/is the block generated by the assistant) with a new one
|
||||
// that includes the loaded document paths and a standard message about the previous analysis session:
|
||||
new ContentBlock
|
||||
{
|
||||
Time = this.chatThread.Blocks.First().Time,
|
||||
Role = ChatRole.USER,
|
||||
HideFromUser = false,
|
||||
ContentType = ContentType.TEXT,
|
||||
Content = new ContentText
|
||||
{
|
||||
Text = this.T("The result of your previous document analysis session."),
|
||||
FileAttachments = this.loadedDocumentPaths.ToList(),
|
||||
}
|
||||
},
|
||||
|
||||
// Then, append the last block of the current chat thread
|
||||
// (which is expected to be the AI response):
|
||||
this.chatThread.Blocks.Last(),
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ResetForm()
|
||||
{
|
||||
@@ -166,6 +203,8 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore<SettingsDialo
|
||||
await this.AddPolicy();
|
||||
this.selectedPolicy = this.SettingsManager.ConfigurationData.DocumentAnalysis.Policies.First();
|
||||
}
|
||||
|
||||
this.policyDefinitionExpanded = !this.selectedPolicy?.IsProtected ?? true;
|
||||
|
||||
var receivedDeferredContent = MessageBus.INSTANCE.CheckDeferredMessages<string>(Event.SEND_TO_DOCUMENT_ANALYSIS_ASSISTANT).FirstOrDefault();
|
||||
if (receivedDeferredContent is not null)
|
||||
@@ -200,6 +239,7 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore<SettingsDialo
|
||||
|
||||
private DataDocumentAnalysisPolicy? selectedPolicy;
|
||||
private bool policyIsProtected;
|
||||
private bool policyDefinitionExpanded;
|
||||
private string policyName = string.Empty;
|
||||
private string policyDescription = string.Empty;
|
||||
private string policyAnalysisRules = string.Empty;
|
||||
@@ -216,6 +256,13 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore<SettingsDialo
|
||||
{
|
||||
this.selectedPolicy = policy;
|
||||
this.ResetForm();
|
||||
this.policyDefinitionExpanded = !this.selectedPolicy?.IsProtected ?? true;
|
||||
}
|
||||
|
||||
private Task PolicyDefinitionExpandedChanged(bool isExpanded)
|
||||
{
|
||||
this.policyDefinitionExpanded = isExpanded;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task AddPolicy()
|
||||
@@ -279,6 +326,7 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore<SettingsDialo
|
||||
|
||||
this.policyIsProtected = state;
|
||||
this.selectedPolicy.IsProtected = state;
|
||||
this.policyDefinitionExpanded = !state;
|
||||
await this.AutoSave(true);
|
||||
}
|
||||
|
||||
@@ -420,6 +468,7 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore<SettingsDialo
|
||||
return;
|
||||
|
||||
this.CreateChatThread();
|
||||
this.chatThread!.IncludeDateTime = true;
|
||||
|
||||
var userRequest = this.AddUserRequest(
|
||||
await this.PromptLoadDocumentsContent(),
|
||||
@@ -442,4 +491,4 @@ public partial class DocumentAnalysisAssistant : AssistantBaseCore<SettingsDialo
|
||||
await this.form!.Validate();
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -382,6 +382,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::CODING::COMMONCODINGLANGUAGEEXTENSIONS::T
|
||||
-- None
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::CODING::COMMONCODINGLANGUAGEEXTENSIONS::T810547195"] = "None"
|
||||
|
||||
-- {0} - Document Analysis Session
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T108097007"] = "{0} - Document Analysis Session"
|
||||
|
||||
-- Use the analysis and output rules to define how the AI evaluates your documents and formats the results.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T1155482668"] = "Use the analysis and output rules to define how the AI evaluates your documents and formats the results."
|
||||
|
||||
@@ -436,9 +439,15 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTA
|
||||
-- Export policy as configuration section
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2556564432"] = "Export policy as configuration section"
|
||||
|
||||
-- The result of your previous document analysis session.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2570551055"] = "The result of your previous document analysis session."
|
||||
|
||||
-- Are you sure you want to delete the document analysis policy '{0}'?
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2582525917"] = "Are you sure you want to delete the document analysis policy '{0}'?"
|
||||
|
||||
-- Expand this section to view and edit the policy definition.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T277813037"] = "Expand this section to view and edit the policy definition."
|
||||
|
||||
-- Policy name
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2879019438"] = "Policy name"
|
||||
|
||||
@@ -469,6 +478,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTA
|
||||
-- Document Analysis Assistant
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T348883878"] = "Document Analysis Assistant"
|
||||
|
||||
-- Empty
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3512147854"] = "Empty"
|
||||
|
||||
-- Analysis and output rules
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3555314296"] = "Analysis and output rules"
|
||||
|
||||
@@ -1666,6 +1678,15 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONPROVIDERSELECTION::T20906218
|
||||
-- Use app default
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONPROVIDERSELECTION::T3672477670"] = "Use app default"
|
||||
|
||||
-- No shortcut configured
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONSHORTCUT::T3099115336"] = "No shortcut configured"
|
||||
|
||||
-- Change shortcut
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONSHORTCUT::T4081853237"] = "Change shortcut"
|
||||
|
||||
-- Configure Keyboard Shortcut
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONSHORTCUT::T636303786"] = "Configure Keyboard Shortcut"
|
||||
|
||||
-- Yes, let the AI decide which data sources are needed.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCESELECTION::T1031370894"] = "Yes, let the AI decide which data sources are needed."
|
||||
|
||||
@@ -2008,6 +2029,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1059411425"]
|
||||
-- Do you want to show preview features in the app?
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1118505044"] = "Do you want to show preview features in the app?"
|
||||
|
||||
-- Voice recording shortcut
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1278320412"] = "Voice recording shortcut"
|
||||
|
||||
-- How often should we check for app updates?
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1364944735"] = "How often should we check for app updates?"
|
||||
|
||||
@@ -2023,6 +2047,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.
|
||||
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?
|
||||
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 +2062,12 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1898060643"]
|
||||
-- Select the language for the app.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1907446663"] = "Select the language for the app."
|
||||
|
||||
-- The global keyboard shortcut for toggling voice recording. This shortcut works system-wide, even when the app is not focused.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2143741496"] = "The global keyboard shortcut for toggling voice recording. This shortcut works system-wide, even when the app is not focused."
|
||||
|
||||
-- Disable dictation and transcription
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T215381891"] = "Disable dictation and transcription"
|
||||
|
||||
-- Language behavior
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2341504363"] = "Language behavior"
|
||||
|
||||
@@ -2065,6 +2098,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.
|
||||
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
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T602293588"] = "Navigation bar behavior"
|
||||
|
||||
@@ -2092,6 +2128,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T14695
|
||||
-- Add Embedding
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1738753945"] = "Add Embedding"
|
||||
|
||||
-- Uses the provider-configured model
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1760715963"] = "Uses the provider-configured model"
|
||||
|
||||
-- Are you sure you want to delete the embedding provider '{0}'?
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1825371968"] = "Are you sure you want to delete the embedding provider '{0}'?"
|
||||
|
||||
@@ -2107,24 +2146,30 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T24199
|
||||
-- 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.
|
||||
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
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T3267849393"] = "Edit"
|
||||
|
||||
-- Configured Embeddings
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T3526613453"] = "Configured Embeddings"
|
||||
|
||||
-- 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.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T4068015588"] = "No embeddings configured yet."
|
||||
|
||||
-- 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
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T511304264"] = "Delete Embedding Provider"
|
||||
|
||||
@@ -2134,9 +2179,6 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T78223
|
||||
-- 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?
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1052533048"] = "Show provider's confidence level?"
|
||||
|
||||
@@ -2152,9 +2194,15 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T162847
|
||||
-- Description
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1725856265"] = "Description"
|
||||
|
||||
-- Uses the provider-configured model
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1760715963"] = "Uses the provider-configured model"
|
||||
|
||||
-- Add Provider
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1806589097"] = "Add Provider"
|
||||
|
||||
-- Configure LLM Providers
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1810190350"] = "Configure LLM Providers"
|
||||
|
||||
-- Edit LLM Provider
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1868766523"] = "Edit LLM Provider"
|
||||
|
||||
@@ -2188,11 +2236,8 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T284206
|
||||
-- No providers configured yet.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T2911731076"] = "No providers configured yet."
|
||||
|
||||
-- Configure Providers
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3027859089"] = "Configure Providers"
|
||||
|
||||
-- as selected by provider
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3082210376"] = "as selected by provider"
|
||||
-- Configured LLM Providers
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3019870540"] = "Configured LLM Providers"
|
||||
|
||||
-- Edit
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3267849393"] = "Edit"
|
||||
@@ -2212,9 +2257,6 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T361241
|
||||
-- No, do not enforce a minimum confidence level
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3642102079"] = "No, do not enforce a minimum confidence level"
|
||||
|
||||
-- Configured Providers
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3850871263"] = "Configured Providers"
|
||||
|
||||
-- Actions
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3865031940"] = "Actions"
|
||||
|
||||
@@ -2242,6 +2284,60 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T853225
|
||||
-- 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"
|
||||
|
||||
-- Uses the provider-configured model
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T1760715963"] = "Uses the provider-configured model"
|
||||
|
||||
-- Add Transcription Provider
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T2066315685"] = "Add Transcription Provider"
|
||||
|
||||
-- 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
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TEXTINFOLINE::T2206391442"] = "Copy {0} to the clipboard"
|
||||
|
||||
@@ -2323,6 +2419,39 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T428040679"] = "Content creation"
|
||||
-- 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."
|
||||
|
||||
-- Failed to start audio recording.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VOICERECORDER::T2144994226"] = "Failed to start audio recording."
|
||||
|
||||
-- Stop recording and start transcription
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VOICERECORDER::T224155287"] = "Stop recording and start transcription"
|
||||
|
||||
-- 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."
|
||||
|
||||
-- Failed to stop audio recording.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VOICERECORDER::T3462568264"] = "Failed to stop audio recording."
|
||||
|
||||
-- The configured transcription provider does not meet the minimum confidence level.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VOICERECORDER::T3834149033"] = "The configured transcription provider does not meet the minimum confidence level."
|
||||
|
||||
-- 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}'?
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1016188706"] = "Are you sure you want to delete the chat '{0}' in the workspace '{1}'?"
|
||||
|
||||
@@ -2443,6 +2572,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.
|
||||
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
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2418769465"] = "Role"
|
||||
|
||||
@@ -2482,6 +2614,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3016903701"] = "The nam
|
||||
-- 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.
|
||||
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 +3229,6 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2189814010"] = "Mo
|
||||
-- (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
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2646845972"] = "Add"
|
||||
|
||||
@@ -3106,9 +3238,15 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2810182573"] = "No
|
||||
-- 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
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T416738168"] = "Model selection"
|
||||
|
||||
-- We are currently unable to communicate with the provider to load models. Please try again later.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T504465522"] = "We are currently unable to communicate with the provider to load models. Please try again later."
|
||||
|
||||
-- Host
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T808120719"] = "Host"
|
||||
|
||||
@@ -3316,9 +3454,18 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3361153305"] = "Show Expert
|
||||
-- Show available models
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3763891899"] = "Show available models"
|
||||
|
||||
-- This host uses the model configured at the provider level. No model selection is available.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3783329915"] = "This host uses the model configured at the provider level. No model selection is available."
|
||||
|
||||
-- Currently, we cannot query the models for the selected provider and/or host. Therefore, please enter the model name manually.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T4116737656"] = "Currently, we cannot query the models for the selected provider and/or host. Therefore, please enter the model name manually."
|
||||
|
||||
-- Model selection
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T416738168"] = "Model selection"
|
||||
|
||||
-- We are currently unable to communicate with the provider to load models. Please try again later.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T504465522"] = "We are currently unable to communicate with the provider to load models. Please try again later."
|
||||
|
||||
-- Host
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T808120719"] = "Host"
|
||||
|
||||
@@ -4483,6 +4630,42 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGWRITINGEMAILS::T3832
|
||||
-- Preselect one of your profiles?
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGWRITINGEMAILS::T4004501229"] = "Preselect one of your profiles?"
|
||||
|
||||
-- Save
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SHORTCUTDIALOG::T1294818664"] = "Save"
|
||||
|
||||
-- Press the desired key combination to set the shortcut. The shortcut will be registered globally and will work even when the app is not focused.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SHORTCUTDIALOG::T1464973299"] = "Press the desired key combination to set the shortcut. The shortcut will be registered globally and will work even when the app is not focused."
|
||||
|
||||
-- Press a key combination...
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SHORTCUTDIALOG::T1468443151"] = "Press a key combination..."
|
||||
|
||||
-- Clear Shortcut
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SHORTCUTDIALOG::T1807313248"] = "Clear Shortcut"
|
||||
|
||||
-- Invalid shortcut: {0}
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SHORTCUTDIALOG::T189893682"] = "Invalid shortcut: {0}"
|
||||
|
||||
-- This shortcut conflicts with: {0}
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SHORTCUTDIALOG::T2633102934"] = "This shortcut conflicts with: {0}"
|
||||
|
||||
-- Please include at least one modifier key (Ctrl, Shift, Alt, or Cmd).
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SHORTCUTDIALOG::T3060573513"] = "Please include at least one modifier key (Ctrl, Shift, Alt, or Cmd)."
|
||||
|
||||
-- Shortcut is valid and available.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SHORTCUTDIALOG::T3159532525"] = "Shortcut is valid and available."
|
||||
|
||||
-- Define a shortcut
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SHORTCUTDIALOG::T3734850493"] = "Define a shortcut"
|
||||
|
||||
-- This is the shortcut you previously used.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SHORTCUTDIALOG::T4167229652"] = "This is the shortcut you previously used."
|
||||
|
||||
-- Supported modifiers: Ctrl/Cmd, Shift, Alt.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SHORTCUTDIALOG::T889258890"] = "Supported modifiers: Ctrl/Cmd, Shift, Alt."
|
||||
|
||||
-- Cancel
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SHORTCUTDIALOG::T900713019"] = "Cancel"
|
||||
|
||||
-- Please enter a value.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SINGLEINPUTDIALOG::T3576780391"] = "Please enter a value."
|
||||
|
||||
@@ -4492,6 +4675,66 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SINGLEINPUTDIALOG::T4030229154"] = "Your Inp
|
||||
-- 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."
|
||||
|
||||
-- This host uses the model configured at the provider level. No model selection is available.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T3783329915"] = "This host uses the model configured at the provider level. No model selection is available."
|
||||
|
||||
-- Model selection
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T416738168"] = "Model selection"
|
||||
|
||||
-- We are currently unable to communicate with the provider to load models. Please try again later.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T504465522"] = "We are currently unable to communicate with the provider to load models. Please try again later."
|
||||
|
||||
-- Host
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T808120719"] = "Host"
|
||||
|
||||
-- Provider
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T900237532"] = "Provider"
|
||||
|
||||
-- Cancel
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T900713019"] = "Cancel"
|
||||
|
||||
-- Install now
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::UPDATEDIALOG::T2366359512"] = "Install now"
|
||||
|
||||
@@ -4861,6 +5104,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2557066213"] = "Used Open Source
|
||||
-- Build time
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T260228112"] = "Build time"
|
||||
|
||||
-- This crate provides derive macros for Rust enums, which we use to reduce boilerplate when implementing string conversions and metadata for runtime types. This is helpful for the communication between our Rust and .NET systems.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2635482790"] = "This crate provides derive macros for Rust enums, which we use to reduce boilerplate when implementing string conversions and metadata for runtime types. This is helpful for the communication between our Rust and .NET systems."
|
||||
|
||||
-- To be able to use the responses of the LLM in other apps, we often use the clipboard of the respective operating system. Unfortunately, in .NET there is no solution that works with all operating systems. Therefore, I have opted for this library in Rust. This way, data transfer to other apps works on every system.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2644379659"] = "To be able to use the responses of the LLM in other apps, we often use the clipboard of the respective operating system. Unfortunately, in .NET there is no solution that works with all operating systems. Therefore, I have opted for this library in Rust. This way, data transfer to other apps works on every system."
|
||||
|
||||
@@ -5107,35 +5353,38 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::WRITER::T3948127789"] = "Suggestion"
|
||||
-- Your stage directions
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::WRITER::T779923726"] = "Your stage directions"
|
||||
|
||||
-- Tried to communicate with the LLM provider '{0}'. The API key might be invalid. The provider message is: '{1}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1073493061"] = "Tried to communicate with the LLM provider '{0}'. The API key might be invalid. The provider message is: '{1}'"
|
||||
-- We tried to communicate with the LLM provider '{0}' (type={1}). The server might be down or having issues. The provider message is: '{2}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1000247110"] = "We tried to communicate with the LLM provider '{0}' (type={1}). The server might be down or having issues. The provider message is: '{2}'"
|
||||
|
||||
-- Tried to stream the LLM provider '{0}' answer. There were some problems with the stream. The message is: '{1}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1487597412"] = "Tried to stream the LLM provider '{0}' answer. There were some problems with the stream. The message is: '{1}'"
|
||||
|
||||
-- Tried to communicate with the LLM provider '{0}'. The required message format might be changed. The provider message is: '{1}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1674355816"] = "Tried to communicate with the LLM provider '{0}'. The required message format might be changed. The provider message is: '{1}'"
|
||||
|
||||
-- Tried to stream the LLM provider '{0}' answer. Was not able to read the stream. The message is: '{1}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1856278860"] = "Tried to stream the LLM provider '{0}' answer. Was not able to read the stream. The message is: '{1}'"
|
||||
|
||||
-- Tried to communicate with the LLM provider '{0}'. Even after {1} retries, there were some problems with the request. The provider message is: '{2}'.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T2181034173"] = "Tried to communicate with the LLM provider '{0}'. Even after {1} retries, there were some problems with the request. The provider message is: '{2}'."
|
||||
-- We tried to communicate with the LLM provider '{0}' (type={1}). The API key might be invalid. The provider message is: '{2}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1924863735"] = "We tried to communicate with the LLM provider '{0}' (type={1}). The API key might be invalid. The provider message is: '{2}'"
|
||||
|
||||
-- Tried to communicate with the LLM provider '{0}'. Something was not found. The provider message is: '{1}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T2780552614"] = "Tried to communicate with the LLM provider '{0}'. Something was not found. The provider message is: '{1}'"
|
||||
-- We tried to communicate with the LLM provider '{0}' (type={1}). The provider is overloaded. The message is: '{2}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1999987800"] = "We tried to communicate with the LLM provider '{0}' (type={1}). The provider is overloaded. The message is: '{2}'"
|
||||
|
||||
-- We tried to communicate with the LLM provider '{0}' (type={1}). You might not be able to use this provider from your location. The provider message is: '{2}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T2107463087"] = "We tried to communicate with the LLM provider '{0}' (type={1}). You might not be able to use this provider from your location. The provider message is: '{2}'"
|
||||
|
||||
-- We tried to communicate with the LLM provider '{0}' (type={1}). Something was not found. The provider message is: '{2}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T3014737766"] = "We tried to communicate with the LLM provider '{0}' (type={1}). Something was not found. The provider message is: '{2}'"
|
||||
|
||||
-- We tried to communicate with the LLM provider '{0}' (type={1}). Even after {2} retries, there were some problems with the request. The provider message is: '{3}'.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T3049689432"] = "We tried to communicate with the LLM provider '{0}' (type={1}). Even after {2} retries, there were some problems with the request. The provider message is: '{3}'."
|
||||
|
||||
-- Tried to communicate with the LLM provider '{0}'. There were some problems with the request. The provider message is: '{1}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T3573577433"] = "Tried to communicate with the LLM provider '{0}'. There were some problems with the request. The provider message is: '{1}'"
|
||||
|
||||
-- Tried to communicate with the LLM provider '{0}'. The server might be down or having issues. The provider message is: '{1}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T3806716694"] = "Tried to communicate with the LLM provider '{0}'. The server might be down or having issues. The provider message is: '{1}'"
|
||||
-- We tried to communicate with the LLM provider '{0}' (type={1}). The required message format might be changed. The provider message is: '{2}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T3759732886"] = "We tried to communicate with the LLM provider '{0}' (type={1}). The required message format might be changed. The provider message is: '{2}'"
|
||||
|
||||
-- Tried to communicate with the LLM provider '{0}'. The provider is overloaded. The message is: '{1}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T4179546180"] = "Tried to communicate with the LLM provider '{0}'. The provider is overloaded. The message is: '{1}'"
|
||||
|
||||
-- Tried to communicate with the LLM provider '{0}'. You might not be able to use this provider from your location. The provider message is: '{1}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T862369179"] = "Tried to communicate with the LLM provider '{0}'. You might not be able to use this provider from your location. The provider message is: '{1}'"
|
||||
-- We tried to communicate with the LLM provider '{0}' (type={1}). The data of the chat, including all file attachments, is probably too large for the selected model and provider. The provider message is: '{2}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T4049517041"] = "We tried to communicate with the LLM provider '{0}' (type={1}). The data of the chat, including all file attachments, is probably too large for the selected model and provider. The provider message is: '{2}'"
|
||||
|
||||
-- The trust level of this provider **has not yet** been thoroughly **investigated and evaluated**. We do not know if your data is safe.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::CONFIDENCE::T1014558951"] = "The trust level of this provider **has not yet** been thoroughly **investigated and evaluated**. We do not know if your data is safe."
|
||||
@@ -5194,6 +5443,12 @@ UI_TEXT_CONTENT["AISTUDIO::PROVIDER::LLMPROVIDERSEXTENSIONS::T3424652889"] = "Un
|
||||
-- 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
|
||||
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CONFIGURATIONSELECTDATAFACTORY::T1095779033"] = "Navigation never expands, but there are tooltips"
|
||||
|
||||
@@ -5365,6 +5620,9 @@ UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::PREVIEWFEATURESEXTENSIONS::T2708
|
||||
-- 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
|
||||
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::SENDTOCHATDATASOURCEBEHAVIOREXTENSIONS::T1223925477"] = "Use no data sources, when sending an assistant result to a chat"
|
||||
|
||||
@@ -5929,6 +6187,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::RAG::RAGPROCESSES::AISRCSELWITHRETCTXVAL::T377
|
||||
-- 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
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T2575722901"] = "All Audio Files"
|
||||
|
||||
@@ -5953,23 +6214,23 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PANDOCAVAILABILITYSERVICE::T18544701
|
||||
-- Pandoc may be required for importing files.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PANDOCAVAILABILITYSERVICE::T2596465560"] = "Pandoc may be required for importing files."
|
||||
|
||||
-- Failed to delete the API key due to an API issue.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::APIKEYS::T3658273365"] = "Failed to delete the API key due to an API issue."
|
||||
|
||||
-- Failed to get the API key due to an API issue.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::APIKEYS::T3875720022"] = "Failed to get the API key due to an API issue."
|
||||
-- Failed to delete the secret data due to an API issue.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T2303057928"] = "Failed to delete the secret data due to an API issue."
|
||||
|
||||
-- Successfully copied the text to your clipboard
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::CLIPBOARD::T3351807428"] = "Successfully copied the text to your clipboard"
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3351807428"] = "Successfully copied the text to your clipboard"
|
||||
|
||||
-- Failed to delete the API key due to an API issue.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3658273365"] = "Failed to delete the API key due to an API issue."
|
||||
|
||||
-- Failed to copy the text to your clipboard.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::CLIPBOARD::T3724548108"] = "Failed to copy the text to your clipboard."
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3724548108"] = "Failed to copy the text to your clipboard."
|
||||
|
||||
-- Failed to delete the secret data due to an API issue.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::SECRETS::T2303057928"] = "Failed to delete the secret data due to an API issue."
|
||||
-- Failed to get the API key due to an API issue.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3875720022"] = "Failed to get the API key due to an API issue."
|
||||
|
||||
-- Failed to get the secret data due to an API issue.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::SECRETS::T4007657575"] = "Failed to get the secret data due to an API issue."
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T4007657575"] = "Failed to get the secret data due to an API issue."
|
||||
|
||||
-- No update found.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::UPDATESERVICE::T1015418291"] = "No update found."
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using System.Globalization;
|
||||
|
||||
using AIStudio.Components;
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Settings.DataModel;
|
||||
@@ -37,6 +39,12 @@ public sealed record ChatThread
|
||||
/// </summary>
|
||||
public string SelectedChatTemplate { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether to include the current date and time in the system prompt.
|
||||
/// False by default for backward compatibility.
|
||||
/// </summary>
|
||||
public bool IncludeDateTime { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// The data source options for this chat thread.
|
||||
/// </summary>
|
||||
@@ -65,7 +73,7 @@ public sealed record ChatThread
|
||||
/// <summary>
|
||||
/// The current system prompt for the chat thread.
|
||||
/// </summary>
|
||||
public string SystemPrompt { get; init; } = string.Empty;
|
||||
public string SystemPrompt { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The content blocks of the chat thread.
|
||||
@@ -83,33 +91,32 @@ public sealed record ChatThread
|
||||
/// is extended with the profile chosen.
|
||||
/// </remarks>
|
||||
/// <param name="settingsManager">The settings manager instance to use.</param>
|
||||
/// <param name="chatThread">The chat thread to prepare the system prompt for.</param>
|
||||
/// <returns>The prepared system prompt.</returns>
|
||||
public string PrepareSystemPrompt(SettingsManager settingsManager, ChatThread chatThread)
|
||||
public string PrepareSystemPrompt(SettingsManager settingsManager)
|
||||
{
|
||||
//
|
||||
// Use the information from the chat template, if provided. Otherwise, use the default system prompt
|
||||
//
|
||||
string systemPromptTextWithChatTemplate;
|
||||
var logMessage = $"Using no chat template for chat thread '{chatThread.Name}'.";
|
||||
if (string.IsNullOrWhiteSpace(chatThread.SelectedChatTemplate))
|
||||
systemPromptTextWithChatTemplate = chatThread.SystemPrompt;
|
||||
var logMessage = $"Using no chat template for chat thread '{this.Name}'.";
|
||||
if (string.IsNullOrWhiteSpace(this.SelectedChatTemplate))
|
||||
systemPromptTextWithChatTemplate = this.SystemPrompt;
|
||||
else
|
||||
{
|
||||
if(!Guid.TryParse(chatThread.SelectedChatTemplate, out var chatTemplateId))
|
||||
systemPromptTextWithChatTemplate = chatThread.SystemPrompt;
|
||||
if(!Guid.TryParse(this.SelectedChatTemplate, out var chatTemplateId))
|
||||
systemPromptTextWithChatTemplate = this.SystemPrompt;
|
||||
else
|
||||
{
|
||||
if(chatThread.SelectedChatTemplate == ChatTemplate.NO_CHAT_TEMPLATE.Id || chatTemplateId == Guid.Empty)
|
||||
systemPromptTextWithChatTemplate = chatThread.SystemPrompt;
|
||||
if(this.SelectedChatTemplate == ChatTemplate.NO_CHAT_TEMPLATE.Id || chatTemplateId == Guid.Empty)
|
||||
systemPromptTextWithChatTemplate = this.SystemPrompt;
|
||||
else
|
||||
{
|
||||
var chatTemplate = settingsManager.ConfigurationData.ChatTemplates.FirstOrDefault(x => x.Id == chatThread.SelectedChatTemplate);
|
||||
var chatTemplate = settingsManager.ConfigurationData.ChatTemplates.FirstOrDefault(x => x.Id == this.SelectedChatTemplate);
|
||||
if(chatTemplate == null)
|
||||
systemPromptTextWithChatTemplate = chatThread.SystemPrompt;
|
||||
systemPromptTextWithChatTemplate = this.SystemPrompt;
|
||||
else
|
||||
{
|
||||
logMessage = $"Using chat template '{chatTemplate.Name}' for chat thread '{chatThread.Name}'.";
|
||||
logMessage = $"Using chat template '{chatTemplate.Name}' for chat thread '{this.Name}'.";
|
||||
this.allowProfile = chatTemplate.AllowProfileUsage;
|
||||
systemPromptTextWithChatTemplate = chatTemplate.ToSystemPrompt();
|
||||
}
|
||||
@@ -120,20 +127,19 @@ public sealed record ChatThread
|
||||
// We need a way to save the changed system prompt in our chat thread.
|
||||
// Otherwise, the chat thread will always tell us that it is using the
|
||||
// default system prompt:
|
||||
chatThread = chatThread with { SystemPrompt = systemPromptTextWithChatTemplate };
|
||||
|
||||
this.SystemPrompt = systemPromptTextWithChatTemplate;
|
||||
LOGGER.LogInformation(logMessage);
|
||||
|
||||
|
||||
//
|
||||
// Add augmented data, if available:
|
||||
//
|
||||
var isAugmentedDataAvailable = !string.IsNullOrWhiteSpace(chatThread.AugmentedData);
|
||||
var isAugmentedDataAvailable = !string.IsNullOrWhiteSpace(this.AugmentedData);
|
||||
var systemPromptWithAugmentedData = isAugmentedDataAvailable switch
|
||||
{
|
||||
true => $"""
|
||||
{systemPromptTextWithChatTemplate}
|
||||
|
||||
{chatThread.AugmentedData}
|
||||
{this.AugmentedData}
|
||||
""",
|
||||
|
||||
false => systemPromptTextWithChatTemplate,
|
||||
@@ -149,25 +155,25 @@ public sealed record ChatThread
|
||||
// Add information from the profile if available and allowed:
|
||||
//
|
||||
string systemPromptText;
|
||||
logMessage = $"Using no profile for chat thread '{chatThread.Name}'.";
|
||||
if (string.IsNullOrWhiteSpace(chatThread.SelectedProfile) || this.allowProfile is false)
|
||||
logMessage = $"Using no profile for chat thread '{this.Name}'.";
|
||||
if (string.IsNullOrWhiteSpace(this.SelectedProfile) || !this.allowProfile)
|
||||
systemPromptText = systemPromptWithAugmentedData;
|
||||
else
|
||||
{
|
||||
if(!Guid.TryParse(chatThread.SelectedProfile, out var profileId))
|
||||
if(!Guid.TryParse(this.SelectedProfile, out var profileId))
|
||||
systemPromptText = systemPromptWithAugmentedData;
|
||||
else
|
||||
{
|
||||
if(chatThread.SelectedProfile == Profile.NO_PROFILE.Id || profileId == Guid.Empty)
|
||||
if(this.SelectedProfile == Profile.NO_PROFILE.Id || profileId == Guid.Empty)
|
||||
systemPromptText = systemPromptWithAugmentedData;
|
||||
else
|
||||
{
|
||||
var profile = settingsManager.ConfigurationData.Profiles.FirstOrDefault(x => x.Id == chatThread.SelectedProfile);
|
||||
if(profile == default)
|
||||
var profile = settingsManager.ConfigurationData.Profiles.FirstOrDefault(x => x.Id == this.SelectedProfile);
|
||||
if(profile is null)
|
||||
systemPromptText = systemPromptWithAugmentedData;
|
||||
else
|
||||
{
|
||||
logMessage = $"Using profile '{profile.Name}' for chat thread '{chatThread.Name}'.";
|
||||
logMessage = $"Using profile '{profile.Name}' for chat thread '{this.Name}'.";
|
||||
systemPromptText = $"""
|
||||
{systemPromptWithAugmentedData}
|
||||
|
||||
@@ -179,7 +185,24 @@ public sealed record ChatThread
|
||||
}
|
||||
|
||||
LOGGER.LogInformation(logMessage);
|
||||
return systemPromptText;
|
||||
if(!this.IncludeDateTime)
|
||||
return systemPromptText;
|
||||
|
||||
//
|
||||
// Prepend the current date and time to the system prompt:
|
||||
//
|
||||
var nowUtc = DateTime.UtcNow;
|
||||
var nowLocal = DateTime.Now;
|
||||
var currentDateTime = string.Create(
|
||||
new CultureInfo("en-US"),
|
||||
$"Today is {nowUtc:dddd, MMMM d, yyyy h:mm tt} (UTC) and {nowLocal:dddd, MMMM d, yyyy h:mm tt} (local time)."
|
||||
);
|
||||
|
||||
return $"""
|
||||
{currentDateTime}
|
||||
|
||||
{systemPromptText}
|
||||
""";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -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):
|
||||
if (FileTypeFilter.PDF.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;
|
||||
|
||||
// All other file types are forbidden:
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using AIStudio.Tools.MIME;
|
||||
using AIStudio.Tools.PluginSystem;
|
||||
|
||||
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));
|
||||
|
||||
public static string DetermineMimeType(this IImageSource image)
|
||||
public static MIMEType DetermineMimeType(this IImageSource image)
|
||||
{
|
||||
switch (image.SourceType)
|
||||
{
|
||||
@@ -18,13 +19,11 @@ public static class IImageSourceExtensions
|
||||
{
|
||||
var mimeEnd = base64Data.IndexOf(';');
|
||||
if (mimeEnd > 5)
|
||||
{
|
||||
return base64Data[5..mimeEnd];
|
||||
}
|
||||
return Builder.FromTextRepresentation(base64Data[5..mimeEnd]);
|
||||
}
|
||||
|
||||
// Fallback:
|
||||
return "application/octet-stream";
|
||||
return Builder.Create().UseApplication().UseSubtype(ApplicationSubtype.OCTET_STREAM).Build();
|
||||
}
|
||||
|
||||
case ContentImageSource.URL:
|
||||
@@ -32,38 +31,36 @@ public static class IImageSourceExtensions
|
||||
// Try to detect the mime type from the URL extension:
|
||||
var uri = new Uri(image.Source);
|
||||
var extension = Path.GetExtension(uri.AbsolutePath).ToLowerInvariant();
|
||||
return extension switch
|
||||
{
|
||||
".png" => "image/png",
|
||||
".jpg" or ".jpeg" => "image/jpeg",
|
||||
".gif" => "image/gif",
|
||||
".bmp" => "image/bmp",
|
||||
".webp" => "image/webp",
|
||||
|
||||
_ => "application/octet-stream"
|
||||
};
|
||||
return DeriveMIMETypeFromExtension(extension);
|
||||
}
|
||||
|
||||
case ContentImageSource.LOCAL_PATH:
|
||||
{
|
||||
var extension = Path.GetExtension(image.Source).ToLowerInvariant();
|
||||
return extension switch
|
||||
{
|
||||
".png" => "image/png",
|
||||
".jpg" or ".jpeg" => "image/jpeg",
|
||||
".gif" => "image/gif",
|
||||
".bmp" => "image/bmp",
|
||||
".webp" => "image/webp",
|
||||
|
||||
_ => "application/octet-stream"
|
||||
};
|
||||
return DeriveMIMETypeFromExtension(extension);
|
||||
}
|
||||
|
||||
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>
|
||||
/// Read the image content as a base64 string.
|
||||
/// </summary>
|
||||
|
||||
@@ -2,5 +2,5 @@ namespace AIStudio.Chat;
|
||||
|
||||
public static class SystemPrompts
|
||||
{
|
||||
public const string DEFAULT = "You are a helpful assistant!";
|
||||
public const string DEFAULT = "You are a helpful assistant.";
|
||||
}
|
||||
@@ -1,30 +1,33 @@
|
||||
@inherits MSGComponentBase
|
||||
@typeparam TSettings
|
||||
|
||||
<MudCard Outlined="@true" Style="@this.BlockStyle">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudStack AlignItems="AlignItems.Center" Row="@true">
|
||||
<MudIcon Icon="@this.Icon" Size="Size.Large" Color="Color.Primary"/>
|
||||
<MudText Typo="Typo.h6">
|
||||
@this.Name
|
||||
@if (this.IsVisible)
|
||||
{
|
||||
<MudCard Outlined="@true" Style="@this.BlockStyle">
|
||||
<MudCardHeader>
|
||||
<CardHeaderContent>
|
||||
<MudStack AlignItems="AlignItems.Center" Row="@true">
|
||||
<MudIcon Icon="@this.Icon" Size="Size.Large" Color="Color.Primary"/>
|
||||
<MudText Typo="Typo.h6">
|
||||
@this.Name
|
||||
</MudText>
|
||||
</MudStack>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent>
|
||||
<MudStack>
|
||||
<MudText>
|
||||
@this.Description
|
||||
</MudText>
|
||||
</MudStack>
|
||||
</CardHeaderContent>
|
||||
</MudCardHeader>
|
||||
<MudCardContent>
|
||||
<MudStack>
|
||||
<MudText>
|
||||
@this.Description
|
||||
</MudText>
|
||||
</MudStack>
|
||||
</MudCardContent>
|
||||
<MudCardActions>
|
||||
<MudButtonGroup Variant="Variant.Outlined">
|
||||
<MudButton Size="Size.Large" Variant="Variant.Filled" StartIcon="@this.Icon" Color="Color.Default" Href="@this.Link">
|
||||
@this.ButtonText
|
||||
</MudButton>
|
||||
<MudIconButton Variant="Variant.Text" Icon="@Icons.Material.Filled.Settings" Color="Color.Default" OnClick="@this.OpenSettingsDialog"/>
|
||||
</MudButtonGroup>
|
||||
</MudCardActions>
|
||||
</MudCard>
|
||||
</MudCardContent>
|
||||
<MudCardActions>
|
||||
<MudButtonGroup Variant="Variant.Outlined">
|
||||
<MudButton Size="Size.Large" Variant="Variant.Filled" StartIcon="@this.Icon" Color="Color.Default" Href="@this.Link">
|
||||
@this.ButtonText
|
||||
</MudButton>
|
||||
<MudIconButton Variant="Variant.Text" Icon="@Icons.Material.Filled.Settings" Color="Color.Default" OnClick="@this.OpenSettingsDialog"/>
|
||||
</MudButtonGroup>
|
||||
</MudCardActions>
|
||||
</MudCard>
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
using AIStudio.Settings.DataModel;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
using DialogOptions = AIStudio.Dialogs.DialogOptions;
|
||||
@@ -8,32 +10,38 @@ public partial class AssistantBlock<TSettings> : MSGComponentBase where TSetting
|
||||
{
|
||||
[Parameter]
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
|
||||
[Parameter]
|
||||
public string Description { get; set; } = string.Empty;
|
||||
|
||||
|
||||
[Parameter]
|
||||
public string Icon { get; set; } = Icons.Material.Filled.DisabledByDefault;
|
||||
|
||||
|
||||
[Parameter]
|
||||
public string ButtonText { get; set; } = "Start";
|
||||
|
||||
|
||||
[Parameter]
|
||||
public string Link { get; set; } = string.Empty;
|
||||
|
||||
|
||||
[Parameter]
|
||||
public Tools.Components Component { get; set; } = Tools.Components.NONE;
|
||||
|
||||
[Parameter]
|
||||
public PreviewFeatures RequiredPreviewFeature { get; set; } = PreviewFeatures.NONE;
|
||||
|
||||
[Inject]
|
||||
private MudTheme ColorTheme { get; init; } = null!;
|
||||
|
||||
|
||||
[Inject]
|
||||
private IDialogService DialogService { get; init; } = null!;
|
||||
|
||||
private async Task OpenSettingsDialog()
|
||||
{
|
||||
var dialogParameters = new DialogParameters();
|
||||
|
||||
|
||||
await this.DialogService.ShowAsync<TSettings>(T("Open Settings"), dialogParameters, DialogOptions.FULLSCREEN);
|
||||
}
|
||||
|
||||
|
||||
private string BorderColor => this.SettingsManager.IsDarkMode switch
|
||||
{
|
||||
true => this.ColorTheme.GetCurrentPalette(this.SettingsManager).GrayLight,
|
||||
@@ -41,4 +49,6 @@ public partial class AssistantBlock<TSettings> : MSGComponentBase where TSetting
|
||||
};
|
||||
|
||||
private string BlockStyle => $"border-width: 2px; border-color: {this.BorderColor}; border-radius: 12px; border-style: solid; max-width: 20em;";
|
||||
|
||||
private bool IsVisible => this.SettingsManager.IsAssistantVisible(this.Component, assistantName: this.Name, requiredPreviewFeature: this.RequiredPreviewFeature);
|
||||
}
|
||||
@@ -17,6 +17,18 @@ public partial class AttachDocuments : MSGComponentBase
|
||||
|
||||
[Parameter]
|
||||
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]
|
||||
public HashSet<FileAttachment> DocumentPaths { get; set; } = [];
|
||||
@@ -36,6 +48,15 @@ public partial class AttachDocuments : MSGComponentBase
|
||||
[Parameter]
|
||||
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]
|
||||
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 static readonly string DROP_FILES_HERE_TEXT = TB("Drop files here to attach them.");
|
||||
|
||||
private uint numDropAreasAboveThis;
|
||||
private bool isComponentHovered;
|
||||
private bool isDraggingOver;
|
||||
|
||||
@@ -61,7 +83,10 @@ public partial class AttachDocuments : MSGComponentBase
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -69,7 +94,35 @@ public partial class AttachDocuments : MSGComponentBase
|
||||
{
|
||||
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 }:
|
||||
if(this.PauseCatchingDrops)
|
||||
return;
|
||||
|
||||
if(!this.isComponentHovered && !this.CatchAllDocuments)
|
||||
{
|
||||
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;
|
||||
|
||||
case Event.TAURI_EVENT_RECEIVED when data is TauriEvent { EventType: TauriEventType.FILE_DROP_CANCELED }:
|
||||
if(this.PauseCatchingDrops)
|
||||
return;
|
||||
|
||||
this.isDraggingOver = false;
|
||||
this.StateHasChanged();
|
||||
break;
|
||||
|
||||
case Event.TAURI_EVENT_RECEIVED when data is TauriEvent { EventType: TauriEventType.WINDOW_NOT_FOCUSED }:
|
||||
if(this.PauseCatchingDrops)
|
||||
return;
|
||||
|
||||
this.isDraggingOver = false;
|
||||
this.isComponentHovered = false;
|
||||
this.ClearDragClass();
|
||||
@@ -94,6 +153,9 @@ public partial class AttachDocuments : MSGComponentBase
|
||||
break;
|
||||
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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;
|
||||
|
||||
this.DocumentPaths.Add(FileAttachment.FromPath(path));
|
||||
@@ -161,7 +223,7 @@ public partial class AttachDocuments : MSGComponentBase
|
||||
if (!File.Exists(selectedFilePath))
|
||||
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;
|
||||
|
||||
this.DocumentPaths.Add(FileAttachment.FromPath(selectedFilePath));
|
||||
@@ -189,6 +251,9 @@ public partial class AttachDocuments : MSGComponentBase
|
||||
|
||||
private void OnMouseEnter(EventArgs _)
|
||||
{
|
||||
if(this.PauseCatchingDrops)
|
||||
return;
|
||||
|
||||
this.Logger.LogDebug("Attach documents component '{Name}' is hovered.", this.Name);
|
||||
this.isComponentHovered = true;
|
||||
this.SetDragClass();
|
||||
@@ -197,6 +262,9 @@ public partial class AttachDocuments : MSGComponentBase
|
||||
|
||||
private void OnMouseLeave(EventArgs _)
|
||||
{
|
||||
if(this.PauseCatchingDrops)
|
||||
return;
|
||||
|
||||
this.Logger.LogDebug("Attach documents component '{Name}' is no longer hovered.", this.Name);
|
||||
this.isComponentHovered = false;
|
||||
this.ClearDragClass();
|
||||
|
||||
@@ -13,6 +13,8 @@ public partial class Changelog
|
||||
|
||||
public static readonly Log[] LOGS =
|
||||
[
|
||||
new (232, "v26.1.2, build 232 (2026-01-25 14:05 UTC)", "v26.1.2.md"),
|
||||
new (231, "v26.1.1, build 231 (2026-01-11 15:53 UTC)", "v26.1.1.md"),
|
||||
new (230, "v0.10.0, build 230 (2025-12-31 14:04 UTC)", "v0.10.0.md"),
|
||||
new (229, "v0.9.54, build 229 (2025-11-24 18:28 UTC)", "v0.9.54.md"),
|
||||
new (228, "v0.9.53, build 228 (2025-11-14 13:14 UTC)", "v0.9.53.md"),
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
OnAdornmentClick="() => this.SendMessage()"
|
||||
Disabled="@this.IsInputForbidden()"
|
||||
Immediate="@true"
|
||||
OnKeyUp="this.InputKeyEvent"
|
||||
OnKeyUp="@this.InputKeyEvent"
|
||||
UserAttributes="@USER_INPUT_ATTRIBUTES"
|
||||
Class="@this.UserInputClass"
|
||||
Style="@this.UserInputStyle"/>
|
||||
@@ -82,7 +82,7 @@
|
||||
}
|
||||
|
||||
<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)
|
||||
{
|
||||
|
||||
@@ -79,7 +79,11 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
// Get the preselected chat template:
|
||||
this.currentChatTemplate = this.SettingsManager.GetPreselectedChatTemplate(Tools.Components.CHAT);
|
||||
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',
|
||||
// aka the user sends an assistant result to the chat:
|
||||
@@ -93,7 +97,9 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
|
||||
// Use chat thread sent by the user:
|
||||
this.ChatThread = deferredContent;
|
||||
this.Logger.LogInformation($"The chat '{this.ChatThread.Name}' with {this.ChatThread.Blocks.Count} messages was deferred and will be rendered now.");
|
||||
this.ChatThread.IncludeDateTime = true;
|
||||
|
||||
this.Logger.LogInformation($"The chat '{this.ChatThread.ChatId}' with {this.ChatThread.Blocks.Count} messages was deferred and will be rendered now.");
|
||||
await this.ChatThreadChanged.InvokeAsync(this.ChatThread);
|
||||
|
||||
// We know already that the chat thread is not null,
|
||||
@@ -198,7 +204,6 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
|
||||
// Select the correct provider:
|
||||
await this.SelectProviderWhenLoadingChat();
|
||||
|
||||
await base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
@@ -328,7 +333,12 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
this.currentChatTemplate = chatTemplate;
|
||||
if(!string.IsNullOrWhiteSpace(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)
|
||||
return;
|
||||
|
||||
@@ -427,6 +437,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
{
|
||||
this.ChatThread = new()
|
||||
{
|
||||
IncludeDateTime = true,
|
||||
SelectedProvider = this.Provider.Id,
|
||||
SelectedProfile = this.currentProfile.Id,
|
||||
SelectedChatTemplate = this.currentChatTemplate.Id,
|
||||
@@ -667,6 +678,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
//
|
||||
this.ChatThread = new()
|
||||
{
|
||||
IncludeDateTime = true,
|
||||
SelectedProvider = this.Provider.Id,
|
||||
SelectedProfile = this.currentProfile.Id,
|
||||
SelectedChatTemplate = this.currentChatTemplate.Id,
|
||||
@@ -677,9 +689,14 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
|
||||
Blocks = this.currentChatTemplate == ChatTemplate.NO_CHAT_TEMPLATE ? [] : this.currentChatTemplate.ExampleConversation.Select(x => x.DeepClone()).ToList(),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
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:
|
||||
this.ApplyStandardDataSourceOptions();
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
@if (this.CurrentChatTemplate != ChatTemplate.NO_CHAT_TEMPLATE)
|
||||
{
|
||||
<MudButton IconSize="Size.Large" StartIcon="@Icons.Material.Filled.RateReview" IconColor="Color.Default">
|
||||
@this.CurrentChatTemplate.Name
|
||||
@this.CurrentChatTemplate.GetSafeName()
|
||||
</MudButton>
|
||||
}
|
||||
else
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
@inherits ConfigurationBaseCore
|
||||
|
||||
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
|
||||
<MudIcon Icon="@this.Icon" Color="@this.IconColor"/>
|
||||
<MudText Typo="Typo.body1" Class="flex-grow-1">
|
||||
@if (string.IsNullOrWhiteSpace(this.Shortcut()))
|
||||
{
|
||||
@T("No shortcut configured")
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudChip T="string" Color="Color.Primary" Size="Size.Small" Variant="Variant.Outlined">
|
||||
@this.GetDisplayShortcut()
|
||||
</MudChip>
|
||||
}
|
||||
</MudText>
|
||||
<MudButton Variant="Variant.Outlined"
|
||||
Color="Color.Primary"
|
||||
Size="Size.Small"
|
||||
StartIcon="@Icons.Material.Filled.Edit"
|
||||
OnClick="@this.OpenDialog"
|
||||
Disabled="@this.IsDisabled"
|
||||
Class="mb-1">
|
||||
@T("Change shortcut")
|
||||
</MudButton>
|
||||
</MudStack>
|
||||
@@ -0,0 +1,109 @@
|
||||
using AIStudio.Dialogs;
|
||||
using AIStudio.Tools.Rust;
|
||||
using AIStudio.Tools.Services;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using DialogOptions = AIStudio.Dialogs.DialogOptions;
|
||||
|
||||
namespace AIStudio.Components;
|
||||
|
||||
/// <summary>
|
||||
/// A configuration component for capturing and displaying keyboard shortcuts.
|
||||
/// </summary>
|
||||
public partial class ConfigurationShortcut : ConfigurationBaseCore
|
||||
{
|
||||
[Inject]
|
||||
private IDialogService DialogService { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private RustService RustService { get; init; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// The current shortcut value.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Func<string> Shortcut { get; set; } = () => string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// An action which is called when the shortcut was changed.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Action<string> ShortcutUpdate { get; set; } = _ => { };
|
||||
|
||||
/// <summary>
|
||||
/// The name/identifier of the shortcut (used for conflict detection and registration).
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Shortcut ShortcutId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// The icon to display.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string Icon { get; set; } = Icons.Material.Filled.Keyboard;
|
||||
|
||||
/// <summary>
|
||||
/// The color of the icon.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Color IconColor { get; set; } = Color.Default;
|
||||
|
||||
#region Overrides of ConfigurationBase
|
||||
|
||||
protected override bool Stretch => true;
|
||||
|
||||
protected override Variant Variant => Variant.Outlined;
|
||||
|
||||
protected override string Label => this.OptionDescription;
|
||||
|
||||
#endregion
|
||||
|
||||
private string GetDisplayShortcut()
|
||||
{
|
||||
var shortcut = this.Shortcut();
|
||||
if (string.IsNullOrWhiteSpace(shortcut))
|
||||
return string.Empty;
|
||||
|
||||
// Convert internal format to display format:
|
||||
return shortcut
|
||||
.Replace("CmdOrControl", OperatingSystem.IsMacOS() ? "Cmd" : "Ctrl")
|
||||
.Replace("CommandOrControl", OperatingSystem.IsMacOS() ? "Cmd" : "Ctrl");
|
||||
}
|
||||
|
||||
private async Task OpenDialog()
|
||||
{
|
||||
// Suspend shortcut processing while the dialog is open, so the user can
|
||||
// press the current shortcut to re-enter it without triggering the action:
|
||||
await this.RustService.SuspendShortcutProcessing();
|
||||
|
||||
try
|
||||
{
|
||||
var dialogParameters = new DialogParameters<ShortcutDialog>
|
||||
{
|
||||
{ x => x.InitialShortcut, this.Shortcut() },
|
||||
{ x => x.ShortcutId, this.ShortcutId },
|
||||
};
|
||||
|
||||
var dialogReference = await this.DialogService.ShowAsync<ShortcutDialog>(
|
||||
this.T("Configure Keyboard Shortcut"),
|
||||
dialogParameters,
|
||||
DialogOptions.FULLSCREEN);
|
||||
|
||||
var dialogResult = await dialogReference.Result;
|
||||
if (dialogResult is null || dialogResult.Canceled)
|
||||
return;
|
||||
|
||||
if (dialogResult.Data is string newShortcut)
|
||||
{
|
||||
this.ShortcutUpdate(newShortcut);
|
||||
await this.SettingsManager.StoreSettings();
|
||||
await this.InformAboutChange();
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Resume the shortcut processing when the dialog is closed:
|
||||
await this.RustService.ResumeShortcutProcessing();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
@if (this.CurrentProfile != Profile.NO_PROFILE)
|
||||
{
|
||||
<MudButton IconSize="Size.Large" StartIcon="@Icons.Material.Filled.Person4" IconColor="Color.Default">
|
||||
@this.CurrentProfile.Name
|
||||
@this.CurrentProfile.GetSafeName()
|
||||
</MudButton>
|
||||
}
|
||||
else
|
||||
@@ -15,12 +15,12 @@
|
||||
}
|
||||
</ActivatorContent>
|
||||
<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/>
|
||||
@foreach (var profile in this.SettingsManager.ConfigurationData.Profiles.GetAllProfiles())
|
||||
{
|
||||
<MudMenuItem Icon="@this.ProfileIcon(profile)" OnClick="() => this.SelectionChanged(profile)">
|
||||
@profile.Name
|
||||
<MudMenuItem Icon="@this.ProfileIcon(profile)" OnClick="@(() => this.SelectionChanged(profile))">
|
||||
@profile.GetSafeName()
|
||||
</MudMenuItem>
|
||||
}
|
||||
</ChildContent>
|
||||
|
||||
@@ -15,6 +15,6 @@
|
||||
UserAttributes="@SPELLCHECK_ATTRIBUTES"/>
|
||||
|
||||
<MudTooltip Text="@this.ToggleVisibilityTooltip">
|
||||
<MudIconButton Icon="@this.InputTypeIcon" OnClick="() => this.ToggleVisibility()"/>
|
||||
<MudIconButton Icon="@this.InputTypeIcon" OnClick="@(() => this.ToggleVisibility())"/>
|
||||
</MudTooltip>
|
||||
</MudStack>
|
||||
@@ -1,5 +1,6 @@
|
||||
@using AIStudio.Settings
|
||||
@using AIStudio.Settings.DataModel
|
||||
@using AIStudio.Tools.Rust
|
||||
@inherits SettingsPanelBase
|
||||
|
||||
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Apps" HeaderText="@T("App Options")">
|
||||
@@ -29,4 +30,10 @@
|
||||
|
||||
<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"/>
|
||||
|
||||
@if (PreviewFeatures.PRE_SPEECH_TO_TEXT_2026.IsEnabled(this.SettingsManager))
|
||||
{
|
||||
<ConfigurationSelect OptionDescription="@T("Select a transcription provider")" SelectedValue="@(() => this.SettingsManager.ConfigurationData.App.UseTranscriptionProvider)" Data="@this.GetFilteredTranscriptionProviders()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.UseTranscriptionProvider = selectedValue)" OptionHelp="@T("Select a transcription provider for transcribing your voice. Without a selected provider, dictation and transcription features will be disabled.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.App, x => x.UseTranscriptionProvider, out var meta) && meta.IsLocked"/>
|
||||
<ConfigurationShortcut ShortcutId="Shortcut.VOICE_RECORDING_TOGGLE" OptionDescription="@T("Voice recording shortcut")" Shortcut="@(() => this.SettingsManager.ConfigurationData.App.ShortcutVoiceRecording)" ShortcutUpdate="@(shortcut => this.SettingsManager.ConfigurationData.App.ShortcutVoiceRecording = shortcut)" OptionHelp="@T("The global keyboard shortcut for toggling voice recording. This shortcut works system-wide, even when the app is not focused.")" IsLocked="() => ManagedConfiguration.TryGet(x => x.App, x => x.ShortcutVoiceRecording, out var meta) && meta.IsLocked"/>
|
||||
}
|
||||
</ExpansionPanel>
|
||||
@@ -1,9 +1,23 @@
|
||||
using AIStudio.Provider;
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Settings.DataModel;
|
||||
|
||||
namespace AIStudio.Components.Settings;
|
||||
|
||||
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)
|
||||
{
|
||||
this.SettingsManager.ConfigurationData.App.PreviewVisibility = previewVisibility;
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
|
||||
@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"/>
|
||||
<MudText Typo="Typo.h4" Class="mb-3">
|
||||
@T("Configured Embeddings")
|
||||
@T("Configured Embedding Providers")
|
||||
</MudText>
|
||||
<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'.")
|
||||
@@ -36,18 +36,27 @@
|
||||
<MudTd>@context.Name</MudTd>
|
||||
<MudTd>@context.UsedLLMProvider.ToName()</MudTd>
|
||||
<MudTd>@this.GetEmbeddingProviderModelName(context)</MudTd>
|
||||
|
||||
|
||||
<MudTd>
|
||||
<MudStack Row="true" Class="mb-2 mt-2" Wrap="Wrap.Wrap">
|
||||
<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 Row="true" Class="mb-2 mt-2" Spacing="1" Wrap="Wrap.Wrap">
|
||||
@if (context.IsEnterpriseConfiguration)
|
||||
{
|
||||
<MudTooltip Text="@T("This embedding 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.EditEmbeddingProvider(context))"/>
|
||||
</MudTooltip>
|
||||
<MudTooltip Text="@T("Delete")">
|
||||
<MudIconButton Color="Color.Error" Icon="@Icons.Material.Filled.Delete" OnClick="@(() => this.DeleteEmbeddingProvider(context))"/>
|
||||
</MudTooltip>
|
||||
}
|
||||
</MudStack>
|
||||
</MudTd>
|
||||
</RowTemplate>
|
||||
|
||||
@@ -17,6 +17,10 @@ public partial class SettingsPanelEmbeddings : SettingsPanelBase
|
||||
|
||||
private string GetEmbeddingProviderModelName(EmbeddingProvider provider)
|
||||
{
|
||||
// For system models, return localized text:
|
||||
if (provider.Model.IsSystemModel)
|
||||
return T("Uses the provider-configured model");
|
||||
|
||||
const int MAX_LENGTH = 36;
|
||||
var modelName = provider.Model.ToString();
|
||||
return modelName.Length > MAX_LENGTH ? "[...] " + modelName[^Math.Min(MAX_LENGTH, modelName.Length)..] : modelName;
|
||||
@@ -100,13 +104,13 @@ public partial class SettingsPanelEmbeddings : SettingsPanelBase
|
||||
if (dialogResult is null || dialogResult.Canceled)
|
||||
return;
|
||||
|
||||
var deleteSecretResponse = await this.RustService.DeleteAPIKey(provider);
|
||||
var deleteSecretResponse = await this.RustService.DeleteAPIKey(provider, SecretStoreType.EMBEDDING_PROVIDER);
|
||||
if(deleteSecretResponse.Success)
|
||||
{
|
||||
this.SettingsManager.ConfigurationData.EmbeddingProviders.Remove(provider);
|
||||
await this.SettingsManager.StoreSettings();
|
||||
}
|
||||
|
||||
|
||||
await this.UpdateEmbeddingProviders();
|
||||
await this.MessageBus.SendMessage<bool>(this, Event.CONFIGURATION_CHANGED);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
@using AIStudio.Provider
|
||||
@using AIStudio.Settings
|
||||
@using AIStudio.Provider.SelfHosted
|
||||
@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">
|
||||
@T("Configured Providers")
|
||||
@T("Configured LLM Providers")
|
||||
</MudText>
|
||||
<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.")
|
||||
@@ -29,20 +28,7 @@
|
||||
<MudTd>@context.Num</MudTd>
|
||||
<MudTd>@context.InstanceName</MudTd>
|
||||
<MudTd>@context.UsedLLMProvider.ToName()</MudTd>
|
||||
<MudTd>
|
||||
@if (context.UsedLLMProvider is not LLMProviders.SELF_HOSTED)
|
||||
{
|
||||
@this.GetLLMProviderModelName(context)
|
||||
}
|
||||
else if (context.UsedLLMProvider is LLMProviders.SELF_HOSTED && context.Host is not Host.LLAMACPP)
|
||||
{
|
||||
@this.GetLLMProviderModelName(context)
|
||||
}
|
||||
else
|
||||
{
|
||||
@T("as selected by provider")
|
||||
}
|
||||
</MudTd>
|
||||
<MudTd>@this.GetLLMProviderModelName(context)</MudTd>
|
||||
<MudTd>
|
||||
<MudStack Row="true" Class="mb-2 mt-2" Spacing="1" Wrap="Wrap.Wrap">
|
||||
@if (context.IsEnterpriseConfiguration)
|
||||
@@ -57,10 +43,10 @@
|
||||
<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.EditLLMProvider(context)"/>
|
||||
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Edit" OnClick="@(() => this.EditLLMProvider(context))"/>
|
||||
</MudTooltip>
|
||||
<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>
|
||||
}
|
||||
</MudStack>
|
||||
|
||||
@@ -107,7 +107,7 @@ public partial class SettingsPanelProviders : SettingsPanelBase
|
||||
if (dialogResult is null || dialogResult.Canceled)
|
||||
return;
|
||||
|
||||
var deleteSecretResponse = await this.RustService.DeleteAPIKey(provider);
|
||||
var deleteSecretResponse = await this.RustService.DeleteAPIKey(provider, SecretStoreType.LLM_PROVIDER);
|
||||
if(deleteSecretResponse.Success)
|
||||
{
|
||||
this.SettingsManager.ConfigurationData.Providers.Remove(provider);
|
||||
@@ -136,6 +136,10 @@ public partial class SettingsPanelProviders : SettingsPanelBase
|
||||
|
||||
private string GetLLMProviderModelName(AIStudio.Settings.Provider provider)
|
||||
{
|
||||
// For system models, return localized text:
|
||||
if (provider.Model.IsSystemModel)
|
||||
return T("Uses the provider-configured model");
|
||||
|
||||
const int MAX_LENGTH = 36;
|
||||
var modelName = provider.Model.ToString();
|
||||
return modelName.Length > MAX_LENGTH ? "[...] " + modelName[^Math.Min(MAX_LENGTH, modelName.Length)..] : modelName;
|
||||
|
||||
@@ -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>@this.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,126 @@
|
||||
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 string GetTranscriptionProviderModelName(TranscriptionProvider provider)
|
||||
{
|
||||
// For system models, return localized text:
|
||||
if (provider.Model.IsSystemModel)
|
||||
return T("Uses the provider-configured model");
|
||||
|
||||
const int MAX_LENGTH = 36;
|
||||
var modelName = provider.Model.ToString();
|
||||
return modelName.Length > MAX_LENGTH ? "[...] " + modelName[^Math.Min(MAX_LENGTH, modelName.Length)..] : modelName;
|
||||
}
|
||||
|
||||
#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 || this.isPreparing)
|
||||
{
|
||||
<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,443 @@
|
||||
using AIStudio.Provider;
|
||||
using AIStudio.Tools.MIME;
|
||||
using AIStudio.Tools.Rust;
|
||||
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!;
|
||||
|
||||
#region Overrides of MSGComponentBase
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
// Register for global shortcut events:
|
||||
this.ApplyFilters([], [Event.TAURI_EVENT_RECEIVED]);
|
||||
|
||||
await base.OnInitializedAsync();
|
||||
|
||||
try
|
||||
{
|
||||
// Initialize sound effects. This "warms up" the AudioContext and preloads all sounds for reliable playback:
|
||||
await this.JsRuntime.InvokeVoidAsync("initSoundEffects");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.Logger.LogError(ex, "Failed to initialize sound effects.");
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task ProcessIncomingMessage<T>(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default
|
||||
{
|
||||
switch (triggeredEvent)
|
||||
{
|
||||
case Event.TAURI_EVENT_RECEIVED when data is TauriEvent { EventType: TauriEventType.GLOBAL_SHORTCUT_PRESSED } tauriEvent:
|
||||
// Check if this is the voice recording toggle shortcut:
|
||||
if (tauriEvent.TryGetShortcut(out var shortcutId) && shortcutId == Shortcut.VOICE_RECORDING_TOGGLE)
|
||||
{
|
||||
this.Logger.LogInformation("Global shortcut triggered for voice recording toggle.");
|
||||
await this.ToggleRecordingFromShortcut();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Toggles the recording state when triggered by a global shortcut.
|
||||
/// </summary>
|
||||
private async Task ToggleRecordingFromShortcut()
|
||||
{
|
||||
// Don't allow toggle if transcription is in progress or preparing:
|
||||
if (this.isTranscribing || this.isPreparing)
|
||||
{
|
||||
this.Logger.LogDebug("Ignoring shortcut: transcription or preparation is in progress.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Toggle the recording state:
|
||||
await this.OnRecordingToggled(!this.isRecording);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private uint numReceivedChunks;
|
||||
private bool isRecording;
|
||||
private bool isPreparing;
|
||||
private bool isTranscribing;
|
||||
private FileStream? currentRecordingStream;
|
||||
private string? currentRecordingPath;
|
||||
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)
|
||||
{
|
||||
this.isPreparing = true;
|
||||
this.StateHasChanged();
|
||||
|
||||
try
|
||||
{
|
||||
// Warm up sound effects:
|
||||
await this.JsRuntime.InvokeVoidAsync("initSoundEffects");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.Logger.LogError(ex, "Failed to initialize sound effects.");
|
||||
}
|
||||
|
||||
var mimeTypes = GetPreferredMimeTypes(
|
||||
Builder.Create().UseAudio().UseSubtype(AudioSubtype.OGG).Build(),
|
||||
Builder.Create().UseAudio().UseSubtype(AudioSubtype.AAC).Build(),
|
||||
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();
|
||||
|
||||
try
|
||||
{
|
||||
var mimeTypeStrings = mimeTypes.ToStringArray();
|
||||
var actualMimeType = await this.JsRuntime.InvokeAsync<string>("audioRecorder.start", this.dotNetReference, mimeTypeStrings);
|
||||
|
||||
// Store the MIME type for later use:
|
||||
this.currentRecordingMimeType = actualMimeType;
|
||||
|
||||
this.Logger.LogInformation("Audio recording started with MIME type: '{ActualMimeType}'.", actualMimeType);
|
||||
this.isPreparing = false;
|
||||
this.isRecording = true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
this.Logger.LogError(e, "Failed to start audio recording.");
|
||||
await this.MessageBus.SendError(new(Icons.Material.Filled.MicOff, this.T("Failed to start audio recording.")));
|
||||
|
||||
// Clean up the recording stream if starting failed:
|
||||
await this.FinalizeRecordingStream();
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.StateHasChanged();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await this.JsRuntime.InvokeAsync<AudioRecordingResult>("audioRecorder.stop");
|
||||
if (result.ChangedMimeType)
|
||||
this.Logger.LogWarning("The recorded audio MIME type was changed to '{ResultMimeType}'.", result.MimeType);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
this.Logger.LogError(e, "Failed to stop audio recording.");
|
||||
await this.MessageBus.SendError(new(Icons.Material.Filled.MicOff, this.T("Failed to stop audio recording.")));
|
||||
}
|
||||
|
||||
// Close and finalize the recording stream:
|
||||
await this.FinalizeRecordingStream();
|
||||
|
||||
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)
|
||||
{
|
||||
// No recording to transcribe, but still release the microphone:
|
||||
await this.ReleaseMicrophoneAsync();
|
||||
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.UsedLLMProvider,
|
||||
providerConfidence.Level,
|
||||
minimumLevel);
|
||||
await this.MessageBus.SendError(new(Icons.Material.Filled.VoiceChat, this.T("The configured transcription provider does not meet the minimum confidence level.")));
|
||||
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.UsedLLMProvider, transcriptionProviderSettings.Model.ToString());
|
||||
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);
|
||||
|
||||
try
|
||||
{
|
||||
// Play the transcription done sound effect:
|
||||
await this.JsRuntime.InvokeVoidAsync("playSound", "/sounds/transcription_done.ogg");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
this.Logger.LogError(ex, "Failed to play transcription done sound effect.");
|
||||
}
|
||||
|
||||
// Copy the transcribed text to the clipboard:
|
||||
await this.RustService.CopyText2Clipboard(this.Snackbar, transcribedText);
|
||||
|
||||
// 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
|
||||
{
|
||||
await this.ReleaseMicrophoneAsync();
|
||||
|
||||
this.finalRecordingPath = null;
|
||||
this.isTranscribing = false;
|
||||
this.StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ReleaseMicrophoneAsync()
|
||||
{
|
||||
// Wait a moment for any queued sounds to finish playing, then release the microphone.
|
||||
// This allows Bluetooth headsets to switch back to A2DP profile without interrupting audio:
|
||||
await Task.Delay(1_800);
|
||||
|
||||
try
|
||||
{
|
||||
await this.JsRuntime.InvokeVoidAsync("audioRecorder.releaseMicrophone");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
this.Logger.LogError(e, "Failed to release the microphone.");
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class AudioRecordingResult
|
||||
{
|
||||
public string MimeType { get; init; } = string.Empty;
|
||||
|
||||
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"
|
||||
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">
|
||||
@T("Profile Usage")
|
||||
</MudText>
|
||||
|
||||
@@ -50,7 +50,10 @@ public partial class ChatTemplateDialog : MSGComponentBase
|
||||
[Parameter]
|
||||
public IReadOnlyCollection<ContentBlock> ExampleConversation { get; init; } = [];
|
||||
|
||||
[Parameter]
|
||||
[Parameter]
|
||||
public IReadOnlyCollection<FileAttachment> FileAttachments { get; init; } = [];
|
||||
|
||||
[Parameter]
|
||||
public bool AllowProfileUsage { get; set; } = true;
|
||||
|
||||
[Parameter]
|
||||
@@ -71,6 +74,7 @@ public partial class ChatTemplateDialog : MSGComponentBase
|
||||
|
||||
private bool dataIsValid;
|
||||
private List<ContentBlock> dataExampleConversation = [];
|
||||
private HashSet<FileAttachment> fileAttachments = [];
|
||||
private string[] dataIssues = [];
|
||||
private string dataEditingPreviousName = string.Empty;
|
||||
private bool isInlineEditOnGoing;
|
||||
@@ -95,6 +99,7 @@ public partial class ChatTemplateDialog : MSGComponentBase
|
||||
{
|
||||
this.dataEditingPreviousName = this.DataName.ToLowerInvariant();
|
||||
this.dataExampleConversation = this.ExampleConversation.Select(n => n.DeepClone()).ToList();
|
||||
this.fileAttachments = [..this.FileAttachments];
|
||||
}
|
||||
|
||||
if (this.CreateFromExistingChatThread && this.ExistingChatThread is not null)
|
||||
@@ -128,6 +133,7 @@ public partial class ChatTemplateDialog : MSGComponentBase
|
||||
SystemPrompt = this.DataSystemPrompt,
|
||||
PredefinedUserPrompt = this.PredefinedUserPrompt,
|
||||
ExampleConversation = this.dataExampleConversation,
|
||||
FileAttachments = [..this.fileAttachments],
|
||||
AllowProfileUsage = this.AllowProfileUsage,
|
||||
|
||||
EnterpriseConfigurationPluginId = Guid.Empty,
|
||||
|
||||
@@ -96,7 +96,7 @@ public partial class DataSourceLocalDirectoryDialog : MSGComponentBase
|
||||
|
||||
#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()
|
||||
{
|
||||
|
||||
@@ -27,7 +27,7 @@ public partial class DataSourceLocalDirectoryInfoDialog : MSGComponentBase, IAsy
|
||||
|
||||
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);
|
||||
|
||||
if (this.directoryInfo.Exists)
|
||||
@@ -46,7 +46,7 @@ public partial class DataSourceLocalDirectoryInfoDialog : MSGComponentBase, IAsy
|
||||
|
||||
private readonly CancellationTokenSource cts = new();
|
||||
|
||||
private EmbeddingProvider embeddingProvider;
|
||||
private EmbeddingProvider embeddingProvider = EmbeddingProvider.NONE;
|
||||
private DirectoryInfo directoryInfo = null!;
|
||||
private long directorySizeBytes;
|
||||
private long directorySizeNumFiles;
|
||||
|
||||
@@ -96,7 +96,7 @@ public partial class DataSourceLocalFileDialog : MSGComponentBase
|
||||
|
||||
#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()
|
||||
{
|
||||
|
||||
@@ -18,14 +18,14 @@ public partial class DataSourceLocalFileInfoDialog : MSGComponentBase
|
||||
|
||||
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);
|
||||
await base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private EmbeddingProvider embeddingProvider;
|
||||
private EmbeddingProvider embeddingProvider = EmbeddingProvider.NONE;
|
||||
private FileInfo fileInfo = null!;
|
||||
|
||||
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">
|
||||
@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">
|
||||
@provider.ToName()
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
@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())
|
||||
@@ -44,10 +44,10 @@
|
||||
|
||||
@if (this.DataLLMProvider.IsHostNeeded())
|
||||
{
|
||||
<MudSelect @bind-Value="@this.DataHost" Label="@T("Host")" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.providerValidation.ValidatingHost">
|
||||
<MudSelect T="Host" Value="@this.DataHost" ValueChanged="@this.OnHostChanged" Label="@T("Host")" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.providerValidation.ValidatingHost">
|
||||
@foreach (Host host in Enum.GetValues(typeof(Host)))
|
||||
{
|
||||
if (host.AreEmbeddingsSupported())
|
||||
if (host.IsEmbeddingSupported())
|
||||
{
|
||||
<MudSelectItem Value="@host">
|
||||
@host.Name()
|
||||
@@ -71,12 +71,12 @@
|
||||
AdornmentColor="Color.Info"
|
||||
Validation="@this.ValidateManuallyModel"
|
||||
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
|
||||
{
|
||||
<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")
|
||||
</MudButton>
|
||||
@if(this.availableModels.Count is 0)
|
||||
@@ -101,6 +101,12 @@
|
||||
}
|
||||
}
|
||||
</MudStack>
|
||||
@if (!string.IsNullOrWhiteSpace(this.dataLoadingModelsIssue))
|
||||
{
|
||||
<MudAlert Severity="Severity.Error" Class="mt-3">
|
||||
@this.dataLoadingModelsIssue
|
||||
</MudAlert>
|
||||
}
|
||||
</MudField>
|
||||
|
||||
@* ReSharper disable once CSharpWarnings::CS8974 *@
|
||||
|
||||
@@ -71,7 +71,10 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
|
||||
|
||||
[Inject]
|
||||
private RustService RustService { get; init; } = null!;
|
||||
|
||||
|
||||
[Inject]
|
||||
private ILogger<EmbeddingProviderDialog> Logger { get; init; } = null!;
|
||||
|
||||
private static readonly Dictionary<string, object?> SPELLCHECK_ATTRIBUTES = new();
|
||||
|
||||
/// <summary>
|
||||
@@ -85,7 +88,8 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
|
||||
private string dataManuallyModel = string.Empty;
|
||||
private string dataAPIKeyStorageIssue = string.Empty;
|
||||
private string dataEditingPreviousInstanceName = string.Empty;
|
||||
|
||||
private string dataLoadingModelsIssue = string.Empty;
|
||||
|
||||
// We get the form reference from Blazor code to validate it manually:
|
||||
private MudForm form = null!;
|
||||
|
||||
@@ -102,6 +106,7 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
|
||||
GetPreviousInstanceName = () => this.dataEditingPreviousInstanceName,
|
||||
GetUsedInstanceNames = () => this.UsedInstanceNames,
|
||||
GetHost = () => this.DataHost,
|
||||
IsModelProvidedManually = () => this.DataLLMProvider is LLMProviders.SELF_HOSTED && this.DataHost is Host.OLLAMA,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -129,6 +134,8 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
|
||||
IsSelfHosted = this.DataLLMProvider is LLMProviders.SELF_HOSTED,
|
||||
Hostname = cleanedHostname.EndsWith('/') ? cleanedHostname[..^1] : cleanedHostname,
|
||||
Host = this.DataHost,
|
||||
IsEnterpriseConfiguration = false,
|
||||
EnterpriseConfigurationPluginId = Guid.Empty,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -136,6 +143,9 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
|
||||
|
||||
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);
|
||||
|
||||
@@ -162,7 +172,7 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
|
||||
}
|
||||
|
||||
// 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)
|
||||
this.dataAPIKey = await requestedSecret.Secret.Decrypt(this.encryption);
|
||||
else
|
||||
@@ -177,8 +187,6 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
|
||||
|
||||
await this.ReloadModels();
|
||||
}
|
||||
|
||||
await base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
@@ -195,7 +203,7 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
|
||||
|
||||
#region Implementation of ISecretId
|
||||
|
||||
public string SecretId => this.DataId;
|
||||
public string SecretId => this.DataLLMProvider.ToName();
|
||||
|
||||
public string SecretName => this.DataName;
|
||||
|
||||
@@ -205,7 +213,16 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
|
||||
{
|
||||
await this.form.Validate();
|
||||
this.dataAPIKeyStorageIssue = string.Empty;
|
||||
|
||||
|
||||
// Manually validate the model selection (needed when no models are loaded
|
||||
// and the MudSelect is not rendered):
|
||||
var modelValidationError = this.providerValidation.ValidatingModel(this.DataModel);
|
||||
if (!string.IsNullOrWhiteSpace(modelValidationError))
|
||||
{
|
||||
this.dataIssues = [..this.dataIssues, modelValidationError];
|
||||
this.dataIsValid = false;
|
||||
}
|
||||
|
||||
// When the data is not valid, we don't store it:
|
||||
if (!this.dataIsValid)
|
||||
return;
|
||||
@@ -216,7 +233,7 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
|
||||
if (!string.IsNullOrWhiteSpace(this.dataAPIKey))
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
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,21 +254,50 @@ public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
|
||||
}
|
||||
|
||||
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 void OnHostChanged(Host selectedHost)
|
||||
{
|
||||
// When the host changes, reset the model selection state:
|
||||
this.DataHost = selectedHost;
|
||||
this.DataModel = default;
|
||||
this.dataManuallyModel = string.Empty;
|
||||
this.availableModels.Clear();
|
||||
this.dataLoadingModelsIssue = string.Empty;
|
||||
}
|
||||
|
||||
private async Task ReloadModels()
|
||||
{
|
||||
this.dataLoadingModelsIssue = string.Empty;
|
||||
var currentEmbeddingProviderSettings = this.CreateEmbeddingProviderSettings();
|
||||
var provider = currentEmbeddingProviderSettings.CreateProvider();
|
||||
if(provider is NoProvider)
|
||||
if (provider is NoProvider)
|
||||
return;
|
||||
|
||||
var models = await provider.GetEmbeddingModels(this.dataAPIKey);
|
||||
|
||||
// Order descending by ID means that the newest models probably come first:
|
||||
var orderedModels = models.OrderByDescending(n => n.Id);
|
||||
|
||||
this.availableModels.Clear();
|
||||
this.availableModels.AddRange(orderedModels);
|
||||
|
||||
try
|
||||
{
|
||||
var models = await provider.GetEmbeddingModels(this.dataAPIKey);
|
||||
|
||||
// Order descending by ID means that the newest models probably come first:
|
||||
var orderedModels = models.OrderByDescending(n => n.Id);
|
||||
|
||||
this.availableModels.Clear();
|
||||
this.availableModels.AddRange(orderedModels);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
this.Logger.LogError($"Failed to load models from provider '{this.DataLLMProvider}' (host={this.DataHost}, hostname='{this.DataHostname}'): {e.Message}");
|
||||
this.dataLoadingModelsIssue = T("We are currently unable to communicate with the provider to load models. Please try again later.");
|
||||
}
|
||||
}
|
||||
|
||||
private string APIKeyText => this.DataLLMProvider switch
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
using System.Reflection;
|
||||
|
||||
using AIStudio.Components;
|
||||
using AIStudio.Tools.Metadata;
|
||||
using AIStudio.Components;
|
||||
using AIStudio.Tools.Services;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
@@ -11,9 +8,8 @@ namespace AIStudio.Dialogs;
|
||||
|
||||
public partial class PandocDialog : MSGComponentBase
|
||||
{
|
||||
private static readonly Assembly ASSEMBLY = Assembly.GetExecutingAssembly();
|
||||
private static readonly MetaDataArchitectureAttribute META_DATA_ARCH = ASSEMBLY.GetCustomAttribute<MetaDataArchitectureAttribute>()!;
|
||||
private static readonly RID CPU_ARCHITECTURE = META_DATA_ARCH.Architecture.ToRID();
|
||||
// Use runtime detection instead of metadata to ensure correct RID on dev machines:
|
||||
private static readonly RID CPU_ARCHITECTURE = RIDExtensions.GetCurrentRID();
|
||||
|
||||
[Parameter]
|
||||
public bool ShowInstallationPage { get; set; }
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
@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())
|
||||
@@ -41,12 +41,15 @@
|
||||
|
||||
@if (this.DataLLMProvider.IsHostNeeded())
|
||||
{
|
||||
<MudSelect @bind-Value="@this.DataHost" Label="@T("Host")" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.providerValidation.ValidatingHost">
|
||||
<MudSelect T="Host" Value="@this.DataHost" ValueChanged="@this.OnHostChanged" Label="@T("Host")" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.providerValidation.ValidatingHost">
|
||||
@foreach (Host host in Enum.GetValues(typeof(Host)))
|
||||
{
|
||||
<MudSelectItem Value="@host">
|
||||
@host.Name()
|
||||
</MudSelectItem>
|
||||
@if (host.IsChatSupported())
|
||||
{
|
||||
<MudSelectItem Value="@host">
|
||||
@host.Name()
|
||||
</MudSelectItem>
|
||||
}
|
||||
}
|
||||
</MudSelect>
|
||||
}
|
||||
@@ -68,51 +71,69 @@
|
||||
@* ReSharper restore Asp.Entity *@
|
||||
}
|
||||
|
||||
<MudField FullWidth="true" Label="@T("Model selection")" Variant="Variant.Outlined" Class="mb-3">
|
||||
<MudStack Row="@true" AlignItems="AlignItems.Center" StretchItems="StretchItems.End">
|
||||
@if (this.DataLLMProvider.IsLLMModelProvidedManually())
|
||||
{
|
||||
<MudButton Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.OpenInBrowser" Href="@this.DataLLMProvider.GetModelsOverviewURL(this.HFInferenceProviderId)" Target="_blank">
|
||||
@T("Show available models")
|
||||
</MudButton>
|
||||
<MudTextField
|
||||
T="string"
|
||||
@bind-Text="@this.dataManuallyModel"
|
||||
Label="@T("Model")"
|
||||
Adornment="Adornment.Start"
|
||||
AdornmentIcon="@Icons.Material.Filled.FaceRetouchingNatural"
|
||||
AdornmentColor="Color.Info"
|
||||
Validation="@this.ValidateManuallyModel"
|
||||
UserAttributes="@SPELLCHECK_ATTRIBUTES"
|
||||
/>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudButton Disabled="@(!this.DataLLMProvider.CanLoadModels(this.DataHost, this.dataAPIKey))" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.Refresh" OnClick="this.ReloadModels">
|
||||
@T("Load models")
|
||||
</MudButton>
|
||||
@if(this.availableModels.Count is 0)
|
||||
@if (!this.DataLLMProvider.IsLLMModelSelectionHidden(this.DataHost))
|
||||
{
|
||||
<MudField FullWidth="true" Label="@T("Model selection")" Variant="Variant.Outlined" Class="mb-3">
|
||||
<MudStack Row="@true" AlignItems="AlignItems.Center" StretchItems="StretchItems.End">
|
||||
@if (this.DataLLMProvider.IsLLMModelProvidedManually())
|
||||
{
|
||||
<MudText Typo="Typo.body1">
|
||||
@T("No models loaded or available.")
|
||||
</MudText>
|
||||
<MudButton Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.OpenInBrowser" Href="@this.DataLLMProvider.GetModelsOverviewURL(this.HFInferenceProviderId)" Target="_blank">
|
||||
@T("Show available models")
|
||||
</MudButton>
|
||||
<MudTextField
|
||||
T="string"
|
||||
@bind-Text="@this.dataManuallyModel"
|
||||
Label="@T("Model")"
|
||||
Adornment="Adornment.Start"
|
||||
AdornmentIcon="@Icons.Material.Filled.FaceRetouchingNatural"
|
||||
AdornmentColor="Color.Info"
|
||||
Validation="@this.ValidateManuallyModel"
|
||||
UserAttributes="@SPELLCHECK_ATTRIBUTES"
|
||||
HelperText="@T("Currently, we cannot query the models for the selected provider and/or host. Therefore, please enter the model name manually.")"
|
||||
/>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudSelect @bind-Value="@this.DataModel"
|
||||
OpenIcon="@Icons.Material.Filled.FaceRetouchingNatural" AdornmentColor="Color.Info"
|
||||
Adornment="Adornment.Start" Validation="@this.providerValidation.ValidatingModel">
|
||||
@foreach (var model in this.availableModels)
|
||||
{
|
||||
<MudSelectItem Value="@model">
|
||||
@model
|
||||
</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
<MudButton Disabled="@(!this.DataLLMProvider.CanLoadModels(this.DataHost, this.dataAPIKey))" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.Refresh" OnClick="@this.ReloadModels">
|
||||
@T("Load models")
|
||||
</MudButton>
|
||||
@if(this.availableModels.Count is 0)
|
||||
{
|
||||
<MudText Typo="Typo.body1">
|
||||
@T("No models loaded or available.")
|
||||
</MudText>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudSelect @bind-Value="@this.DataModel"
|
||||
OpenIcon="@Icons.Material.Filled.FaceRetouchingNatural" AdornmentColor="Color.Info"
|
||||
Adornment="Adornment.Start" Validation="@this.providerValidation.ValidatingModel">
|
||||
@foreach (var model in this.availableModels)
|
||||
{
|
||||
<MudSelectItem Value="@model">
|
||||
@model
|
||||
</MudSelectItem>
|
||||
}
|
||||
</MudSelect>
|
||||
}
|
||||
}
|
||||
</MudStack>
|
||||
@if (!string.IsNullOrWhiteSpace(this.dataLoadingModelsIssue))
|
||||
{
|
||||
<MudAlert Severity="Severity.Error" Class="mt-3">
|
||||
@this.dataLoadingModelsIssue
|
||||
</MudAlert>
|
||||
}
|
||||
</MudStack>
|
||||
</MudField>
|
||||
</MudField>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudField FullWidth="true" Label="@T("Model selection")" Variant="Variant.Outlined" Class="mb-3">
|
||||
<MudText Typo="Typo.body1">
|
||||
@T("This host uses the model configured at the provider level. No model selection is available.")
|
||||
</MudText>
|
||||
</MudField>
|
||||
}
|
||||
|
||||
@* ReSharper disable once CSharpWarnings::CS8974 *@
|
||||
<MudTextField
|
||||
|
||||
@@ -84,6 +84,9 @@ public partial class ProviderDialog : MSGComponentBase, ISecretId
|
||||
[Inject]
|
||||
private RustService RustService { get; init; } = null!;
|
||||
|
||||
[Inject]
|
||||
private ILogger<ProviderDialog> Logger { get; init; } = null!;
|
||||
|
||||
private static readonly Dictionary<string, object?> SPELLCHECK_ATTRIBUTES = new();
|
||||
|
||||
/// <summary>
|
||||
@@ -97,6 +100,7 @@ public partial class ProviderDialog : MSGComponentBase, ISecretId
|
||||
private string dataManuallyModel = string.Empty;
|
||||
private string dataAPIKeyStorageIssue = string.Empty;
|
||||
private string dataEditingPreviousInstanceName = string.Empty;
|
||||
private string dataLoadingModelsIssue = string.Empty;
|
||||
private bool showExpertSettings;
|
||||
|
||||
// We get the form reference from Blazor code to validate it manually:
|
||||
@@ -115,25 +119,36 @@ public partial class ProviderDialog : MSGComponentBase, ISecretId
|
||||
GetPreviousInstanceName = () => this.dataEditingPreviousInstanceName,
|
||||
GetUsedInstanceNames = () => this.UsedInstanceNames,
|
||||
GetHost = () => this.DataHost,
|
||||
IsModelProvidedManually = () => this.DataLLMProvider.IsLLMModelProvidedManually(),
|
||||
};
|
||||
}
|
||||
|
||||
private AIStudio.Settings.Provider CreateProviderSettings()
|
||||
{
|
||||
var cleanedHostname = this.DataHostname.Trim();
|
||||
|
||||
// Determine the model based on the provider and host configuration:
|
||||
Model model;
|
||||
if (this.DataLLMProvider.IsLLMModelSelectionHidden(this.DataHost))
|
||||
{
|
||||
// Use system model placeholder for hosts that don't support model selection (e.g., llama.cpp):
|
||||
model = Model.SYSTEM_MODEL;
|
||||
}
|
||||
else if (this.DataLLMProvider is LLMProviders.FIREWORKS or LLMProviders.HUGGINGFACE)
|
||||
{
|
||||
// These providers require manual model entry:
|
||||
model = new Model(this.dataManuallyModel, null);
|
||||
}
|
||||
else
|
||||
model = this.DataModel;
|
||||
|
||||
return new()
|
||||
{
|
||||
Num = this.DataNum,
|
||||
Id = this.DataId,
|
||||
InstanceName = this.DataInstanceName,
|
||||
UsedLLMProvider = this.DataLLMProvider,
|
||||
|
||||
Model = this.DataLLMProvider switch
|
||||
{
|
||||
LLMProviders.FIREWORKS or LLMProviders.HUGGINGFACE => new Model(this.dataManuallyModel, null),
|
||||
_ => this.DataModel
|
||||
},
|
||||
|
||||
Model = model,
|
||||
IsSelfHosted = this.DataLLMProvider is LLMProviders.SELF_HOSTED,
|
||||
IsEnterpriseConfiguration = false,
|
||||
Hostname = cleanedHostname.EndsWith('/') ? cleanedHostname[..^1] : cleanedHostname,
|
||||
@@ -147,6 +162,9 @@ public partial class ProviderDialog : MSGComponentBase, ISecretId
|
||||
|
||||
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);
|
||||
|
||||
@@ -177,7 +195,7 @@ public partial class ProviderDialog : MSGComponentBase, ISecretId
|
||||
}
|
||||
|
||||
// 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)
|
||||
this.dataAPIKey = await requestedSecret.Secret.Decrypt(this.encryption);
|
||||
else
|
||||
@@ -192,8 +210,6 @@ public partial class ProviderDialog : MSGComponentBase, ISecretId
|
||||
|
||||
await this.ReloadModels();
|
||||
}
|
||||
|
||||
await base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
@@ -221,7 +237,16 @@ public partial class ProviderDialog : MSGComponentBase, ISecretId
|
||||
await this.form.Validate();
|
||||
if (!string.IsNullOrWhiteSpace(this.dataAPIKeyStorageIssue))
|
||||
this.dataAPIKeyStorageIssue = string.Empty;
|
||||
|
||||
|
||||
// Manually validate the model selection (needed when no models are loaded
|
||||
// and the MudSelect is not rendered):
|
||||
var modelValidationError = this.providerValidation.ValidatingModel(this.DataModel);
|
||||
if (!string.IsNullOrWhiteSpace(modelValidationError))
|
||||
{
|
||||
this.dataIssues = [..this.dataIssues, modelValidationError];
|
||||
this.dataIsValid = false;
|
||||
}
|
||||
|
||||
// When the data is not valid, we don't store it:
|
||||
if (!this.dataIsValid)
|
||||
return;
|
||||
@@ -232,7 +257,7 @@ public partial class ProviderDialog : MSGComponentBase, ISecretId
|
||||
if (!string.IsNullOrWhiteSpace(this.dataAPIKey))
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
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,21 +278,50 @@ public partial class ProviderDialog : MSGComponentBase, ISecretId
|
||||
}
|
||||
|
||||
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 void OnHostChanged(Host selectedHost)
|
||||
{
|
||||
// When the host changes, reset the model selection state:
|
||||
this.DataHost = selectedHost;
|
||||
this.DataModel = default;
|
||||
this.dataManuallyModel = string.Empty;
|
||||
this.availableModels.Clear();
|
||||
this.dataLoadingModelsIssue = string.Empty;
|
||||
}
|
||||
|
||||
private async Task ReloadModels()
|
||||
{
|
||||
this.dataLoadingModelsIssue = string.Empty;
|
||||
var currentProviderSettings = this.CreateProviderSettings();
|
||||
var provider = currentProviderSettings.CreateProvider();
|
||||
if(provider is NoProvider)
|
||||
if (provider is NoProvider)
|
||||
return;
|
||||
|
||||
var models = await provider.GetTextModels(this.dataAPIKey);
|
||||
|
||||
// Order descending by ID means that the newest models probably come first:
|
||||
var orderedModels = models.OrderByDescending(n => n.Id);
|
||||
|
||||
this.availableModels.Clear();
|
||||
this.availableModels.AddRange(orderedModels);
|
||||
|
||||
try
|
||||
{
|
||||
var models = await provider.GetTextModels(this.dataAPIKey);
|
||||
|
||||
// Order descending by ID means that the newest models probably come first:
|
||||
var orderedModels = models.OrderByDescending(n => n.Id);
|
||||
|
||||
this.availableModels.Clear();
|
||||
this.availableModels.AddRange(orderedModels);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
this.Logger.LogError($"Failed to load models from provider '{this.DataLLMProvider}' (host={this.DataHost}, hostname='{this.DataHostname}'): {e.Message}");
|
||||
this.dataLoadingModelsIssue = T("We are currently unable to communicate with the provider to load models. Please try again later.");
|
||||
}
|
||||
}
|
||||
|
||||
private string APIKeyText => this.DataLLMProvider switch
|
||||
|
||||
@@ -41,10 +41,10 @@
|
||||
{
|
||||
<MudStack Row="true" Class="mb-2 mt-2" Wrap="Wrap.Wrap">
|
||||
<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 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>
|
||||
</MudStack>
|
||||
}
|
||||
|
||||
@@ -65,6 +65,7 @@ public partial class SettingsDialogChatTemplate : SettingsDialogBase
|
||||
{ x => x.PredefinedUserPrompt, chatTemplate.PredefinedUserPrompt },
|
||||
{ x => x.IsEditing, true },
|
||||
{ x => x.ExampleConversation, chatTemplate.ExampleConversation },
|
||||
{ x => x.FileAttachments, chatTemplate.FileAttachments },
|
||||
{ x => x.AllowProfileUsage, chatTemplate.AllowProfileUsage },
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
@inherits MSGComponentBase
|
||||
|
||||
<MudDialog>
|
||||
<DialogContent>
|
||||
<MudJustifiedText Typo="Typo.body1" Class="mb-3">
|
||||
@T("Press the desired key combination to set the shortcut. The shortcut will be registered globally and will work even when the app is not focused.")
|
||||
</MudJustifiedText>
|
||||
|
||||
<MudFocusTrap DefaultFocus="DefaultFocus.FirstChild">
|
||||
<MudTextField
|
||||
@ref="@this.inputField"
|
||||
T="string"
|
||||
Text="@this.ShowText"
|
||||
Variant="Variant.Outlined"
|
||||
Label="@T("Define a shortcut")"
|
||||
Placeholder="@T("Press a key combination...")"
|
||||
Adornment="Adornment.Start"
|
||||
AdornmentIcon="@Icons.Material.Filled.Keyboard"
|
||||
Immediate="@true"
|
||||
TextUpdateSuppression="false"
|
||||
OnKeyDown="@this.HandleKeyDown"
|
||||
OnBlur="@this.HandleBlur"
|
||||
UserAttributes="@USER_INPUT_ATTRIBUTES"
|
||||
AutoFocus="true"
|
||||
KeyDownPreventDefault="true"
|
||||
KeyUpPreventDefault="true"
|
||||
HelperText="@T("Supported modifiers: Ctrl/Cmd, Shift, Alt.")"
|
||||
Class="me-3"/>
|
||||
</MudFocusTrap>
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(this.validationMessage))
|
||||
{
|
||||
<MudAlert Severity="@this.validationSeverity" Variant="Variant.Filled" Class="mb-3">
|
||||
@this.validationMessage
|
||||
</MudAlert>
|
||||
}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<MudButton OnClick="@this.ClearShortcut" Variant="Variant.Filled" Color="Color.Info" StartIcon="@Icons.Material.Filled.Clear">
|
||||
@T("Clear Shortcut")
|
||||
</MudButton>
|
||||
<MudSpacer/>
|
||||
<MudButton OnClick="@this.Cancel" Variant="Variant.Filled">
|
||||
@T("Cancel")
|
||||
</MudButton>
|
||||
<MudButton OnClick="@this.Confirm" Variant="Variant.Filled" Color="Color.Primary" Disabled="@this.hasValidationError">
|
||||
@T("Save")
|
||||
</MudButton>
|
||||
</DialogActions>
|
||||
</MudDialog>
|
||||
@@ -0,0 +1,385 @@
|
||||
using AIStudio.Components;
|
||||
using AIStudio.Tools.Rust;
|
||||
using AIStudio.Tools.Services;
|
||||
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Components.Web;
|
||||
|
||||
namespace AIStudio.Dialogs;
|
||||
|
||||
/// <summary>
|
||||
/// A dialog for capturing and configuring keyboard shortcuts.
|
||||
/// </summary>
|
||||
public partial class ShortcutDialog : MSGComponentBase
|
||||
{
|
||||
[CascadingParameter]
|
||||
private IMudDialogInstance MudDialog { get; set; } = null!;
|
||||
|
||||
[Inject]
|
||||
private RustService RustService { get; init; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// The initial shortcut value (in internal format, e.g., "CmdOrControl+1").
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public string InitialShortcut { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// The identifier of the shortcut for conflict detection.
|
||||
/// </summary>
|
||||
[Parameter]
|
||||
public Shortcut ShortcutId { get; set; }
|
||||
|
||||
private static readonly Dictionary<string, object?> USER_INPUT_ATTRIBUTES = new();
|
||||
|
||||
private string currentShortcut = string.Empty;
|
||||
private string originalShortcut = string.Empty;
|
||||
private string validationMessage = string.Empty;
|
||||
private Severity validationSeverity = Severity.Info;
|
||||
private bool hasValidationError;
|
||||
|
||||
//
|
||||
// Current key state:
|
||||
//
|
||||
private bool hasCtrl;
|
||||
private bool hasShift;
|
||||
private bool hasAlt;
|
||||
private bool hasMeta;
|
||||
private string? currentKey;
|
||||
private MudTextField<string>? inputField;
|
||||
|
||||
#region Overrides of ComponentBase
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await base.OnInitializedAsync();
|
||||
|
||||
// Configure the spellchecking for the user input:
|
||||
this.SettingsManager.InjectSpellchecking(USER_INPUT_ATTRIBUTES);
|
||||
|
||||
this.currentShortcut = this.InitialShortcut;
|
||||
this.originalShortcut = this.InitialShortcut;
|
||||
this.ParseExistingShortcut();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private string ShowText => string.IsNullOrWhiteSpace(this.currentShortcut)
|
||||
? T("Press a key combination...")
|
||||
: this.GetDisplayShortcut();
|
||||
|
||||
private void ParseExistingShortcut()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(this.currentShortcut))
|
||||
return;
|
||||
|
||||
// Parse the existing shortcut to set the state
|
||||
var parts = this.currentShortcut.Split('+');
|
||||
foreach (var part in parts)
|
||||
{
|
||||
switch (part.ToLowerInvariant())
|
||||
{
|
||||
case "cmdorcontrol":
|
||||
case "commandorcontrol":
|
||||
case "ctrl":
|
||||
case "control":
|
||||
case "cmd":
|
||||
case "command":
|
||||
this.hasCtrl = true;
|
||||
break;
|
||||
|
||||
case "shift":
|
||||
this.hasShift = true;
|
||||
break;
|
||||
|
||||
case "alt":
|
||||
this.hasAlt = true;
|
||||
break;
|
||||
|
||||
case "meta":
|
||||
case "super":
|
||||
this.hasMeta = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
this.currentKey = part;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleKeyDown(KeyboardEventArgs e)
|
||||
{
|
||||
// Ignore pure modifier key presses:
|
||||
if (IsModifierKey(e.Code))
|
||||
{
|
||||
this.UpdateModifiers(e);
|
||||
this.currentKey = null;
|
||||
this.UpdateShortcutString();
|
||||
return;
|
||||
}
|
||||
|
||||
this.UpdateModifiers(e);
|
||||
|
||||
// Get the key:
|
||||
this.currentKey = TranslateKeyCode(e.Code);
|
||||
|
||||
// Validate: must have at least one modifier + a key
|
||||
if (!this.hasCtrl && !this.hasShift && !this.hasAlt && !this.hasMeta)
|
||||
{
|
||||
this.validationMessage = T("Please include at least one modifier key (Ctrl, Shift, Alt, or Cmd).");
|
||||
this.validationSeverity = Severity.Warning;
|
||||
this.hasValidationError = true;
|
||||
this.StateHasChanged();
|
||||
return;
|
||||
}
|
||||
|
||||
this.UpdateShortcutString();
|
||||
await this.ValidateShortcut();
|
||||
|
||||
this.StateHasChanged();
|
||||
}
|
||||
|
||||
private void UpdateModifiers(KeyboardEventArgs e)
|
||||
{
|
||||
this.hasCtrl = e.CtrlKey || e.MetaKey; // Treat Meta (Cmd on Mac) same as Ctrl for cross-platform
|
||||
this.hasShift = e.ShiftKey;
|
||||
this.hasAlt = e.AltKey;
|
||||
this.hasMeta = e is { MetaKey: true, CtrlKey: false }; // Only set meta if not already using ctrl
|
||||
}
|
||||
|
||||
private void UpdateShortcutString()
|
||||
{
|
||||
var parts = new List<string>();
|
||||
|
||||
if (this.hasCtrl)
|
||||
parts.Add("CmdOrControl");
|
||||
|
||||
if (this.hasShift)
|
||||
parts.Add("Shift");
|
||||
|
||||
if (this.hasAlt)
|
||||
parts.Add("Alt");
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(this.currentKey))
|
||||
parts.Add(this.currentKey);
|
||||
|
||||
this.currentShortcut = parts.Count > 0 ? string.Join("+", parts) : string.Empty;
|
||||
this.StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task ValidateShortcut()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(this.currentShortcut) || string.IsNullOrWhiteSpace(this.currentKey))
|
||||
{
|
||||
this.validationMessage = string.Empty;
|
||||
this.hasValidationError = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the shortcut is valid by trying to register it with Rust
|
||||
var result = await this.RustService.ValidateShortcut(this.currentShortcut);
|
||||
if (result.IsValid)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(this.originalShortcut)
|
||||
&& this.currentShortcut.Equals(this.originalShortcut, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
this.validationMessage = T("This is the shortcut you previously used.");
|
||||
this.validationSeverity = Severity.Info;
|
||||
this.hasValidationError = false;
|
||||
this.StateHasChanged();
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.HasConflict)
|
||||
{
|
||||
this.validationMessage = string.Format(T("This shortcut conflicts with: {0}"), result.ConflictDescription);
|
||||
this.validationSeverity = Severity.Warning;
|
||||
this.hasValidationError = false; // Allow saving, but warn
|
||||
}
|
||||
else
|
||||
{
|
||||
this.validationMessage = T("Shortcut is valid and available.");
|
||||
this.validationSeverity = Severity.Success;
|
||||
this.hasValidationError = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this.validationMessage = string.Format(T("Invalid shortcut: {0}"), result.ErrorMessage);
|
||||
this.validationSeverity = Severity.Error;
|
||||
this.hasValidationError = true;
|
||||
}
|
||||
|
||||
this.StateHasChanged();
|
||||
}
|
||||
|
||||
private string GetDisplayShortcut()
|
||||
{
|
||||
// Convert internal format to display format:
|
||||
return this.currentShortcut
|
||||
.Replace("CmdOrControl", OperatingSystem.IsMacOS() ? "Cmd" : "Ctrl")
|
||||
.Replace("CommandOrControl", OperatingSystem.IsMacOS() ? "Cmd" : "Ctrl");
|
||||
}
|
||||
|
||||
private void ClearShortcut()
|
||||
{
|
||||
this.currentShortcut = string.Empty;
|
||||
this.currentKey = null;
|
||||
this.hasCtrl = false;
|
||||
this.hasShift = false;
|
||||
this.hasAlt = false;
|
||||
this.hasMeta = false;
|
||||
this.validationMessage = string.Empty;
|
||||
this.hasValidationError = false;
|
||||
this.StateHasChanged();
|
||||
}
|
||||
|
||||
private void Cancel() => this.MudDialog.Cancel();
|
||||
|
||||
private void Confirm() => this.MudDialog.Close(DialogResult.Ok(this.currentShortcut));
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the key code represents a modifier key.
|
||||
/// </summary>
|
||||
private static bool IsModifierKey(string code) => code switch
|
||||
{
|
||||
"ShiftLeft" or "ShiftRight" => true,
|
||||
"ControlLeft" or "ControlRight" => true,
|
||||
"AltLeft" or "AltRight" => true,
|
||||
"MetaLeft" or "MetaRight" => true,
|
||||
|
||||
_ => false,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Translates a JavaScript KeyboardEvent.code to Tauri shortcut format.
|
||||
/// </summary>
|
||||
private static string TranslateKeyCode(string code) => code switch
|
||||
{
|
||||
// Letters
|
||||
"KeyA" => "A",
|
||||
"KeyB" => "B",
|
||||
"KeyC" => "C",
|
||||
"KeyD" => "D",
|
||||
"KeyE" => "E",
|
||||
"KeyF" => "F",
|
||||
"KeyG" => "G",
|
||||
"KeyH" => "H",
|
||||
"KeyI" => "I",
|
||||
"KeyJ" => "J",
|
||||
"KeyK" => "K",
|
||||
"KeyL" => "L",
|
||||
"KeyM" => "M",
|
||||
"KeyN" => "N",
|
||||
"KeyO" => "O",
|
||||
"KeyP" => "P",
|
||||
"KeyQ" => "Q",
|
||||
"KeyR" => "R",
|
||||
"KeyS" => "S",
|
||||
"KeyT" => "T",
|
||||
"KeyU" => "U",
|
||||
"KeyV" => "V",
|
||||
"KeyW" => "W",
|
||||
"KeyX" => "X",
|
||||
"KeyY" => "Y",
|
||||
"KeyZ" => "Z",
|
||||
|
||||
// Numbers
|
||||
"Digit0" => "0",
|
||||
"Digit1" => "1",
|
||||
"Digit2" => "2",
|
||||
"Digit3" => "3",
|
||||
"Digit4" => "4",
|
||||
"Digit5" => "5",
|
||||
"Digit6" => "6",
|
||||
"Digit7" => "7",
|
||||
"Digit8" => "8",
|
||||
"Digit9" => "9",
|
||||
|
||||
// Function keys
|
||||
"F1" => "F1",
|
||||
"F2" => "F2",
|
||||
"F3" => "F3",
|
||||
"F4" => "F4",
|
||||
"F5" => "F5",
|
||||
"F6" => "F6",
|
||||
"F7" => "F7",
|
||||
"F8" => "F8",
|
||||
"F9" => "F9",
|
||||
"F10" => "F10",
|
||||
"F11" => "F11",
|
||||
"F12" => "F12",
|
||||
"F13" => "F13",
|
||||
"F14" => "F14",
|
||||
"F15" => "F15",
|
||||
"F16" => "F16",
|
||||
"F17" => "F17",
|
||||
"F18" => "F18",
|
||||
"F19" => "F19",
|
||||
"F20" => "F20",
|
||||
"F21" => "F21",
|
||||
"F22" => "F22",
|
||||
"F23" => "F23",
|
||||
"F24" => "F24",
|
||||
|
||||
// Special keys
|
||||
"Space" => "Space",
|
||||
"Enter" => "Enter",
|
||||
"Tab" => "Tab",
|
||||
"Escape" => "Escape",
|
||||
"Backspace" => "Backspace",
|
||||
"Delete" => "Delete",
|
||||
"Insert" => "Insert",
|
||||
"Home" => "Home",
|
||||
"End" => "End",
|
||||
"PageUp" => "PageUp",
|
||||
"PageDown" => "PageDown",
|
||||
|
||||
// Arrow keys
|
||||
"ArrowUp" => "Up",
|
||||
"ArrowDown" => "Down",
|
||||
"ArrowLeft" => "Left",
|
||||
"ArrowRight" => "Right",
|
||||
|
||||
// Numpad
|
||||
"Numpad0" => "Num0",
|
||||
"Numpad1" => "Num1",
|
||||
"Numpad2" => "Num2",
|
||||
"Numpad3" => "Num3",
|
||||
"Numpad4" => "Num4",
|
||||
"Numpad5" => "Num5",
|
||||
"Numpad6" => "Num6",
|
||||
"Numpad7" => "Num7",
|
||||
"Numpad8" => "Num8",
|
||||
"Numpad9" => "Num9",
|
||||
"NumpadAdd" => "NumAdd",
|
||||
"NumpadSubtract" => "NumSubtract",
|
||||
"NumpadMultiply" => "NumMultiply",
|
||||
"NumpadDivide" => "NumDivide",
|
||||
"NumpadDecimal" => "NumDecimal",
|
||||
"NumpadEnter" => "NumEnter",
|
||||
|
||||
// Punctuation
|
||||
"Minus" => "Minus",
|
||||
"Equal" => "Equal",
|
||||
"BracketLeft" => "BracketLeft",
|
||||
"BracketRight" => "BracketRight",
|
||||
"Backslash" => "Backslash",
|
||||
"Semicolon" => "Semicolon",
|
||||
"Quote" => "Quote",
|
||||
"Backquote" => "Backquote",
|
||||
"Comma" => "Comma",
|
||||
"Period" => "Period",
|
||||
"Slash" => "Slash",
|
||||
|
||||
// Default: return as-is
|
||||
_ => code,
|
||||
};
|
||||
|
||||
private void HandleBlur()
|
||||
{
|
||||
// Re-focus the input field to keep capturing keys:
|
||||
this.inputField?.FocusAsync();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,157 @@
|
||||
@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 T="Host" Value="@this.DataHost" ValueChanged="@this.OnHostChanged" Label="@T("Host")" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.providerValidation.ValidatingHost">
|
||||
@foreach (Host host in Enum.GetValues(typeof(Host)))
|
||||
{
|
||||
if (host.IsTranscriptionSupported())
|
||||
{
|
||||
<MudSelectItem Value="@host">
|
||||
@host.Name()
|
||||
</MudSelectItem>
|
||||
}
|
||||
}
|
||||
</MudSelect>
|
||||
}
|
||||
|
||||
@if (!this.DataLLMProvider.IsTranscriptionModelSelectionHidden(this.DataHost))
|
||||
{
|
||||
<MudField FullWidth="true" Label="@T("Model selection")" Variant="Variant.Outlined" Class="mb-3">
|
||||
<MudStack Row="@true" AlignItems="AlignItems.Center" StretchItems="StretchItems.End">
|
||||
@if (this.DataLLMProvider.IsTranscriptionModelProvidedManually(this.DataHost))
|
||||
{
|
||||
<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>
|
||||
@if (!string.IsNullOrWhiteSpace(this.dataLoadingModelsIssue))
|
||||
{
|
||||
<MudAlert Severity="Severity.Error" Class="mt-3">
|
||||
@this.dataLoadingModelsIssue
|
||||
</MudAlert>
|
||||
}
|
||||
</MudField>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudField FullWidth="true" Label="@T("Model selection")" Variant="Variant.Outlined" Class="mb-3">
|
||||
<MudText Typo="Typo.body1">
|
||||
@T("This host uses the model configured at the provider level. No model selection is available.")
|
||||
</MudText>
|
||||
</MudField>
|
||||
}
|
||||
|
||||
@* ReSharper disable once CSharpWarnings::CS8974 *@
|
||||
<MudTextField
|
||||
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,325 @@
|
||||
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!;
|
||||
|
||||
[Inject]
|
||||
private ILogger<TranscriptionProviderDialog> Logger { 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;
|
||||
private string dataLoadingModelsIssue = 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,
|
||||
IsModelProvidedManually = () => this.DataLLMProvider.IsTranscriptionModelProvidedManually(this.DataHost),
|
||||
};
|
||||
}
|
||||
|
||||
private TranscriptionProvider CreateTranscriptionProviderSettings()
|
||||
{
|
||||
var cleanedHostname = this.DataHostname.Trim();
|
||||
|
||||
// Determine the model based on the provider and host configuration:
|
||||
Model model;
|
||||
if (this.DataLLMProvider.IsTranscriptionModelSelectionHidden(this.DataHost))
|
||||
{
|
||||
// Use system model placeholder for hosts that don't support model selection (e.g., whisper.cpp):
|
||||
model = Model.SYSTEM_MODEL;
|
||||
}
|
||||
else if (this.DataLLMProvider is LLMProviders.SELF_HOSTED)
|
||||
{
|
||||
switch (this.DataHost)
|
||||
{
|
||||
case Host.OLLAMA:
|
||||
model = new Model(this.dataManuallyModel, null);
|
||||
break;
|
||||
|
||||
case Host.VLLM:
|
||||
case Host.LM_STUDIO:
|
||||
default:
|
||||
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;
|
||||
|
||||
// Manually validate the model selection (needed when no models are loaded
|
||||
// and the MudSelect is not rendered):
|
||||
var modelValidationError = this.providerValidation.ValidatingModel(this.DataModel);
|
||||
if (!string.IsNullOrWhiteSpace(modelValidationError))
|
||||
{
|
||||
this.dataIssues = [..this.dataIssues, modelValidationError];
|
||||
this.dataIsValid = false;
|
||||
}
|
||||
|
||||
// When the data is not valid, we don't store it:
|
||||
if (!this.dataIsValid)
|
||||
return;
|
||||
|
||||
// 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 void OnHostChanged(Host selectedHost)
|
||||
{
|
||||
// When the host changes, reset the model selection state:
|
||||
this.DataHost = selectedHost;
|
||||
this.DataModel = default;
|
||||
this.dataManuallyModel = string.Empty;
|
||||
this.availableModels.Clear();
|
||||
this.dataLoadingModelsIssue = string.Empty;
|
||||
}
|
||||
|
||||
private async Task ReloadModels()
|
||||
{
|
||||
this.dataLoadingModelsIssue = string.Empty;
|
||||
var currentTranscriptionProviderSettings = this.CreateTranscriptionProviderSettings();
|
||||
var provider = currentTranscriptionProviderSettings.CreateProvider();
|
||||
if (provider is NoProvider)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
var models = await provider.GetTranscriptionModels(this.dataAPIKey);
|
||||
|
||||
// Order descending by ID means that the newest models probably come first:
|
||||
var orderedModels = models.OrderByDescending(n => n.Id);
|
||||
|
||||
this.availableModels.Clear();
|
||||
this.availableModels.AddRange(orderedModels);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
this.Logger.LogError($"Failed to load models from provider '{this.DataLLMProvider}' (host={this.DataHost}, hostname='{this.DataHostname}'): {e.Message}");;
|
||||
this.dataLoadingModelsIssue = T("We are currently unable to communicate with the provider to load models. Please try again later.");
|
||||
}
|
||||
}
|
||||
|
||||
private string APIKeyText => this.DataLLMProvider switch
|
||||
{
|
||||
LLMProviders.SELF_HOSTED => T("(Optional) API Key"),
|
||||
_ => T("API Key"),
|
||||
};
|
||||
|
||||
private bool IsNoneProvider => this.DataLLMProvider is LLMProviders.NONE;
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
@using AIStudio.Settings.DataModel
|
||||
@using AIStudio.Components
|
||||
|
||||
@using Microsoft.AspNetCore.Components.Routing
|
||||
@using MudBlazor
|
||||
|
||||
@@ -20,12 +22,20 @@
|
||||
</MudNavLink>
|
||||
}
|
||||
</MudNavMenu>
|
||||
|
||||
<MudSpacer/>
|
||||
|
||||
<MudStack AlignItems="AlignItems.Center">
|
||||
<MudToolBar WrapContent="true">
|
||||
<VoiceRecorder />
|
||||
</MudToolBar>
|
||||
</MudStack>
|
||||
</MudDrawer>
|
||||
</MudDrawerContainer>
|
||||
}
|
||||
else
|
||||
{
|
||||
<MudPaper Width="4em" Class="mud-height-full absolute">
|
||||
<MudPaper Width="4em" Class="mud-height-full absolute" Style="display: flex; flex-direction: column;">
|
||||
<MudNavMenu>
|
||||
@foreach (var navBarItem in this.navItems)
|
||||
{
|
||||
@@ -41,6 +51,14 @@
|
||||
}
|
||||
}
|
||||
</MudNavMenu>
|
||||
|
||||
<MudSpacer/>
|
||||
|
||||
<MudStack AlignItems="AlignItems.Center">
|
||||
<MudToolBar WrapContent="true">
|
||||
<VoiceRecorder />
|
||||
</MudToolBar>
|
||||
</MudStack>
|
||||
</MudPaper>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,6 +97,7 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan
|
||||
|
||||
// Set the snackbar for the update service:
|
||||
UpdateService.SetBlazorDependencies(this.Snackbar);
|
||||
GlobalShortcutService.Initialize();
|
||||
TemporaryChatService.Initialize();
|
||||
|
||||
// Should the navigation bar be open by default?
|
||||
@@ -116,11 +117,6 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan
|
||||
await base.OnInitializedAsync();
|
||||
}
|
||||
|
||||
private void LoadNavItems()
|
||||
{
|
||||
this.navItems = new List<NavBarItem>(this.GetNavItems());
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation of ILang
|
||||
@@ -251,6 +247,11 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan
|
||||
|
||||
#endregion
|
||||
|
||||
private void LoadNavItems()
|
||||
{
|
||||
this.navItems = new List<NavBarItem>(this.GetNavItems());
|
||||
}
|
||||
|
||||
private IEnumerable<NavBarItem> GetNavItems()
|
||||
{
|
||||
var palette = this.ColorTheme.GetCurrentPalette(this.SettingsManager);
|
||||
@@ -341,7 +342,7 @@ public partial class MainLayout : LayoutComponentBase, IMessageBusReceiver, ILan
|
||||
await this.MessageBus.SendMessage<bool>(this, Event.COLOR_THEME_CHANGED);
|
||||
this.StateHasChanged();
|
||||
}
|
||||
|
||||
|
||||
#region Implementation of IDisposable
|
||||
|
||||
public void Dispose()
|
||||
|
||||
@@ -49,11 +49,11 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CodeBeam.MudBlazor.Extensions" Version="8.3.0" />
|
||||
<PackageReference Include="HtmlAgilityPack" Version="1.12.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="9.0.11" />
|
||||
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="9.0.12" />
|
||||
<PackageReference Include="MudBlazor" Version="8.15.0" />
|
||||
<PackageReference Include="MudBlazor.Markdown" Version="8.11.0" />
|
||||
<PackageReference Include="ReverseMarkdown" Version="4.7.1" />
|
||||
<PackageReference Include="LuaCSharp" Version="0.5.1" />
|
||||
<PackageReference Include="ReverseMarkdown" Version="5.0.0" />
|
||||
<PackageReference Include="LuaCSharp" Version="0.5.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -9,60 +9,88 @@
|
||||
</MudText>
|
||||
|
||||
<InnerScrolling>
|
||||
|
||||
<MudText Typo="Typo.h4" Class="mb-2 mr-3">
|
||||
@T("General")
|
||||
</MudText>
|
||||
<MudStack Row="@true" Wrap="@Wrap.Wrap" Class="mb-3">
|
||||
<AssistantBlock TSettings="SettingsDialogTextSummarizer" Name="@T("Text Summarizer")" Description="@T("Use an LLM to summarize a given text.")" Icon="@Icons.Material.Filled.TextSnippet" Link="@Routes.ASSISTANT_SUMMARIZER"/>
|
||||
<AssistantBlock TSettings="SettingsDialogTranslation" Name="@T("Translation")" Description="@T("Translate text into another language.")" Icon="@Icons.Material.Filled.Translate" Link="@Routes.ASSISTANT_TRANSLATION"/>
|
||||
<AssistantBlock TSettings="SettingsDialogGrammarSpelling" Name="@T("Grammar & Spelling")" Description="@T("Check grammar and spelling of a given text.")" Icon="@Icons.Material.Filled.Edit" Link="@Routes.ASSISTANT_GRAMMAR_SPELLING"/>
|
||||
<AssistantBlock TSettings="SettingsDialogRewrite" Name="@T("Rewrite & Improve")" Description="@T("Rewrite and improve a given text for a chosen style.")" Icon="@Icons.Material.Filled.Edit" Link="@Routes.ASSISTANT_REWRITE"/>
|
||||
<AssistantBlock TSettings="SettingsDialogSynonyms" Name="@T("Synonyms")" Description="@T("Find synonyms for a given word or phrase.")" Icon="@Icons.Material.Filled.Spellcheck" Link="@Routes.ASSISTANT_SYNONYMS"/>
|
||||
</MudStack>
|
||||
|
||||
<MudText Typo="Typo.h4" Class="mb-2 mr-3 mt-6">
|
||||
@T("Business")
|
||||
</MudText>
|
||||
<MudStack Row="@true" Wrap="@Wrap.Wrap" Class="mb-3">
|
||||
<AssistantBlock TSettings="SettingsDialogWritingEMails" Name="@T("E-Mail")" Description="@T("Generate an e-mail for a given context.")" Icon="@Icons.Material.Filled.Email" Link="@Routes.ASSISTANT_EMAIL"/>
|
||||
|
||||
@if (PreviewFeatures.PRE_DOCUMENT_ANALYSIS_2025.IsEnabled(this.SettingsManager))
|
||||
{
|
||||
<AssistantBlock TSettings="SettingsDialogDocumentAnalysis" Name="@T("Document Analysis")" Description="@T("Analyze a document regarding defined rules and extract key information.")" Icon="@Icons.Material.Filled.DocumentScanner" Link="@Routes.ASSISTANT_DOCUMENT_ANALYSIS"/>
|
||||
}
|
||||
|
||||
<AssistantBlock TSettings="SettingsDialogMyTasks" Name="@T("My Tasks")" Description="@T("Analyze a text or an email for tasks you need to complete.")" Icon="@Icons.Material.Filled.Task" Link="@Routes.ASSISTANT_MY_TASKS"/>
|
||||
<AssistantBlock TSettings="SettingsDialogAgenda" Name="@T("Agenda Planner")" Description="@T("Generate an agenda for a given meeting, seminar, etc.")" Icon="@Icons.Material.Filled.CalendarToday" Link="@Routes.ASSISTANT_AGENDA"/>
|
||||
<AssistantBlock TSettings="SettingsDialogJobPostings" Name="@T("Job Posting")" Description="@T("Generate a job posting for a given job description.")" Icon="@Icons.Material.Filled.Work" Link="@Routes.ASSISTANT_JOB_POSTING"/>
|
||||
<AssistantBlock TSettings="SettingsDialogLegalCheck" Name="@T("Legal Check")" Description="@T("Ask a question about a legal document.")" Icon="@Icons.Material.Filled.Gavel" Link="@Routes.ASSISTANT_LEGAL_CHECK"/>
|
||||
<AssistantBlock TSettings="SettingsDialogIconFinder" Name="@T("Icon Finder")" Description="@T("Use an LLM to find an icon for a given context.")" Icon="@Icons.Material.Filled.FindInPage" Link="@Routes.ASSISTANT_ICON_FINDER"/>
|
||||
</MudStack>
|
||||
@if (this.SettingsManager.IsAnyCategoryAssistantVisible("General",
|
||||
(Components.TEXT_SUMMARIZER_ASSISTANT, PreviewFeatures.NONE),
|
||||
(Components.TRANSLATION_ASSISTANT, PreviewFeatures.NONE),
|
||||
(Components.GRAMMAR_SPELLING_ASSISTANT, PreviewFeatures.NONE),
|
||||
(Components.REWRITE_ASSISTANT, PreviewFeatures.NONE),
|
||||
(Components.SYNONYMS_ASSISTANT, PreviewFeatures.NONE)
|
||||
))
|
||||
{
|
||||
<MudText Typo="Typo.h4" Class="mb-2 mr-3">
|
||||
@T("General")
|
||||
</MudText>
|
||||
<MudStack Row="@true" Wrap="@Wrap.Wrap" Class="mb-3">
|
||||
<AssistantBlock TSettings="SettingsDialogTextSummarizer" Component="Components.TEXT_SUMMARIZER_ASSISTANT" Name="@T("Text Summarizer")" Description="@T("Use an LLM to summarize a given text.")" Icon="@Icons.Material.Filled.TextSnippet" Link="@Routes.ASSISTANT_SUMMARIZER"/>
|
||||
<AssistantBlock TSettings="SettingsDialogTranslation" Component="Components.TRANSLATION_ASSISTANT" Name="@T("Translation")" Description="@T("Translate text into another language.")" Icon="@Icons.Material.Filled.Translate" Link="@Routes.ASSISTANT_TRANSLATION"/>
|
||||
<AssistantBlock TSettings="SettingsDialogGrammarSpelling" Component="Components.GRAMMAR_SPELLING_ASSISTANT" Name="@T("Grammar & Spelling")" Description="@T("Check grammar and spelling of a given text.")" Icon="@Icons.Material.Filled.Edit" Link="@Routes.ASSISTANT_GRAMMAR_SPELLING"/>
|
||||
<AssistantBlock TSettings="SettingsDialogRewrite" Component="Components.REWRITE_ASSISTANT" Name="@T("Rewrite & Improve")" Description="@T("Rewrite and improve a given text for a chosen style.")" Icon="@Icons.Material.Filled.Edit" Link="@Routes.ASSISTANT_REWRITE"/>
|
||||
<AssistantBlock TSettings="SettingsDialogSynonyms" Component="Components.SYNONYMS_ASSISTANT" Name="@T("Synonyms")" Description="@T("Find synonyms for a given word or phrase.")" Icon="@Icons.Material.Filled.Spellcheck" Link="@Routes.ASSISTANT_SYNONYMS"/>
|
||||
</MudStack>
|
||||
}
|
||||
|
||||
<MudText Typo="Typo.h4" Class="mb-2 mr-3 mt-6">
|
||||
@T("Learning")
|
||||
</MudText>
|
||||
<MudStack Row="@true" Wrap="@Wrap.Wrap" Class="mb-3">
|
||||
<AssistantBlock TSettings="SettingsDialogAssistantBias" Name="@T("Bias of the Day")" Description="@T("Learn about one cognitive bias every day.")" Icon="@Icons.Material.Filled.Psychology" Link="@Routes.ASSISTANT_BIAS"/>
|
||||
</MudStack>
|
||||
|
||||
<MudText Typo="Typo.h4" Class="mb-2 mr-3 mt-6">
|
||||
@T("Software Engineering")
|
||||
</MudText>
|
||||
<MudStack Row="@true" Wrap="@Wrap.Wrap" Class="mb-3">
|
||||
<AssistantBlock TSettings="SettingsDialogCoding" Name="@T("Coding")" Description="@T("Get coding and debugging support from an LLM.")" Icon="@Icons.Material.Filled.Code" Link="@Routes.ASSISTANT_CODING"/>
|
||||
@if (PreviewFeatures.PRE_RAG_2024.IsEnabled(this.SettingsManager))
|
||||
{
|
||||
<AssistantBlock TSettings="SettingsDialogERIServer" Name="@T("ERI Server")" Description="@T("Generate an ERI server to integrate business systems.")" Icon="@Icons.Material.Filled.PrivateConnectivity" Link="@Routes.ASSISTANT_ERI"/>
|
||||
}
|
||||
</MudStack>
|
||||
|
||||
<MudText Typo="Typo.h4" Class="mb-2 mr-3 mt-6">
|
||||
@T("AI Studio Development")
|
||||
</MudText>
|
||||
<MudStack Row="@true" Wrap="@Wrap.Wrap" Class="mb-3">
|
||||
<AssistantBlock TSettings="SettingsDialogI18N" Name="@T("Localization")" Description="@T("Translate AI Studio text content into other languages")" Icon="@Icons.Material.Filled.Translate" Link="@Routes.ASSISTANT_AI_STUDIO_I18N"/>
|
||||
</MudStack>
|
||||
@if (this.SettingsManager.IsAnyCategoryAssistantVisible("Business",
|
||||
(Components.EMAIL_ASSISTANT, PreviewFeatures.NONE),
|
||||
(Components.DOCUMENT_ANALYSIS_ASSISTANT, PreviewFeatures.PRE_DOCUMENT_ANALYSIS_2025),
|
||||
(Components.MY_TASKS_ASSISTANT, PreviewFeatures.NONE),
|
||||
(Components.AGENDA_ASSISTANT, PreviewFeatures.NONE),
|
||||
(Components.JOB_POSTING_ASSISTANT, PreviewFeatures.NONE),
|
||||
(Components.LEGAL_CHECK_ASSISTANT, PreviewFeatures.NONE),
|
||||
(Components.ICON_FINDER_ASSISTANT, PreviewFeatures.NONE)
|
||||
))
|
||||
{
|
||||
<MudText Typo="Typo.h4" Class="mb-2 mr-3 mt-6">
|
||||
@T("Business")
|
||||
</MudText>
|
||||
<MudStack Row="@true" Wrap="@Wrap.Wrap" Class="mb-3">
|
||||
<AssistantBlock TSettings="SettingsDialogWritingEMails" Component="Components.EMAIL_ASSISTANT" Name="@T("E-Mail")" Description="@T("Generate an e-mail for a given context.")" Icon="@Icons.Material.Filled.Email" Link="@Routes.ASSISTANT_EMAIL"/>
|
||||
<AssistantBlock TSettings="SettingsDialogDocumentAnalysis" Component="Components.DOCUMENT_ANALYSIS_ASSISTANT" RequiredPreviewFeature="PreviewFeatures.PRE_DOCUMENT_ANALYSIS_2025" Name="@T("Document Analysis")" Description="@T("Analyze a document regarding defined rules and extract key information.")" Icon="@Icons.Material.Filled.DocumentScanner" Link="@Routes.ASSISTANT_DOCUMENT_ANALYSIS"/>
|
||||
<AssistantBlock TSettings="SettingsDialogMyTasks" Component="Components.MY_TASKS_ASSISTANT" Name="@T("My Tasks")" Description="@T("Analyze a text or an email for tasks you need to complete.")" Icon="@Icons.Material.Filled.Task" Link="@Routes.ASSISTANT_MY_TASKS"/>
|
||||
<AssistantBlock TSettings="SettingsDialogAgenda" Component="Components.AGENDA_ASSISTANT" Name="@T("Agenda Planner")" Description="@T("Generate an agenda for a given meeting, seminar, etc.")" Icon="@Icons.Material.Filled.CalendarToday" Link="@Routes.ASSISTANT_AGENDA"/>
|
||||
<AssistantBlock TSettings="SettingsDialogJobPostings" Component="Components.JOB_POSTING_ASSISTANT" Name="@T("Job Posting")" Description="@T("Generate a job posting for a given job description.")" Icon="@Icons.Material.Filled.Work" Link="@Routes.ASSISTANT_JOB_POSTING"/>
|
||||
<AssistantBlock TSettings="SettingsDialogLegalCheck" Component="Components.LEGAL_CHECK_ASSISTANT" Name="@T("Legal Check")" Description="@T("Ask a question about a legal document.")" Icon="@Icons.Material.Filled.Gavel" Link="@Routes.ASSISTANT_LEGAL_CHECK"/>
|
||||
<AssistantBlock TSettings="SettingsDialogIconFinder" Component="Components.ICON_FINDER_ASSISTANT" Name="@T("Icon Finder")" Description="@T("Use an LLM to find an icon for a given context.")" Icon="@Icons.Material.Filled.FindInPage" Link="@Routes.ASSISTANT_ICON_FINDER"/>
|
||||
</MudStack>
|
||||
}
|
||||
|
||||
@if (this.SettingsManager.IsAnyCategoryAssistantVisible("Learning",
|
||||
(Components.BIAS_DAY_ASSISTANT, PreviewFeatures.NONE)
|
||||
))
|
||||
{
|
||||
<MudText Typo="Typo.h4" Class="mb-2 mr-3 mt-6">
|
||||
@T("Learning")
|
||||
</MudText>
|
||||
<MudStack Row="@true" Wrap="@Wrap.Wrap" Class="mb-3">
|
||||
<AssistantBlock TSettings="SettingsDialogAssistantBias" Component="Components.BIAS_DAY_ASSISTANT" Name="@T("Bias of the Day")" Description="@T("Learn about one cognitive bias every day.")" Icon="@Icons.Material.Filled.Psychology" Link="@Routes.ASSISTANT_BIAS"/>
|
||||
</MudStack>
|
||||
}
|
||||
|
||||
@if (this.SettingsManager.IsAnyCategoryAssistantVisible("Software Engineering",
|
||||
(Components.CODING_ASSISTANT, PreviewFeatures.NONE),
|
||||
(Components.ERI_ASSISTANT, PreviewFeatures.PRE_RAG_2024)
|
||||
))
|
||||
{
|
||||
<MudText Typo="Typo.h4" Class="mb-2 mr-3 mt-6">
|
||||
@T("Software Engineering")
|
||||
</MudText>
|
||||
<MudStack Row="@true" Wrap="@Wrap.Wrap" Class="mb-3">
|
||||
<AssistantBlock TSettings="SettingsDialogCoding" Component="Components.CODING_ASSISTANT" Name="@T("Coding")" Description="@T("Get coding and debugging support from an LLM.")" Icon="@Icons.Material.Filled.Code" Link="@Routes.ASSISTANT_CODING"/>
|
||||
<AssistantBlock TSettings="SettingsDialogERIServer" Component="Components.ERI_ASSISTANT" RequiredPreviewFeature="PreviewFeatures.PRE_RAG_2024" Name="@T("ERI Server")" Description="@T("Generate an ERI server to integrate business systems.")" Icon="@Icons.Material.Filled.PrivateConnectivity" Link="@Routes.ASSISTANT_ERI"/>
|
||||
</MudStack>
|
||||
}
|
||||
|
||||
@if (this.SettingsManager.IsAnyCategoryAssistantVisible("AI Studio Development",
|
||||
(Components.I18N_ASSISTANT, PreviewFeatures.NONE)
|
||||
))
|
||||
{
|
||||
<MudText Typo="Typo.h4" Class="mb-2 mr-3 mt-6">
|
||||
@T("AI Studio Development")
|
||||
</MudText>
|
||||
<MudStack Row="@true" Wrap="@Wrap.Wrap" Class="mb-3">
|
||||
<AssistantBlock TSettings="SettingsDialogI18N" Component="Components.I18N_ASSISTANT" Name="@T("Localization")" Description="@T("Translate AI Studio text content into other languages")" Icon="@Icons.Material.Filled.Translate" Link="@Routes.ASSISTANT_AI_STUDIO_I18N"/>
|
||||
</MudStack>
|
||||
}
|
||||
|
||||
</InnerScrolling>
|
||||
</div>
|
||||
</div>
|
||||
@@ -196,6 +196,7 @@
|
||||
<ThirdPartyComponent Name="Tauri" Developer="Daniel Thompson-Yvetot, Lucas Nogueira, Tensor, Boscop, Serge Zaitsev, George Burton & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/tauri-apps/tauri/blob/dev/LICENSE_MIT" RepositoryUrl="https://github.com/tauri-apps/tauri" UseCase="@T("Tauri is used to host the Blazor user interface. It is a great project that allows the creation of desktop applications using web technologies. I love Tauri!")"/>
|
||||
<ThirdPartyComponent Name="Rocket" Developer="Sergio Benitez & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/rwf2/Rocket/blob/master/LICENSE-MIT" RepositoryUrl="https://github.com/rwf2/Rocket" UseCase="@T("We use Rocket to implement the runtime API. This is necessary because the runtime must be able to communicate with the user interface (IPC). Rocket is a great framework for implementing web APIs in Rust.")"/>
|
||||
<ThirdPartyComponent Name="serde" Developer="Erick Tryzelaar, David Tolnay & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/serde-rs/serde/blob/master/LICENSE-MIT" RepositoryUrl="https://github.com/serde-rs/serde" UseCase="@T("Now we have multiple systems, some developed in .NET and others in Rust. The data format JSON is responsible for translating data between both worlds (called data serialization and deserialization). Serde takes on this task in the Rust world. The counterpart in the .NET world is an integral part of .NET and is located in System.Text.Json.")"/>
|
||||
<ThirdPartyComponent Name="strum_macros" Developer="Peter Glotfelty & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/Peternator7/strum/blob/master/LICENSE" RepositoryUrl="https://github.com/Peternator7/strum" UseCase="@T("This crate provides derive macros for Rust enums, which we use to reduce boilerplate when implementing string conversions and metadata for runtime types. This is helpful for the communication between our Rust and .NET systems.")"/>
|
||||
<ThirdPartyComponent Name="keyring" Developer="Walther Chen, Daniel Brotsky & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/hwchen/keyring-rs/blob/master/LICENSE-MIT" RepositoryUrl="https://github.com/hwchen/keyring-rs" UseCase="@T("In order to use any LLM, each user must store their so-called API key for each LLM provider. This key must be kept secure, similar to a password. The safest way to do this is offered by operating systems like macOS, Windows, and Linux: They have mechanisms to store such data, if available, on special security hardware. Since this is currently not possible in .NET, we use this Rust library.")"/>
|
||||
<ThirdPartyComponent Name="arboard" Developer="Artur Kovacs, Avi Weinstock, 1Password & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/1Password/arboard/blob/master/LICENSE-MIT.txt" RepositoryUrl="https://github.com/1Password/arboard" UseCase="@T("To be able to use the responses of the LLM in other apps, we often use the clipboard of the respective operating system. Unfortunately, in .NET there is no solution that works with all operating systems. Therefore, I have opted for this library in Rust. This way, data transfer to other apps works on every system.")"/>
|
||||
<ThirdPartyComponent Name="tokio" Developer="Alex Crichton, Carl Lerche, Alice Ryhl, Taiki Endo, Ivan Petkov, Eliza Weisman, Lucio Franco & Open Source Community" LicenseName="MIT" LicenseUrl="https://github.com/tokio-rs/tokio/blob/master/LICENSE" RepositoryUrl="https://github.com/tokio-rs/tokio" UseCase="@T("Code in the Rust language can be specified as synchronous or asynchronous. Unlike .NET and the C# language, Rust cannot execute asynchronous code by itself. Rust requires support in the form of an executor for this. Tokio is one such executor.")"/>
|
||||
@@ -224,4 +225,4 @@
|
||||
</ExpansionPanel>
|
||||
</MudExpansionPanels>
|
||||
</InnerScrolling>
|
||||
</div>
|
||||
</div>
|
||||
@@ -190,7 +190,7 @@ public partial class Information : MSGComponentBase
|
||||
|
||||
## Notice
|
||||
|
||||
Copyright 2025 Thorsten Sommer
|
||||
Copyright 2026 Thorsten Sommer
|
||||
|
||||
## Terms and Conditions
|
||||
|
||||
|
||||
@@ -12,18 +12,23 @@
|
||||
|
||||
@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))
|
||||
{
|
||||
<SettingsPanelAgentDataSourceSelection AvailableLLMProvidersFunc="() => this.availableLLMProviders"/>
|
||||
<SettingsPanelAgentRetrievalContextValidation AvailableLLMProvidersFunc="() => this.availableLLMProviders"/>
|
||||
<SettingsPanelAgentDataSourceSelection AvailableLLMProvidersFunc="@(() => this.availableLLMProviders)"/>
|
||||
<SettingsPanelAgentRetrievalContextValidation AvailableLLMProvidersFunc="@(() => this.availableLLMProviders)"/>
|
||||
}
|
||||
|
||||
<SettingsPanelAgentContentCleaner AvailableLLMProvidersFunc="() => this.availableLLMProviders"/>
|
||||
<SettingsPanelAgentContentCleaner AvailableLLMProvidersFunc="@(() => this.availableLLMProviders)"/>
|
||||
</MudExpansionPanels>
|
||||
</InnerScrolling>
|
||||
</div>
|
||||
@@ -9,6 +9,7 @@ public partial class Settings : MSGComponentBase
|
||||
{
|
||||
private List<ConfigurationSelectData<string>> availableLLMProviders = new();
|
||||
private List<ConfigurationSelectData<string>> availableEmbeddingProviders = new();
|
||||
private List<ConfigurationSelectData<string>> availableTranscriptionProviders = new();
|
||||
|
||||
#region Overrides of ComponentBase
|
||||
|
||||
|
||||
@@ -53,11 +53,11 @@ CONFIG["LLM_PROVIDERS"][#CONFIG["LLM_PROVIDERS"]+1] = {
|
||||
["Id"] = "00000000-0000-0000-0000-000000000000",
|
||||
["InstanceName"] = "<user-friendly name for the combination of server and model>",
|
||||
["UsedLLMProvider"] = "SELF_HOSTED",
|
||||
|
||||
|
||||
-- Allowed values for Host are: LM_STUDIO, LLAMACPP, OLLAMA, and VLLM
|
||||
["Host"] = "OLLAMA",
|
||||
["Hostname"] = "<https address of the server>",
|
||||
|
||||
|
||||
-- Optional: Additional parameters for the API.
|
||||
-- Please refer to the documentation of the selected host for details.
|
||||
-- Might be something like ... \"temperature\": 0.5 ... for one parameter.
|
||||
@@ -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"] = {}
|
||||
|
||||
-- Configure the update check interval:
|
||||
@@ -101,6 +137,32 @@ CONFIG["SETTINGS"] = {}
|
||||
-- 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"
|
||||
|
||||
-- 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"
|
||||
|
||||
-- Configure which assistants should be hidden from the UI.
|
||||
-- Allowed values are:
|
||||
-- GRAMMAR_SPELLING_ASSISTANT, ICON_FINDER_ASSISTANT, REWRITE_ASSISTANT,
|
||||
-- TRANSLATION_ASSISTANT, AGENDA_ASSISTANT, CODING_ASSISTANT,
|
||||
-- TEXT_SUMMARIZER_ASSISTANT, EMAIL_ASSISTANT, LEGAL_CHECK_ASSISTANT,
|
||||
-- SYNONYMS_ASSISTANT, MY_TASKS_ASSISTANT, JOB_POSTING_ASSISTANT,
|
||||
-- BIAS_DAY_ASSISTANT, ERI_ASSISTANT, DOCUMENT_ANALYSIS_ASSISTANT,
|
||||
-- I18N_ASSISTANT
|
||||
-- CONFIG["SETTINGS"]["DataApp.HiddenAssistants"] = { "ERI_ASSISTANT", "I18N_ASSISTANT" }
|
||||
|
||||
-- Configure a global shortcut for starting and stopping dictation.
|
||||
--
|
||||
-- The format follows the Rust and Tauri conventions. Especially,
|
||||
-- when you want to use the CTRL key on Windows (or the CMD key on macOS),
|
||||
-- please use "CmdOrControl" as the key name. All parts of the shortcut
|
||||
-- must be separated by a plus sign (+).
|
||||
--
|
||||
-- Examples are: "CmdOrControl+Shift+D", "Alt+F9", "F8"
|
||||
-- CONFIG["SETTINGS"]["DataApp.ShortcutVoiceRecording"] = "CmdOrControl+1"
|
||||
|
||||
-- Example chat templates for this configuration:
|
||||
CONFIG["CHAT_TEMPLATES"] = {}
|
||||
|
||||
@@ -125,6 +187,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:
|
||||
CONFIG["PROFILES"] = {}
|
||||
|
||||
|
||||
+301
-43
@@ -384,6 +384,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::CODING::COMMONCODINGLANGUAGEEXTENSIONS::T
|
||||
-- None
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::CODING::COMMONCODINGLANGUAGEEXTENSIONS::T810547195"] = "Keine"
|
||||
|
||||
-- {0} - Document Analysis Session
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T108097007"] = "{0} – Sitzung zur Dokumentenanalyse"
|
||||
|
||||
-- Use the analysis and output rules to define how the AI evaluates your documents and formats the results.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T1155482668"] = "Verwenden Sie die Analyse- und Ausgaberegeln, um festzulegen, wie die KI Ihre Dokumente bewertet und die Ergebnisse formatiert."
|
||||
|
||||
@@ -438,9 +441,15 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTA
|
||||
-- Export policy as configuration section
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2556564432"] = "Exportieren Sie das Regelwerk als Konfigurationsabschnitt"
|
||||
|
||||
-- The result of your previous document analysis session.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2570551055"] = "Das Ergebnis Ihrer vorherigen Dokumentenanalyse-Sitzung."
|
||||
|
||||
-- Are you sure you want to delete the document analysis policy '{0}'?
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2582525917"] = "Möchten Sie das Regelwerk '{0}' wirklich löschen?"
|
||||
|
||||
-- Expand this section to view and edit the policy definition.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T277813037"] = "Erweitern Sie diesen Abschnitt, um das Regelwerk anzuzeigen und zu bearbeiten."
|
||||
|
||||
-- Policy name
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2879019438"] = "Name des Regelwerks"
|
||||
|
||||
@@ -471,6 +480,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTA
|
||||
-- Document Analysis Assistant
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T348883878"] = "Assistent für die Dokumentenanalyse"
|
||||
|
||||
-- Empty
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3512147854"] = "Leer"
|
||||
|
||||
-- Analysis and output rules
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3555314296"] = "Analyse- und Ausgaberegeln"
|
||||
|
||||
@@ -1668,6 +1680,15 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONPROVIDERSELECTION::T20906218
|
||||
-- Use app default
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONPROVIDERSELECTION::T3672477670"] = "App-Standard verwenden"
|
||||
|
||||
-- No shortcut configured
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONSHORTCUT::T3099115336"] = "Keinen Tastaturkurzbefehl konfiguriert"
|
||||
|
||||
-- Change shortcut
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONSHORTCUT::T4081853237"] = "Tastaturkurzbefehl ändern"
|
||||
|
||||
-- Configure Keyboard Shortcut
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONSHORTCUT::T636303786"] = "Tastaturkurzbefehl konfigurieren"
|
||||
|
||||
-- Yes, let the AI decide which data sources are needed.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCESELECTION::T1031370894"] = "Ja, die KI soll entscheiden, welche Datenquellen benötigt werden."
|
||||
|
||||
@@ -2010,6 +2031,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1059411425"]
|
||||
-- Do you want to show preview features in the app?
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1118505044"] = "Möchten Sie Vorschaufunktionen in der App anzeigen lassen?"
|
||||
|
||||
-- Voice recording shortcut
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1278320412"] = "Tastaturkurzbefehl für Sprachaufnahme"
|
||||
|
||||
-- How often should we check for app updates?
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1364944735"] = "Wie oft sollen wir nach App-Updates suchen?"
|
||||
|
||||
@@ -2025,6 +2049,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.
|
||||
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?
|
||||
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 +2064,12 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1898060643"]
|
||||
-- Select the language for the app.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1907446663"] = "Wählen Sie die Sprache für die App aus."
|
||||
|
||||
-- The global keyboard shortcut for toggling voice recording. This shortcut works system-wide, even when the app is not focused.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2143741496"] = "Der globale Tastaturkurzbefehl zum Ein- und Ausschalten der Sprachaufnahme. Dieser Kurzbefehl funktioniert systemweit, auch wenn die App nicht im Vordergrund ist."
|
||||
|
||||
-- Disable dictation and transcription
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T215381891"] = "Diktieren und Transkribieren deaktivieren"
|
||||
|
||||
-- Language behavior
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2341504363"] = "Sprachverhalten"
|
||||
|
||||
@@ -2067,6 +2100,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.
|
||||
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
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T602293588"] = "Verhalten der Navigationsleiste"
|
||||
|
||||
@@ -2094,6 +2130,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T14695
|
||||
-- Add Embedding
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1738753945"] = "Einbettung hinzufügen"
|
||||
|
||||
-- Uses the provider-configured model
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1760715963"] = "Verwendet das vom Anbieter konfigurierte Modell"
|
||||
|
||||
-- Are you sure you want to delete the embedding provider '{0}'?
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1825371968"] = "Sind Sie sicher, dass Sie den Einbettungsanbieter '{0}' löschen möchten?"
|
||||
|
||||
@@ -2109,24 +2148,30 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T24199
|
||||
-- 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.
|
||||
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
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T3267849393"] = "Bearbeiten"
|
||||
|
||||
-- Configured Embeddings
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T3526613453"] = "Konfigurierte Einbettungen"
|
||||
|
||||
-- Actions
|
||||
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.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T4068015588"] = "Es wurden bislang keine Einbettungen konfiguriert."
|
||||
|
||||
-- Edit Embedding Provider
|
||||
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
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T511304264"] = "Einbettungsanbieter löschen"
|
||||
|
||||
@@ -2136,9 +2181,6 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T78223
|
||||
-- Provider
|
||||
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?
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1052533048"] = "Anzeigen, wie sicher sich der Anbieter ist?"
|
||||
|
||||
@@ -2154,9 +2196,15 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T162847
|
||||
-- Description
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1725856265"] = "Beschreibung"
|
||||
|
||||
-- Uses the provider-configured model
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1760715963"] = "Verwendet das vom Anbieter konfigurierte Modell"
|
||||
|
||||
-- Add Provider
|
||||
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 LLMs konfigurieren"
|
||||
|
||||
-- Edit LLM Provider
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1868766523"] = "LLM-Anbieter bearbeiten"
|
||||
|
||||
@@ -2190,11 +2238,8 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T284206
|
||||
-- No providers configured yet.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T2911731076"] = "Noch keine Anbieter konfiguriert."
|
||||
|
||||
-- Configure Providers
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3027859089"] = "Anbieter konfigurieren"
|
||||
|
||||
-- as selected by provider
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3082210376"] = "wie vom Anbieter ausgewählt"
|
||||
-- Configured LLM Providers
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3019870540"] = "Konfigurierte Anbieter für LLMs"
|
||||
|
||||
-- Edit
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3267849393"] = "Bearbeiten"
|
||||
@@ -2214,9 +2259,6 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T361241
|
||||
-- No, do not enforce a minimum confidence level
|
||||
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
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3865031940"] = "Aktionen"
|
||||
|
||||
@@ -2244,6 +2286,60 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T853225
|
||||
-- Provider
|
||||
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"
|
||||
|
||||
-- Uses the provider-configured model
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T1760715963"] = "Verwendet das vom Anbieter konfigurierte Modell"
|
||||
|
||||
-- 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"
|
||||
|
||||
-- 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.
|
||||
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 LLMs konfigurieren“."
|
||||
|
||||
-- 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?"
|
||||
|
||||
-- Provider
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T900237532"] = "Anbieter"
|
||||
|
||||
-- Copy {0} to the clipboard
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TEXTINFOLINE::T2206391442"] = "Kopiere {0} in die Zwischenablage"
|
||||
|
||||
@@ -2325,6 +2421,39 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T428040679"] = "Erstellung von In
|
||||
-- Useful assistants
|
||||
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."
|
||||
|
||||
-- Failed to start audio recording.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VOICERECORDER::T2144994226"] = "Audioaufnahme konnte nicht gestartet 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."
|
||||
|
||||
-- Failed to stop audio recording.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VOICERECORDER::T3462568264"] = "Audioaufnahme konnte nicht beendet werden."
|
||||
|
||||
-- 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}'?
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1016188706"] = "Möchten Sie den Chat „{0}“ im Arbeitsbereich „{1}“ wirklich löschen?"
|
||||
|
||||
@@ -2445,6 +2574,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.
|
||||
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
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2418769465"] = "Rolle"
|
||||
|
||||
@@ -2484,6 +2616,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3016903701"] = "Der Nam
|
||||
-- Image content
|
||||
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.
|
||||
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 +3231,6 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2189814010"] = "Mo
|
||||
-- (Optional) API Key
|
||||
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
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2646845972"] = "Hinzufügen"
|
||||
|
||||
@@ -3108,9 +3240,15 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2810182573"] = "Ke
|
||||
-- Instance Name
|
||||
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
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T416738168"] = "Modellauswahl"
|
||||
|
||||
-- We are currently unable to communicate with the provider to load models. Please try again later.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T504465522"] = "Wir können derzeit nicht mit dem Anbieter kommunizieren, um Modelle zu laden. Bitte versuchen Sie es später erneut."
|
||||
|
||||
-- Host
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T808120719"] = "Host"
|
||||
|
||||
@@ -3318,9 +3456,18 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3361153305"] = "Experten-Ei
|
||||
-- Show available models
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3763891899"] = "Verfügbare Modelle anzeigen"
|
||||
|
||||
-- This host uses the model configured at the provider level. No model selection is available.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3783329915"] = "Dieser Host verwendet das auf Anbieterebene konfigurierte Modell. Es ist keine Modellauswahl verfügbar."
|
||||
|
||||
-- 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
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T416738168"] = "Modellauswahl"
|
||||
|
||||
-- We are currently unable to communicate with the provider to load models. Please try again later.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T504465522"] = "Wir können derzeit nicht mit dem Anbieter kommunizieren, um Modelle zu laden. Bitte versuchen Sie es später erneut."
|
||||
|
||||
-- Host
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T808120719"] = "Host"
|
||||
|
||||
@@ -4485,6 +4632,42 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGWRITINGEMAILS::T3832
|
||||
-- Preselect one of your profiles?
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGWRITINGEMAILS::T4004501229"] = "Eines ihrer Profile vorauswählen?"
|
||||
|
||||
-- Save
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SHORTCUTDIALOG::T1294818664"] = "Speichern"
|
||||
|
||||
-- Press the desired key combination to set the shortcut. The shortcut will be registered globally and will work even when the app is not focused.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SHORTCUTDIALOG::T1464973299"] = "Drücken Sie die gewünschte Tastenkombination, um den Kurzbefehl festzulegen. Der Tastaturkurzbefehl wird global registriert und funktioniert auch, wenn die App nicht im Vordergrund ist."
|
||||
|
||||
-- Press a key combination...
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SHORTCUTDIALOG::T1468443151"] = "Drücken Sie eine Tastenkombination …"
|
||||
|
||||
-- Clear Shortcut
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SHORTCUTDIALOG::T1807313248"] = "Tastaturkurzbefehl löschen"
|
||||
|
||||
-- Invalid shortcut: {0}
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SHORTCUTDIALOG::T189893682"] = "Ungültige Tastenkombination: {0}"
|
||||
|
||||
-- This shortcut conflicts with: {0}
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SHORTCUTDIALOG::T2633102934"] = "Dieser Tastaturkurzbefehl steht in Konflikt mit: {0}"
|
||||
|
||||
-- Please include at least one modifier key (Ctrl, Shift, Alt, or Cmd).
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SHORTCUTDIALOG::T3060573513"] = "Bitte fügen Sie mindestens einen Modifikatortaste hinzu (Strg, Umschalt, Alt oder Cmd)."
|
||||
|
||||
-- Shortcut is valid and available.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SHORTCUTDIALOG::T3159532525"] = "Der Tastaturkurzbefehl ist gültig und verfügbar."
|
||||
|
||||
-- Define a shortcut
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SHORTCUTDIALOG::T3734850493"] = "Tastaturkurzbefehl festlegen"
|
||||
|
||||
-- This is the shortcut you previously used.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SHORTCUTDIALOG::T4167229652"] = "Dies ist der Tastaturkurzbefehl, den Sie zuvor verwendet haben."
|
||||
|
||||
-- Supported modifiers: Ctrl/Cmd, Shift, Alt.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SHORTCUTDIALOG::T889258890"] = "Unterstützte Modifikatortasten: Strg/Cmd, Umschalt, Alt."
|
||||
|
||||
-- Cancel
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SHORTCUTDIALOG::T900713019"] = "Abbrechen"
|
||||
|
||||
-- Please enter a value.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SINGLEINPUTDIALOG::T3576780391"] = "Bitte geben Sie einen Wert ein."
|
||||
|
||||
@@ -4494,6 +4677,66 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SINGLEINPUTDIALOG::T4030229154"] = "Ihre Ein
|
||||
-- Cancel
|
||||
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."
|
||||
|
||||
-- This host uses the model configured at the provider level. No model selection is available.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T3783329915"] = "Dieser Host verwendet das auf Anbieterebene konfigurierte Modell. Eine Modellauswahl ist nicht verfügbar."
|
||||
|
||||
-- Model selection
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T416738168"] = "Modellauswahl"
|
||||
|
||||
-- We are currently unable to communicate with the provider to load models. Please try again later.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T504465522"] = "Wir können derzeit nicht mit dem Anbieter kommunizieren, um Modelle zu laden. Bitte versuchen Sie es später erneut."
|
||||
|
||||
-- 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
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::UPDATEDIALOG::T2366359512"] = "Jetzt installieren"
|
||||
|
||||
@@ -4863,6 +5106,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2557066213"] = "Verwendete Open-
|
||||
-- Build time
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T260228112"] = "Build-Zeit"
|
||||
|
||||
-- This crate provides derive macros for Rust enums, which we use to reduce boilerplate when implementing string conversions and metadata for runtime types. This is helpful for the communication between our Rust and .NET systems.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2635482790"] = "Dieses Crate stellt Derive-Makros für Rust-Enums bereit, die wir verwenden, um Boilerplate zu reduzieren, wenn wir String-Konvertierungen und Metadaten für Laufzeittypen implementieren. Das ist hilfreich für die Kommunikation zwischen unseren Rust- und .NET-Systemen."
|
||||
|
||||
-- To be able to use the responses of the LLM in other apps, we often use the clipboard of the respective operating system. Unfortunately, in .NET there is no solution that works with all operating systems. Therefore, I have opted for this library in Rust. This way, data transfer to other apps works on every system.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2644379659"] = "Um die Antworten des LLM in anderen Apps nutzen zu können, verwenden wir häufig die Zwischenablage des jeweiligen Betriebssystems. Leider gibt es in .NET keine Lösung, die auf allen Betriebssystemen funktioniert. Deshalb habe ich mich für diese Bibliothek in Rust entschieden. So funktioniert die Datenübertragung zu anderen Apps auf jedem System."
|
||||
|
||||
@@ -5109,35 +5355,38 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::WRITER::T3948127789"] = "Vorschlag"
|
||||
-- Your stage directions
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::WRITER::T779923726"] = "Ihre Regieanweisungen"
|
||||
|
||||
-- Tried to communicate with the LLM provider '{0}'. The API key might be invalid. The provider message is: '{1}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1073493061"] = "Es wurde versucht mit dem LLM-Anbieter '{0}' zu kommunizieren. Der API-Schlüssel könnte ungültig sein. Die Anbietermeldung lautet: '{1}'"
|
||||
-- We tried to communicate with the LLM provider '{0}' (type={1}). The server might be down or having issues. The provider message is: '{2}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1000247110"] = "Wir haben versucht, mit dem LLM-Anbieter „{0}“ (Typ={1}) zu kommunizieren. Der Server ist möglicherweise nicht erreichbar oder hat Probleme. Die Nachricht des Anbieters lautet: „{2}“"
|
||||
|
||||
-- Tried to stream the LLM provider '{0}' answer. There were some problems with the stream. The message is: '{1}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1487597412"] = "Beim Versuch, die Antwort des LLM-Anbieters '{0}' zu streamen, sind Probleme aufgetreten. Die Meldung lautet: '{1}'"
|
||||
|
||||
-- Tried to communicate with the LLM provider '{0}'. The required message format might be changed. The provider message is: '{1}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1674355816"] = "Es wurde versucht, mit dem LLM-Anbieter '{0}' zu kommunizieren. Das erforderliche Nachrichtenformat könnte sich geändert haben. Die Mitteilung des Anbieters lautet: '{1}'"
|
||||
|
||||
-- Tried to stream the LLM provider '{0}' answer. Was not able to read the stream. The message is: '{1}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1856278860"] = "Der Versuch, die Antwort des LLM-Anbieters '{0}' zu streamen, ist fehlgeschlagen. Der Stream konnte nicht gelesen werden. Die Meldung lautet: '{1}'"
|
||||
|
||||
-- Tried to communicate with the LLM provider '{0}'. Even after {1} retries, there were some problems with the request. The provider message is: '{2}'.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T2181034173"] = "Versuchte, mit dem LLM-Anbieter '{0}' zu kommunizieren. Auch nach {1} Wiederholungsversuchen gab es Probleme mit der Anfrage. Die Meldung des Anbieters lautet: '{2}'."
|
||||
-- We tried to communicate with the LLM provider '{0}' (type={1}). The API key might be invalid. The provider message is: '{2}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1924863735"] = "Wir haben versucht, mit dem LLM-Anbieter „{0}“ (Typ={1}) zu kommunizieren. Der API-Schlüssel ist möglicherweise ungültig. Die Nachricht des Anbieters lautet: „{2}“."
|
||||
|
||||
-- Tried to communicate with the LLM provider '{0}'. Something was not found. The provider message is: '{1}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T2780552614"] = "Es wurde versucht, mit dem LLM-Anbieter '{0}' zu kommunizieren. Etwas wurde nicht gefunden. Die Meldung des Anbieters lautet: '{1}'"
|
||||
-- We tried to communicate with the LLM provider '{0}' (type={1}). The provider is overloaded. The message is: '{2}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1999987800"] = "Wir haben versucht, mit dem LLM-Anbieter „{0}“ (Typ={1}) zu kommunizieren. Der Anbieter ist überlastet. Die Meldung lautet: „{2}“."
|
||||
|
||||
-- We tried to communicate with the LLM provider '{0}' (type={1}). You might not be able to use this provider from your location. The provider message is: '{2}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T2107463087"] = "Wir haben versucht, mit dem LLM-Anbieter „{0}“ (Typ={1}) zu kommunizieren. Möglicherweise können Sie diesen Anbieter von Ihrem Standort aus nicht nutzen. Die Nachricht des Anbieters lautet: „{2}“."
|
||||
|
||||
-- We tried to communicate with the LLM provider '{0}' (type={1}). Something was not found. The provider message is: '{2}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T3014737766"] = "Wir haben versucht, mit dem LLM-Anbieter „{0}“ (Typ={1}) zu kommunizieren. Etwas wurde nicht gefunden. Die Nachricht des Anbieters lautet: „{2}“"
|
||||
|
||||
-- We tried to communicate with the LLM provider '{0}' (type={1}). Even after {2} retries, there were some problems with the request. The provider message is: '{3}'.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T3049689432"] = "Wir haben versucht, mit dem LLM-Anbieter „{0}“ (Typ={1}) zu kommunizieren. Selbst nach {2} erneuten Versuchen gab es weiterhin Probleme mit der Anfrage. Die Meldung des Anbieters lautet: „{3}“."
|
||||
|
||||
-- Tried to communicate with the LLM provider '{0}'. There were some problems with the request. The provider message is: '{1}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T3573577433"] = "Es wurde versucht, mit dem LLM-Anbieter '{0}' zu kommunizieren. Dabei sind Probleme bei der Anfrage aufgetreten. Die Meldung des Anbieters lautet: '{1}'"
|
||||
|
||||
-- Tried to communicate with the LLM provider '{0}'. The server might be down or having issues. The provider message is: '{1}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T3806716694"] = "Es wurde versucht, mit dem LLM-Anbieter '{0}' zu kommunizieren. Der Server ist möglicherweise nicht erreichbar oder hat Probleme. Die Anbietermeldung lautet: '{1}'"
|
||||
-- We tried to communicate with the LLM provider '{0}' (type={1}). The required message format might be changed. The provider message is: '{2}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T3759732886"] = "Wir haben versucht, mit dem LLM-Anbieter „{0}“ (Typ={1}) zu kommunizieren. Das erforderliche Nachrichtenformat hat sich möglicherweise geändert. Die Nachricht des Anbieters lautet: „{2}“"
|
||||
|
||||
-- Tried to communicate with the LLM provider '{0}'. The provider is overloaded. The message is: '{1}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T4179546180"] = "Es wurde versucht, mit dem LLM-Anbieter '{0}' zu kommunizieren. Der Anbieter ist überlastet. Die Meldung lautet: '{1}'"
|
||||
|
||||
-- Tried to communicate with the LLM provider '{0}'. You might not be able to use this provider from your location. The provider message is: '{1}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T862369179"] = "Es wurde versucht, mit dem LLM-Anbieter '{0}' zu kommunizieren. Möglicherweise können Sie diesen Anbieter von ihrem Standort aus nicht nutzen. Die Mitteilung des Anbieters lautet: '{1}'"
|
||||
-- We tried to communicate with the LLM provider '{0}' (type={1}). The data of the chat, including all file attachments, is probably too large for the selected model and provider. The provider message is: '{2}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T4049517041"] = "Wir haben versucht, mit dem LLM-Anbieter „{0}“ (Typ={1}) zu kommunizieren. Die Daten des Chats, einschließlich aller Dateianhänge, sind vermutlich zu groß für das ausgewählte Modell und den Anbieter. Die Nachricht des Anbieters lautet: „{2}“"
|
||||
|
||||
-- The trust level of this provider **has not yet** been thoroughly **investigated and evaluated**. We do not know if your data is safe.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::CONFIDENCE::T1014558951"] = "Das Vertrauensniveau dieses Anbieters wurde **noch nicht** gründlich **untersucht und bewertet**. Wir wissen nicht, ob ihre Daten sicher sind."
|
||||
@@ -5196,6 +5445,9 @@ UI_TEXT_CONTENT["AISTUDIO::PROVIDER::LLMPROVIDERSEXTENSIONS::T3424652889"] = "Un
|
||||
-- no model selected
|
||||
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
|
||||
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::CHATTEMPLATE::T4258819635"] = "Keine Chat-Vorlage verwenden"
|
||||
|
||||
@@ -5370,6 +5622,9 @@ UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::PREVIEWFEATURESEXTENSIONS::T2708
|
||||
-- Unknown preview feature
|
||||
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
|
||||
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 +6189,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::RAG::RAGPROCESSES::AISRCSELWITHRETCTXVAL::T377
|
||||
-- Executable Files
|
||||
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
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T2575722901"] = "Alle Audiodateien"
|
||||
|
||||
@@ -5958,23 +6216,23 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PANDOCAVAILABILITYSERVICE::T18544701
|
||||
-- Pandoc may be required for importing files.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PANDOCAVAILABILITYSERVICE::T2596465560"] = "Zum Importieren von Dateien kann Pandoc erforderlich sein."
|
||||
|
||||
-- Failed to delete the API key due to an API issue.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::APIKEYS::T3658273365"] = "Das API-Schlüssel konnte aufgrund eines API-Problems nicht gelöscht werden."
|
||||
|
||||
-- Failed to get the API key due to an API issue.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::APIKEYS::T3875720022"] = "Der API-Schlüssel konnte aufgrund eines API-Problems nicht abgerufen werden."
|
||||
-- Failed to delete the secret data due to an API issue.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T2303057928"] = "Das Löschen der geheimen Daten ist aufgrund eines API-Problems fehlgeschlagen."
|
||||
|
||||
-- Successfully copied the text to your clipboard
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::CLIPBOARD::T3351807428"] = "Der Text wurde erfolgreich in die Zwischenablage kopiert."
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3351807428"] = "Der Text wurde erfolgreich in die Zwischenablage kopiert."
|
||||
|
||||
-- Failed to delete the API key due to an API issue.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3658273365"] = "Das API-Schlüssel konnte aufgrund eines API-Problems nicht gelöscht werden."
|
||||
|
||||
-- Failed to copy the text to your clipboard.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::CLIPBOARD::T3724548108"] = "Der Text konnte nicht in die Zwischenablage kopiert werden."
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3724548108"] = "Der Text konnte nicht in die Zwischenablage kopiert werden."
|
||||
|
||||
-- Failed to delete the secret data due to an API issue.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::SECRETS::T2303057928"] = "Das Löschen der geheimen Daten ist aufgrund eines API-Problems fehlgeschlagen."
|
||||
-- Failed to get the API key due to an API issue.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3875720022"] = "Der API-Schlüssel konnte aufgrund eines API-Problems nicht abgerufen werden."
|
||||
|
||||
-- Failed to get the secret data due to an API issue.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::SECRETS::T4007657575"] = "Abrufen der geheimen Daten aufgrund eines API-Problems fehlgeschlagen."
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T4007657575"] = "Abrufen der geheimen Daten aufgrund eines API-Problems fehlgeschlagen."
|
||||
|
||||
-- No update found.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::UPDATESERVICE::T1015418291"] = "Kein Update gefunden."
|
||||
|
||||
+301
-43
@@ -384,6 +384,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::CODING::COMMONCODINGLANGUAGEEXTENSIONS::T
|
||||
-- None
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::CODING::COMMONCODINGLANGUAGEEXTENSIONS::T810547195"] = "None"
|
||||
|
||||
-- {0} - Document Analysis Session
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T108097007"] = "{0} - Document Analysis Session"
|
||||
|
||||
-- Use the analysis and output rules to define how the AI evaluates your documents and formats the results.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T1155482668"] = "Use the analysis and output rules to define how the AI evaluates your documents and formats the results."
|
||||
|
||||
@@ -438,9 +441,15 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTA
|
||||
-- Export policy as configuration section
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2556564432"] = "Export policy as configuration section"
|
||||
|
||||
-- The result of your previous document analysis session.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2570551055"] = "The result of your previous document analysis session."
|
||||
|
||||
-- Are you sure you want to delete the document analysis policy '{0}'?
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2582525917"] = "Are you sure you want to delete the document analysis policy '{0}'?"
|
||||
|
||||
-- Expand this section to view and edit the policy definition.
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T277813037"] = "Expand this section to view and edit the policy definition."
|
||||
|
||||
-- Policy name
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2879019438"] = "Policy name"
|
||||
|
||||
@@ -471,6 +480,9 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTA
|
||||
-- Document Analysis Assistant
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T348883878"] = "Document Analysis Assistant"
|
||||
|
||||
-- Empty
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3512147854"] = "Empty"
|
||||
|
||||
-- Analysis and output rules
|
||||
UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3555314296"] = "Analysis and output rules"
|
||||
|
||||
@@ -1668,6 +1680,15 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONPROVIDERSELECTION::T20906218
|
||||
-- Use app default
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONPROVIDERSELECTION::T3672477670"] = "Use app default"
|
||||
|
||||
-- No shortcut configured
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONSHORTCUT::T3099115336"] = "No shortcut configured"
|
||||
|
||||
-- Change shortcut
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONSHORTCUT::T4081853237"] = "Change shortcut"
|
||||
|
||||
-- Configure Keyboard Shortcut
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CONFIGURATIONSHORTCUT::T636303786"] = "Configure Keyboard Shortcut"
|
||||
|
||||
-- Yes, let the AI decide which data sources are needed.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::DATASOURCESELECTION::T1031370894"] = "Yes, let the AI decide which data sources are needed."
|
||||
|
||||
@@ -2010,6 +2031,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1059411425"]
|
||||
-- Do you want to show preview features in the app?
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1118505044"] = "Do you want to show preview features in the app?"
|
||||
|
||||
-- Voice recording shortcut
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1278320412"] = "Voice recording shortcut"
|
||||
|
||||
-- How often should we check for app updates?
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1364944735"] = "How often should we check for app updates?"
|
||||
|
||||
@@ -2025,6 +2049,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.
|
||||
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?
|
||||
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 +2064,12 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1898060643"]
|
||||
-- Select the language for the app.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T1907446663"] = "Select the language for the app."
|
||||
|
||||
-- The global keyboard shortcut for toggling voice recording. This shortcut works system-wide, even when the app is not focused.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2143741496"] = "The global keyboard shortcut for toggling voice recording. This shortcut works system-wide, even when the app is not focused."
|
||||
|
||||
-- Disable dictation and transcription
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T215381891"] = "Disable dictation and transcription"
|
||||
|
||||
-- Language behavior
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T2341504363"] = "Language behavior"
|
||||
|
||||
@@ -2067,6 +2100,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.
|
||||
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
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T602293588"] = "Navigation bar behavior"
|
||||
|
||||
@@ -2094,6 +2130,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T14695
|
||||
-- Add Embedding
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1738753945"] = "Add Embedding"
|
||||
|
||||
-- Uses the provider-configured model
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1760715963"] = "Uses the provider-configured model"
|
||||
|
||||
-- Are you sure you want to delete the embedding provider '{0}'?
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1825371968"] = "Are you sure you want to delete the embedding provider '{0}'?"
|
||||
|
||||
@@ -2109,24 +2148,30 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T24199
|
||||
-- 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.
|
||||
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
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T3267849393"] = "Edit"
|
||||
|
||||
-- Configured Embeddings
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T3526613453"] = "Configured Embeddings"
|
||||
|
||||
-- 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.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T4068015588"] = "No embeddings configured yet."
|
||||
|
||||
-- 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
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T511304264"] = "Delete Embedding Provider"
|
||||
|
||||
@@ -2136,9 +2181,6 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T78223
|
||||
-- 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?
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1052533048"] = "Show provider's confidence level?"
|
||||
|
||||
@@ -2154,9 +2196,15 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T162847
|
||||
-- Description
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1725856265"] = "Description"
|
||||
|
||||
-- Uses the provider-configured model
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1760715963"] = "Uses the provider-configured model"
|
||||
|
||||
-- Add Provider
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1806589097"] = "Add Provider"
|
||||
|
||||
-- Configure LLM Providers
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1810190350"] = "Configure LLM Providers"
|
||||
|
||||
-- Edit LLM Provider
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T1868766523"] = "Edit LLM Provider"
|
||||
|
||||
@@ -2190,11 +2238,8 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T284206
|
||||
-- No providers configured yet.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T2911731076"] = "No providers configured yet."
|
||||
|
||||
-- Configure Providers
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3027859089"] = "Configure Providers"
|
||||
|
||||
-- as selected by provider
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3082210376"] = "as selected by provider"
|
||||
-- Configured LLM Providers
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3019870540"] = "Configured LLM Providers"
|
||||
|
||||
-- Edit
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3267849393"] = "Edit"
|
||||
@@ -2214,9 +2259,6 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T361241
|
||||
-- No, do not enforce a minimum confidence level
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3642102079"] = "No, do not enforce a minimum confidence level"
|
||||
|
||||
-- Configured Providers
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3850871263"] = "Configured Providers"
|
||||
|
||||
-- Actions
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T3865031940"] = "Actions"
|
||||
|
||||
@@ -2244,6 +2286,60 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELPROVIDERS::T853225
|
||||
-- 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"
|
||||
|
||||
-- Uses the provider-configured model
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T1760715963"] = "Uses the provider-configured model"
|
||||
|
||||
-- Add Transcription Provider
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELTRANSCRIPTION::T2066315685"] = "Add Transcription Provider"
|
||||
|
||||
-- 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 LLM 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
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::TEXTINFOLINE::T2206391442"] = "Copy {0} to the clipboard"
|
||||
|
||||
@@ -2325,6 +2421,39 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VISION::T428040679"] = "Content creation"
|
||||
-- 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."
|
||||
|
||||
-- Failed to start audio recording.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VOICERECORDER::T2144994226"] = "Failed to start audio recording."
|
||||
|
||||
-- Stop recording and start transcription
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VOICERECORDER::T224155287"] = "Stop recording and start transcription"
|
||||
|
||||
-- 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."
|
||||
|
||||
-- Failed to stop audio recording.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VOICERECORDER::T3462568264"] = "Failed to stop audio recording."
|
||||
|
||||
-- The configured transcription provider does not meet the minimum confidence level.
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::VOICERECORDER::T3834149033"] = "The configured transcription provider does not meet the minimum confidence level."
|
||||
|
||||
-- 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}'?
|
||||
UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::WORKSPACES::T1016188706"] = "Are you sure you want to delete the chat '{0}' in the workspace '{1}'?"
|
||||
|
||||
@@ -2445,6 +2574,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.
|
||||
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
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T2418769465"] = "Role"
|
||||
|
||||
@@ -2484,6 +2616,9 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::CHATTEMPLATEDIALOG::T3016903701"] = "The nam
|
||||
-- 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.
|
||||
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 +3231,6 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2189814010"] = "Mo
|
||||
-- (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
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2646845972"] = "Add"
|
||||
|
||||
@@ -3108,9 +3240,15 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T2810182573"] = "No
|
||||
-- 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
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T416738168"] = "Model selection"
|
||||
|
||||
-- We are currently unable to communicate with the provider to load models. Please try again later.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T504465522"] = "We are currently unable to communicate with the provider to load models. Please try again later."
|
||||
|
||||
-- Host
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T808120719"] = "Host"
|
||||
|
||||
@@ -3318,9 +3456,18 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3361153305"] = "Show Expert
|
||||
-- Show available models
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3763891899"] = "Show available models"
|
||||
|
||||
-- This host uses the model configured at the provider level. No model selection is available.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T3783329915"] = "This host uses the model configured at the provider level. No model selection is available."
|
||||
|
||||
-- Currently, we cannot query the models for the selected provider and/or host. Therefore, please enter the model name manually.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T4116737656"] = "Currently, we cannot query the models for the selected provider and/or host. Therefore, please enter the model name manually."
|
||||
|
||||
-- Model selection
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T416738168"] = "Model selection"
|
||||
|
||||
-- We are currently unable to communicate with the provider to load models. Please try again later.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T504465522"] = "We are currently unable to communicate with the provider to load models. Please try again later."
|
||||
|
||||
-- Host
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROVIDERDIALOG::T808120719"] = "Host"
|
||||
|
||||
@@ -4485,6 +4632,42 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGWRITINGEMAILS::T3832
|
||||
-- Preselect one of your profiles?
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGWRITINGEMAILS::T4004501229"] = "Preselect one of your profiles?"
|
||||
|
||||
-- Save
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SHORTCUTDIALOG::T1294818664"] = "Save"
|
||||
|
||||
-- Press the desired key combination to set the shortcut. The shortcut will be registered globally and will work even when the app is not focused.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SHORTCUTDIALOG::T1464973299"] = "Press the desired key combination to set the shortcut. The shortcut will be registered globally and will work even when the app is not focused."
|
||||
|
||||
-- Press a key combination...
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SHORTCUTDIALOG::T1468443151"] = "Press a key combination..."
|
||||
|
||||
-- Clear Shortcut
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SHORTCUTDIALOG::T1807313248"] = "Clear Shortcut"
|
||||
|
||||
-- Invalid shortcut: {0}
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SHORTCUTDIALOG::T189893682"] = "Invalid shortcut: {0}"
|
||||
|
||||
-- This shortcut conflicts with: {0}
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SHORTCUTDIALOG::T2633102934"] = "This shortcut conflicts with: {0}"
|
||||
|
||||
-- Please include at least one modifier key (Ctrl, Shift, Alt, or Cmd).
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SHORTCUTDIALOG::T3060573513"] = "Please include at least one modifier key (Ctrl, Shift, Alt, or Cmd)."
|
||||
|
||||
-- Shortcut is valid and available.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SHORTCUTDIALOG::T3159532525"] = "Shortcut is valid and available."
|
||||
|
||||
-- Define a shortcut
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SHORTCUTDIALOG::T3734850493"] = "Define a shortcut"
|
||||
|
||||
-- This is the shortcut you previously used.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SHORTCUTDIALOG::T4167229652"] = "This is the shortcut you previously used."
|
||||
|
||||
-- Supported modifiers: Ctrl/Cmd, Shift, Alt.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SHORTCUTDIALOG::T889258890"] = "Supported modifiers: Ctrl/Cmd, Shift, Alt."
|
||||
|
||||
-- Cancel
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SHORTCUTDIALOG::T900713019"] = "Cancel"
|
||||
|
||||
-- Please enter a value.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SINGLEINPUTDIALOG::T3576780391"] = "Please enter a value."
|
||||
|
||||
@@ -4494,6 +4677,66 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SINGLEINPUTDIALOG::T4030229154"] = "Your Inp
|
||||
-- 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."
|
||||
|
||||
-- This host uses the model configured at the provider level. No model selection is available.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T3783329915"] = "This host uses the model configured at the provider level. No model selection is available."
|
||||
|
||||
-- Model selection
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T416738168"] = "Model selection"
|
||||
|
||||
-- We are currently unable to communicate with the provider to load models. Please try again later.
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T504465522"] = "We are currently unable to communicate with the provider to load models. Please try again later."
|
||||
|
||||
-- Host
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T808120719"] = "Host"
|
||||
|
||||
-- Provider
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T900237532"] = "Provider"
|
||||
|
||||
-- Cancel
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::TRANSCRIPTIONPROVIDERDIALOG::T900713019"] = "Cancel"
|
||||
|
||||
-- Install now
|
||||
UI_TEXT_CONTENT["AISTUDIO::DIALOGS::UPDATEDIALOG::T2366359512"] = "Install now"
|
||||
|
||||
@@ -4863,6 +5106,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2557066213"] = "Used Open Source
|
||||
-- Build time
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T260228112"] = "Build time"
|
||||
|
||||
-- This crate provides derive macros for Rust enums, which we use to reduce boilerplate when implementing string conversions and metadata for runtime types. This is helpful for the communication between our Rust and .NET systems.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2635482790"] = "This crate provides derive macros for Rust enums, which we use to reduce boilerplate when implementing string conversions and metadata for runtime types. This is helpful for the communication between our Rust and .NET systems."
|
||||
|
||||
-- To be able to use the responses of the LLM in other apps, we often use the clipboard of the respective operating system. Unfortunately, in .NET there is no solution that works with all operating systems. Therefore, I have opted for this library in Rust. This way, data transfer to other apps works on every system.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::INFORMATION::T2644379659"] = "To be able to use the responses of the LLM in other apps, we often use the clipboard of the respective operating system. Unfortunately, in .NET there is no solution that works with all operating systems. Therefore, I have opted for this library in Rust. This way, data transfer to other apps works on every system."
|
||||
|
||||
@@ -5109,35 +5355,38 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::WRITER::T3948127789"] = "Suggestion"
|
||||
-- Your stage directions
|
||||
UI_TEXT_CONTENT["AISTUDIO::PAGES::WRITER::T779923726"] = "Your stage directions"
|
||||
|
||||
-- Tried to communicate with the LLM provider '{0}'. The API key might be invalid. The provider message is: '{1}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1073493061"] = "Tried to communicate with the LLM provider '{0}'. The API key might be invalid. The provider message is: '{1}'"
|
||||
-- We tried to communicate with the LLM provider '{0}' (type={1}). The server might be down or having issues. The provider message is: '{2}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1000247110"] = "We tried to communicate with the LLM provider '{0}' (type={1}). The server might be down or having issues. The provider message is: '{2}'"
|
||||
|
||||
-- Tried to stream the LLM provider '{0}' answer. There were some problems with the stream. The message is: '{1}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1487597412"] = "Tried to stream the LLM provider '{0}' answer. There were some problems with the stream. The message is: '{1}'"
|
||||
|
||||
-- Tried to communicate with the LLM provider '{0}'. The required message format might be changed. The provider message is: '{1}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1674355816"] = "Tried to communicate with the LLM provider '{0}'. The required message format might be changed. The provider message is: '{1}'"
|
||||
|
||||
-- Tried to stream the LLM provider '{0}' answer. Was not able to read the stream. The message is: '{1}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1856278860"] = "Tried to stream the LLM provider '{0}' answer. Was not able to read the stream. The message is: '{1}'"
|
||||
|
||||
-- Tried to communicate with the LLM provider '{0}'. Even after {1} retries, there were some problems with the request. The provider message is: '{2}'.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T2181034173"] = "Tried to communicate with the LLM provider '{0}'. Even after {1} retries, there were some problems with the request. The provider message is: '{2}'."
|
||||
-- We tried to communicate with the LLM provider '{0}' (type={1}). The API key might be invalid. The provider message is: '{2}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1924863735"] = "We tried to communicate with the LLM provider '{0}' (type={1}). The API key might be invalid. The provider message is: '{2}'"
|
||||
|
||||
-- Tried to communicate with the LLM provider '{0}'. Something was not found. The provider message is: '{1}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T2780552614"] = "Tried to communicate with the LLM provider '{0}'. Something was not found. The provider message is: '{1}'"
|
||||
-- We tried to communicate with the LLM provider '{0}' (type={1}). The provider is overloaded. The message is: '{2}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T1999987800"] = "We tried to communicate with the LLM provider '{0}' (type={1}). The provider is overloaded. The message is: '{2}'"
|
||||
|
||||
-- We tried to communicate with the LLM provider '{0}' (type={1}). You might not be able to use this provider from your location. The provider message is: '{2}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T2107463087"] = "We tried to communicate with the LLM provider '{0}' (type={1}). You might not be able to use this provider from your location. The provider message is: '{2}'"
|
||||
|
||||
-- We tried to communicate with the LLM provider '{0}' (type={1}). Something was not found. The provider message is: '{2}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T3014737766"] = "We tried to communicate with the LLM provider '{0}' (type={1}). Something was not found. The provider message is: '{2}'"
|
||||
|
||||
-- We tried to communicate with the LLM provider '{0}' (type={1}). Even after {2} retries, there were some problems with the request. The provider message is: '{3}'.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T3049689432"] = "We tried to communicate with the LLM provider '{0}' (type={1}). Even after {2} retries, there were some problems with the request. The provider message is: '{3}'."
|
||||
|
||||
-- Tried to communicate with the LLM provider '{0}'. There were some problems with the request. The provider message is: '{1}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T3573577433"] = "Tried to communicate with the LLM provider '{0}'. There were some problems with the request. The provider message is: '{1}'"
|
||||
|
||||
-- Tried to communicate with the LLM provider '{0}'. The server might be down or having issues. The provider message is: '{1}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T3806716694"] = "Tried to communicate with the LLM provider '{0}'. The server might be down or having issues. The provider message is: '{1}'"
|
||||
-- We tried to communicate with the LLM provider '{0}' (type={1}). The required message format might be changed. The provider message is: '{2}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T3759732886"] = "We tried to communicate with the LLM provider '{0}' (type={1}). The required message format might be changed. The provider message is: '{2}'"
|
||||
|
||||
-- Tried to communicate with the LLM provider '{0}'. The provider is overloaded. The message is: '{1}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T4179546180"] = "Tried to communicate with the LLM provider '{0}'. The provider is overloaded. The message is: '{1}'"
|
||||
|
||||
-- Tried to communicate with the LLM provider '{0}'. You might not be able to use this provider from your location. The provider message is: '{1}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T862369179"] = "Tried to communicate with the LLM provider '{0}'. You might not be able to use this provider from your location. The provider message is: '{1}'"
|
||||
-- We tried to communicate with the LLM provider '{0}' (type={1}). The data of the chat, including all file attachments, is probably too large for the selected model and provider. The provider message is: '{2}'
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::BASEPROVIDER::T4049517041"] = "We tried to communicate with the LLM provider '{0}' (type={1}). The data of the chat, including all file attachments, is probably too large for the selected model and provider. The provider message is: '{2}'"
|
||||
|
||||
-- The trust level of this provider **has not yet** been thoroughly **investigated and evaluated**. We do not know if your data is safe.
|
||||
UI_TEXT_CONTENT["AISTUDIO::PROVIDER::CONFIDENCE::T1014558951"] = "The trust level of this provider **has not yet** been thoroughly **investigated and evaluated**. We do not know if your data is safe."
|
||||
@@ -5196,6 +5445,9 @@ UI_TEXT_CONTENT["AISTUDIO::PROVIDER::LLMPROVIDERSEXTENSIONS::T3424652889"] = "Un
|
||||
-- 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"
|
||||
|
||||
@@ -5370,6 +5622,9 @@ UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::PREVIEWFEATURESEXTENSIONS::T2708
|
||||
-- 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
|
||||
UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::SENDTOCHATDATASOURCEBEHAVIOREXTENSIONS::T1223925477"] = "Use no data sources, when sending an assistant result to a chat"
|
||||
|
||||
@@ -5934,6 +6189,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::RAG::RAGPROCESSES::AISRCSELWITHRETCTXVAL::T377
|
||||
-- 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
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T2575722901"] = "All Audio Files"
|
||||
|
||||
@@ -5958,23 +6216,23 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PANDOCAVAILABILITYSERVICE::T18544701
|
||||
-- Pandoc may be required for importing files.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::PANDOCAVAILABILITYSERVICE::T2596465560"] = "Pandoc may be required for importing files."
|
||||
|
||||
-- Failed to delete the API key due to an API issue.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::APIKEYS::T3658273365"] = "Failed to delete the API key due to an API issue."
|
||||
|
||||
-- Failed to get the API key due to an API issue.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::APIKEYS::T3875720022"] = "Failed to get the API key due to an API issue."
|
||||
-- Failed to delete the secret data due to an API issue.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T2303057928"] = "Failed to delete the secret data due to an API issue."
|
||||
|
||||
-- Successfully copied the text to your clipboard
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::CLIPBOARD::T3351807428"] = "Successfully copied the text to your clipboard"
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3351807428"] = "Successfully copied the text to your clipboard"
|
||||
|
||||
-- Failed to delete the API key due to an API issue.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3658273365"] = "Failed to delete the API key due to an API issue."
|
||||
|
||||
-- Failed to copy the text to your clipboard.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::CLIPBOARD::T3724548108"] = "Failed to copy the text to your clipboard."
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3724548108"] = "Failed to copy the text to your clipboard."
|
||||
|
||||
-- Failed to delete the secret data due to an API issue.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::SECRETS::T2303057928"] = "Failed to delete the secret data due to an API issue."
|
||||
-- Failed to get the API key due to an API issue.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T3875720022"] = "Failed to get the API key due to an API issue."
|
||||
|
||||
-- Failed to get the secret data due to an API issue.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::SECRETS::T4007657575"] = "Failed to get the secret data due to an API issue."
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::RUSTSERVICE::T4007657575"] = "Failed to get the secret data due to an API issue."
|
||||
|
||||
-- No update found.
|
||||
UI_TEXT_CONTENT["AISTUDIO::TOOLS::SERVICES::UPDATESERVICE::T1015418291"] = "No update found."
|
||||
|
||||
@@ -83,7 +83,6 @@ internal sealed class Program
|
||||
}
|
||||
|
||||
var builder = WebApplication.CreateBuilder();
|
||||
|
||||
builder.WebHost.ConfigureKestrel(kestrelServerOptions =>
|
||||
{
|
||||
kestrelServerOptions.ConfigureEndpointDefaults(listenOptions =>
|
||||
@@ -134,6 +133,8 @@ internal sealed class Program
|
||||
builder.Services.AddHostedService<UpdateService>();
|
||||
builder.Services.AddHostedService<TemporaryChatService>();
|
||||
builder.Services.AddHostedService<EnterpriseEnvironmentService>();
|
||||
builder.Services.AddHostedService<GlobalShortcutService>();
|
||||
builder.Services.AddHostedService<RustAvailabilityMonitorService>();
|
||||
|
||||
// ReSharper disable AccessToDisposedClosure
|
||||
builder.Services.AddHostedService<RustService>(_ => rust);
|
||||
@@ -186,6 +187,7 @@ internal sealed class Program
|
||||
var rustLogger = app.Services.GetRequiredService<ILogger<RustService>>();
|
||||
rust.SetLogger(rustLogger);
|
||||
rust.SetEncryptor(encryption);
|
||||
TerminalLogger.SetRustService(rust);
|
||||
|
||||
RUST_SERVICE = rust;
|
||||
ENCRYPTION = encryption;
|
||||
@@ -229,4 +231,4 @@ internal sealed class Program
|
||||
PluginFactory.Dispose();
|
||||
programLogger.LogInformation("The AI Studio server was stopped.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
// Get the API key:
|
||||
var requestedSecret = await RUST_SERVICE.GetAPIKey(this);
|
||||
var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.LLM_PROVIDER);
|
||||
if(!requestedSecret.Success)
|
||||
yield break;
|
||||
|
||||
@@ -33,7 +33,7 @@ public sealed class ProviderAlibabaCloud() : BaseProvider(LLMProviders.ALIBABA_C
|
||||
var systemPrompt = new TextMessage
|
||||
{
|
||||
Role = "system",
|
||||
Content = chatThread.PrepareSystemPrompt(settingsManager, chatThread),
|
||||
Content = chatThread.PrepareSystemPrompt(settingsManager),
|
||||
};
|
||||
|
||||
// Parse the API parameters:
|
||||
@@ -80,6 +80,12 @@ public sealed class ProviderAlibabaCloud() : BaseProvider(LLMProviders.ALIBABA_C
|
||||
yield break;
|
||||
}
|
||||
#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 />
|
||||
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"),
|
||||
};
|
||||
|
||||
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 />
|
||||
@@ -129,18 +135,27 @@ public sealed class ProviderAlibabaCloud() : BaseProvider(LLMProviders.ALIBABA_C
|
||||
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
|
||||
|
||||
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
|
||||
{
|
||||
not null => apiKeyProvisional,
|
||||
_ => await RUST_SERVICE.GetAPIKey(this) switch
|
||||
_ => await RUST_SERVICE.GetAPIKey(this, storeType) switch
|
||||
{
|
||||
{ Success: true } result => await result.Secret.Decrypt(ENCRYPTION),
|
||||
_ => 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)
|
||||
{
|
||||
// Get the API key:
|
||||
var requestedSecret = await RUST_SERVICE.GetAPIKey(this);
|
||||
var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.LLM_PROVIDER);
|
||||
if(!requestedSecret.Success)
|
||||
yield break;
|
||||
|
||||
@@ -72,7 +72,7 @@ public sealed class ProviderAnthropic() : BaseProvider(LLMProviders.ANTHROPIC, "
|
||||
// Build the messages:
|
||||
Messages = [..messages],
|
||||
|
||||
System = chatThread.PrepareSystemPrompt(settingsManager, chatThread),
|
||||
System = chatThread.PrepareSystemPrompt(settingsManager),
|
||||
MaxTokens = apiParameters.TryGetValue("max_tokens", out var value) && value is int intValue ? intValue : 4_096,
|
||||
|
||||
// Right now, we only support streaming completions:
|
||||
@@ -107,6 +107,12 @@ public sealed class ProviderAnthropic() : BaseProvider(LLMProviders.ANTHROPIC, "
|
||||
yield break;
|
||||
}
|
||||
#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 />
|
||||
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)"),
|
||||
};
|
||||
|
||||
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 />
|
||||
@@ -136,14 +142,20 @@ public sealed class ProviderAnthropic() : BaseProvider(LLMProviders.ANTHROPIC, "
|
||||
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
|
||||
|
||||
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
|
||||
{
|
||||
not null => apiKeyProvisional,
|
||||
_ => await RUST_SERVICE.GetAPIKey(this) switch
|
||||
_ => await RUST_SERVICE.GetAPIKey(this, storeType) switch
|
||||
{
|
||||
{ Success: true } result => await result.Secret.Decrypt(ENCRYPTION),
|
||||
_ => null,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Net;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
@@ -6,10 +7,15 @@ using System.Text.Json.Serialization;
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Provider.Anthropic;
|
||||
using AIStudio.Provider.OpenAI;
|
||||
using AIStudio.Provider.SelfHosted;
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Tools.MIME;
|
||||
using AIStudio.Tools.PluginSystem;
|
||||
using AIStudio.Tools.Rust;
|
||||
using AIStudio.Tools.Services;
|
||||
|
||||
using Host = AIStudio.Provider.SelfHosted.Host;
|
||||
|
||||
namespace AIStudio.Provider;
|
||||
|
||||
/// <summary>
|
||||
@@ -89,6 +95,9 @@ public abstract class BaseProvider : IProvider, ISecretId
|
||||
/// <inheritdoc />
|
||||
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 />
|
||||
public abstract Task<IEnumerable<Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default);
|
||||
|
||||
@@ -97,6 +106,9 @@ public abstract class BaseProvider : IProvider, ISecretId
|
||||
|
||||
/// <inheritdoc />
|
||||
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
|
||||
|
||||
@@ -144,7 +156,7 @@ public abstract class BaseProvider : IProvider, ISecretId
|
||||
var errorBody = await nextResponse.Content.ReadAsStringAsync(token);
|
||||
if (nextResponse.StatusCode is HttpStatusCode.Forbidden)
|
||||
{
|
||||
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.Block, string.Format(TB("Tried to communicate with the LLM provider '{0}'. You might not be able to use this provider from your location. The provider message is: '{1}'"), this.InstanceName, nextResponse.ReasonPhrase)));
|
||||
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.Block, string.Format(TB("We tried to communicate with the LLM provider '{0}' (type={1}). You might not be able to use this provider from your location. The provider message is: '{2}'"), this.InstanceName, this.Provider, nextResponse.ReasonPhrase)));
|
||||
this.logger.LogError("Failed request with status code {ResponseStatusCode} (message = '{ResponseReasonPhrase}', error body = '{ErrorBody}').", nextResponse.StatusCode, nextResponse.ReasonPhrase, errorBody);
|
||||
errorMessage = nextResponse.ReasonPhrase;
|
||||
break;
|
||||
@@ -152,7 +164,18 @@ public abstract class BaseProvider : IProvider, ISecretId
|
||||
|
||||
if(nextResponse.StatusCode is HttpStatusCode.BadRequest)
|
||||
{
|
||||
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.CloudOff, string.Format(TB("Tried to communicate with the LLM provider '{0}'. The required message format might be changed. The provider message is: '{1}'"), this.InstanceName, nextResponse.ReasonPhrase)));
|
||||
// Check if the error body contains "context" and "token" (case-insensitive),
|
||||
// which indicates that the context window is likely exceeded:
|
||||
if(errorBody.Contains("context", StringComparison.InvariantCultureIgnoreCase) &&
|
||||
errorBody.Contains("token", StringComparison.InvariantCultureIgnoreCase))
|
||||
{
|
||||
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.CloudOff, string.Format(TB("We tried to communicate with the LLM provider '{0}' (type={1}). The data of the chat, including all file attachments, is probably too large for the selected model and provider. The provider message is: '{2}'"), this.InstanceName, this.Provider, nextResponse.ReasonPhrase)));
|
||||
}
|
||||
else
|
||||
{
|
||||
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.CloudOff, string.Format(TB("We tried to communicate with the LLM provider '{0}' (type={1}). The required message format might be changed. The provider message is: '{2}'"), this.InstanceName, this.Provider, nextResponse.ReasonPhrase)));
|
||||
}
|
||||
|
||||
this.logger.LogError("Failed request with status code {ResponseStatusCode} (message = '{ResponseReasonPhrase}', error body = '{ErrorBody}').", nextResponse.StatusCode, nextResponse.ReasonPhrase, errorBody);
|
||||
errorMessage = nextResponse.ReasonPhrase;
|
||||
break;
|
||||
@@ -160,7 +183,7 @@ public abstract class BaseProvider : IProvider, ISecretId
|
||||
|
||||
if(nextResponse.StatusCode is HttpStatusCode.NotFound)
|
||||
{
|
||||
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.CloudOff, string.Format(TB("Tried to communicate with the LLM provider '{0}'. Something was not found. The provider message is: '{1}'"), this.InstanceName, nextResponse.ReasonPhrase)));
|
||||
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.CloudOff, string.Format(TB("We tried to communicate with the LLM provider '{0}' (type={1}). Something was not found. The provider message is: '{2}'"), this.InstanceName, this.Provider, nextResponse.ReasonPhrase)));
|
||||
this.logger.LogError("Failed request with status code {ResponseStatusCode} (message = '{ResponseReasonPhrase}', error body = '{ErrorBody}').", nextResponse.StatusCode, nextResponse.ReasonPhrase, errorBody);
|
||||
errorMessage = nextResponse.ReasonPhrase;
|
||||
break;
|
||||
@@ -168,7 +191,7 @@ public abstract class BaseProvider : IProvider, ISecretId
|
||||
|
||||
if(nextResponse.StatusCode is HttpStatusCode.Unauthorized)
|
||||
{
|
||||
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.Key, string.Format(TB("Tried to communicate with the LLM provider '{0}'. The API key might be invalid. The provider message is: '{1}'"), this.InstanceName, nextResponse.ReasonPhrase)));
|
||||
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.Key, string.Format(TB("We tried to communicate with the LLM provider '{0}' (type={1}). The API key might be invalid. The provider message is: '{2}'"), this.InstanceName, this.Provider, nextResponse.ReasonPhrase)));
|
||||
this.logger.LogError("Failed request with status code {ResponseStatusCode} (message = '{ResponseReasonPhrase}', error body = '{ErrorBody}').", nextResponse.StatusCode, nextResponse.ReasonPhrase, errorBody);
|
||||
errorMessage = nextResponse.ReasonPhrase;
|
||||
break;
|
||||
@@ -176,7 +199,7 @@ public abstract class BaseProvider : IProvider, ISecretId
|
||||
|
||||
if(nextResponse.StatusCode is HttpStatusCode.InternalServerError)
|
||||
{
|
||||
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.CloudOff, string.Format(TB("Tried to communicate with the LLM provider '{0}'. The server might be down or having issues. The provider message is: '{1}'"), this.InstanceName, nextResponse.ReasonPhrase)));
|
||||
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.CloudOff, string.Format(TB("We tried to communicate with the LLM provider '{0}' (type={1}). The server might be down or having issues. The provider message is: '{2}'"), this.InstanceName, this.Provider, nextResponse.ReasonPhrase)));
|
||||
this.logger.LogError("Failed request with status code {ResponseStatusCode} (message = '{ResponseReasonPhrase}', error body = '{ErrorBody}').", nextResponse.StatusCode, nextResponse.ReasonPhrase, errorBody);
|
||||
errorMessage = nextResponse.ReasonPhrase;
|
||||
break;
|
||||
@@ -184,7 +207,7 @@ public abstract class BaseProvider : IProvider, ISecretId
|
||||
|
||||
if(nextResponse.StatusCode is HttpStatusCode.ServiceUnavailable)
|
||||
{
|
||||
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.CloudOff, string.Format(TB("Tried to communicate with the LLM provider '{0}'. The provider is overloaded. The message is: '{1}'"), this.InstanceName, nextResponse.ReasonPhrase)));
|
||||
await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.CloudOff, string.Format(TB("We tried to communicate with the LLM provider '{0}' (type={1}). The provider is overloaded. The message is: '{2}'"), this.InstanceName, this.Provider, nextResponse.ReasonPhrase)));
|
||||
this.logger.LogError("Failed request with status code {ResponseStatusCode} (message = '{ResponseReasonPhrase}', error body = '{ErrorBody}').", nextResponse.StatusCode, nextResponse.ReasonPhrase, errorBody);
|
||||
errorMessage = nextResponse.ReasonPhrase;
|
||||
break;
|
||||
@@ -201,7 +224,7 @@ public abstract class BaseProvider : IProvider, ISecretId
|
||||
|
||||
if(retry >= MAX_RETRIES || !string.IsNullOrWhiteSpace(errorMessage))
|
||||
{
|
||||
await MessageBus.INSTANCE.SendError(new DataErrorMessage(Icons.Material.Filled.CloudOff, string.Format(TB("Tried to communicate with the LLM provider '{0}'. Even after {1} retries, there were some problems with the request. The provider message is: '{2}'."), this.InstanceName, MAX_RETRIES, errorMessage)));
|
||||
await MessageBus.INSTANCE.SendError(new DataErrorMessage(Icons.Material.Filled.CloudOff, string.Format(TB("We tried to communicate with the LLM provider '{0}' (type={1}). Even after {2} retries, there were some problems with the request. The provider message is: '{3}'."), this.InstanceName, this.Provider, MAX_RETRIES, errorMessage)));
|
||||
return new HttpRateLimitedStreamResult(false, true, errorMessage ?? $"Failed after {MAX_RETRIES} retries; no provider message available", response);
|
||||
}
|
||||
|
||||
@@ -533,6 +556,90 @@ public abstract class BaseProvider : IProvider, ISecretId
|
||||
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);
|
||||
|
||||
// Set the content type based on the file extension:
|
||||
fileContent.Headers.ContentType = new MediaTypeHeaderValue(mimeType);
|
||||
|
||||
// Add the file content to the form data:
|
||||
form.Add(fileContent, "file", Path.GetFileName(audioFilePath));
|
||||
|
||||
//
|
||||
// Add the model name to the form data. Ensure that a model name is always provided.
|
||||
// Otherwise, the StringContent constructor will throw an exception.
|
||||
//
|
||||
var modelName = transcriptionModel.Id;
|
||||
if (string.IsNullOrWhiteSpace(modelName))
|
||||
modelName = "placeholder";
|
||||
|
||||
form.Add(new StringContent(modelName), "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>
|
||||
/// Parse and convert API parameters from a provided JSON string into a dictionary,
|
||||
/// 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)
|
||||
{
|
||||
// Get the API key:
|
||||
var requestedSecret = await RUST_SERVICE.GetAPIKey(this);
|
||||
var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.LLM_PROVIDER);
|
||||
if(!requestedSecret.Success)
|
||||
yield break;
|
||||
|
||||
@@ -33,7 +33,7 @@ public sealed class ProviderDeepSeek() : BaseProvider(LLMProviders.DEEP_SEEK, "h
|
||||
var systemPrompt = new TextMessage
|
||||
{
|
||||
Role = "system",
|
||||
Content = chatThread.PrepareSystemPrompt(settingsManager, chatThread),
|
||||
Content = chatThread.PrepareSystemPrompt(settingsManager),
|
||||
};
|
||||
|
||||
// Parse the API parameters:
|
||||
@@ -80,11 +80,17 @@ public sealed class ProviderDeepSeek() : BaseProvider(LLMProviders.DEEP_SEEK, "h
|
||||
yield break;
|
||||
}
|
||||
#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 />
|
||||
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 />
|
||||
@@ -99,15 +105,20 @@ public sealed class ProviderDeepSeek() : BaseProvider(LLMProviders.DEEP_SEEK, "h
|
||||
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
|
||||
|
||||
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
|
||||
{
|
||||
not null => apiKeyProvisional,
|
||||
_ => await RUST_SERVICE.GetAPIKey(this) switch
|
||||
_ => await RUST_SERVICE.GetAPIKey(this, storeType) switch
|
||||
{
|
||||
{ Success: true } result => await result.Secret.Decrypt(ENCRYPTION),
|
||||
_ => 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)
|
||||
{
|
||||
// Get the API key:
|
||||
var requestedSecret = await RUST_SERVICE.GetAPIKey(this);
|
||||
var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.LLM_PROVIDER);
|
||||
if(!requestedSecret.Success)
|
||||
yield break;
|
||||
|
||||
@@ -33,7 +33,7 @@ public class ProviderFireworks() : BaseProvider(LLMProviders.FIREWORKS, "https:/
|
||||
var systemPrompt = new TextMessage
|
||||
{
|
||||
Role = "system",
|
||||
Content = chatThread.PrepareSystemPrompt(settingsManager, chatThread),
|
||||
Content = chatThread.PrepareSystemPrompt(settingsManager),
|
||||
};
|
||||
|
||||
// Parse the API parameters:
|
||||
@@ -81,6 +81,13 @@ public class ProviderFireworks() : BaseProvider(LLMProviders.FIREWORKS, "https:/
|
||||
yield break;
|
||||
}
|
||||
#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 />
|
||||
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>());
|
||||
}
|
||||
|
||||
/// <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
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
// Get the API key:
|
||||
var requestedSecret = await RUST_SERVICE.GetAPIKey(this);
|
||||
var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.LLM_PROVIDER);
|
||||
if(!requestedSecret.Success)
|
||||
yield break;
|
||||
|
||||
@@ -33,7 +33,7 @@ public sealed class ProviderGWDG() : BaseProvider(LLMProviders.GWDG, "https://ch
|
||||
var systemPrompt = new TextMessage
|
||||
{
|
||||
Role = "system",
|
||||
Content = chatThread.PrepareSystemPrompt(settingsManager, chatThread),
|
||||
Content = chatThread.PrepareSystemPrompt(settingsManager),
|
||||
};
|
||||
|
||||
// Parse the API parameters:
|
||||
@@ -80,11 +80,18 @@ public sealed class ProviderGWDG() : BaseProvider(LLMProviders.GWDG, "https://ch
|
||||
yield break;
|
||||
}
|
||||
#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 />
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -97,18 +104,29 @@ public sealed class ProviderGWDG() : BaseProvider(LLMProviders.GWDG, "https://ch
|
||||
/// <inheritdoc />
|
||||
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));
|
||||
}
|
||||
|
||||
/// <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
|
||||
|
||||
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
|
||||
{
|
||||
not null => apiKeyProvisional,
|
||||
_ => await RUST_SERVICE.GetAPIKey(this) switch
|
||||
_ => await RUST_SERVICE.GetAPIKey(this, storeType) switch
|
||||
{
|
||||
{ Success: true } result => await result.Secret.Decrypt(ENCRYPTION),
|
||||
_ => null,
|
||||
|
||||
@@ -9,7 +9,7 @@ using AIStudio.Settings;
|
||||
|
||||
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>();
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
// Get the API key:
|
||||
var requestedSecret = await RUST_SERVICE.GetAPIKey(this);
|
||||
var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.LLM_PROVIDER);
|
||||
if(!requestedSecret.Success)
|
||||
yield break;
|
||||
|
||||
@@ -33,7 +33,7 @@ public class ProviderGoogle() : BaseProvider(LLMProviders.GOOGLE, "https://gener
|
||||
var systemPrompt = new TextMessage
|
||||
{
|
||||
Role = "system",
|
||||
Content = chatThread.PrepareSystemPrompt(settingsManager, chatThread),
|
||||
Content = chatThread.PrepareSystemPrompt(settingsManager),
|
||||
};
|
||||
|
||||
// Parse the API parameters:
|
||||
@@ -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
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<string> TranscribeAudioAsync(Provider.Model transcriptionModel, string audioFilePath, SettingsManager settingsManager, CancellationToken token = default)
|
||||
{
|
||||
return Task.FromResult(string.Empty);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
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)
|
||||
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)
|
||||
{
|
||||
var modelResponse = await this.LoadModels(token, apiKeyProvisional);
|
||||
var modelResponse = await this.LoadModels(SecretStoreType.EMBEDDING_PROVIDER, token, apiKeyProvisional);
|
||||
if(modelResponse == default)
|
||||
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));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<IEnumerable<Provider.Model>> GetTranscriptionModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
{
|
||||
return Task.FromResult(Enumerable.Empty<Provider.Model>());
|
||||
}
|
||||
|
||||
#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
|
||||
{
|
||||
not null => apiKeyProvisional,
|
||||
_ => await RUST_SERVICE.GetAPIKey(this) switch
|
||||
_ => await RUST_SERVICE.GetAPIKey(this, storeType) switch
|
||||
{
|
||||
{ Success: true } result => await result.Secret.Decrypt(ENCRYPTION),
|
||||
_ => 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)
|
||||
{
|
||||
// Get the API key:
|
||||
var requestedSecret = await RUST_SERVICE.GetAPIKey(this);
|
||||
var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.LLM_PROVIDER);
|
||||
if(!requestedSecret.Success)
|
||||
yield break;
|
||||
|
||||
@@ -33,7 +33,7 @@ public class ProviderGroq() : BaseProvider(LLMProviders.GROQ, "https://api.groq.
|
||||
var systemPrompt = new TextMessage
|
||||
{
|
||||
Role = "system",
|
||||
Content = chatThread.PrepareSystemPrompt(settingsManager, chatThread),
|
||||
Content = chatThread.PrepareSystemPrompt(settingsManager),
|
||||
};
|
||||
|
||||
// Parse the API parameters:
|
||||
@@ -81,17 +81,23 @@ public class ProviderGroq() : BaseProvider(LLMProviders.GROQ, "https://api.groq.
|
||||
yield break;
|
||||
}
|
||||
#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 />
|
||||
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 />
|
||||
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 />
|
||||
@@ -100,14 +106,20 @@ public class ProviderGroq() : BaseProvider(LLMProviders.GROQ, "https://api.groq.
|
||||
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
|
||||
|
||||
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
|
||||
{
|
||||
not null => apiKeyProvisional,
|
||||
_ => await RUST_SERVICE.GetAPIKey(this) switch
|
||||
_ => await RUST_SERVICE.GetAPIKey(this, storeType) switch
|
||||
{
|
||||
{ Success: true } result => await result.Secret.Decrypt(ENCRYPTION),
|
||||
_ => 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)
|
||||
{
|
||||
// Get the API key:
|
||||
var requestedSecret = await RUST_SERVICE.GetAPIKey(this);
|
||||
var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.LLM_PROVIDER);
|
||||
if(!requestedSecret.Success)
|
||||
yield break;
|
||||
|
||||
@@ -33,7 +33,7 @@ public sealed class ProviderHelmholtz() : BaseProvider(LLMProviders.HELMHOLTZ, "
|
||||
var systemPrompt = new TextMessage
|
||||
{
|
||||
Role = "system",
|
||||
Content = chatThread.PrepareSystemPrompt(settingsManager, chatThread),
|
||||
Content = chatThread.PrepareSystemPrompt(settingsManager),
|
||||
};
|
||||
|
||||
// Parse the API parameters:
|
||||
@@ -80,11 +80,17 @@ public sealed class ProviderHelmholtz() : BaseProvider(LLMProviders.HELMHOLTZ, "
|
||||
yield break;
|
||||
}
|
||||
#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 />
|
||||
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) &&
|
||||
!model.Id.StartsWith("alias-embedding", StringComparison.InvariantCultureIgnoreCase));
|
||||
}
|
||||
@@ -98,21 +104,27 @@ public sealed class ProviderHelmholtz() : BaseProvider(LLMProviders.HELMHOLTZ, "
|
||||
/// <inheritdoc />
|
||||
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("alias-embedding", StringComparison.InvariantCultureIgnoreCase) ||
|
||||
model.Id.StartsWith("text-", 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
|
||||
|
||||
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
|
||||
{
|
||||
not null => apiKeyProvisional,
|
||||
_ => await RUST_SERVICE.GetAPIKey(this) switch
|
||||
_ => await RUST_SERVICE.GetAPIKey(this, storeType) switch
|
||||
{
|
||||
{ Success: true } result => await result.Secret.Decrypt(ENCRYPTION),
|
||||
_ => 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)
|
||||
{
|
||||
// Get the API key:
|
||||
var requestedSecret = await RUST_SERVICE.GetAPIKey(this);
|
||||
var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.LLM_PROVIDER);
|
||||
if(!requestedSecret.Success)
|
||||
yield break;
|
||||
|
||||
@@ -38,7 +38,7 @@ public sealed class ProviderHuggingFace : BaseProvider
|
||||
var systemPrompt = new TextMessage
|
||||
{
|
||||
Role = "system",
|
||||
Content = chatThread.PrepareSystemPrompt(settingsManager, chatThread),
|
||||
Content = chatThread.PrepareSystemPrompt(settingsManager),
|
||||
};
|
||||
|
||||
// Parse the API parameters:
|
||||
@@ -85,6 +85,12 @@ public sealed class ProviderHuggingFace : BaseProvider
|
||||
yield break;
|
||||
}
|
||||
#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 />
|
||||
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>());
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<IEnumerable<Model>> GetTranscriptionModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
{
|
||||
return Task.FromResult(Enumerable.Empty<Model>());
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@@ -50,6 +50,16 @@ public interface IProvider
|
||||
/// <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);
|
||||
|
||||
/// <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>
|
||||
/// Load all possible text models that can be used with this provider.
|
||||
/// </summary>
|
||||
@@ -74,4 +84,11 @@ public interface IProvider
|
||||
/// <returns>The list of embedding models.</returns>
|
||||
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>
|
||||
/// <param name="llmProvider">The provider to check.</param>
|
||||
/// <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:
|
||||
@@ -141,6 +141,43 @@ public static class LLMProvidersExtensions
|
||||
|
||||
_ => 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>
|
||||
/// 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);
|
||||
}
|
||||
|
||||
/// <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 = "")
|
||||
{
|
||||
try
|
||||
@@ -274,6 +321,37 @@ public static class LLMProvidersExtensions
|
||||
LLMProviders.SELF_HOSTED => host is not Host.LM_STUDIO,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
public static bool IsTranscriptionModelProvidedManually(this LLMProviders provider, Host host) => provider switch
|
||||
{
|
||||
_ => false,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the model selection should be completely hidden for LLM providers.
|
||||
/// This is the case when the host does not support model selection (e.g., llama.cpp).
|
||||
/// </summary>
|
||||
/// <param name="provider">The provider.</param>
|
||||
/// <param name="host">The host for self-hosted providers.</param>
|
||||
/// <returns>True if model selection should be hidden; otherwise, false.</returns>
|
||||
public static bool IsLLMModelSelectionHidden(this LLMProviders provider, Host host) => provider switch
|
||||
{
|
||||
LLMProviders.SELF_HOSTED => host is Host.LLAMA_CPP,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the model selection should be completely hidden for transcription providers.
|
||||
/// This is the case when the host does not support model selection (e.g., whisper.cpp).
|
||||
/// </summary>
|
||||
/// <param name="provider">The provider.</param>
|
||||
/// <param name="host">The host for self-hosted providers.</param>
|
||||
/// <returns>True if model selection should be hidden; otherwise, false.</returns>
|
||||
public static bool IsTranscriptionModelSelectionHidden(this LLMProviders provider, Host host) => provider switch
|
||||
{
|
||||
LLMProviders.SELF_HOSTED => host is Host.WHISPER_CPP,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
public static bool IsHostNeeded(this LLMProviders provider) => provider switch
|
||||
{
|
||||
@@ -338,7 +416,8 @@ public static class LLMProvidersExtensions
|
||||
switch (host)
|
||||
{
|
||||
case Host.NONE:
|
||||
case Host.LLAMACPP:
|
||||
case Host.LLAMA_CPP:
|
||||
case Host.WHISPER_CPP:
|
||||
default:
|
||||
return false;
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
// Get the API key:
|
||||
var requestedSecret = await RUST_SERVICE.GetAPIKey(this);
|
||||
var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.LLM_PROVIDER);
|
||||
if(!requestedSecret.Success)
|
||||
yield break;
|
||||
|
||||
@@ -31,7 +31,7 @@ public sealed class ProviderMistral() : BaseProvider(LLMProviders.MISTRAL, "http
|
||||
var systemPrompt = new TextMessage
|
||||
{
|
||||
Role = "system",
|
||||
Content = chatThread.PrepareSystemPrompt(settingsManager, chatThread),
|
||||
Content = chatThread.PrepareSystemPrompt(settingsManager),
|
||||
};
|
||||
|
||||
// Parse the API parameters:
|
||||
@@ -81,11 +81,18 @@ public sealed class ProviderMistral() : BaseProvider(LLMProviders.MISTRAL, "http
|
||||
yield break;
|
||||
}
|
||||
#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 />
|
||||
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)
|
||||
return [];
|
||||
|
||||
@@ -99,7 +106,7 @@ public sealed class ProviderMistral() : BaseProvider(LLMProviders.MISTRAL, "http
|
||||
/// <inheritdoc />
|
||||
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)
|
||||
return [];
|
||||
|
||||
@@ -113,14 +120,25 @@ public sealed class ProviderMistral() : BaseProvider(LLMProviders.MISTRAL, "http
|
||||
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
|
||||
|
||||
private async Task<ModelsResponse> LoadModelList(string? apiKeyProvisional, CancellationToken token)
|
||||
private async Task<ModelsResponse> LoadModelList(SecretStoreType storeType, string? apiKeyProvisional, CancellationToken token)
|
||||
{
|
||||
var secretKey = apiKeyProvisional switch
|
||||
{
|
||||
not null => apiKeyProvisional,
|
||||
_ => await RUST_SERVICE.GetAPIKey(this) switch
|
||||
_ => await RUST_SERVICE.GetAPIKey(this, storeType) switch
|
||||
{
|
||||
{ Success: true } result => await result.Secret.Decrypt(ENCRYPTION),
|
||||
_ => null,
|
||||
|
||||
@@ -9,6 +9,22 @@ namespace AIStudio.Provider;
|
||||
/// <param name="DisplayName">The model's display name.</param>
|
||||
public readonly record struct Model(string Id, string? DisplayName)
|
||||
{
|
||||
/// <summary>
|
||||
/// Special model ID used when the model is selected by the system/host
|
||||
/// and cannot be changed by the user (e.g., llama.cpp, whisper.cpp).
|
||||
/// </summary>
|
||||
private const string SYSTEM_MODEL_ID = "::system::";
|
||||
|
||||
/// <summary>
|
||||
/// Creates a system-configured model placeholder.
|
||||
/// </summary>
|
||||
public static readonly Model SYSTEM_MODEL = new(SYSTEM_MODEL_ID, null);
|
||||
|
||||
/// <summary>
|
||||
/// Checks if this model is the system-configured placeholder.
|
||||
/// </summary>
|
||||
public bool IsSystemModel => this == SYSTEM_MODEL;
|
||||
|
||||
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(Model).Namespace, nameof(Model));
|
||||
|
||||
#region Overrides of ValueType
|
||||
|
||||
@@ -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>> 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)
|
||||
{
|
||||
@@ -36,6 +38,8 @@ public class NoProvider : IProvider
|
||||
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 ];
|
||||
|
||||
#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)
|
||||
{
|
||||
// Get the API key:
|
||||
var requestedSecret = await RUST_SERVICE.GetAPIKey(this);
|
||||
var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.LLM_PROVIDER);
|
||||
if(!requestedSecret.Success)
|
||||
yield break;
|
||||
|
||||
@@ -73,7 +73,7 @@ public sealed class ProviderOpenAI() : BaseProvider(LLMProviders.OPEN_AI, "https
|
||||
var systemPrompt = new TextMessage
|
||||
{
|
||||
Role = systemPromptRole,
|
||||
Content = chatThread.PrepareSystemPrompt(settingsManager, chatThread),
|
||||
Content = chatThread.PrepareSystemPrompt(settingsManager),
|
||||
};
|
||||
|
||||
//
|
||||
@@ -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
|
||||
|
||||
/// <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 />
|
||||
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) &&
|
||||
!model.Id.Contains("realtime", StringComparison.OrdinalIgnoreCase) &&
|
||||
!model.Id.Contains("audio", StringComparison.OrdinalIgnoreCase) &&
|
||||
@@ -232,23 +239,31 @@ public sealed class ProviderOpenAI() : BaseProvider(LLMProviders.OPEN_AI, "https
|
||||
/// <inheritdoc />
|
||||
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 />
|
||||
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
|
||||
|
||||
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
|
||||
{
|
||||
not null => apiKeyProvisional,
|
||||
_ => await RUST_SERVICE.GetAPIKey(this) switch
|
||||
_ => await RUST_SERVICE.GetAPIKey(this, storeType) switch
|
||||
{
|
||||
{ Success: true } result => await result.Secret.Decrypt(ENCRYPTION),
|
||||
_ => 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)
|
||||
{
|
||||
// Get the API key:
|
||||
var requestedSecret = await RUST_SERVICE.GetAPIKey(this);
|
||||
var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.LLM_PROVIDER);
|
||||
if(!requestedSecret.Success)
|
||||
yield break;
|
||||
|
||||
@@ -36,7 +36,7 @@ public sealed class ProviderOpenRouter() : BaseProvider(LLMProviders.OPEN_ROUTER
|
||||
var systemPrompt = new TextMessage
|
||||
{
|
||||
Role = "system",
|
||||
Content = chatThread.PrepareSystemPrompt(settingsManager, chatThread),
|
||||
Content = chatThread.PrepareSystemPrompt(settingsManager),
|
||||
};
|
||||
|
||||
// Parse the API parameters:
|
||||
@@ -88,11 +88,17 @@ public sealed class ProviderOpenRouter() : BaseProvider(LLMProviders.OPEN_ROUTER
|
||||
yield break;
|
||||
}
|
||||
#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 />
|
||||
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 />
|
||||
@@ -106,15 +112,21 @@ public sealed class ProviderOpenRouter() : BaseProvider(LLMProviders.OPEN_ROUTER
|
||||
{
|
||||
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
|
||||
|
||||
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
|
||||
{
|
||||
not null => apiKeyProvisional,
|
||||
_ => await RUST_SERVICE.GetAPIKey(this) switch
|
||||
_ => await RUST_SERVICE.GetAPIKey(this, storeType) switch
|
||||
{
|
||||
{ Success: true } result => await result.Secret.Decrypt(ENCRYPTION),
|
||||
_ => null,
|
||||
@@ -156,7 +168,7 @@ public sealed class ProviderOpenRouter() : BaseProvider(LLMProviders.OPEN_ROUTER
|
||||
var secretKey = apiKeyProvisional switch
|
||||
{
|
||||
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),
|
||||
_ => 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)
|
||||
{
|
||||
// Get the API key:
|
||||
var requestedSecret = await RUST_SERVICE.GetAPIKey(this);
|
||||
var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.LLM_PROVIDER);
|
||||
if(!requestedSecret.Success)
|
||||
yield break;
|
||||
|
||||
@@ -42,7 +42,7 @@ public sealed class ProviderPerplexity() : BaseProvider(LLMProviders.PERPLEXITY,
|
||||
var systemPrompt = new TextMessage
|
||||
{
|
||||
Role = "system",
|
||||
Content = chatThread.PrepareSystemPrompt(settingsManager, chatThread),
|
||||
Content = chatThread.PrepareSystemPrompt(settingsManager),
|
||||
};
|
||||
|
||||
// Parse the API parameters:
|
||||
@@ -88,6 +88,12 @@ public sealed class ProviderPerplexity() : BaseProvider(LLMProviders.PERPLEXITY,
|
||||
yield break;
|
||||
}
|
||||
#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 />
|
||||
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>());
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override Task<IEnumerable<Model>> GetTranscriptionModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
{
|
||||
return Task.FromResult(Enumerable.Empty<Model>());
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private Task<IEnumerable<Model>> LoadModels() => Task.FromResult<IEnumerable<Model>>(KNOWN_MODELS);
|
||||
|
||||
@@ -5,7 +5,8 @@ public enum Host
|
||||
NONE,
|
||||
|
||||
LM_STUDIO,
|
||||
LLAMACPP,
|
||||
LLAMA_CPP,
|
||||
WHISPER_CPP,
|
||||
OLLAMA,
|
||||
VLLM,
|
||||
}
|
||||
@@ -7,7 +7,8 @@ public static class HostExtensions
|
||||
Host.NONE => "None",
|
||||
|
||||
Host.LM_STUDIO => "LM Studio",
|
||||
Host.LLAMACPP => "llama.cpp",
|
||||
Host.LLAMA_CPP => "llama.cpp",
|
||||
Host.WHISPER_CPP => "whisper.cpp",
|
||||
Host.OLLAMA => "ollama",
|
||||
Host.VLLM => "vLLM",
|
||||
|
||||
@@ -24,7 +25,28 @@ public static class HostExtensions
|
||||
_ => "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)
|
||||
{
|
||||
@@ -34,7 +56,23 @@ public static class HostExtensions
|
||||
return true;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,12 +6,15 @@ using System.Text.Json;
|
||||
using AIStudio.Chat;
|
||||
using AIStudio.Provider.OpenAI;
|
||||
using AIStudio.Settings;
|
||||
using AIStudio.Tools.PluginSystem;
|
||||
|
||||
namespace AIStudio.Provider.SelfHosted;
|
||||
|
||||
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 string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(ProviderSelfHosted).Namespace, nameof(ProviderSelfHosted));
|
||||
|
||||
#region Implementation of IProvider
|
||||
|
||||
@@ -23,13 +26,13 @@ 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)
|
||||
{
|
||||
// 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:
|
||||
var systemPrompt = new TextMessage
|
||||
{
|
||||
Role = "system",
|
||||
Content = chatThread.PrepareSystemPrompt(settingsManager, chatThread),
|
||||
Content = chatThread.PrepareSystemPrompt(settingsManager),
|
||||
};
|
||||
|
||||
// Parse the API parameters:
|
||||
@@ -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
|
||||
|
||||
/// <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)
|
||||
{
|
||||
try
|
||||
{
|
||||
switch (host)
|
||||
{
|
||||
case Host.LLAMACPP:
|
||||
case Host.LLAMA_CPP:
|
||||
// Right now, llama.cpp only supports one model.
|
||||
// There is no API to list the model(s).
|
||||
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.OLLAMA:
|
||||
case Host.VLLM:
|
||||
return await this.LoadModels(["embed"], [], token, apiKeyProvisional);
|
||||
return await this.LoadModels( SecretStoreType.LLM_PROVIDER, ["embed"], [], token, apiKeyProvisional);
|
||||
}
|
||||
|
||||
return [];
|
||||
@@ -126,7 +136,7 @@ public sealed class ProviderSelfHosted(Host host, string hostname) : BaseProvide
|
||||
case Host.LM_STUDIO:
|
||||
case Host.OLLAMA:
|
||||
case Host.VLLM:
|
||||
return await this.LoadModels([], ["embed"], token, apiKeyProvisional);
|
||||
return await this.LoadModels( SecretStoreType.EMBEDDING_PROVIDER, [], ["embed"], token, apiKeyProvisional);
|
||||
}
|
||||
|
||||
return [];
|
||||
@@ -138,14 +148,42 @@ public sealed class ProviderSelfHosted(Host host, string hostname) : BaseProvide
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task<IEnumerable<Provider.Model>> GetTranscriptionModels(string? apiKeyProvisional = null, CancellationToken token = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
switch (host)
|
||||
{
|
||||
case Host.WHISPER_CPP:
|
||||
return new List<Provider.Model>
|
||||
{
|
||||
new("loaded-model", TB("Model as configured by whisper.cpp")),
|
||||
};
|
||||
|
||||
case Host.OLLAMA:
|
||||
case Host.VLLM:
|
||||
return await this.LoadModels(SecretStoreType.TRANSCRIPTION_PROVIDER, [], [], token, apiKeyProvisional);
|
||||
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
LOGGER.LogError($"Failed to load transcription models from self-hosted provider: {e.Message}");
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
#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
|
||||
{
|
||||
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),
|
||||
_ => null,
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace AIStudio.Provider;
|
||||
|
||||
public sealed record TranscriptionResponse(string Text);
|
||||
@@ -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)
|
||||
{
|
||||
// Get the API key:
|
||||
var requestedSecret = await RUST_SERVICE.GetAPIKey(this);
|
||||
var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.LLM_PROVIDER);
|
||||
if(!requestedSecret.Success)
|
||||
yield break;
|
||||
|
||||
@@ -33,7 +33,7 @@ public sealed class ProviderX() : BaseProvider(LLMProviders.X, "https://api.x.ai
|
||||
var systemPrompt = new TextMessage
|
||||
{
|
||||
Role = "system",
|
||||
Content = chatThread.PrepareSystemPrompt(settingsManager, chatThread),
|
||||
Content = chatThread.PrepareSystemPrompt(settingsManager),
|
||||
};
|
||||
|
||||
// Parse the API parameters:
|
||||
@@ -81,11 +81,17 @@ public sealed class ProviderX() : BaseProvider(LLMProviders.X, "https://api.x.ai
|
||||
yield break;
|
||||
}
|
||||
#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 />
|
||||
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));
|
||||
}
|
||||
|
||||
@@ -101,14 +107,20 @@ public sealed class ProviderX() : BaseProvider(LLMProviders.X, "https://api.x.ai
|
||||
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
|
||||
|
||||
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
|
||||
{
|
||||
not null => apiKeyProvisional,
|
||||
_ => await RUST_SERVICE.GetAPIKey(this) switch
|
||||
_ => await RUST_SERVICE.GetAPIKey(this, storeType) switch
|
||||
{
|
||||
{ Success: true } result => await result.Secret.Decrypt(ENCRYPTION),
|
||||
_ => null,
|
||||
|
||||
@@ -12,13 +12,12 @@ public record ChatTemplate(
|
||||
string SystemPrompt,
|
||||
string PredefinedUserPrompt,
|
||||
List<ContentBlock> ExampleConversation,
|
||||
List<FileAttachment> FileAttachments,
|
||||
bool AllowProfileUsage,
|
||||
bool IsEnterpriseConfiguration = false,
|
||||
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()
|
||||
{
|
||||
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,
|
||||
PredefinedUserPrompt = string.Empty,
|
||||
Id = Guid.Empty.ToString(),
|
||||
Num = uint.MaxValue,
|
||||
ExampleConversation = [],
|
||||
FileAttachments = [],
|
||||
AllowProfileUsage = true,
|
||||
EnterpriseConfigurationPluginId = Guid.Empty,
|
||||
IsEnterpriseConfiguration = false,
|
||||
@@ -61,7 +61,7 @@ public record ChatTemplate(
|
||||
public string GetSafeName()
|
||||
{
|
||||
if(this == NO_CHAT_TEMPLATE)
|
||||
return TB(USE_NO_CHAT_TEMPLATE_TEXT);
|
||||
return TB("Use no chat template");
|
||||
|
||||
return this.Name;
|
||||
}
|
||||
@@ -102,7 +102,9 @@ public record ChatTemplate(
|
||||
var allowProfileUsage = false;
|
||||
if (table.TryGetValue("AllowProfileUsage", out var allowProfileValue) && allowProfileValue.TryRead<bool>(out var allow))
|
||||
allowProfileUsage = allow;
|
||||
|
||||
|
||||
var fileAttachments = ParseFileAttachments(idx, table);
|
||||
|
||||
template = new ChatTemplate
|
||||
{
|
||||
Num = 0,
|
||||
@@ -111,6 +113,7 @@ public record ChatTemplate(
|
||||
SystemPrompt = systemPrompt,
|
||||
PredefinedUserPrompt = predefinedUserPrompt,
|
||||
ExampleConversation = ParseExampleConversation(idx, table),
|
||||
FileAttachments = fileAttachments,
|
||||
AllowProfileUsage = allowProfileUsage,
|
||||
IsEnterpriseConfiguration = true,
|
||||
EnterpriseConfigurationPluginId = configPluginId,
|
||||
@@ -165,4 +168,26 @@ public record ChatTemplate(
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
namespace AIStudio.Settings;
|
||||
|
||||
/// <summary>
|
||||
/// Enum representing assistants that can be hidden via configuration plugin.
|
||||
/// </summary>
|
||||
public enum ConfigurableAssistant
|
||||
{
|
||||
NONE = 0,
|
||||
UNKNOWN = 1,
|
||||
|
||||
GRAMMAR_SPELLING_ASSISTANT,
|
||||
ICON_FINDER_ASSISTANT,
|
||||
REWRITE_ASSISTANT,
|
||||
TRANSLATION_ASSISTANT,
|
||||
AGENDA_ASSISTANT,
|
||||
CODING_ASSISTANT,
|
||||
TEXT_SUMMARIZER_ASSISTANT,
|
||||
EMAIL_ASSISTANT,
|
||||
LEGAL_CHECK_ASSISTANT,
|
||||
SYNONYMS_ASSISTANT,
|
||||
MY_TASKS_ASSISTANT,
|
||||
JOB_POSTING_ASSISTANT,
|
||||
BIAS_DAY_ASSISTANT,
|
||||
ERI_ASSISTANT,
|
||||
DOCUMENT_ANALYSIS_ASSISTANT,
|
||||
|
||||
// ReSharper disable InconsistentNaming
|
||||
I18N_ASSISTANT,
|
||||
// ReSharper restore InconsistentNaming
|
||||
}
|
||||
@@ -203,6 +203,12 @@ public static class ConfigurationSelectDataFactory
|
||||
foreach (var profile in profiles.GetAllProfiles())
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -25,6 +25,11 @@ public sealed class Data
|
||||
/// A collection of embedding providers configured.
|
||||
/// </summary>
|
||||
public List<EmbeddingProvider> EmbeddingProviders { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// A collection of speech providers configured.
|
||||
/// </summary>
|
||||
public List<TranscriptionProvider> TranscriptionProviders { get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// A collection of data sources configured.
|
||||
@@ -52,9 +57,14 @@ public sealed class Data
|
||||
public uint NextProviderNum { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// The next embedding number to use.
|
||||
/// The next embedding provider number to use.
|
||||
/// </summary>
|
||||
public uint NextEmbeddingNum { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// The next transcription provider number to use.
|
||||
/// </summary>
|
||||
public uint NextTranscriptionNum { get; set; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// The next data source number to use.
|
||||
|
||||
@@ -77,8 +77,25 @@ public sealed class DataApp(Expression<Func<Data, DataApp>>? configSelection = n
|
||||
/// </summary>
|
||||
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>
|
||||
/// The global keyboard shortcut for toggling voice recording.
|
||||
/// Uses Tauri's shortcut format, e.g., "CmdOrControl+1" (Cmd+1 on macOS, Ctrl+1 on Windows/Linux).
|
||||
/// Set to empty string to disable the global shortcut.
|
||||
/// </summary>
|
||||
public string ShortcutVoiceRecording { get; set; } = ManagedConfiguration.Register(configSelection, n => n.ShortcutVoiceRecording, string.Empty);
|
||||
|
||||
/// <summary>
|
||||
/// Should the user be allowed to add providers?
|
||||
/// </summary>
|
||||
public bool AllowUserToAddProvider { get; set; } = ManagedConfiguration.Register(configSelection, n => n.AllowUserToAddProvider, true);
|
||||
|
||||
/// <summary>
|
||||
/// List of assistants that should be hidden from the UI.
|
||||
/// </summary>
|
||||
public HashSet<ConfigurableAssistant> HiddenAssistants { get; set; } = ManagedConfiguration.Register(configSelection, n => n.HiddenAssistants, []);
|
||||
}
|
||||
@@ -2,6 +2,8 @@ namespace AIStudio.Settings.DataModel;
|
||||
|
||||
public enum PreviewFeatures
|
||||
{
|
||||
NONE = 0,
|
||||
|
||||
//
|
||||
// Important: Never delete any enum value from this list.
|
||||
// We must be able to deserialize old settings files that may contain these values.
|
||||
@@ -12,4 +14,5 @@ public enum PreviewFeatures
|
||||
PRE_PLUGINS_2025,
|
||||
PRE_READ_PDF_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_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_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")
|
||||
};
|
||||
|
||||
@@ -21,6 +21,7 @@ public static class PreviewVisibilityExtensions
|
||||
{
|
||||
features.Add(PreviewFeatures.PRE_RAG_2024);
|
||||
features.Add(PreviewFeatures.PRE_DOCUMENT_ANALYSIS_2025);
|
||||
features.Add(PreviewFeatures.PRE_SPEECH_TO_TEXT_2026);
|
||||
}
|
||||
|
||||
if (visibility >= PreviewVisibility.EXPERIMENTAL)
|
||||
|
||||
@@ -1,32 +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 readonly record struct EmbeddingProvider(
|
||||
public sealed record EmbeddingProvider(
|
||||
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) : 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;
|
||||
|
||||
|
||||
#region Implementation of ISecretId
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
[JsonIgnore]
|
||||
public string SecretId => this.Id;
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
[JsonIgnore]
|
||||
public string SecretName => this.Name;
|
||||
|
||||
|
||||
#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;
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ public record Profile(
|
||||
|
||||
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,
|
||||
Actions = string.Empty,
|
||||
Id = Guid.Empty.ToString(),
|
||||
@@ -39,6 +39,23 @@ public record Profile(
|
||||
|
||||
#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()
|
||||
{
|
||||
if(this.Num == uint.MaxValue)
|
||||
|
||||
@@ -263,7 +263,7 @@ public sealed class SettingsManager
|
||||
if (preselection != Profile.NO_PROFILE)
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -273,7 +273,7 @@ public sealed class SettingsManager
|
||||
if (preselection != ChatTemplate.NO_CHAT_TEMPLATE)
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
Loaded 100 of 171 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user