Compare commits

...
23 Commits
Author SHA1 Message Date
Thorsten Sommer b9b850b2c1 Prepared release v0.9.44 (#470) 2025-05-18 21:37:54 +02:00
Thorsten Sommer 9aa0a91465 Added more text content to the localization (#469) 2025-05-18 21:31:15 +02:00
Thorsten Sommer 217d31bc44 Released PDF import feature (#468) 2025-05-18 13:50:53 +02:00
Thorsten Sommer 038ddec46b Upgraded dependencies to .NET 9.0.5 and Rust 1.87.0 (#467) 2025-05-17 21:07:29 +02:00
Thorsten Sommer d02efce689 Improved table views for providers, embeddings, and profiles (#466) 2025-05-17 20:54:51 +02:00
Thorsten Sommer 2e58ecb97d Fixed a rare bug regarding renaming chats (#465) 2025-05-17 20:35:51 +02:00
Thorsten Sommer a548cabe24 Improved compatibility with some Ubuntu linux versions (#464) 2025-05-17 19:53:08 +02:00
Thorsten Sommer 4b2da61a90 Fixed an issue that caused live translation to stop working (#463) 2025-05-17 19:17:29 +02:00
Thorsten Sommer 2ae292009d Improved I18N (#462) 2025-05-17 14:22:10 +02:00
Thorsten Sommer 03e196b66f Improved dev experience & allow native .NET debugging (#461) 2025-05-17 13:49:43 +02:00
Thorsten Sommer 4ca1fd54d9 Improved dev experience & allow native .NET debugging (#460) 2025-05-17 13:36:28 +02:00
Thorsten Sommer c0cf620fe3 Refactored some text fields to use the DebouncedTextField component (#459) 2025-05-16 15:36:08 +02:00
Thorsten Sommer 47a0598e8f Improved German translations and localization code (#458) 2025-05-12 20:57:03 +02:00
Thorsten Sommer 85a867459e Prepared release v0.9.43 (#457) 2025-05-11 19:30:25 +02:00
Thorsten Sommer ba236c4012 Add the DLR as the first titan supporter (#456) 2025-05-11 17:40:24 +02:00
Thorsten Sommer 9fa0f95611 Localized the embedding configuration (#455) 2025-05-11 16:57:52 +02:00
Thorsten Sommer f10d9a5d88 Improved the embedding configuration dialog (#454) 2025-05-11 15:35:19 +02:00
Thorsten Sommer 1a4e4781cd Enhanced Google model selection and updated the changelog (#453) 2025-05-11 15:01:04 +02:00
Thorsten Sommer 99b4d616c7 Updated changelog (#452) 2025-05-11 14:40:39 +02:00
Peer Schütt ace125d7cb Proofreading the automatic German translation (#451) 2025-05-11 13:58:43 +02:00
Thorsten Sommer d6af5a3afd Add model capabilities (#450) 2025-05-11 12:51:35 +02:00
Thorsten Sommer 07b858fd72 Updated README to reflect recent I18N enhancements (#449) 2025-05-05 18:49:06 +02:00
Thorsten Sommer 5d7949211f Fixed build script for Windows by fixing Windows PDFium download (#448) 2025-05-05 08:42:54 +02:00
88 changed files with 4335 additions and 652 deletions

No files matched your search

+2 -2
View File
@@ -425,13 +425,13 @@ jobs:
if: matrix.platform == 'ubuntu-22.04' && contains(matrix.rust_target, 'x86_64')
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf
sudo apt-get install -y libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf libfuse2
- name: Setup dependencies (Ubuntu-specific, ARM)
if: matrix.platform == 'ubuntu-22.04-arm' && contains(matrix.rust_target, 'aarch64')
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf
sudo apt-get install -y libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf libfuse2
- name: Setup Tauri (Unix)
if: matrix.platform != 'windows-latest'
+3
View File
@@ -1,3 +1,6 @@
# Ignore any startup.env file:
startup.env
# Ignore pdfium library:
libpdfium.dylib
libpdfium.so
+2 -2
View File
@@ -31,9 +31,9 @@ Things we are currently working on:
- [x] ~~Plan & implement the base plugin system ([PR #322](https://github.com/MindWorkAI/AI-Studio/pull/322))~~
- [x] ~~Start the plugin system ([PR #372](https://github.com/MindWorkAI/AI-Studio/pull/372))~~
- [x] ~~Added hot-reload support for plugins ([PR #377](https://github.com/MindWorkAI/AI-Studio/pull/377), [PR #391](https://github.com/MindWorkAI/AI-Studio/pull/391))~~
- [ ] Add support for other languages (I18N) to AI Studio (~~[PR #381](https://github.com/MindWorkAI/AI-Studio/pull/381), [PR #400](https://github.com/MindWorkAI/AI-Studio/pull/400), [PR #404](https://github.com/MindWorkAI/AI-Studio/pull/404), [PR #429](https://github.com/MindWorkAI/AI-Studio/pull/429))~~
- [ ] Add support for other languages (I18N) to AI Studio (~~[PR #381](https://github.com/MindWorkAI/AI-Studio/pull/381), [PR #400](https://github.com/MindWorkAI/AI-Studio/pull/400), [PR #404](https://github.com/MindWorkAI/AI-Studio/pull/404), [PR #429](https://github.com/MindWorkAI/AI-Studio/pull/429), [PR #446](https://github.com/MindWorkAI/AI-Studio/pull/446))~~
- [x] ~~Add an I18N assistant to translate all AI Studio texts to a certain language & culture ([PR #422](https://github.com/MindWorkAI/AI-Studio/pull/422))~~
- [ ] Provide MindWork AI Studio in German ([PR #430](https://github.com/MindWorkAI/AI-Studio/pull/430))
- [ ] Provide MindWork AI Studio in German (~~[PR #430](https://github.com/MindWorkAI/AI-Studio/pull/430), [PR #446](https://github.com/MindWorkAI/AI-Studio/pull/446)~~)
- [ ] Add configuration plugins, which allow pre-defining some LLM providers in organizations
- [ ] Add an app store for plugins, showcasing community-contributed plugins from public GitHub and GitLab repositories. This will enable AI Studio users to discover, install, and update plugins directly within the platform.
- [ ] Add assistant plugins
@@ -1,5 +1,5 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Tauri Dev" type="ShConfigurationType">
<configuration default="false" name="[1] Start Tauri" type="ShConfigurationType">
<option name="SCRIPT_TEXT" value="cargo tauri dev --no-watch" />
<option name="INDEPENDENT_SCRIPT_PATH" value="true" />
<option name="SCRIPT_PATH" value="" />
+27
View File
@@ -0,0 +1,27 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="[2] Start .NET Server" type="DotNetProject" factoryName=".NET Project">
<option name="EXE_PATH" value="$PROJECT_DIR$/MindWork AI Studio/bin/Debug/net9.0/osx-arm64/mindworkAIStudio" />
<option name="PROGRAM_PARAMETERS" value="" />
<option name="WORKING_DIRECTORY" value="$PROJECT_DIR$/MindWork AI Studio" />
<option name="PASS_PARENT_ENVS" value="1" />
<option name="USE_EXTERNAL_CONSOLE" value="0" />
<option name="ENV_FILE_PATHS" value="" />
<option name="REDIRECT_INPUT_PATH" value="" />
<option name="PTY_MODE" value="Auto" />
<option name="USE_MONO" value="0" />
<option name="RUNTIME_ARGUMENTS" value="" />
<option name="AUTO_ATTACH_CHILDREN" value="0" />
<option name="MIXED_MODE_DEBUG" value="0" />
<option name="RUNTIME_TYPE" value="coreclr" />
<option name="PROJECT_PATH" value="$PROJECT_DIR$/MindWork AI Studio/MindWork AI Studio.csproj" />
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<option name="PROJECT_KIND" value="DotNetCore" />
<option name="PROJECT_TFM" value="net9.0" />
<method v="2">
<option name="Build" />
<option name="RunConfigurationTask" enabled="true" run_configuration_name="Collect I18N content" run_configuration_type="ShConfigurationType" />
</method>
</configuration>
</component>
+3 -3
View File
@@ -25,7 +25,7 @@ public static class Pdfium
var response = await client.GetAsync(pdfiumUrl);
if (!response.IsSuccessStatusCode)
{
Console.WriteLine($" failed to download Pdfium {version} for {rid.ToUserFriendlyName()}");
Console.WriteLine($" failed to download Pdfium {version} for {rid.ToUserFriendlyName()} from {pdfiumUrl}");
return;
}
@@ -98,8 +98,8 @@ public static class Pdfium
RID.OSX_ARM64 => $"{baseUrl}mac-arm64.tgz",
RID.OSX_X64 => $"{baseUrl}mac-x64.tgz",
RID.WIN_ARM64 => $"{baseUrl}win-arm64.zip",
RID.WIN_X64 => $"{baseUrl}win-x64.zip",
RID.WIN_ARM64 => $"{baseUrl}win-arm64.tgz",
RID.WIN_X64 => $"{baseUrl}win-x64.tgz",
_ => string.Empty,
};
@@ -1,51 +1,55 @@
@attribute [Route(Routes.ASSISTANT_AGENDA)]
@inherits AssistantBaseCore<AIStudio.Dialogs.Settings.SettingsDialogAgenda>
<MudTextField T="string" @bind-Text="@this.inputName" Validation="@this.ValidateName" Label="Meeting Name" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Tag" Variant="Variant.Outlined" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES" HelperText="Name the meeting, seminar, etc." Placeholder="Weekly jour fixe" Class="mb-3"/>
<MudTextField T="string" @bind-Text="@this.inputTopic" Validation="@this.ValidateTopic" Label="Topic" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.EventNote" Variant="Variant.Outlined" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES" HelperText="Describe the topic of the meeting, seminar, etc. Is it about quantum computing, software engineering, or is it a general business meeting?" Placeholder="Project meeting" Class="mb-3"/>
<MudTextField T="string" @bind-Text="@this.inputContent" Validation="@this.ValidateContent" Label="Content list" Variant="Variant.Outlined" Lines="6" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES" HelperText="Bullet list the content of the meeting, seminar, etc. roughly. Use dashes (-) to separate the items." Placeholder="@PLACEHOLDER_CONTENT" Class="mb-3" Immediate="@false" DebounceInterval="1_000" OnDebounceIntervalElapsed="@this.OnContentChanged" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.ListAlt"/>
<MudSelect T="string" Label="(Optional) What topics should be the focus?" MultiSelection="@true" @bind-SelectedValues="@this.selectedFoci" Variant="Variant.Outlined" Class="mb-3" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.ListAlt">
<MudTextField T="string" @bind-Text="@this.inputName" Validation="@this.ValidateName" Label="@T("Meeting Name")" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Tag" Variant="Variant.Outlined" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES" HelperText="@T("Name the meeting, seminar, etc.")" Placeholder="@T("Weekly jour fixe")" Class="mb-3"/>
<MudTextField T="string" @bind-Text="@this.inputTopic" Validation="@this.ValidateTopic" Label="@T("Topic")" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.EventNote" Variant="Variant.Outlined" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES" HelperText="@T("Describe the topic of the meeting, seminar, etc. Is it about quantum computing, software engineering, or is it a general business meeting?")" Placeholder="@T("Project meeting")" Class="mb-3"/>
<DebouncedTextField @bind-Text="@this.inputContent" ValidationFunc="@this.ValidateContent" DebounceTime="TimeSpan.FromSeconds(1)" Label="@T("Content list")" Lines="6" Attributes="@USER_INPUT_ATTRIBUTES" HelpText="@T("Bullet list the content of the meeting, seminar, etc. roughly. Use dashes (-) to separate the items.")" Placeholder="@PLACEHOLDER_CONTENT" WhenTextCanged="@this.OnContentChanged" Icon="@Icons.Material.Filled.ListAlt"/>
<MudSelect T="string" Label="@T("(Optional) What topics should be the focus?")" MultiSelection="@true" @bind-SelectedValues="@this.selectedFoci" Variant="Variant.Outlined" Class="mb-3" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.ListAlt">
@foreach (var contentLine in this.contentLines)
{
@if(!this.justBriefly.Contains(contentLine))
{
<MudSelectItem T="string" Value="@contentLine">@contentLine</MudSelectItem>
<MudSelectItem T="string" Value="@contentLine">
@contentLine
</MudSelectItem>
}
}
</MudSelect>
<MudSelect T="string" Label="(Optional) What topics should only be briefly addressed?" MultiSelection="@true" @bind-SelectedValues="@this.justBriefly" Variant="Variant.Outlined" Class="mb-3" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.ListAlt">
<MudSelect T="string" Label="@T("(Optional) What topics should only be briefly addressed?")" MultiSelection="@true" @bind-SelectedValues="@this.justBriefly" Variant="Variant.Outlined" Class="mb-3" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.ListAlt">
@foreach (var contentLine in this.contentLines)
{
@if(!this.selectedFoci.Contains(contentLine))
{
<MudSelectItem T="string" Value="@contentLine">@contentLine</MudSelectItem>
<MudSelectItem T="string" Value="@contentLine">
@contentLine
</MudSelectItem>
}
}
</MudSelect>
<MudTextField T="string" @bind-Text="@this.inputObjective" Validation="@this.ValidateObjective" Label="Objective" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Flag" Variant="Variant.Outlined" Lines="3" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES" HelperText="Describe the objective(s) of the meeting, seminar, etc. What should be achieved?" Placeholder="Discuss the current project status and plan the next steps." Class="mb-3"/>
<MudTextField T="string" @bind-Text="@this.inputModerator" Validation="@this.ValidateModerator" Label="Moderator" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Person3" Variant="Variant.Outlined" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES" HelperText="Who will moderate the meeting, seminar, etc.?" Placeholder="Jane Doe" Class="mb-3" />
<MudTextField T="string" @bind-Text="@this.inputDuration" Validation="@this.ValidateDuration" Label="Duration" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Schedule" Variant="Variant.Outlined" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES" HelperText="How long will the meeting, seminar, etc. last? E.g., '2 hours', or '2 days (first day 8 hours, then 4 hours)', etc." Placeholder="2 hours" Class="mb-3"/>
<MudTextField T="string" @bind-Text="@this.inputStartTime" Validation="@this.ValidateStartTime" Label="Start time" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Schedule" Variant="Variant.Outlined" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES" HelperText="When will the meeting, seminar, etc. start? E.g., '9:00 AM', or '9:00 AM (CET)', etc. When the meeting is a multi-day event, specify the start time for each day." Placeholder="9:00 AM" Class="mb-3"/>
<MudTextField T="string" @bind-Text="@this.inputWhoIsPresenting" Label="(Optional) Who is presenting?" Variant="Variant.Outlined" Lines="6" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES" HelperText="(Optional) List the persons who will present at the meeting, seminar, etc. Use dashes (-) to separate the items." Placeholder="@PLACEHOLDER_WHO_IS_PRESENTING" Class="mb-3"/>
<MudTextSwitch Label="Do the participants need to get to know each other first?" @bind-Value="@this.introduceParticipants" LabelOn="Yes, introduce participants" LabelOff="No, participants know each other" />
<EnumSelection T="NumberParticipants" @bind-Value="@this.numberParticipants" NameFunc="@(participants => participants.Name())" Icon="@Icons.Material.Filled.Group" Label="Number of participants" ValidateSelection="@this.ValidateNumberParticipants"/>
<MudTextSwitch Label="Should the participants be involved passively or actively?" @bind-Value="@this.activeParticipation" LabelOn="Active participation, like world café, discussions, etc." LabelOff="Passive participation, like presentations, lectures, etc." />
<MudTextSwitch Label="Is this a virtual event, e.g., a call or webinar?" @bind-Value="@this.isMeetingVirtual" LabelOn="Yes, this is a virtual event" LabelOff="No, this is a physical event" />
<MudTextField T="string" @bind-Text="@this.inputObjective" Validation="@this.ValidateObjective" Label="@T("Objective")" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Flag" Variant="Variant.Outlined" Lines="3" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES" HelperText="@T("Describe the objective(s) of the meeting, seminar, etc. What should be achieved?")" Placeholder="@T("Discuss the current project status and plan the next steps.")" Class="mb-3"/>
<MudTextField T="string" @bind-Text="@this.inputModerator" Validation="@this.ValidateModerator" Label="@T("Moderator")" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Person3" Variant="Variant.Outlined" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES" HelperText="@T("Who will moderate the meeting, seminar, etc.?")" Placeholder="Jane Doe" Class="mb-3" />
<MudTextField T="string" @bind-Text="@this.inputDuration" Validation="@this.ValidateDuration" Label="@T("Duration")" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Schedule" Variant="Variant.Outlined" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES" HelperText="@T("How long will the meeting, seminar, etc. last? E.g., '2 hours', or '2 days (first day 8 hours, then 4 hours)', etc.")" Placeholder="@T("2 hours")" Class="mb-3"/>
<MudTextField T="string" @bind-Text="@this.inputStartTime" Validation="@this.ValidateStartTime" Label="Start time" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Schedule" Variant="Variant.Outlined" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES" HelperText="@T("When will the meeting, seminar, etc. start? E.g., '9:00 AM', or '9:00 AM (CET)', etc. When the meeting is a multi-day event, specify the start time for each day.")" Placeholder="@T("9:00 AM")" Class="mb-3"/>
<MudTextField T="string" @bind-Text="@this.inputWhoIsPresenting" Label="@T("(Optional) Who is presenting?")" Variant="Variant.Outlined" Lines="6" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES" HelperText="@T("(Optional) List the persons who will present at the meeting, seminar, etc. Use dashes (-) to separate the items.")" Placeholder="@PLACEHOLDER_WHO_IS_PRESENTING" Class="mb-3"/>
<MudTextSwitch Label="@T("Do the participants need to get to know each other first?")" @bind-Value="@this.introduceParticipants" LabelOn="@T("Yes, introduce participants")" LabelOff="@T("No, participants know each other")" />
<EnumSelection T="NumberParticipants" @bind-Value="@this.numberParticipants" NameFunc="@(participants => participants.Name())" Icon="@Icons.Material.Filled.Group" Label="@T("Number of participants")" ValidateSelection="@this.ValidateNumberParticipants"/>
<MudTextSwitch Label="@T("Should the participants be involved passively or actively?")" @bind-Value="@this.activeParticipation" LabelOn="@T("Active participation, like world café, discussions, etc.")" LabelOff="@T("Passive participation, like presentations, lectures, etc.")" />
<MudTextSwitch Label="@T("Is this a virtual event, e.g., a call or webinar?")" @bind-Value="@this.isMeetingVirtual" LabelOn="@T("Yes, this is a virtual event")" LabelOff="@T("No, this is a physical event")" />
@if (!this.isMeetingVirtual)
{
<MudPaper Class="pa-3 mb-8 border-dashed border rounded-lg">
<MudTextField T="string" @bind-Text="@this.inputLocation" Validation="@this.ValidateLocation" Label="Location" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.MyLocation" Variant="Variant.Outlined" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES" HelperText="Where will the meeting, seminar, etc. take place?" Placeholder="Hamburg, Germany" Class="mb-3"/>
<MudTextSwitch Label="Should there be a joint dinner?" @bind-Value="@this.goingToDinner" LabelOn="Yes, there should be a joint dinner" LabelOff="No, there should be no joint dinner" />
<MudTextSwitch Label="Should there be a social activity?" @bind-Value="@this.doingSocialActivity" LabelOn="Yes, there should be a social activity" LabelOff="No, there should be no social activity" />
<MudTextSwitch Label="Do participants need to arrive and depart?" @bind-Value="@this.needToArriveAndDepart" LabelOn="Yes, participants need to arrive and depart" LabelOff="No, participants do not need to arrive and depart" />
<MudTextField T="string" @bind-Text="@this.inputLocation" Validation="@this.ValidateLocation" Label="@T("Location")" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.MyLocation" Variant="Variant.Outlined" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES" HelperText="@T("Where will the meeting, seminar, etc. take place?")" Placeholder="@T("Hamburg, Germany")" Class="mb-3"/>
<MudTextSwitch Label="@T("Should there be a joint dinner?")" @bind-Value="@this.goingToDinner" LabelOn="@T("Yes, there should be a joint dinner")" LabelOff="@T("No, there should be no joint dinner")" />
<MudTextSwitch Label="@T("Should there be a social activity?")" @bind-Value="@this.doingSocialActivity" LabelOn="@T("Yes, there should be a social activity")" LabelOff="@T("No, there should be no social activity")" />
<MudTextSwitch Label="@T("Do participants need to arrive and depart?")" @bind-Value="@this.needToArriveAndDepart" LabelOn="@T("Yes, participants need to arrive and depart")" LabelOff="@T("No, participants do not need to arrive and depart")" />
<MudStack Row="@true" Wrap="Wrap.Wrap">
<MudTextSlider T="int" Label="Approx. duration of the lunch break" @bind-Value="@this.durationLunchBreak" Min="30" Max="120" Step="5" Unit="minutes"/>
<MudTextSlider T="int" Label="Approx. duration of the coffee or tea breaks" @bind-Value="@this.durationBreaks" Min="10" Max="60" Step="5" Unit="minutes"/>
<MudTextSlider T="int" Label="@T("Approx. duration of the lunch break")" @bind-Value="@this.durationLunchBreak" Min="30" Max="120" Step="5" Unit="@T("minutes")"/>
<MudTextSlider T="int" Label="@T("Approx. duration of the coffee or tea breaks")" @bind-Value="@this.durationBreaks" Min="10" Max="60" Step="5" Unit="@T("minutes")"/>
</MudStack>
</MudPaper>
}
<EnumSelection T="CommonLanguages" NameFunc="@(language => language.NameSelecting())" @bind-Value="@this.selectedTargetLanguage" ValidateSelection="@this.ValidateTargetLanguage" Icon="@Icons.Material.Filled.Translate" Label="Target language" AllowOther="@true" OtherValue="CommonLanguages.OTHER" @bind-OtherInput="@this.customTargetLanguage" ValidateOther="@this.ValidateCustomLanguage" LabelOther="Custom target language" />
<EnumSelection T="CommonLanguages" NameFunc="@(language => language.NameSelecting())" @bind-Value="@this.selectedTargetLanguage" ValidateSelection="@this.ValidateTargetLanguage" Icon="@Icons.Material.Filled.Translate" Label="@T("Target language")" AllowOther="@true" OtherValue="CommonLanguages.OTHER" @bind-OtherInput="@this.customTargetLanguage" ValidateOther="@this.ValidateCustomLanguage" LabelOther="@T("Custom target language")" />
<ProviderSelection @bind-ProviderSettings="@this.providerSettings" ValidateProvider="@this.ValidatingProvider"/>
@@ -9,14 +9,9 @@ public partial class AssistantAgenda : AssistantBaseCore<SettingsDialogAgenda>
{
public override Tools.Components Component => Tools.Components.AGENDA_ASSISTANT;
protected override string Title => "Agenda Planner";
protected override string Title => T("Agenda Planner");
protected override string Description =>
"""
This agenda planner helps you create a structured agenda for your meeting or seminar. Just provide some basic
information about the event, and the assistant will generate an agenda for you. You can also specify the
duration, the start time, the location, the target language, and other details.
""";
protected override string Description => T("This agenda planner helps you create a structured agenda for your meeting or seminar. Just provide some basic information about the event, and the assistant will generate an agenda for you. You can also specify the duration, the start time, the location, the target language, and other details.");
protected override string SystemPrompt =>
$"""
@@ -98,7 +93,7 @@ public partial class AssistantAgenda : AssistantBaseCore<SettingsDialogAgenda>
protected override IReadOnlyList<IButtonData> FooterButtons => [];
protected override string SubmitText => "Create Agenda";
protected override string SubmitText => T("Create Agenda");
protected override Func<Task> SubmitAction => this.CreateAgenda;
@@ -235,7 +230,7 @@ public partial class AssistantAgenda : AssistantBaseCore<SettingsDialogAgenda>
private string? ValidateLocation(string location)
{
if(!this.isMeetingVirtual && string.IsNullOrWhiteSpace(location))
return "Please provide a location for the meeting or the seminar.";
return T("Please provide a location for the meeting or the seminar.");
return null;
}
@@ -243,7 +238,7 @@ public partial class AssistantAgenda : AssistantBaseCore<SettingsDialogAgenda>
private string? ValidateNumberParticipants(NumberParticipants selectedSize)
{
if(selectedSize is NumberParticipants.NOT_SPECIFIED)
return "Please select the number of participants.";
return T("Please select the number of participants.");
return null;
}
@@ -251,7 +246,7 @@ public partial class AssistantAgenda : AssistantBaseCore<SettingsDialogAgenda>
private string? ValidateTargetLanguage(CommonLanguages language)
{
if(language is CommonLanguages.AS_IS)
return "Please select a target language for the agenda.";
return T("Please select a target language for the agenda.");
return null;
}
@@ -259,7 +254,7 @@ public partial class AssistantAgenda : AssistantBaseCore<SettingsDialogAgenda>
private string? ValidateDuration(string duration)
{
if(string.IsNullOrWhiteSpace(duration))
return "Please provide a duration for the meeting or the seminar, e.g. '2 hours', or '2 days (8 hours and 4 hours)', etc.";
return T("Please provide a duration for the meeting or the seminar, e.g. '2 hours', or '2 days (8 hours and 4 hours)', etc.");
return null;
}
@@ -267,7 +262,7 @@ public partial class AssistantAgenda : AssistantBaseCore<SettingsDialogAgenda>
private string? ValidateStartTime(string startTime)
{
if(string.IsNullOrWhiteSpace(startTime))
return "Please provide a start time for the meeting or the seminar. When the meeting is a multi-day event, specify the start time for each day, e.g. '9:00 AM, 10:00 AM', etc.";
return T("Please provide a start time for the meeting or the seminar. When the meeting is a multi-day event, specify the start time for each day, e.g. '9:00 AM, 10:00 AM', etc.");
return null;
}
@@ -275,7 +270,7 @@ public partial class AssistantAgenda : AssistantBaseCore<SettingsDialogAgenda>
private string? ValidateCustomLanguage(string language)
{
if(this.selectedTargetLanguage == CommonLanguages.OTHER && string.IsNullOrWhiteSpace(language))
return "Please provide a custom language.";
return T("Please provide a custom language.");
return null;
}
@@ -283,7 +278,7 @@ public partial class AssistantAgenda : AssistantBaseCore<SettingsDialogAgenda>
private string? ValidateTopic(string topic)
{
if(string.IsNullOrWhiteSpace(topic))
return "Please provide a topic for the agenda. What is the meeting or the seminar about?";
return T("Please provide a topic for the agenda. What is the meeting or the seminar about?");
return null;
}
@@ -291,7 +286,7 @@ public partial class AssistantAgenda : AssistantBaseCore<SettingsDialogAgenda>
private string? ValidateName(string name)
{
if(string.IsNullOrWhiteSpace(name))
return "Please provide a name for the meeting or the seminar.";
return T("Please provide a name for the meeting or the seminar.");
return null;
}
@@ -299,12 +294,12 @@ public partial class AssistantAgenda : AssistantBaseCore<SettingsDialogAgenda>
private string? ValidateContent(string content)
{
if(string.IsNullOrWhiteSpace(content))
return "Please provide some content for the agenda. What are the main points of the meeting or the seminar?";
return T("Please provide some content for the agenda. What are the main points of the meeting or the seminar?");
var lines = content.Split('\n', StringSplitOptions.RemoveEmptyEntries);
foreach (var line in lines)
if(!line.TrimStart().StartsWith('-'))
return "Please start each line of your content list with a dash (-) to create a bullet point list.";
return T("Please start each line of your content list with a dash (-) to create a bullet point list.");
return null;
}
@@ -312,7 +307,7 @@ public partial class AssistantAgenda : AssistantBaseCore<SettingsDialogAgenda>
private string? ValidateObjective(string objective)
{
if(string.IsNullOrWhiteSpace(objective))
return "Please provide an objective for the meeting or the seminar. What do you want to achieve?";
return T("Please provide an objective for the meeting or the seminar. What do you want to achieve?");
return null;
}
@@ -320,7 +315,7 @@ public partial class AssistantAgenda : AssistantBaseCore<SettingsDialogAgenda>
private string? ValidateModerator(string moderator)
{
if(string.IsNullOrWhiteSpace(moderator))
return "Please provide a moderator for the meeting or the seminar. Who will lead the discussion?";
return T("Please provide a moderator for the meeting or the seminar. Who will lead the discussion?");
return null;
}
@@ -144,7 +144,7 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
protected string? ValidatingProvider(AIStudio.Settings.Provider provider)
{
if(provider.UsedLLMProvider == LLMProviders.NONE)
return "Please select a provider.";
return this.TB("Please select a provider.");
return null;
}
@@ -196,7 +196,7 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
SystemPrompt = this.SystemPrompt,
WorkspaceId = Guid.Empty,
ChatId = Guid.NewGuid(),
Name = string.Format(T("Assistant - {0}"), this.Title),
Name = string.Format(this.TB("Assistant - {0}"), this.Title),
Seed = this.RNG.Next(),
Blocks = [],
};
@@ -242,6 +242,9 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
protected async Task<string> AddAIResponseAsync(DateTimeOffset time, bool hideContentFromUser = false)
{
var manageCancellationLocally = this.cancellationTokenSource is null;
this.cancellationTokenSource ??= new CancellationTokenSource();
var aiText = new ContentText
{
// We have to wait for the remote
@@ -275,6 +278,12 @@ public abstract partial class AssistantBase<TSettings> : AssistantLowerBase wher
this.isProcessing = false;
this.StateHasChanged();
if(manageCancellationLocally)
{
this.cancellationTokenSource.Dispose();
this.cancellationTokenSource = null;
}
// Return the AI response:
return aiText.Text;
}
@@ -5,10 +5,10 @@
<b>Links:</b>
</MudText>
<MudList T="string" Class="mb-6">
<MudListItem T="string" Icon="@Icons.Material.Filled.Link" Target="_blank" Href="https://en.wikipedia.org/wiki/List_of_cognitive_biases">Wikipedia list of cognitive biases</MudListItem>
<MudListItem T="string" Icon="@Icons.Material.Filled.Link" Target="_blank" Href="https://commons.wikimedia.org/wiki/File:Cognitive_Bias_Codex_With_Definitions_1-2,_an_Extension_of_the_work_of_John_Manoogian_by_Brian_Rene_Morrissette.png">Extended bias poster</MudListItem>
<MudListItem T="string" Icon="@Icons.Material.Filled.Link" Target="_blank" Href="https://betterhumans.pub/cognitive-bias-cheat-sheet-55a472476b18">Blog post of Buster Benson: "Cognitive bias cheat sheet"</MudListItem>
<MudListItem T="string" Icon="@Icons.Material.Filled.Link" Target="_blank" Href="https://en.wikipedia.org/wiki/List_of_cognitive_biases">@T("Wikipedia list of cognitive biases")</MudListItem>
<MudListItem T="string" Icon="@Icons.Material.Filled.Link" Target="_blank" Href="https://commons.wikimedia.org/wiki/File:Cognitive_Bias_Codex_With_Definitions_1-2,_an_Extension_of_the_work_of_John_Manoogian_by_Brian_Rene_Morrissette.png">@T("Extended bias poster")</MudListItem>
<MudListItem T="string" Icon="@Icons.Material.Filled.Link" Target="_blank" Href="https://betterhumans.pub/cognitive-bias-cheat-sheet-55a472476b18">@T("Blog post of Buster Benson:") "Cognitive bias cheat sheet"</MudListItem>
</MudList>
<EnumSelection T="CommonLanguages" NameFunc="@(language => language.NameSelecting())" @bind-Value="@this.selectedTargetLanguage" ValidateSelection="@this.ValidateTargetLanguage" Icon="@Icons.Material.Filled.Translate" Label="Target language" AllowOther="@true" OtherValue="CommonLanguages.OTHER" @bind-OtherInput="@this.customTargetLanguage" ValidateOther="@this.ValidateCustomLanguage" LabelOther="Custom target language" />
<EnumSelection T="CommonLanguages" NameFunc="@(language => language.NameSelecting())" @bind-Value="@this.selectedTargetLanguage" ValidateSelection="@this.ValidateTargetLanguage" Icon="@Icons.Material.Filled.Translate" Label="@T("Target language")" AllowOther="@true" OtherValue="CommonLanguages.OTHER" @bind-OtherInput="@this.customTargetLanguage" ValidateOther="@this.ValidateCustomLanguage" LabelOther="@T("Custom target language")" />
<ProviderSelection @bind-ProviderSettings="@this.providerSettings" ValidateProvider="@this.ValidatingProvider"/>
@@ -10,18 +10,9 @@ public partial class BiasOfTheDayAssistant : AssistantBaseCore<SettingsDialogAss
{
public override Tools.Components Component => Tools.Components.BIAS_DAY_ASSISTANT;
protected override string Title => "Bias of the Day";
protected override string Title => T("Bias of the Day");
protected override string Description =>
"""
Learn about a different cognitive bias every day. You can also ask the LLM your questions. The idea behind
"Bias of the Day" is based on work by Buster Benson, John Manoogian III, and Brian Rene Morrissette. Buster
Benson grouped the biases, and the original texts come from Wikipedia. Brian Rene Morrissette condensed them
into a shorter version. Finally, John Manoogian III created the original poster based on Benson's work and
Morrissette's texts. Thorsten Sommer compared all texts for integration into AI Studio with the current Wikipedia
versions, updated them, and added source references. The idea of learning about one bias each day based on John's
poster comes from Drew Nelson.
""";
protected override string Description => T("""Learn about a different cognitive bias every day. You can also ask the LLM your questions. The idea behind "Bias of the Day" is based on work by Buster Benson, John Manoogian III, and Brian Rene Morrissette. Buster Benson grouped the biases, and the original texts come from Wikipedia. Brian Rene Morrissette condensed them into a shorter version. Finally, John Manoogian III created the original poster based on Benson's work and Morrissette's texts. Thorsten Sommer compared all texts for integration into AI Studio with the current Wikipedia versions, updated them, and added source references. The idea of learning about one bias each day based on John's poster comes from Drew Nelson.""");
protected override string SystemPrompt => $"""
You are a friendly, helpful expert on cognitive bias. You studied psychology and
@@ -41,7 +32,7 @@ public partial class BiasOfTheDayAssistant : AssistantBaseCore<SettingsDialogAss
protected override IReadOnlyList<IButtonData> FooterButtons => [];
protected override string SubmitText => "Show me the bias of the day";
protected override string SubmitText => T("Show me the bias of the day");
protected override Func<Task> SubmitAction => this.TellBias;
@@ -79,7 +70,7 @@ public partial class BiasOfTheDayAssistant : AssistantBaseCore<SettingsDialogAss
private string? ValidateTargetLanguage(CommonLanguages language)
{
if(language is CommonLanguages.AS_IS)
return "Please select a target language for the bias.";
return T("Please select a target language for the bias.");
return null;
}
@@ -87,7 +78,7 @@ public partial class BiasOfTheDayAssistant : AssistantBaseCore<SettingsDialogAss
private string? ValidateCustomLanguage(string language)
{
if(this.selectedTargetLanguage == CommonLanguages.OTHER && string.IsNullOrWhiteSpace(language))
return "Please provide a custom language.";
return T("Please provide a custom language.");
return null;
}
@@ -10,7 +10,7 @@
}
<MudTextField T="string" @bind-Text="@this.inputGreeting" Label="@T("(Optional) The greeting phrase to use")" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Person" Variant="Variant.Outlined" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES" Placeholder="@T("Dear Colleagues")" Class="mb-3"/>
<MudTextField T="string" @bind-Text="@this.inputBulletPoints" Validation="@this.ValidateBulletPoints" AdornmentIcon="@Icons.Material.Filled.ListAlt" Adornment="Adornment.Start" Label="@T("Your bullet points")" Variant="Variant.Outlined" Lines="6" AutoGrow="@true" MaxLines="12" Class="mb-3" UserAttributes="@USER_INPUT_ATTRIBUTES" HelperText="@T("Bullet list the content of the e-mail roughly. Use dashes (-) to separate the items.")" Immediate="@false" DebounceInterval="1_000" OnDebounceIntervalElapsed="@this.OnContentChanged" Placeholder="@PLACEHOLDER_BULLET_POINTS"/>
<DebouncedTextField @bind-Text="@this.inputBulletPoints" ValidationFunc="@this.ValidateBulletPoints" Icon="@Icons.Material.Filled.ListAlt" Label="@T("Your bullet points")" Lines="6" MaxLines="12" Attributes="@USER_INPUT_ATTRIBUTES" HelpText="@T("Bullet list the content of the e-mail roughly. Use dashes (-) to separate the items.")" DebounceTime="TimeSpan.FromSeconds(1)" WhenTextCanged="@this.OnContentChanged" Placeholder="@PLACEHOLDER_BULLET_POINTS"/>
<MudSelect T="string" Label="@T("(Optional) Are any of your points particularly important?")" MultiSelection="@true" @bind-SelectedValues="@this.selectedFoci" Variant="Variant.Outlined" Class="mb-3" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.ListAlt">
@foreach (var contentLine in this.bulletPointsLines)
{
File diff suppressed because it is too large. Load diff
@@ -1,5 +1,4 @@
@attribute [Route(Routes.ASSISTANT_LEGAL_CHECK)]
@using AIStudio.Settings.DataModel
@inherits AssistantBaseCore<AIStudio.Dialogs.Settings.SettingsDialogLegalCheck>
@if (!this.SettingsManager.ConfigurationData.LegalCheck.HideWebContentReader)
@@ -7,11 +6,7 @@
<ReadWebContent @bind-Content="@this.inputLegalDocument" ProviderSettings="@this.providerSettings" @bind-AgentIsRunning="@this.isAgentRunning" Preselect="@(this.SettingsManager.ConfigurationData.LegalCheck.PreselectOptions && this.SettingsManager.ConfigurationData.LegalCheck.PreselectWebContentReader)" PreselectContentCleanerAgent="@(this.SettingsManager.ConfigurationData.LegalCheck.PreselectOptions && this.SettingsManager.ConfigurationData.LegalCheck.PreselectContentCleanerAgent)"/>
}
@if (PreviewFeatures.PRE_READ_PDF_2025.IsEnabled(this.SettingsManager))
{
<ReadPDFContent @bind-PDFContent="@this.inputLegalDocument"/>
}
<ReadPDFContent @bind-PDFContent="@this.inputLegalDocument"/>
<MudTextField T="string" Disabled="@this.isAgentRunning" @bind-Text="@this.inputLegalDocument" Validation="@this.ValidatingLegalDocument" AdornmentIcon="@Icons.Material.Filled.DocumentScanner" Adornment="Adornment.Start" Label="@T("Legal document")" Variant="Variant.Outlined" Lines="12" AutoGrow="@true" MaxLines="24" Class="mb-3" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
<MudTextField T="string" Disabled="@this.isAgentRunning" @bind-Text="@this.inputQuestions" Validation="@this.ValidatingQuestions" AdornmentIcon="@Icons.Material.Filled.QuestionAnswer" Adornment="Adornment.Start" Label="@T("Your questions")" Variant="Variant.Outlined" Lines="6" AutoGrow="@true" MaxLines="12" Class="mb-3" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
<ProviderSelection @bind-ProviderSettings="@this.providerSettings" ValidateProvider="@this.ValidatingProvider"/>
@@ -1,5 +1,4 @@
@attribute [Route(Routes.ASSISTANT_SUMMARIZER)]
@using AIStudio.Settings.DataModel
@inherits AssistantBaseCore<AIStudio.Dialogs.Settings.SettingsDialogTextSummarizer>
@if (!this.SettingsManager.ConfigurationData.TextSummarizer.HideWebContentReader)
@@ -7,11 +6,7 @@
<ReadWebContent @bind-Content="@this.inputText" ProviderSettings="@this.providerSettings" @bind-AgentIsRunning="@this.isAgentRunning" Preselect="@(this.SettingsManager.ConfigurationData.TextSummarizer.PreselectOptions && this.SettingsManager.ConfigurationData.TextSummarizer.PreselectWebContentReader)" PreselectContentCleanerAgent="@(this.SettingsManager.ConfigurationData.TextSummarizer.PreselectOptions && this.SettingsManager.ConfigurationData.TextSummarizer.PreselectContentCleanerAgent)"/>
}
@if (PreviewFeatures.PRE_READ_PDF_2025.IsEnabled(this.SettingsManager))
{
<ReadPDFContent @bind-PDFContent="@this.inputText"/>
}
<ReadPDFContent @bind-PDFContent="@this.inputText"/>
<MudTextField T="string" Disabled="@this.isAgentRunning" @bind-Text="@this.inputText" Validation="@this.ValidatingText" AdornmentIcon="@Icons.Material.Filled.DocumentScanner" Adornment="Adornment.Start" Label="@T("Your input")" Variant="Variant.Outlined" Lines="6" AutoGrow="@true" MaxLines="12" Class="mb-3" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
<EnumSelection T="CommonLanguages" NameFunc="@(language => language.Name())" @bind-Value="@this.selectedTargetLanguage" Icon="@Icons.Material.Filled.Translate" Label="@T("Target language")" AllowOther="@true" @bind-OtherInput="@this.customTargetLanguage" OtherValue="CommonLanguages.OTHER" LabelOther="@T("Custom target language")" ValidateOther="@this.ValidateCustomLanguage" />
<EnumSelection T="Complexity" NameFunc="@(complexity => complexity.Name())" @bind-Value="@this.selectedComplexity" Icon="@Icons.Material.Filled.Layers" Label="@T("Target complexity")" AllowOther="@true" @bind-OtherInput="@this.expertInField" OtherValue="Complexity.SCIENTIFIC_LANGUAGE_OTHER_EXPERTS" LabelOther="@T("Your expertise")" ValidateOther="@this.ValidateExpertInField" />
@@ -1,5 +1,4 @@
@attribute [Route(Routes.ASSISTANT_TRANSLATION)]
@using AIStudio.Settings.DataModel
@inherits AssistantBaseCore<AIStudio.Dialogs.Settings.SettingsDialogTranslation>
@if (!this.SettingsManager.ConfigurationData.Translation.HideWebContentReader)
@@ -7,15 +6,11 @@
<ReadWebContent @bind-Content="@this.inputText" ProviderSettings="@this.providerSettings" @bind-AgentIsRunning="@this.isAgentRunning" Preselect="@(this.SettingsManager.ConfigurationData.Translation.PreselectOptions && this.SettingsManager.ConfigurationData.Translation.PreselectWebContentReader)" PreselectContentCleanerAgent="@(this.SettingsManager.ConfigurationData.Translation.PreselectOptions && this.SettingsManager.ConfigurationData.Translation.PreselectContentCleanerAgent)"/>
}
@if (PreviewFeatures.PRE_READ_PDF_2025.IsEnabled(this.SettingsManager))
{
<ReadPDFContent @bind-PDFContent="@this.inputText"/>
}
<ReadPDFContent @bind-PDFContent="@this.inputText"/>
<MudTextSwitch Label="@T("Live translation")" @bind-Value="@this.liveTranslation" LabelOn="@T("Live translation")" LabelOff="@T("No live translation")"/>
@if (this.liveTranslation)
{
<MudTextField T="string" Disabled="@this.isAgentRunning" @bind-Text="@this.inputText" Validation="@this.ValidatingText" AdornmentIcon="@Icons.Material.Filled.DocumentScanner" Adornment="Adornment.Start" Label="@T("Your input")" Variant="Variant.Outlined" Lines="6" AutoGrow="@true" MaxLines="12" Class="mb-3" Immediate="@true" DebounceInterval="@this.SettingsManager.ConfigurationData.Translation.DebounceIntervalMilliseconds" OnDebounceIntervalElapsed="() => this.TranslateText(force: false)" UserAttributes="@USER_INPUT_ATTRIBUTES"/>
<DebouncedTextField Disabled="@this.isAgentRunning" @bind-Text="@this.inputText" ValidationFunc="@this.ValidatingText" Icon="@Icons.Material.Filled.DocumentScanner" Label="@T("Your input")" Lines="6" MaxLines="12" DebounceTime="@TimeSpan.FromMilliseconds(this.SettingsManager.ConfigurationData.Translation.DebounceIntervalMilliseconds)" WhenTextChangedAsync="async _ => await this.TranslateText(force: false)" Attributes="@USER_INPUT_ATTRIBUTES"/>
}
else
{
@@ -13,6 +13,8 @@ public partial class Changelog
public static readonly Log[] LOGS =
[
new (219, "v0.9.44, build 219 (2025-05-18 19:33 UTC)", "v0.9.44.md"),
new (218, "v0.9.43, build 218 (2025-05-11 17:22 UTC)", "v0.9.43.md"),
new (217, "v0.9.42, build 217 (2025-05-04 13:03 UTC)", "v0.9.42.md"),
new (216, "v0.9.41, build 216 (2025-04-27 14:18 UTC)", "v0.9.41.md"),
new (215, "v0.9.40, build 215 (2025-04-20 13:30 UTC)", "v0.9.40.md"),
@@ -270,9 +270,7 @@ public partial class ChatComponent : MSGComponentBase, IAsyncDisposable
private bool CanThreadBeSaved => this.ChatThread is not null && this.ChatThread.Blocks.Count > 0;
private string TooltipAddChatToWorkspace => string.Format(T("""
Start new chat in workspace "{0}"
"""), this.currentWorkspaceName);
private string TooltipAddChatToWorkspace => string.Format(T("Start new chat in workspace '{0}'"), this.currentWorkspaceName);
private string UserInputStyle => this.SettingsManager.ConfigurationData.LLMProviders.ShowProviderConfidence ? this.Provider.UsedLLMProvider.GetConfidence(this.SettingsManager).SetColorStyle(this.SettingsManager) : string.Empty;
@@ -2,13 +2,15 @@ using System.Diagnostics.CodeAnalysis;
using AIStudio.Provider;
using AIStudio.Settings;
using AIStudio.Tools.PluginSystem;
using Microsoft.AspNetCore.Components;
namespace AIStudio.Components;
public partial class ConfigurationProviderSelection : MSGComponentBase
{
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(ConfigurationProviderSelection).Namespace, nameof(ConfigurationProviderSelection));
[Parameter]
public Func<string> SelectedValue { get; set; } = () => string.Empty;
@@ -25,7 +27,7 @@ public partial class ConfigurationProviderSelection : MSGComponentBase
public Func<bool> Disabled { get; set; } = () => false;
[Parameter]
public Func<string> HelpText { get; set; } = () => "Select a provider that is preselected.";
public Func<string> HelpText { get; set; } = () => TB("Select a provider that is preselected.");
[Parameter]
public Tools.Components Component { get; set; } = Tools.Components.NONE;
@@ -0,0 +1,3 @@
@inherits MudComponentBase
<MudTextField T="string" Disabled="@this.Disabled" Text="@this.text" TextChanged="@this.OnTextChanged" Validation="@this.ValidationFunc" Label="@this.Label" Variant="Variant.Outlined" Lines="@this.Lines" MaxLines="@this.MaxLines" AutoGrow="@(this.MaxLines > this.Lines)" Margin="Margin.Dense" UserAttributes="@this.Attributes" HelperText="@this.HelpText" Placeholder="@this.Placeholder" Class="mb-3" Immediate="true" Adornment="Adornment.Start" AdornmentIcon="@this.Icon"/>
@@ -0,0 +1,80 @@
using Microsoft.AspNetCore.Components;
using Timer = System.Timers.Timer;
namespace AIStudio.Components;
public partial class DebouncedTextField : MudComponentBase
{
[Parameter]
public string Label { get; set; } = string.Empty;
[Parameter]
public string Text { get; set; } = string.Empty;
[Parameter]
public EventCallback<string> TextChanged { get; set; }
[Parameter]
public Func<string, Task> WhenTextChangedAsync { get; set; } = _ => Task.CompletedTask;
[Parameter]
public Action<string> WhenTextCanged { get; set; } = _ => { };
[Parameter]
public int Lines { get; set; } = 1;
[Parameter]
public int MaxLines { get; set; } = 1;
[Parameter]
public Dictionary<string, object?> Attributes { get; set; } = [];
[Parameter]
public Func<string, string?> ValidationFunc { get; set; } = _ => null;
[Parameter]
public string HelpText { get; set; } = string.Empty;
[Parameter]
public string Placeholder { get; set; } = string.Empty;
[Parameter]
public string Icon { get; set; } = string.Empty;
[Parameter]
public TimeSpan DebounceTime { get; set; } = TimeSpan.FromMilliseconds(800);
[Parameter]
public bool Disabled { get; set; }
private readonly Timer debounceTimer = new();
private string text = string.Empty;
#region Overrides of ComponentBase
protected override async Task OnInitializedAsync()
{
this.text = this.Text;
this.debounceTimer.AutoReset = false;
this.debounceTimer.Interval = this.DebounceTime.TotalMilliseconds;
this.debounceTimer.Elapsed += (_, _) =>
{
this.debounceTimer.Stop();
this.InvokeAsync(async () => await this.TextChanged.InvokeAsync(this.text));
this.InvokeAsync(async () => await this.WhenTextChangedAsync(this.text));
this.InvokeAsync(() => this.WhenTextCanged(this.text));
};
await base.OnInitializedAsync();
}
#endregion
private void OnTextChanged(string value)
{
this.text = value;
this.debounceTimer.Stop();
this.debounceTimer.Start();
}
}
@@ -26,7 +26,7 @@
var availablePreviewFeatures = ConfigurationSelectDataFactory.GetPreviewFeaturesData(this.SettingsManager).ToList();
if (availablePreviewFeatures.Count > 0)
{
<ConfigurationMultiSelect OptionDescription="@T("Select preview features")" SelectedValues="@(() => this.SettingsManager.ConfigurationData.App.EnabledPreviewFeatures)" Data="@availablePreviewFeatures" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.EnabledPreviewFeatures = selectedValue)" OptionHelp="@T("Which preview features would you like to enable?")"/>
<ConfigurationMultiSelect OptionDescription="@T("Select preview features")" SelectedValues="@(() => this.SettingsManager.ConfigurationData.App.EnabledPreviewFeatures.Where(x => !x.IsReleased()).ToHashSet())" Data="@availablePreviewFeatures" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.App.EnabledPreviewFeatures = selectedValue)" OptionHelp="@T("Which preview features would you like to enable?")"/>
}
}
@@ -4,24 +4,17 @@
@if (PreviewFeatures.PRE_RAG_2024.IsEnabled(this.SettingsManager))
{
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.IntegrationInstructions" HeaderText="Configure Embeddings">
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.IntegrationInstructions" HeaderText="@T("Configure Embeddings")">
<PreviewPrototype/>
<MudText Typo="Typo.h4" Class="mb-3">
Configured Embeddings
@T("Configured Embeddings")
</MudText>
<MudJustifiedText Typo="Typo.body1" Class="mb-3">
Embeddings are a way to represent words, sentences, entire documents, or even images and videos as digital
fingerprints. Just like each person has a unique fingerprint, embedding models create unique digital patterns
that capture the meaning and characteristics of the content they analyze. When two things are similar in meaning
or content, their digital fingerprints will look very similar. For example, the fingerprints for 'happy' and
'joyful' would be more alike than those for 'happy' and 'sad'.
@T("Embeddings are a way to represent words, sentences, entire documents, or even images and videos as digital fingerprints. Just like each person has a unique fingerprint, embedding models create unique digital patterns that capture the meaning and characteristics of the content they analyze. When two things are similar in meaning or content, their digital fingerprints will look very similar. For example, the fingerprints for 'happy' and 'joyful' would be more alike than those for 'happy' and 'sad'.")
</MudJustifiedText>
<MudJustifiedText Typo="Typo.body1" Class="mb-3">
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.
@T("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.")
</MudJustifiedText>
<MudTable Items="@this.SettingsManager.ConfigurationData.EmbeddingProviders" Hover="@true" Class="border-dashed border rounded-lg">
<ColGroup>
@@ -29,14 +22,14 @@
<col style="width: 12em;"/>
<col style="width: 12em;"/>
<col/>
<col style="width: 40em;"/>
<col style="width: 16em;"/>
</ColGroup>
<HeaderContent>
<MudTh>#</MudTh>
<MudTh>Name</MudTh>
<MudTh>Provider</MudTh>
<MudTh>Model</MudTh>
<MudTh>Actions</MudTh>
<MudTh>@T("Name")</MudTh>
<MudTh>@T("Provider")</MudTh>
<MudTh>@T("Model")</MudTh>
<MudTh>@T("Actions")</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd>@context.Num</MudTd>
@@ -46,15 +39,15 @@
<MudTd>
<MudStack Row="true" Class="mb-2 mt-2" Wrap="Wrap.Wrap">
<MudButton Variant="Variant.Filled" Color="Color.Info" StartIcon="@Icons.Material.Filled.OpenInBrowser" Href="@context.UsedLLMProvider.GetDashboardURL()" Target="_blank" Disabled="@(!context.UsedLLMProvider.HasDashboard())">
Open Dashboard
</MudButton>
<MudButton Variant="Variant.Filled" Color="Color.Info" StartIcon="@Icons.Material.Filled.Edit" OnClick="() => this.EditEmbeddingProvider(context)">
Edit
</MudButton>
<MudButton Variant="Variant.Filled" Color="Color.Error" StartIcon="@Icons.Material.Filled.Delete" OnClick="() => this.DeleteEmbeddingProvider(context)">
Delete
</MudButton>
<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>
@@ -62,11 +55,13 @@
@if (this.SettingsManager.ConfigurationData.EmbeddingProviders.Count == 0)
{
<MudText Typo="Typo.h6" Class="mt-3">No embeddings configured yet.</MudText>
<MudText Typo="Typo.h6" Class="mt-3">
@T("No embeddings configured yet.")
</MudText>
}
<MudButton Variant="Variant.Filled" Color="@Color.Primary" StartIcon="@Icons.Material.Filled.AddRoad" Class="mt-3 mb-6" OnClick="@this.AddEmbeddingProvider">
Add Embedding
@T("Add Embedding")
</MudButton>
</ExpansionPanel>
}
@@ -39,7 +39,7 @@ public partial class SettingsPanelEmbeddings : SettingsPanelBase
{ x => x.IsEditing, false },
};
var dialogReference = await this.DialogService.ShowAsync<EmbeddingProviderDialog>("Add Embedding Provider", dialogParameters, DialogOptions.FULLSCREEN);
var dialogReference = await this.DialogService.ShowAsync<EmbeddingProviderDialog>(T("Add Embedding Provider"), dialogParameters, DialogOptions.FULLSCREEN);
var dialogResult = await dialogReference.Result;
if (dialogResult is null || dialogResult.Canceled)
return;
@@ -69,7 +69,7 @@ public partial class SettingsPanelEmbeddings : SettingsPanelBase
{ x => x.DataHost, embeddingProvider.Host },
};
var dialogReference = await this.DialogService.ShowAsync<EmbeddingProviderDialog>("Edit Embedding Provider", dialogParameters, DialogOptions.FULLSCREEN);
var dialogReference = await this.DialogService.ShowAsync<EmbeddingProviderDialog>(T("Edit Embedding Provider"), dialogParameters, DialogOptions.FULLSCREEN);
var dialogResult = await dialogReference.Result;
if (dialogResult is null || dialogResult.Canceled)
return;
@@ -92,10 +92,10 @@ public partial class SettingsPanelEmbeddings : SettingsPanelBase
{
var dialogParameters = new DialogParameters
{
{ "Message", $"Are you sure you want to delete the embedding provider '{provider.Name}'?" },
{ "Message", string.Format(T("Are you sure you want to delete the embedding provider '{0}'?"), provider.Name) },
};
var dialogReference = await this.DialogService.ShowAsync<ConfirmDialog>("Delete Embedding Provider", dialogParameters, DialogOptions.FULLSCREEN);
var dialogReference = await this.DialogService.ShowAsync<ConfirmDialog>(T("Delete Embedding Provider"), dialogParameters, DialogOptions.FULLSCREEN);
var dialogResult = await dialogReference.Result;
if (dialogResult is null || dialogResult.Canceled)
return;
@@ -15,7 +15,7 @@
<ColGroup>
<col style="width: 3em;"/>
<col/>
<col style="width: 40em;"/>
<col style="width: 16em;"/>
</ColGroup>
<HeaderContent>
<MudTh>#</MudTh>
@@ -27,12 +27,12 @@
<MudTd>@context.Name</MudTd>
<MudTd>
<MudStack Row="true" Class="mb-2 mt-2" Wrap="Wrap.Wrap">
<MudButton Variant="Variant.Filled" Color="Color.Info" StartIcon="@Icons.Material.Filled.Edit" OnClick="() => this.EditProfile(context)">
@T("Edit")
</MudButton>
<MudButton Variant="Variant.Filled" Color="Color.Error" StartIcon="@Icons.Material.Filled.Delete" OnClick="() => this.DeleteProfile(context)">
@T("Delete")
</MudButton>
<MudTooltip Text="@T("Edit")">
<MudIconButton Color="Color.Info" Icon="@Icons.Material.Filled.Edit" OnClick="() => this.EditProfile(context)"/>
</MudTooltip>
<MudTooltip Text="@T("Delete")">
<MudIconButton Color="Color.Error" Icon="@Icons.Material.Filled.Delete" OnClick="() => this.DeleteProfile(context)"/>
</MudTooltip>
</MudStack>
</MudTd>
</RowTemplate>
@@ -16,7 +16,7 @@
<col style="width: 12em;"/>
<col style="width: 12em;"/>
<col/>
<col style="width: 34em;"/>
<col style="width: 16em;"/>
</ColGroup>
<HeaderContent>
<MudTh>#</MudTh>
@@ -44,16 +44,16 @@
}
</MudTd>
<MudTd>
<MudStack Row="true" Class="mb-2 mt-2" Wrap="Wrap.Wrap">
<MudButton Variant="Variant.Filled" Color="Color.Info" StartIcon="@Icons.Material.Filled.OpenInBrowser" Href="@context.UsedLLMProvider.GetDashboardURL()" Target="_blank" Disabled="@(!context.UsedLLMProvider.HasDashboard())">
@T("Open Dashboard")
</MudButton>
<MudButton Variant="Variant.Filled" Color="Color.Info" StartIcon="@Icons.Material.Filled.Edit" OnClick="() => this.EditLLMProvider(context)">
@T("Edit")
</MudButton>
<MudButton Variant="Variant.Filled" Color="Color.Error" StartIcon="@Icons.Material.Filled.Delete" OnClick="() => this.DeleteLLMProvider(context)">
@T("Delete")
</MudButton>
<MudStack Row="true" Class="mb-2 mt-2" Spacing="1" 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.EditLLMProvider(context)"/>
</MudTooltip>
<MudTooltip Text="@T("Delete")">
<MudIconButton Color="Color.Error" Icon="@Icons.Material.Filled.Delete" OnClick="() => this.DeleteLLMProvider(context)"/>
</MudTooltip>
</MudStack>
</MudTd>
</RowTemplate>
@@ -0,0 +1,16 @@
<MudListItem T="string" Href="@this.URL" Target="_blank">
<AvatarContent>
<MudAvatar Variant="Variant.Filled" Size="Size.Large" Style="background-color: white;" Class="mr-6">
<MudImage Src="@this.ImageSrc"/>
</MudAvatar>
</AvatarContent>
<ChildContent>
<MudText Typo="Typo.h6" Inline="@true">
@this.Name
</MudText>
<MudText Typo="Typo.body1" Class="ml-3" Inline="@true">
@this.Acknowledgment
</MudText>
</ChildContent>
</MudListItem>
@@ -0,0 +1,21 @@
using Microsoft.AspNetCore.Components;
namespace AIStudio.Components;
public partial class Titan : ComponentBase
{
[Parameter]
public string Name { get; set; } = string.Empty;
[Parameter]
public string Acknowledgment { get; set; } = string.Empty;
[Parameter]
public string? URL { get; set; }
[Parameter]
public SupporterType Type { get; set; }
[Parameter]
public string? ImageSrc { get; set; }
}
@@ -344,6 +344,13 @@ public partial class Workspaces : MSGComponentBase
return;
chat.Name = (dialogResult.Data as string)!;
if(this.CurrentChatThread?.ChatId == chat.ChatId)
{
this.CurrentChatThread.Name = chat.Name;
await this.CurrentChatThreadChanged.InvokeAsync(this.CurrentChatThread);
await MessageBus.INSTANCE.SendMessage<bool>(this, Event.WORKSPACE_LOADED_CHAT_CHANGED);
}
await this.StoreChat(chat);
await this.LoadTreeItems();
}
@@ -1,5 +1,7 @@
@using AIStudio.Settings.DataModel
@using AIStudio.Tools.ERIClient.DataModel
@inherits MSGComponentBase
<MudDialog>
<DialogContent>
<MudForm @ref="@this.form" @bind-IsValid="@this.dataIsValid" @bind-Errors="@this.dataIssues">
@@ -7,7 +9,7 @@
<MudTextField
T="string"
@bind-Text="@this.dataName"
Label="Data Source Name"
Label="@T("Data Source Name")"
Class="mb-6"
MaxLength="40"
Counter="40"
@@ -24,7 +26,7 @@
<MudTextField
T="string"
@bind-Text="@this.dataHostname"
Label="ERI v1 Server Hostname"
Label="@T("ERI v1 Server Hostname")"
Class="mb-6"
Immediate="@true"
Validation="@this.dataSourceValidation.ValidatingHostname"
@@ -35,7 +37,7 @@
UserAttributes="@SPELLCHECK_ATTRIBUTES"/>
<MudNumericField
Label="Port"
Label="@T("Port")"
Immediate="@true"
Min="1" Max="65535"
Validation="@this.dataSourceValidation.ValidatePort"
@@ -47,9 +49,7 @@
@if (!this.IsConnectionEncrypted())
{
<MudJustifiedText Typo="Typo.body1" Color="Color.Error" Class="mb-3">
Please note: the connection to the ERI v1 server is not encrypted. This means that all
data sent to the server is transmitted in plain text. Please ask the ERI server administrator
to enable encryption.
@T("Please note: the connection to the ERI v1 server is not encrypted. This means that all data sent to the server is transmitted in plain text. Please ask the ERI server administrator to enable encryption.")
</MudJustifiedText>
}
@@ -57,7 +57,7 @@
{
<MudStack Row="@true" AlignItems="AlignItems.Center">
<MudButton Variant="Variant.Filled" Color="@this.GetTestResultColor()" StartIcon="@this.GetTestResultIcon()" Class="mb-3" OnClick="@this.TestConnection">
Test connection & read available metadata
@T("Test connection & read available metadata")
</MudButton>
<MudText Typo="Typo.body1" Class="mb-3">
@this.GetTestResultText()
@@ -70,7 +70,9 @@
<MudSelect @bind-Value="@this.dataAuthMethod" Text="@this.dataAuthMethod.DisplayName()" Label="Authentication Method" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.dataSourceValidation.ValidateAuthMethod">
@foreach (var authMethod in this.availableAuthMethods)
{
<MudSelectItem Value="@authMethod">@authMethod.DisplayName()</MudSelectItem>
<MudSelectItem Value="@authMethod">
@authMethod.DisplayName()
</MudSelectItem>
}
</MudSelect>
}
@@ -83,7 +85,7 @@
<MudTextField
T="string"
@bind-Text="@this.dataUsername"
Label="Username"
Label="@T("Username")"
Class="mb-6"
Immediate="@true"
Validation="@this.dataSourceValidation.ValidateUsername"
@@ -112,7 +114,7 @@
@if (this.availableRetrievalProcesses.Count > 0)
{
<MudSelect @bind-Value="@this.dataSelectedRetrievalProcess" Text="@this.dataSelectedRetrievalProcess.Name" Label="Select one retrieval process" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.dataSourceValidation.ValidateRetrievalProcess">
<MudSelect @bind-Value="@this.dataSelectedRetrievalProcess" Text="@this.dataSelectedRetrievalProcess.Name" Label="@T("Select one retrieval process")" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.dataSourceValidation.ValidateRetrievalProcess">
@foreach (var retrievalProcess in this.availableRetrievalProcesses)
{
<MudSelectItem Value="@retrievalProcess">
@@ -122,7 +124,7 @@
</MudSelect>
}
<MudSelect @bind-Value="@this.dataSecurityPolicy" Text="@this.dataSecurityPolicy.ToSelectionText()" Label="Your security policy" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.dataSourceValidation.ValidateSecurityPolicy">
<MudSelect @bind-Value="@this.dataSecurityPolicy" Text="@this.dataSecurityPolicy.ToSelectionText()" Label="@T("Your security policy")" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.dataSourceValidation.ValidateSecurityPolicy">
@foreach (var policy in Enum.GetValues<DataSourceSecurity>())
{
<MudSelectItem Value="@policy">
@@ -135,15 +137,17 @@
<Issues IssuesData="@this.dataIssues"/>
</DialogContent>
<DialogActions>
<MudButton OnClick="@this.Cancel" Variant="Variant.Filled">Cancel</MudButton>
<MudButton OnClick="@this.Cancel" Variant="Variant.Filled">
@T("Cancel")
</MudButton>
<MudButton OnClick="@this.Store" Variant="Variant.Filled" Color="Color.Primary">
@if(this.IsEditing)
{
@:Update
@T("Update")
}
else
{
@:Add
@T("Add")
}
</MudButton>
</DialogActions>
@@ -1,4 +1,5 @@
using AIStudio.Assistants.ERI;
using AIStudio.Components;
using AIStudio.Settings;
using AIStudio.Settings.DataModel;
using AIStudio.Tools.ERIClient;
@@ -13,7 +14,7 @@ using RetrievalInfo = AIStudio.Tools.ERIClient.DataModel.RetrievalInfo;
// ReSharper disable InconsistentNaming
namespace AIStudio.Dialogs;
public partial class DataSourceERI_V1Dialog : ComponentBase, ISecretId
public partial class DataSourceERI_V1Dialog : MSGComponentBase, ISecretId
{
[CascadingParameter]
private IMudDialogInstance MudDialog { get; set; } = null!;
@@ -24,9 +25,6 @@ public partial class DataSourceERI_V1Dialog : ComponentBase, ISecretId
[Parameter]
public DataSourceERI_V1 DataSource { get; set; }
[Inject]
private SettingsManager SettingsManager { get; init; } = null!;
[Inject]
private ILogger<ProviderDialog> Logger { get; init; } = null!;
@@ -195,7 +193,7 @@ public partial class DataSourceERI_V1Dialog : ComponentBase, ISecretId
await this.form.Validate();
Array.Resize(ref this.dataIssues, this.dataIssues.Length + 1);
this.dataIssues[^1] = "Failed to connect to the ERI v1 server. The server is not supported.";
this.dataIssues[^1] = T("Failed to connect to the ERI v1 server. The server is not supported.");
return;
}
@@ -254,7 +252,7 @@ public partial class DataSourceERI_V1Dialog : ComponentBase, ISecretId
await this.form.Validate();
Array.Resize(ref this.dataIssues, this.dataIssues.Length + 1);
this.dataIssues[^1] = $"Failed to connect to the ERI v1 server. The message was: {e.Message}";
this.dataIssues[^1] = string.Format(T("Failed to connect to the ERI v1 server. The message was: {0}"), e.Message);
this.Logger.LogError($"Failed to connect to the ERI v1 server. Message: {e.Message}");
this.connectionTested = true;
@@ -265,9 +263,9 @@ public partial class DataSourceERI_V1Dialog : ComponentBase, ISecretId
private string GetTestResultText()
{
if(!this.connectionTested)
return "Not tested yet.";
return T("Not tested yet.");
return this.connectionSuccessfulTested ? "Connection successful." : "Connection failed.";
return this.connectionSuccessfulTested ? T("Connection successful.") : T("Connection failed.");
}
private Color GetTestResultColor()
@@ -290,9 +288,9 @@ public partial class DataSourceERI_V1Dialog : ComponentBase, ISecretId
private string GetSecretLabel() => this.dataAuthMethod switch
{
AuthMethod.TOKEN => "Access Token",
AuthMethod.USERNAME_PASSWORD => "Password",
_ => "Secret",
AuthMethod.TOKEN => T("Access Token"),
AuthMethod.USERNAME_PASSWORD => T("Password"),
_ => T("Secret"),
};
private async Task Store()
@@ -320,7 +318,7 @@ public partial class DataSourceERI_V1Dialog : ComponentBase, ISecretId
var storeResponse = await this.RustService.SetSecret(this, this.dataSecret);
if (!storeResponse.Success)
{
this.dataSecretStorageIssue = $"Failed to store the auth. secret in the operating system. The message was: {storeResponse.Issue}. Please try again.";
this.dataSecretStorageIssue = string.Format(T("Failed to store the auth. secret in the operating system. The message was: {0}. Please try again."), storeResponse.Issue);
await this.form.Validate();
return;
}
@@ -1,66 +1,64 @@
@using AIStudio.Settings.DataModel
@using AIStudio.Tools.ERIClient.DataModel
@inherits MSGComponentBase
<MudDialog>
<DialogContent>
<MudText Typo="Typo.h5">
Common data source information
@T("Common data source information")
</MudText>
<TextInfoLine Icon="@Icons.Material.Filled.Tag" Label="Data source name" Value="@this.DataSource.Name" ClipboardTooltipSubject="the data source name"/>
<TextInfoLine Icon="@Icons.Material.Filled.NetworkCheck" Label="ERI server hostname" Value="@this.DataSource.Hostname" ClipboardTooltipSubject="the ERI server hostname"/>
<TextInfoLine Icon="@Icons.Material.Filled.Tag" Label="ERI server port" Value="@this.Port" ClipboardTooltipSubject="the ERI server port"/>
<TextInfoLine Icon="@Icons.Material.Filled.Tag" Label="@T("Data source name")" Value="@this.DataSource.Name" ClipboardTooltipSubject="@T("the data source name")"/>
<TextInfoLine Icon="@Icons.Material.Filled.NetworkCheck" Label="@T("ERI server hostname")" Value="@this.DataSource.Hostname" ClipboardTooltipSubject="@T("the ERI server hostname")"/>
<TextInfoLine Icon="@Icons.Material.Filled.Tag" Label="@T("ERI server port")" Value="@this.Port" ClipboardTooltipSubject="@T("the ERI server port")"/>
@if (!this.IsConnectionEncrypted())
{
<MudJustifiedText Typo="Typo.body1" Color="Color.Error" Class="mb-3">
Please note: the connection to the ERI v1 server is not encrypted. This means that all
data sent to the server is transmitted in plain text. Please ask the ERI server administrator
to enable encryption.
@T("Please note: the connection to the ERI v1 server is not encrypted. This means that all data sent to the server is transmitted in plain text. Please ask the ERI server administrator to enable encryption.")
</MudJustifiedText>
}
@if (this.DataSource.AuthMethod is AuthMethod.USERNAME_PASSWORD)
{
<TextInfoLine Icon="@Icons.Material.Filled.Person2" Label="Username" Value="@this.DataSource.Username" ClipboardTooltipSubject="the username"/>
<TextInfoLine Icon="@Icons.Material.Filled.Person2" Label="@T("Username")" Value="@this.DataSource.Username" ClipboardTooltipSubject="@T("the username")"/>
}
<TextInfoLines Label="Server description" MaxLines="14" Value="@this.serverDescription" ClipboardTooltipSubject="the server description"/>
<TextInfoLines Label="Security requirements of the data provider" MaxLines="3" Value="@this.securityRequirements.Explain()" ClipboardTooltipSubject="the security requirements of the data provider"/>
<TextInfoLines Label="Your security policy" MaxLines="3" Value="@this.DataSource.SecurityPolicy.ToInfoText()" Color="@this.DataSource.SecurityPolicy.GetColor()" ClipboardTooltipSubject="your security policy"/>
<TextInfoLines Label="@T("Server description")" MaxLines="14" Value="@this.serverDescription" ClipboardTooltipSubject="@T("the server description")"/>
<TextInfoLines Label="@T("Security requirements of the data provider")" MaxLines="3" Value="@this.securityRequirements.Explain()" ClipboardTooltipSubject="@T("the security requirements of the data provider")"/>
<TextInfoLines Label="@T("Your security policy")" MaxLines="3" Value="@this.DataSource.SecurityPolicy.ToInfoText()" Color="@this.DataSource.SecurityPolicy.GetColor()" ClipboardTooltipSubject="@T("your security policy")"/>
<MudText Typo="Typo.h5" Class="mt-6">
Retrieval information
@T("Retrieval information")
</MudText>
@if (!this.retrievalInfoformation.Any())
{
<MudJustifiedText Typo="Typo.body1" Color="Color.Info" Class="mb-3">
The data source does not provide any retrieval information.
@T("The data source does not provide any retrieval information.")
</MudJustifiedText>
}
else
{
<MudExpansionPanels Class="mb-3">
<ExpansionPanel HeaderText="@this.RetrievalName(this.selectedRetrievalInfo)" HeaderIcon="@Icons.Material.Filled.Info">
<TextInfoLines Label="Description" MaxLines="14" Value="@this.selectedRetrievalInfo.Description" ClipboardTooltipSubject="the retrieval description"/>
<TextInfoLines Label="Parameters" MaxLines="14" Value="@this.RetrievalParameters(this.selectedRetrievalInfo)" ClipboardTooltipSubject="the retrieval parameters"/>
<TextInfoLines Label="Description" MaxLines="14" Value="@this.selectedRetrievalInfo.Description" ClipboardTooltipSubject="@T("the retrieval description")"/>
<TextInfoLines Label="Parameters" MaxLines="14" Value="@this.RetrievalParameters(this.selectedRetrievalInfo)" ClipboardTooltipSubject="@T("the retrieval parameters")"/>
@if (!string.IsNullOrWhiteSpace(this.selectedRetrievalInfo.Link))
{
<MudButton Href="@this.selectedRetrievalInfo.Link" Target="_blank" Class="mt-3" Color="Color.Primary" StartIcon="@Icons.Material.Filled.OpenInNew">
Open web link, show more information
@T("Open web link, show more information")
</MudButton>
}
<MudText Typo="Typo.h6" Class="mt-3">
Embeddings
@T("Embeddings")
</MudText>
@* ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract *@
@if (this.selectedRetrievalInfo.Embeddings is null || !this.selectedRetrievalInfo.Embeddings.Any())
{
<MudJustifiedText Typo="Typo.body1" Color="Color.Info" Class="mb-3">
The data source does not provide any embedding information.
@T("The data source does not provide any embedding information.")
</MudJustifiedText>
}
else
@@ -70,14 +68,14 @@
{
var embedding = this.selectedRetrievalInfo.Embeddings[embeddingIndex];
<ExpansionPanel HeaderText="@embedding.EmbeddingName" HeaderIcon="@Icons.Material.Filled.Info" IsExpanded="embeddingIndex == 0">
<TextInfoLine Icon="@Icons.Material.Filled.FormatShapes" Label="Type" Value="@embedding.EmbeddingType" ClipboardTooltipSubject="the embedding type"/>
<TextInfoLines Label="Description" MaxLines="14" Value="@embedding.Description" ClipboardTooltipSubject="the embedding description"/>
<TextInfoLines Label="When to use" MaxLines="3" Value="@embedding.UsedWhen" ClipboardTooltipSubject="when is the embedding used"/>
<TextInfoLine Icon="@Icons.Material.Filled.FormatShapes" Label="Type" Value="@embedding.EmbeddingType" ClipboardTooltipSubject="@T("the embedding type")"/>
<TextInfoLines Label="@T("Description")" MaxLines="14" Value="@embedding.Description" ClipboardTooltipSubject="@T("the embedding description")"/>
<TextInfoLines Label="@T("When to use")" MaxLines="3" Value="@embedding.UsedWhen" ClipboardTooltipSubject="@T("when is the embedding used")"/>
@if (!string.IsNullOrWhiteSpace(embedding.Link))
{
<MudButton Href="@embedding.Link" Target="_blank" Class="mt-3" Color="Color.Primary" StartIcon="@Icons.Material.Filled.OpenInNew">
Open web link, show more information
@T("Open web link, show more information")
</MudButton>
}
</ExpansionPanel>
@@ -95,7 +93,11 @@
{
<MudProgressLinear Color="Color.Primary" Indeterminate="true" Class="ml-5 mr-5"/>
}
<MudButton OnClick="@this.GetERIMetadata" Variant="Variant.Filled" Color="Color.Info">Reload</MudButton>
<MudButton OnClick="@this.Close" Variant="Variant.Filled">Close</MudButton>
<MudButton OnClick="@this.GetERIMetadata" Variant="Variant.Filled" Color="Color.Info">
@T("Reload")
</MudButton>
<MudButton OnClick="@this.Close" Variant="Variant.Filled">
@T("Close")
</MudButton>
</DialogActions>
</MudDialog>
@@ -3,6 +3,7 @@
using System.Text;
using AIStudio.Assistants.ERI;
using AIStudio.Components;
using AIStudio.Settings.DataModel;
using AIStudio.Tools.ERIClient;
using AIStudio.Tools.ERIClient.DataModel;
@@ -14,7 +15,7 @@ using RetrievalInfo = AIStudio.Tools.ERIClient.DataModel.RetrievalInfo;
namespace AIStudio.Dialogs;
public partial class DataSourceERI_V1InfoDialog : ComponentBase, IAsyncDisposable, ISecretId
public partial class DataSourceERI_V1InfoDialog : MSGComponentBase, IAsyncDisposable, ISecretId
{
[CascadingParameter]
private IMudDialogInstance MudDialog { get; set; } = null!;
@@ -29,8 +30,8 @@ public partial class DataSourceERI_V1InfoDialog : ComponentBase, IAsyncDisposabl
protected override async Task OnInitializedAsync()
{
this.eriServerTasks.Add(this.GetERIMetadata());
await base.OnInitializedAsync();
this.eriServerTasks.Add(this.GetERIMetadata());
}
#endregion
@@ -59,21 +60,21 @@ public partial class DataSourceERI_V1InfoDialog : ComponentBase, IAsyncDisposabl
return $"[{retrievalInfo.Id}] {retrievalInfo.Name}";
if (hasId)
return $"[{retrievalInfo.Id}] Unnamed retrieval process";
return string.Format(T("[{0}] Unnamed retrieval process"), retrievalInfo.Id);
return hasName ? retrievalInfo.Name : "Unnamed retrieval process";
return hasName ? retrievalInfo.Name : T("Unnamed retrieval process");
}
private string RetrievalParameters(RetrievalInfo retrievalInfo)
{
var parameters = retrievalInfo.ParametersDescription;
if (parameters is null || parameters.Count == 0)
return "This retrieval process has no parameters.";
return T("This retrieval process has no parameters.");
var sb = new StringBuilder();
foreach (var (paramName, description) in parameters)
{
sb.Append("Parameter: ");
sb.Append(T("Parameter: "));
sb.AppendLine(paramName);
sb.AppendLine(description);
sb.AppendLine();
@@ -94,7 +95,7 @@ public partial class DataSourceERI_V1InfoDialog : ComponentBase, IAsyncDisposabl
using var client = ERIClientFactory.Get(ERIVersion.V1, this.DataSource);
if(client is null)
{
this.dataIssues.Add("Failed to connect to the ERI v1 server. The server is not supported.");
this.dataIssues.Add(T("Failed to connect to the ERI v1 server. The server is not supported."));
return;
}
@@ -136,7 +137,7 @@ public partial class DataSourceERI_V1InfoDialog : ComponentBase, IAsyncDisposabl
}
catch (Exception e)
{
this.dataIssues.Add($"Failed to connect to the ERI v1 server. The message was: {e.Message}");
this.dataIssues.Add(string.Format(T("Failed to connect to the ERI v1 server. The message was: {0}"), e.Message));
}
finally
{
@@ -1,4 +1,5 @@
@using AIStudio.Settings.DataModel
@inherits MSGComponentBase
<MudDialog>
<DialogContent>
@@ -7,7 +8,7 @@
<MudTextField
T="string"
@bind-Text="@this.dataName"
Label="Data Source Name"
Label="@T("Data Source Name")"
Class="mb-6"
MaxLength="40"
Counter="40"
@@ -20,19 +21,19 @@
/>
<MudJustifiedText Typo="Typo.body1" Class="mb-3">
Select a root directory for this data source. All data in this directory and all
its subdirectories will be processed for this data source.
@T("Select a root directory for this data source. All data in this directory and all its subdirectories will be processed for this data source.")
</MudJustifiedText>
<SelectDirectory @bind-Directory="@this.dataPath" Label="Selected base directory for this data source" DirectoryDialogTitle="Select the base directory" Validation="@this.dataSourceValidation.ValidatePath" />
<SelectDirectory @bind-Directory="@this.dataPath" Label="@T("Selected base directory for this data source")" DirectoryDialogTitle="@T("Select the base directory")" Validation="@this.dataSourceValidation.ValidatePath" />
<MudJustifiedText Typo="Typo.body1" Class="mb-3">
In order for the AI to be able to determine the appropriate data at any time, you must
choose an embedding method.
@T("In order for the AI to be able to determine the appropriate data at any time, you must choose an embedding method.")
</MudJustifiedText>
<MudSelect @bind-Value="@this.dataEmbeddingId" Label="Embedding" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.dataSourceValidation.ValidateEmbeddingId">
<MudSelect @bind-Value="@this.dataEmbeddingId" Label="@T("Embedding")" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.dataSourceValidation.ValidateEmbeddingId">
@foreach (var embedding in this.AvailableEmbeddings)
{
<MudSelectItem Value="@embedding.Value">@embedding.Name</MudSelectItem>
<MudSelectItem Value="@embedding.Value">
@embedding.Name
</MudSelectItem>
}
</MudSelect>
@@ -43,45 +44,46 @@
<MudJustifiedText Typo="Typo.body1" Color="Color.Error" Class="mb-3">
@if (string.IsNullOrWhiteSpace(this.dataPath))
{
@: Please note: the embedding you selected runs in the cloud. All your data will be sent to the cloud.
@: Please confirm that you have read and understood this.
@T("Please note: the embedding you selected runs in the cloud. All your data will be sent to the cloud. Please confirm that you have read and understood this.")
}
else
{
@: Please note: the embedding you selected runs in the cloud. All your data from the
@: folder '@this.dataPath' and all its subdirectories will be sent to the cloud. Please
@: confirm that you have read and understood this.
@string.Format(T("Please note: the embedding you selected runs in the cloud. All your data from the folder '{0}' and all its subdirectories will be sent to the cloud. Please confirm that you have read and understood this."), this.dataPath)
}
</MudJustifiedText>
<MudTextSwitch @bind-Value="@this.dataUserAcknowledgedCloudEmbedding" Label="I confirm that I have read and understood the above" LabelOn="Yes, please send my data to the cloud" LabelOff="No, I will chose another embedding" Validation="@this.dataSourceValidation.ValidateUserAcknowledgedCloudEmbedding"/>
<MudTextSwitch @bind-Value="@this.dataUserAcknowledgedCloudEmbedding" Label="@T("I confirm that I have read and understood the above")" LabelOn="@T("Yes, please send my data to the cloud")" LabelOff="@T("No, I will chose another embedding")" Validation="@this.dataSourceValidation.ValidateUserAcknowledgedCloudEmbedding"/>
}
else
{
<MudJustifiedText Typo="Typo.body1" Color="Color.Tertiary" Class="mb-3">
The embedding you selected runs locally or in your organization. Your data is not sent to the cloud.
@T("The embedding you selected runs locally or in your organization. Your data is not sent to the cloud.")
</MudJustifiedText>
}
}
<MudSelect @bind-Value="@this.dataSecurityPolicy" Text="@this.dataSecurityPolicy.ToSelectionText()" Label="Your security policy" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.dataSourceValidation.ValidateSecurityPolicy">
<MudSelect @bind-Value="@this.dataSecurityPolicy" Text="@this.dataSecurityPolicy.ToSelectionText()" Label="@T("Your security policy")" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.dataSourceValidation.ValidateSecurityPolicy">
@foreach (var policy in Enum.GetValues<DataSourceSecurity>())
{
<MudSelectItem Value="@policy">@policy.ToSelectionText()</MudSelectItem>
<MudSelectItem Value="@policy">
@policy.ToSelectionText()
</MudSelectItem>
}
</MudSelect>
</MudForm>
<Issues IssuesData="@this.dataIssues"/>
</DialogContent>
<DialogActions>
<MudButton OnClick="@this.Cancel" Variant="Variant.Filled">Cancel</MudButton>
<MudButton OnClick="@this.Cancel" Variant="Variant.Filled">
@T("Cancel")
</MudButton>
<MudButton OnClick="@this.Store" Variant="Variant.Filled" Color="Color.Primary">
@if(this.IsEditing)
{
@:Update
@T("Update")
}
else
{
@:Add
@T("Add")
}
</MudButton>
</DialogActions>
@@ -1,3 +1,4 @@
using AIStudio.Components;
using AIStudio.Settings;
using AIStudio.Settings.DataModel;
using AIStudio.Tools.Validation;
@@ -6,7 +7,7 @@ using Microsoft.AspNetCore.Components;
namespace AIStudio.Dialogs;
public partial class DataSourceLocalDirectoryDialog : ComponentBase
public partial class DataSourceLocalDirectoryDialog : MSGComponentBase
{
[CascadingParameter]
private IMudDialogInstance MudDialog { get; set; } = null!;
@@ -20,9 +21,6 @@ public partial class DataSourceLocalDirectoryDialog : ComponentBase
[Parameter]
public IReadOnlyList<ConfigurationSelectData<string>> AvailableEmbeddings { get; set; } = [];
[Inject]
private SettingsManager SettingsManager { get; init; } = null!;
private static readonly Dictionary<string, object?> SPELLCHECK_ATTRIBUTES = new();
private readonly DataSourceValidation dataSourceValidation;
@@ -1,56 +1,58 @@
@using AIStudio.Settings.DataModel
@inherits MSGComponentBase
<MudDialog>
<DialogContent>
<TextInfoLine Icon="@Icons.Material.Filled.Tag" Label="Data source name" Value="@this.DataSource.Name" ClipboardTooltipSubject="the data source name"/>
<TextInfoLine Icon="@Icons.Material.Filled.Tag" Label="@T("Data source name")" Value="@this.DataSource.Name" ClipboardTooltipSubject="@T("the data source name")"/>
<TextInfoLine Icon="@Icons.Material.Filled.FolderOpen" Label="Path" Value="@this.DataSource.Path" ClipboardTooltipSubject="this path"/>
<TextInfoLine Icon="@Icons.Material.Filled.FolderOpen" Label="@T("Path")" Value="@this.DataSource.Path" ClipboardTooltipSubject="@T("this path")"/>
@if (!this.IsDirectoryAvailable)
{
<MudJustifiedText Typo="Typo.body1" Color="Color.Error" Class="mb-3">
The directory chosen for the data source does not exist anymore. Please edit the data source and correct the path.
@T("The directory chosen for the data source does not exist anymore. Please edit the data source and correct the path.")
</MudJustifiedText>
}
else
{
<MudJustifiedText Typo="Typo.body1" Color="Color.Tertiary" Class="mb-3">
The directory chosen for the data source exists.
@T("The directory chosen for the data source exists.")
</MudJustifiedText>
}
<TextInfoLine Icon="@Icons.Material.Filled.Layers" Label="Embedding name" Value="@this.embeddingProvider.Name" ClipboardTooltipSubject="the embedding name"/>
<TextInfoLine Icon="@Icons.Material.Filled.Layers" Label="@T("Embedding name")" Value="@this.embeddingProvider.Name" ClipboardTooltipSubject="@T("the embedding name")"/>
@if (this.IsCloudEmbedding)
{
<MudJustifiedText Typo="Typo.body1" Color="Color.Error" Class="mb-3">
The embedding runs in the cloud. All your data from the folder '@this.DataSource.Path' and all its subdirectories
will be sent to the cloud.
@string.Format(T("The embedding runs in the cloud. All your data from the folder '{0}' and all its subdirectories will be sent to the cloud."), this.DataSource.Path)
</MudJustifiedText>
}
else
{
<MudJustifiedText Typo="Typo.body1" Color="Color.Tertiary" Class="mb-3">
The embedding runs locally or in your organization. Your data is not sent to the cloud.
@T("The embedding runs locally or in your organization. Your data is not sent to the cloud.")
</MudJustifiedText>
}
<TextInfoLines Label="Your security policy" MaxLines="3" Value="@this.DataSource.SecurityPolicy.ToInfoText()" Color="@this.DataSource.SecurityPolicy.GetColor()" ClipboardTooltipSubject="your security policy"/>
<TextInfoLines Label="@T("Your security policy")" MaxLines="3" Value="@this.DataSource.SecurityPolicy.ToInfoText()" Color="@this.DataSource.SecurityPolicy.GetColor()" ClipboardTooltipSubject="@T("your security policy")"/>
<TextInfoLine Icon="@Icons.Material.Filled.SquareFoot" Label="Number of files" Value="@this.NumberFilesInDirectory" ClipboardTooltipSubject="the number of files in the directory"/>
<TextInfoLines Label="Files list" MaxLines="14" Value="@this.directoryFiles.ToString()" ClipboardTooltipSubject="the files list"/>
<TextInfoLine Icon="@Icons.Material.Filled.SquareFoot" Label="@T("Number of files")" Value="@this.NumberFilesInDirectory" ClipboardTooltipSubject="@T("the number of files in the directory")"/>
<TextInfoLines Label="@T("Files list")" MaxLines="14" Value="@this.directoryFiles.ToString()" ClipboardTooltipSubject="@T("the files list")"/>
@if (this.directorySizeNumFiles > 100)
{
<MudJustifiedText Typo="Typo.body1" Color="Color.Warning" Class="mb-3">
For performance reasons, only the first 100 files are shown. The directory contains @this.NumberFilesInDirectory files in total.
@string.Format(T("For performance reasons, only the first 100 files are shown. The directory contains {0} files in total."), this.NumberFilesInDirectory)
</MudJustifiedText>
}
<TextInfoLine Icon="@Icons.Material.Filled.SquareFoot" Label="Total directory size" Value="@this.directorySizeBytes.FileSize()" ClipboardTooltipSubject="the total directory size"/>
<TextInfoLine Icon="@Icons.Material.Filled.SquareFoot" Label="@T("Total directory size")" Value="@this.directorySizeBytes.FileSize()" ClipboardTooltipSubject="@T("the total directory size")"/>
</DialogContent>
<DialogActions>
@if (this.IsOperationInProgress)
{
<MudProgressLinear Color="Color.Primary" Indeterminate="true" Class="ml-5 mr-5"/>
}
<MudButton OnClick="@this.Close" Variant="Variant.Filled">Close</MudButton>
<MudButton OnClick="@this.Close" Variant="Variant.Filled">
@T("Close")
</MudButton>
</DialogActions>
</MudDialog>
@@ -1,5 +1,6 @@
using System.Text;
using AIStudio.Components;
using AIStudio.Settings;
using AIStudio.Settings.DataModel;
@@ -9,7 +10,7 @@ using Timer = System.Timers.Timer;
namespace AIStudio.Dialogs;
public partial class DataSourceLocalDirectoryInfoDialog : ComponentBase, IAsyncDisposable
public partial class DataSourceLocalDirectoryInfoDialog : MSGComponentBase, IAsyncDisposable
{
[CascadingParameter]
private IMudDialogInstance MudDialog { get; set; } = null!;
@@ -17,9 +18,6 @@ public partial class DataSourceLocalDirectoryInfoDialog : ComponentBase, IAsyncD
[Parameter]
public DataSourceLocalDirectory DataSource { get; set; }
[Inject]
private SettingsManager SettingsManager { get; init; } = null!;
private readonly Timer refreshTimer = new(TimeSpan.FromSeconds(1.6))
{
AutoReset = true,
@@ -1,4 +1,6 @@
@using AIStudio.Settings.DataModel
@inherits MSGComponentBase
<MudDialog>
<DialogContent>
<MudForm @ref="@this.form" @bind-IsValid="@this.dataIsValid" @bind-Errors="@this.dataIssues">
@@ -6,7 +8,7 @@
<MudTextField
T="string"
@bind-Text="@this.dataName"
Label="Data Source Name"
Label="@T("Data Source Name")"
Class="mb-6"
MaxLength="40"
Counter="40"
@@ -19,18 +21,19 @@
/>
<MudJustifiedText Typo="Typo.body1" Class="mb-3">
Select a file for this data source. The content of this file will be processed for the data source.
@T("Select a file for this data source. The content of this file will be processed for the data source.")
</MudJustifiedText>
<SelectFile @bind-File="@this.dataFilePath" Label="Selected file path for this data source" FileDialogTitle="Select the file" Validation="@this.dataSourceValidation.ValidateFilePath" />
<SelectFile @bind-File="@this.dataFilePath" Label="@T("Selected file path for this data source")" FileDialogTitle="@T("Select the file")" Validation="@this.dataSourceValidation.ValidateFilePath" />
<MudJustifiedText Typo="Typo.body1" Class="mb-3">
In order for the AI to be able to determine the appropriate data at any time, you must
choose an embedding method.
@T("In order for the AI to be able to determine the appropriate data at any time, you must choose an embedding method.")
</MudJustifiedText>
<MudSelect @bind-Value="@this.dataEmbeddingId" Label="Embedding" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.dataSourceValidation.GetSelectedCloudEmbedding">
<MudSelect @bind-Value="@this.dataEmbeddingId" Label="@T("Embedding")" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.dataSourceValidation.GetSelectedCloudEmbedding">
@foreach (var embedding in this.AvailableEmbeddings)
{
<MudSelectItem Value="@embedding.Value">@embedding.Name</MudSelectItem>
<MudSelectItem Value="@embedding.Value">
@embedding.Name
</MudSelectItem>
}
</MudSelect>
@@ -41,45 +44,46 @@
<MudJustifiedText Typo="Typo.body1" Color="Color.Error" Class="mb-3">
@if (string.IsNullOrWhiteSpace(this.dataFilePath))
{
@: Please note: the embedding you selected runs in the cloud. All your data will be sent to the cloud.
@: Please confirm that you have read and understood this.
@T("Please note: the embedding you selected runs in the cloud. All your data will be sent to the cloud. Please confirm that you have read and understood this.")
}
else
{
@: Please note: the embedding you selected runs in the cloud. All your data within the
@: file '@this.dataFilePath' will be sent to the cloud. Please confirm that you have read
@: and understood this.
@string.Format(T("Please note: the embedding you selected runs in the cloud. All your data within the file '{0}' will be sent to the cloud. Please confirm that you have read and understood this."), this.dataFilePath)
}
</MudJustifiedText>
<MudTextSwitch @bind-Value="@this.dataUserAcknowledgedCloudEmbedding" Label="I confirm that I have read and understood the above" LabelOn="Yes, please send my data to the cloud" LabelOff="No, I will chose another embedding" Validation="@this.dataSourceValidation.ValidateUserAcknowledgedCloudEmbedding"/>
<MudTextSwitch @bind-Value="@this.dataUserAcknowledgedCloudEmbedding" Label="@T("I confirm that I have read and understood the above")" LabelOn="@T("Yes, please send my data to the cloud")" LabelOff="@T("No, I will chose another embedding")" Validation="@this.dataSourceValidation.ValidateUserAcknowledgedCloudEmbedding"/>
}
else
{
<MudJustifiedText Typo="Typo.body1" Color="Color.Tertiary" Class="mb-3">
The embedding you selected runs locally or in your organization. Your data is not sent to the cloud.
@T("The embedding you selected runs locally or in your organization. Your data is not sent to the cloud.")
</MudJustifiedText>
}
}
<MudSelect @bind-Value="@this.dataSecurityPolicy" Text="@this.dataSecurityPolicy.ToSelectionText()" Label="Your security policy" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.dataSourceValidation.ValidateSecurityPolicy">
<MudSelect @bind-Value="@this.dataSecurityPolicy" Text="@this.dataSecurityPolicy.ToSelectionText()" Label="@T("Your security policy")" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.dataSourceValidation.ValidateSecurityPolicy">
@foreach (var policy in Enum.GetValues<DataSourceSecurity>())
{
<MudSelectItem Value="@policy">@policy.ToSelectionText()</MudSelectItem>
<MudSelectItem Value="@policy">
@policy.ToSelectionText()
</MudSelectItem>
}
</MudSelect>
</MudForm>
<Issues IssuesData="@this.dataIssues"/>
</DialogContent>
<DialogActions>
<MudButton OnClick="@this.Cancel" Variant="Variant.Filled">Cancel</MudButton>
<MudButton OnClick="@this.Cancel" Variant="Variant.Filled">
@T("Cancel")
</MudButton>
<MudButton OnClick="@this.Store" Variant="Variant.Filled" Color="Color.Primary">
@if(this.IsEditing)
{
@:Update
@T("Update")
}
else
{
@:Add
@T("Add")
}
</MudButton>
</DialogActions>
@@ -1,3 +1,4 @@
using AIStudio.Components;
using AIStudio.Settings;
using AIStudio.Settings.DataModel;
using AIStudio.Tools.Validation;
@@ -6,7 +7,7 @@ using Microsoft.AspNetCore.Components;
namespace AIStudio.Dialogs;
public partial class DataSourceLocalFileDialog : ComponentBase
public partial class DataSourceLocalFileDialog : MSGComponentBase
{
[CascadingParameter]
private IMudDialogInstance MudDialog { get; set; } = null!;
@@ -20,9 +21,6 @@ public partial class DataSourceLocalFileDialog : ComponentBase
[Parameter]
public IReadOnlyList<ConfigurationSelectData<string>> AvailableEmbeddings { get; set; } = [];
[Inject]
private SettingsManager SettingsManager { get; init; } = null!;
private static readonly Dictionary<string, object?> SPELLCHECK_ATTRIBUTES = new();
private readonly DataSourceValidation dataSourceValidation;
@@ -1,42 +1,44 @@
@using AIStudio.Settings.DataModel
@inherits MSGComponentBase
<MudDialog>
<DialogContent>
<TextInfoLine Icon="@Icons.Material.Filled.Tag" Label="Data source name" Value="@this.DataSource.Name" ClipboardTooltipSubject="the data source name"/>
<TextInfoLine Icon="@Icons.Material.Filled.Tag" Label="@T("Data source name")" Value="@this.DataSource.Name" ClipboardTooltipSubject="@T("the data source name")"/>
<TextInfoLine Icon="@Icons.Material.Filled.FolderOpen" Label="File path" Value="@this.DataSource.FilePath" ClipboardTooltipSubject="this path"/>
<TextInfoLine Icon="@Icons.Material.Filled.FolderOpen" Label="@T("File path")" Value="@this.DataSource.FilePath" ClipboardTooltipSubject="@T("this path")"/>
@if (!this.IsFileAvailable)
{
<MudJustifiedText Typo="Typo.body1" Color="Color.Error" Class="mb-3">
The file chosen for the data source does not exist anymore. Please edit the data source and choose another file or correct the path.
@T("The file chosen for the data source does not exist anymore. Please edit the data source and choose another file or correct the path.")
</MudJustifiedText>
}
else
{
<MudJustifiedText Typo="Typo.body1" Color="Color.Tertiary" Class="mb-3">
The file chosen for the data source exists.
@T("The file chosen for the data source exists.")
</MudJustifiedText>
}
<TextInfoLine Icon="@Icons.Material.Filled.Layers" Label="Embedding name" Value="@this.embeddingProvider.Name" ClipboardTooltipSubject="the embedding name"/>
<TextInfoLine Icon="@Icons.Material.Filled.Layers" Label="@T("Embedding name")" Value="@this.embeddingProvider.Name" ClipboardTooltipSubject="@T("the embedding name")"/>
@if (this.IsCloudEmbedding)
{
<MudJustifiedText Typo="Typo.body1" Color="Color.Error" Class="mb-3">
The embedding runs in the cloud. All your data within the
file '@this.DataSource.FilePath' will be sent to the cloud.
@string.Format(T("The embedding runs in the cloud. All your data within the file '{0}' will be sent to the cloud."), this.DataSource.FilePath)
</MudJustifiedText>
}
else
{
<MudJustifiedText Typo="Typo.body1" Color="Color.Tertiary" Class="mb-3">
The embedding runs locally or in your organization. Your data is not sent to the cloud.
@T("The embedding runs locally or in your organization. Your data is not sent to the cloud.")
</MudJustifiedText>
}
<TextInfoLines Label="Your security policy" MaxLines="3" Value="@this.DataSource.SecurityPolicy.ToInfoText()" Color="@this.DataSource.SecurityPolicy.GetColor()" ClipboardTooltipSubject="your security policy"/>
<TextInfoLine Icon="@Icons.Material.Filled.SquareFoot" Label="File size" Value="@this.FileSize" ClipboardTooltipSubject="the file size"/>
<TextInfoLines Label="@T("Your security policy")" MaxLines="3" Value="@this.DataSource.SecurityPolicy.ToInfoText()" Color="@this.DataSource.SecurityPolicy.GetColor()" ClipboardTooltipSubject="@T("your security policy")"/>
<TextInfoLine Icon="@Icons.Material.Filled.SquareFoot" Label="@T("File size")" Value="@this.FileSize" ClipboardTooltipSubject="@T("the file size")"/>
</DialogContent>
<DialogActions>
<MudButton OnClick="@this.Close" Variant="Variant.Filled">Close</MudButton>
<MudButton OnClick="@this.Close" Variant="Variant.Filled">
@T("Close")
</MudButton>
</DialogActions>
</MudDialog>
@@ -1,3 +1,4 @@
using AIStudio.Components;
using AIStudio.Settings;
using AIStudio.Settings.DataModel;
@@ -5,7 +6,7 @@ using Microsoft.AspNetCore.Components;
namespace AIStudio.Dialogs;
public partial class DataSourceLocalFileInfoDialog : ComponentBase
public partial class DataSourceLocalFileInfoDialog : MSGComponentBase
{
[CascadingParameter]
private IMudDialogInstance MudDialog { get; set; } = null!;
@@ -13,9 +14,6 @@ public partial class DataSourceLocalFileInfoDialog : ComponentBase
[Parameter]
public DataSourceLocalFile DataSource { get; set; }
[Inject]
private SettingsManager SettingsManager { get; init; } = null!;
#region Overrides of ComponentBase
protected override async Task OnInitializedAsync()
@@ -1,3 +1,5 @@
@inherits MSGComponentBase
<MudDialog>
<DialogContent>
<MudForm @ref="@this.form" @bind-IsValid="@this.dataIsValid" @bind-Errors="@this.dataIssues">
@@ -5,8 +7,8 @@
<MudTextField
T="string"
@bind-Text="@this.DataEmbeddingName"
Label="Embedding Name"
HelperText="The name of the embedding method."
Label="@T("Embedding Name")"
HelperText="@T("The name of the embedding method.")"
Class="mb-3"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Label"
@@ -22,8 +24,8 @@
<MudTextField
T="string"
@bind-Text="@this.DataEmbeddingType"
Label="Embedding Type"
HelperText="What kind of embedding is used. For example, Transformer Embedding, Contextual Word Embedding, Graph Embedding, etc."
Label="@T("Embedding Type")"
HelperText="@T("What kind of embedding is used. For example, Transformer Embedding, Contextual Word Embedding, Graph Embedding, etc.")"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Extension"
AdornmentColor="Color.Info"
@@ -35,16 +37,16 @@
/>
<MudList T="@string" Class="mb-3">
<MudListItem Icon="@Icons.Material.Filled.Link" Href="https://en.wikipedia.org/wiki/Word_embedding" Target="_blank" Text="See Wikipedia for more information about word embeddings"/>
<MudListItem Icon="@Icons.Material.Filled.Link" Href="https://en.wikipedia.org/wiki/Knowledge_graph_embedding" Target="_blank" Text="See Wikipedia for more information about knowledge graph embeddings"/>
<MudListItem Icon="@Icons.Material.Filled.Link" Href="https://en.wikipedia.org/wiki/Word_embedding" Target="_blank" Text="@T("See Wikipedia for more information about word embeddings")"/>
<MudListItem Icon="@Icons.Material.Filled.Link" Href="https://en.wikipedia.org/wiki/Knowledge_graph_embedding" Target="_blank" Text="@T("See Wikipedia for more information about knowledge graph embeddings")"/>
</MudList>
@* ReSharper disable once CSharpWarnings::CS8974 *@
<MudTextField
T="string"
@bind-Text="@this.DataDescription"
Label="Embedding Description"
HelperText="A short description of the embedding method."
Label="@T("Embedding Description")"
HelperText="@T("A short description of the embedding method.")"
Lines="3"
AutoGrow="@true"
MaxLines="6"
@@ -62,8 +64,8 @@
<MudTextField
T="string"
@bind-Text="@this.DataUsedWhen"
Label="Used when"
HelperText="When is this embedding used? When you define multiple embeddings, it is helpful to know when to use which one."
Label="@T("Used when")"
HelperText="@T("When is this embedding used? When you define multiple embeddings, it is helpful to know when to use which one.")"
Lines="3"
AutoGrow="@true"
MaxLines="6"
@@ -81,8 +83,8 @@
<MudTextField
T="string"
@bind-Text="@this.DataLink"
Label="Embedding Link"
HelperText="A link to the embedding, e.g., to the model, the source code, the paper, it's Wikipedia page, etc."
Label="@T("Embedding Link")"
HelperText="@T("A link to the embedding, e.g., to the model, the source code, the paper, it's Wikipedia page, etc.")"
Class="mb-3"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Link"
@@ -94,15 +96,17 @@
<Issues IssuesData="@this.dataIssues"/>
</DialogContent>
<DialogActions>
<MudButton OnClick="@this.Cancel" Variant="Variant.Filled">Cancel</MudButton>
<MudButton OnClick="@this.Cancel" Variant="Variant.Filled">
@T("Cancel")
</MudButton>
<MudButton OnClick="@this.Store" Variant="Variant.Filled" Color="Color.Primary">
@if(this.IsEditing)
{
@:Update
@T("Update")
}
else
{
@:Add
@T("Add")
}
</MudButton>
</DialogActions>
@@ -1,11 +1,12 @@
using AIStudio.Assistants.ERI;
using AIStudio.Components;
using AIStudio.Settings;
using Microsoft.AspNetCore.Components;
namespace AIStudio.Dialogs;
public partial class EmbeddingMethodDialog : ComponentBase
public partial class EmbeddingMethodDialog : MSGComponentBase
{
[CascadingParameter]
private IMudDialogInstance MudDialog { get; set; } = null!;
@@ -52,9 +53,6 @@ public partial class EmbeddingMethodDialog : ComponentBase
[Parameter]
public bool IsEditing { get; init; }
[Inject]
private SettingsManager SettingsManager { get; init; } = null!;
private static readonly Dictionary<string, object?> SPELLCHECK_ATTRIBUTES = new();
private bool dataIsValid;
@@ -90,13 +88,13 @@ public partial class EmbeddingMethodDialog : ComponentBase
private string? ValidateName(string name)
{
if (string.IsNullOrWhiteSpace(name))
return "The embedding name must not be empty. Please name the embedding.";
return T("The embedding name must not be empty. Please name the embedding.");
if (name.Length > 26)
return "The embedding name must not be longer than 26 characters.";
return T("The embedding name must not be longer than 26 characters.");
if (this.UsedEmbeddingMethodNames.Contains(name))
return $"The embedding method name '{name}' is already used. Please choose a unique name.";
return string.Format(T("The embedding method name '{0}' is already used. Please choose a unique name."), name);
return null;
}
@@ -104,10 +102,10 @@ public partial class EmbeddingMethodDialog : ComponentBase
private string? ValidateType(string type)
{
if (string.IsNullOrWhiteSpace(type))
return "The embedding type must not be empty. Please specify the embedding type.";
return T("The embedding type must not be empty. Please specify the embedding type.");
if (type.Length > 56)
return "The embedding type must not be longer than 56 characters.";
return T("The embedding type must not be longer than 56 characters.");
return null;
}
@@ -115,7 +113,7 @@ public partial class EmbeddingMethodDialog : ComponentBase
private string? ValidateDescription(string description)
{
if (string.IsNullOrWhiteSpace(description))
return "The description must not be empty. Please describe the embedding method.";
return T("The description must not be empty. Please describe the embedding method.");
return null;
}
@@ -123,7 +121,7 @@ public partial class EmbeddingMethodDialog : ComponentBase
private string? ValidateUsedWhen(string usedWhen)
{
if (string.IsNullOrWhiteSpace(usedWhen))
return "Please describe when the embedding is used. Might be anytime or when certain keywords are present, etc.";
return T("Please describe when the embedding is used. Might be anytime or when certain keywords are present, etc.");
return null;
}
@@ -1,15 +1,16 @@
@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="Provider" Class="mb-3" OpenIcon="@Icons.Material.Filled.AccountBalance" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.providerValidation.ValidatingProvider">
<MudSelect @bind-Value="@this.DataLLMProvider" Label="@T("Provider")" Class="mb-3" OpenIcon="@Icons.Material.Filled.AccountBalance" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.providerValidation.ValidatingProvider">
@foreach (LLMProviders provider in Enum.GetValues(typeof(LLMProviders)))
{
if (provider.ProvideEmbeddings())
if (provider.ProvideEmbeddings() || provider is LLMProviders.NONE)
{
<MudSelectItem Value="@provider">
@provider.ToName()
@@ -17,7 +18,9 @@
}
}
</MudSelect>
<MudButton Disabled="@(!this.DataLLMProvider.ShowRegisterButton())" Variant="Variant.Filled" Size="Size.Small" StartIcon="@Icons.Material.Filled.OpenInBrowser" Href="@this.DataLLMProvider.GetCreationURL()" Target="_blank">Create account</MudButton>
<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))
@@ -32,6 +35,7 @@
AdornmentIcon="@Icons.Material.Filled.VpnKey"
AdornmentColor="Color.Info"
InputType="InputType.Password"
Immediate="true"
Validation="@this.providerValidation.ValidatingAPIKey"/>
}
@@ -40,7 +44,7 @@
<MudTextField
T="string"
@bind-Text="@this.DataHostname"
Label="Hostname"
Label="@T("Hostname")"
Class="mb-3"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Dns"
@@ -51,7 +55,7 @@
@if (this.DataLLMProvider.IsHostNeeded())
{
<MudSelect @bind-Value="@this.DataHost" Label="Host" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.providerValidation.ValidatingHost">
<MudSelect @bind-Value="@this.DataHost" Label="@T("Host")" Class="mb-3" OpenIcon="@Icons.Material.Filled.ExpandMore" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.providerValidation.ValidatingHost">
@foreach (Host host in Enum.GetValues(typeof(Host)))
{
if (host.AreEmbeddingsSupported())
@@ -64,39 +68,57 @@
</MudSelect>
}
<MudStack Row="@true" AlignItems="AlignItems.Center">
<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.IsEmbeddingModelProvidedManually(this.DataHost))
{
<MudTextField
T="string"
@bind-Text="@this.dataManuallyModel"
Label="Model"
Label="@T("Model")"
Class="mb-3"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Dns"
AdornmentColor="Color.Info"
Validation="@this.ValidateManuallyModel"
UserAttributes="@SPELLCHECK_ATTRIBUTES"
HelperText="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 of self-hosted systems. Therefore, 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">Load</MudButton>
<MudSelect Disabled="@this.IsNoneProvider" @bind-Value="@this.DataModel" Label="Model" Class="mb-3" OpenIcon="@Icons.Material.Filled.FaceRetouchingNatural" AdornmentColor="Color.Info" Adornment="Adornment.Start" Validation="@this.providerValidation.ValidatingModel">
<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>
<MudSelectItem Value="@model">
@model
</MudSelectItem>
}
</MudSelect>
}
}
</MudStack>
</MudField>
@* ReSharper disable once CSharpWarnings::CS8974 *@
<MudTextField
T="string"
@bind-Text="@this.DataName"
Label="Instance Name"
Label="@T("Instance Name")"
Class="mb-3"
MaxLength="40"
Counter="40"
@@ -112,15 +134,17 @@
<Issues IssuesData="@this.dataIssues"/>
</DialogContent>
<DialogActions>
<MudButton OnClick="@this.Cancel" Variant="Variant.Filled">Cancel</MudButton>
<MudButton OnClick="@this.Cancel" Variant="Variant.Filled">
@T("Cancel")
</MudButton>
<MudButton OnClick="@this.Store" Variant="Variant.Filled" Color="Color.Primary">
@if(this.IsEditing)
{
@:Update
@T("Update")
}
else
{
@:Add
@T("Add")
}
</MudButton>
</DialogActions>
@@ -1,3 +1,4 @@
using AIStudio.Components;
using AIStudio.Provider;
using AIStudio.Settings;
using AIStudio.Tools.Services;
@@ -9,7 +10,7 @@ using Host = AIStudio.Provider.SelfHosted.Host;
namespace AIStudio.Dialogs;
public partial class EmbeddingProviderDialog : ComponentBase, ISecretId
public partial class EmbeddingProviderDialog : MSGComponentBase, ISecretId
{
[CascadingParameter]
private IMudDialogInstance MudDialog { get; set; } = null!;
@@ -68,9 +69,6 @@ public partial class EmbeddingProviderDialog : ComponentBase, ISecretId
[Parameter]
public bool IsEditing { get; init; }
[Inject]
private SettingsManager SettingsManager { get; init; } = null!;
[Inject]
private ILogger<ProviderDialog> Logger { get; init; } = null!;
@@ -175,7 +173,7 @@ public partial class EmbeddingProviderDialog : ComponentBase, ISecretId
this.dataAPIKey = string.Empty;
if (this.DataLLMProvider is not LLMProviders.SELF_HOSTED)
{
this.dataAPIKeyStorageIssue = $"Failed to load the API key from the operating system. The message was: {requestedSecret.Issue}. You might ignore this message and provide the API key again.";
this.dataAPIKeyStorageIssue = string.Format(T("Failed to load the API key from the operating system. The message was: {0}. You might ignore this message and provide the API key again."), requestedSecret.Issue);
await this.form.Validate();
}
}
@@ -224,7 +222,7 @@ public partial class EmbeddingProviderDialog : ComponentBase, ISecretId
var storeResponse = await this.RustService.SetAPIKey(this, this.dataAPIKey);
if (!storeResponse.Success)
{
this.dataAPIKeyStorageIssue = $"Failed to store the API key in the operating system. The message was: {storeResponse.Issue}. Please try again.";
this.dataAPIKeyStorageIssue = string.Format(T("Failed to store the API key in the operating system. The message was: {0}. Please try again."), storeResponse.Issue);
await this.form.Validate();
return;
}
@@ -236,7 +234,7 @@ public partial class EmbeddingProviderDialog : ComponentBase, ISecretId
private string? ValidateManuallyModel(string manuallyModel)
{
if (this.DataLLMProvider is LLMProviders.SELF_HOSTED && string.IsNullOrWhiteSpace(manuallyModel))
return "Please enter an embedding model name.";
return T("Please enter an embedding model name.");
return null;
}
@@ -261,8 +259,8 @@ public partial class EmbeddingProviderDialog : ComponentBase, ISecretId
private string APIKeyText => this.DataLLMProvider switch
{
LLMProviders.SELF_HOSTED => "(Optional) API Key",
_ => "API Key",
LLMProviders.SELF_HOSTED => T("(Optional) API Key"),
_ => T("API Key"),
};
private bool IsNoneProvider => this.DataLLMProvider is LLMProviders.NONE;
@@ -1,24 +1,25 @@
@using AIStudio.Assistants.ERI
@using MudExtensions
@inherits MSGComponentBase
<MudDialog>
<DialogContent>
<MudForm @ref="@this.form" @bind-IsValid="@this.dataIsValid" @bind-Errors="@this.dataIssues">
<MudText Typo="Typo.h5" Class="mb-3">
General Information
@T("General Information")
</MudText>
<MudText Typo="Typo.body1" Class="mb-3">
Please provide some general information about your retrieval process first. This data may be
displayed to the users.
@T("Please provide some general information about your retrieval process first. This data may be displayed to the users.")
</MudText>
@* ReSharper disable once CSharpWarnings::CS8974 *@
<MudTextField
T="string"
@bind-Text="@this.DataName"
Label="Retrieval Process Name"
HelperText="The name of your retrieval process."
Label="@T("Retrieval Process Name")"
HelperText="@T("The name of your retrieval process.")"
Class="mb-3"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Label"
@@ -34,8 +35,8 @@
<MudTextField
T="string"
@bind-Text="@this.DataDescription"
Label="Retrieval Process Description"
HelperText="A short description of the retrieval process."
Label="@T("Retrieval Process Description")"
HelperText="@T("A short description of the retrieval process.")"
Lines="3"
AutoGrow="@true"
MaxLines="6"
@@ -53,8 +54,8 @@
<MudTextField
T="string"
@bind-Text="@this.DataLink"
Label="Retrieval Process Link"
HelperText="A link to the retrieval process, e.g., the source code, the paper, it's Wikipedia page, etc. Make sense for common retrieval processes. Leave empty if not applicable."
Label="@T("Retrieval Process Link")"
HelperText="@T("A link to the retrieval process, e.g., the source code, the paper, it's Wikipedia page, etc. Make sense for common retrieval processes. Leave empty if not applicable.")"
Class="mb-6"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Link"
@@ -63,13 +64,11 @@
/>
<MudText Typo="Typo.h5" Class="mb-3">
Retrieval Process Parameters
@T("Retrieval Process Parameters")
</MudText>
<MudText Typo="Typo.body1" Class="mb-3">
You may want to parameterize your retrieval process. However, this is optional. You can specify any
parameters that can be set by the user or the system during the call. Nevertheless, you should use
sensible default values in your code so that users are not forced to set the parameters manually.
@T("You may want to parameterize your retrieval process. However, this is optional. You can specify any parameters that can be set by the user or the system during the call. Nevertheless, you should use sensible default values in your code so that users are not forced to set the parameters manually.")
</MudText>
<MudStack Row="@true" Spacing="6" AlignItems="AlignItems.Start" StretchItems="StretchItems.None">
@@ -87,7 +86,7 @@
</MudList>
}
<MudButton OnClick="@this.AddRetrievalProcessParameter" Variant="Variant.Filled" Color="Color.Primary" Class="mt-1">
Add Parameter
@T("Add Parameter")
</MudButton>
</MudStack>
@@ -98,13 +97,13 @@
@if(this.retrievalParameters.Count == 0)
{
<MudText>
Add a parameter first, then select it to edit.
@T("Add a parameter first, then select it to edit.")
</MudText>
}
else
{
<MudText>
Select a parameter to show and edit it.
@T("Select a parameter to show and edit it.")
</MudText>
}
}
@@ -114,8 +113,8 @@
<MudTextField
T="string"
@bind-Text="@this.selectedParameter.Name"
Label="Parameter Name"
HelperText="The parameter name. It must be unique within the retrieval process."
Label="@T("Parameter Name")"
HelperText="@T("The parameter name. It must be unique within the retrieval process.")"
Class="mb-3"
Adornment="Adornment.Start"
AdornmentIcon="@Icons.Material.Filled.Label"
@@ -130,8 +129,8 @@
<MudTextField
T="string"
@bind-Text="@this.selectedParameter.Description"
Label="Parameter Description"
HelperText="A short description of the parameter. What data type is it? What is it used for? What are the possible values?"
Label="@T("Parameter Description")"
HelperText="@T("A short description of the parameter. What data type is it? What is it used for? What are the possible values?")"
Lines="3"
AutoGrow="@true"
MaxLines="6"
@@ -146,7 +145,7 @@
<MudStack Row="@true">
<MudButton OnClick="@this.RemoveRetrievalProcessParameter" Variant="Variant.Filled" Color="Color.Secondary">
Delete this parameter
@T("Delete this parameter")
</MudButton>
</MudStack>
@@ -155,22 +154,19 @@
</MudStack>
<MudText Typo="Typo.h5" Class="mb-3">
Embeddings
@T("Embeddings")
</MudText>
@if(this.AvailableEmbeddings.Count == 0)
{
<MudText Typo="Typo.body1" Class="mb-3">
Currently, you have not defined any embedding methods. If your retrieval process does not require embedding, you can ignore this part.
Otherwise, you can define one or more embedding methods in the previous view to assign them to your retrieval process here.
@T("Currently, you have not defined any embedding methods. If your retrieval process does not require embedding, you can ignore this part. Otherwise, you can define one or more embedding methods in the previous view to assign them to your retrieval process here.")
</MudText>
}
else
{
<MudText Typo="Typo.body1" Class="mb-3">
Here you can select which embedding methods are used for this retrieval process. Embeddings are optional;
if your retrieval process works without embedding, you can ignore this part. You can only choose the embedding
methods you have previously defined.
@T("Here you can select which embedding methods are used for this retrieval process. Embeddings are optional; if your retrieval process works without embedding, you can ignore this part. You can only choose the embedding methods you have previously defined.")
</MudText>
<MudSelectExtended
@@ -181,11 +177,11 @@
SelectedValuesChanged="@this.EmbeddingsChanged"
Strict="@true"
Margin="Margin.Dense"
Label="Embeddings methods"
Label="@T("Embeddings methods")"
ShrinkLabel="@true"
Class="mb-3"
Variant="Variant.Outlined"
HelperText="Optional. Select the embedding methods that are used for this retrieval process.">
HelperText="@T("Optional. Select the embedding methods that are used for this retrieval process.")">
@foreach (var embedding in this.AvailableEmbeddings)
{
<MudSelectItemExtended Value="@embedding">
@@ -198,15 +194,17 @@
<Issues IssuesData="@this.dataIssues"/>
</DialogContent>
<DialogActions>
<MudButton OnClick="@this.Cancel" Variant="Variant.Filled">Cancel</MudButton>
<MudButton OnClick="@this.Cancel" Variant="Variant.Filled">
@T("Cancel")
</MudButton>
<MudButton OnClick="@this.Store" Variant="Variant.Filled" Color="Color.Primary">
@if(this.IsEditing)
{
@:Update
@T("Update")
}
else
{
@:Add
@T("Add")
}
</MudButton>
</DialogActions>
@@ -1,11 +1,11 @@
using AIStudio.Assistants.ERI;
using AIStudio.Settings;
using AIStudio.Components;
using Microsoft.AspNetCore.Components;
namespace AIStudio.Dialogs;
public partial class RetrievalProcessDialog : ComponentBase
public partial class RetrievalProcessDialog : MSGComponentBase
{
[CascadingParameter]
private IMudDialogInstance MudDialog { get; set; } = null!;
@@ -60,9 +60,6 @@ public partial class RetrievalProcessDialog : ComponentBase
[Parameter]
public bool IsEditing { get; init; }
[Inject]
private SettingsManager SettingsManager { get; init; } = null!;
private static readonly Dictionary<string, object?> SPELLCHECK_ATTRIBUTES = new();
private bool dataIsValid;
@@ -80,13 +77,13 @@ public partial class RetrievalProcessDialog : ComponentBase
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
// Configure the spellchecking for the instance name input:
this.SettingsManager.InjectSpellchecking(SPELLCHECK_ATTRIBUTES);
// Convert the parameters:
this.retrievalParameters = this.DataParametersDescription.Select(pair => new RetrievalParameter { Name = pair.Key, Description = pair.Value }).ToList();
await base.OnInitializedAsync();
}
protected override async Task OnAfterRenderAsync(bool firstRender)
@@ -104,13 +101,13 @@ public partial class RetrievalProcessDialog : ComponentBase
private string? ValidateName(string name)
{
if (string.IsNullOrWhiteSpace(name))
return "The retrieval process name must not be empty. Please name your retrieval process.";
return T("The retrieval process name must not be empty. Please name your retrieval process.");
if (name.Length > 26)
return "The retrieval process name must not be longer than 26 characters.";
return T("The retrieval process name must not be longer than 26 characters.");
if (this.UsedRetrievalProcessNames.Contains(name))
return $"The retrieval process name '{name}' must be unique. Please choose a different name.";
return string.Format(T("The retrieval process name '{0}' must be unique. Please choose a different name."), name);
return null;
}
@@ -118,26 +115,26 @@ public partial class RetrievalProcessDialog : ComponentBase
private string? ValidateDescription(string description)
{
if (string.IsNullOrWhiteSpace(description))
return "The description must not be empty. Please describe the retrieval process.";
return T("The description must not be empty. Please describe the retrieval process.");
return null;
}
private void AddRetrievalProcessParameter()
{
this.retrievalParameters.Add(new() { Name = $"New Parameter {this.nextParameterId++}", Description = string.Empty });
this.retrievalParameters.Add(new() { Name = string.Format(T("New Parameter {0}"), this.nextParameterId++), Description = string.Empty });
}
private string? ValidateParameterName(string name)
{
if (string.IsNullOrWhiteSpace(name))
return "The parameter name must not be empty. Please name the parameter.";
return T("The parameter name must not be empty. Please name the parameter.");
if(name.Length > 26)
return "The parameter name must not be longer than 26 characters.";
return T("The parameter name must not be longer than 26 characters.");
if (this.retrievalParameters.Count(parameter => parameter.Name == name) > 1)
return $"The parameter name '{name}' must be unique. Please choose a different name.";
return string.Format(T("The parameter name '{0}' must be unique. Please choose a different name."), name);
return null;
}
@@ -145,7 +142,7 @@ public partial class RetrievalProcessDialog : ComponentBase
private string? ValidateParameterDescription(string description)
{
if (string.IsNullOrWhiteSpace(description))
return $"The parameter description must not be empty. Please describe the parameter '{this.selectedParameter?.Name}'. What data type is it? What is it used for? What are the possible values?";
return string.Format(T("The parameter description must not be empty. Please describe the parameter '{0}'. What data type is it? What is it used for? What are the possible values?"), this.selectedParameter?.Name);
return null;
}
@@ -156,7 +153,7 @@ public partial class RetrievalProcessDialog : ComponentBase
return nameIssue;
if (string.IsNullOrWhiteSpace(parameter.Description))
return $"The parameter description must not be empty. Please describe the parameter '{parameter.Name}'. What data type is it? What is it used for? What are the possible values?";
return string.Format(T("The parameter description must not be empty. Please describe the parameter '{0}'. What data type is it? What is it used for? What are the possible values?"), parameter.Name);
return null;
}
@@ -172,12 +169,12 @@ public partial class RetrievalProcessDialog : ComponentBase
private string GetMultiSelectionText(List<EmbeddingInfo> selectedEmbeddings)
{
if(selectedEmbeddings.Count == 0)
return "No embedding methods selected.";
return T("No embedding methods selected.");
if(selectedEmbeddings.Count == 1)
return "You have selected 1 embedding method.";
return T("You have selected 1 embedding method.");
return $"You have selected {selectedEmbeddings.Count} embedding methods.";
return string.Format(T("You have selected {0} embedding methods."), selectedEmbeddings.Count);
}
private void EmbeddingsChanged(IEnumerable<EmbeddingInfo>? updatedEmbeddings)
@@ -10,7 +10,7 @@
</TitleContent>
<DialogContent>
<MudPaper Class="pa-3 mb-8 border-dashed border rounded-lg">
<ConfigurationOption OptionDescription="Preselect grammar & spell checker options?" LabelOn="Grammar & spell checker options are preselected" LabelOff="No grammar & spell checker options are preselected" State="@(() => this.SettingsManager.ConfigurationData.GrammarSpelling.PreselectOptions)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.GrammarSpelling.PreselectOptions = updatedState)" OptionHelp="When enabled, you can preselect the grammar & spell checker options. This is might be useful when you prefer a specific language or LLM model."/>
<ConfigurationOption OptionDescription="@T("Preselect grammar & spell checker options?")" LabelOn="@T("Grammar & spell checker options are preselected")" LabelOff="@T("No grammar & spell checker options are preselected")" State="@(() => this.SettingsManager.ConfigurationData.GrammarSpelling.PreselectOptions)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.GrammarSpelling.PreselectOptions = updatedState)" OptionHelp="@T("When enabled, you can preselect the grammar & spell checker options. This is might be useful when you prefer a specific language or LLM model.")"/>
<ConfigurationSelect OptionDescription="@T("Preselect the target language")" Disabled="@(() => !this.SettingsManager.ConfigurationData.GrammarSpelling.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.GrammarSpelling.PreselectedTargetLanguage)" Data="@ConfigurationSelectDataFactory.GetCommonLanguagesOptionalData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.GrammarSpelling.PreselectedTargetLanguage = selectedValue)" OptionHelp="@T("Which target language should be preselected?")"/>
@if (this.SettingsManager.ConfigurationData.GrammarSpelling.PreselectedTargetLanguage is CommonLanguages.OTHER)
{
@@ -10,13 +10,13 @@
</TitleContent>
<DialogContent>
<MudPaper Class="pa-3 mb-8 border-dashed border rounded-lg">
<ConfigurationOption OptionDescription="Preselect localization options?" LabelOn="Localization options are preselected" LabelOff="No localization options are preselected" State="@(() => this.SettingsManager.ConfigurationData.I18N.PreselectOptions)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.I18N.PreselectOptions = updatedState)" OptionHelp="When enabled, you can preselect the localization options. This is might be useful when you prefer a specific language or LLM model."/>
<ConfigurationOption OptionDescription="@T("Preselect localization options?")" LabelOn="@T("Localization options are preselected")" LabelOff="@T("No localization options are preselected")" State="@(() => this.SettingsManager.ConfigurationData.I18N.PreselectOptions)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.I18N.PreselectOptions = updatedState)" OptionHelp="@T("When enabled, you can preselect the localization options. This is might be useful when you prefer a specific language or LLM model.")"/>
<ConfigurationSelect OptionDescription="@T("Preselect the target language")" Disabled="@(() => !this.SettingsManager.ConfigurationData.I18N.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.I18N.PreselectedTargetLanguage)" Data="@ConfigurationSelectDataFactory.GetCommonLanguagesOptionalData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.I18N.PreselectedTargetLanguage = selectedValue)" OptionHelp="@T("Which target language should be preselected?")"/>
@if (this.SettingsManager.ConfigurationData.I18N.PreselectedTargetLanguage is CommonLanguages.OTHER)
{
<ConfigurationText OptionDescription="@T("Preselect another target language")" Disabled="@(() => !this.SettingsManager.ConfigurationData.I18N.PreselectOptions)" Icon="@Icons.Material.Filled.Translate" Text="@(() => this.SettingsManager.ConfigurationData.I18N.PreselectOtherLanguage)" TextUpdate="@(updatedText => this.SettingsManager.ConfigurationData.I18N.PreselectOtherLanguage = updatedText)"/>
}
<ConfigurationSelect OptionDescription="Language plugin used for comparision" SelectedValue="@(() => this.SettingsManager.ConfigurationData.I18N.PreselectedLanguagePluginId)" Data="@ConfigurationSelectDataFactory.GetLanguagesData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.I18N.PreselectedLanguagePluginId = selectedValue)" OptionHelp="Select the language plugin used for comparision."/>
<ConfigurationSelect OptionDescription="@T("Language plugin used for comparision")" SelectedValue="@(() => this.SettingsManager.ConfigurationData.I18N.PreselectedLanguagePluginId)" Data="@ConfigurationSelectDataFactory.GetLanguagesData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.I18N.PreselectedLanguagePluginId = selectedValue)" OptionHelp="@T("Select the language plugin used for comparision.")"/>
<ConfigurationProviderSelection Component="Components.I18N_ASSISTANT" Data="@this.availableLLMProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.I18N.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.I18N.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.I18N.PreselectedProvider = selectedValue)"/>
</MudPaper>
</DialogContent>
@@ -10,7 +10,7 @@
</TitleContent>
<DialogContent>
<MudPaper Class="pa-3 mb-8 border-dashed border rounded-lg">
<ConfigurationOption OptionDescription="Preselect rewrite & improve text options?" LabelOn="Rewrite & improve text options are preselected" LabelOff="No rewrite & improve text options are preselected" State="@(() => this.SettingsManager.ConfigurationData.RewriteImprove.PreselectOptions)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.RewriteImprove.PreselectOptions = updatedState)" OptionHelp="When enabled, you can preselect the rewrite & improve text options. This is might be useful when you prefer a specific language or LLM model."/>
<ConfigurationOption OptionDescription="@T("Preselect rewrite & improve text options?")" LabelOn="@T("Rewrite & improve text options are preselected")" LabelOff="@T("No rewrite & improve text options are preselected")" State="@(() => this.SettingsManager.ConfigurationData.RewriteImprove.PreselectOptions)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.RewriteImprove.PreselectOptions = updatedState)" OptionHelp="@T("When enabled, you can preselect the rewrite & improve text options. This is might be useful when you prefer a specific language or LLM model.")"/>
<ConfigurationSelect OptionDescription="@T("Preselect the target language")" Disabled="@(() => !this.SettingsManager.ConfigurationData.RewriteImprove.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.RewriteImprove.PreselectedTargetLanguage)" Data="@ConfigurationSelectDataFactory.GetCommonLanguagesOptionalData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.RewriteImprove.PreselectedTargetLanguage = selectedValue)" OptionHelp="@T("Which target language should be preselected?")"/>
@if (this.SettingsManager.ConfigurationData.RewriteImprove.PreselectedTargetLanguage is CommonLanguages.OTHER)
{
@@ -47,11 +47,11 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="CodeBeam.MudBlazor.Extensions" Version="8.1.0" />
<PackageReference Include="CodeBeam.MudBlazor.Extensions" Version="8.2.1" />
<PackageReference Include="HtmlAgilityPack" Version="1.12.1" />
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="9.0.4" />
<PackageReference Include="Microsoft.Extensions.FileProviders.Embedded" Version="9.0.5" />
<PackageReference Include="MudBlazor" Version="8.6.0" />
<PackageReference Include="MudBlazor.Markdown" Version="8.5.1.1" />
<PackageReference Include="MudBlazor.Markdown" Version="8.6.0" />
<PackageReference Include="ReverseMarkdown" Version="4.6.0" />
<PackageReference Include="LuaCSharp" Version="0.4.2" />
</ItemGroup>
@@ -20,8 +20,13 @@
<MudText Typo="Typo.body1" Style="text-align: justify; hyphens: auto;" Class="mb-3">
@T("For companies, sponsoring MindWork AI Studio is not only a way to support innovation but also a valuable opportunity for public relations and marketing. Your company's name and logo will be featured prominently, showcasing your commitment to using cutting-edge AI tools and enhancing your reputation as an innovative enterprise.")
</MudText>
<MudList T="string" Class="mb-3">
<Titan Name="@T("German Aerospace Center (DLR)")" Type="SupporterType.ORGANIZATION" URL="https://www.dlr.de/" ImageSrc="svg/titans/DLR.svg" Acknowledgment="@T("We would like to thank the DLR for its courage in supporting MindWork AI Studio at an early stage. The DLR not only uses AI Studio in various projects but also supports its further development with personnel resources.")"/>
</MudList>
<MudButton Href="https://github.com/sponsors/MindWorkAI" StartIcon="@Icons.Material.Filled.Business" Variant="Variant.Filled" Target="_blank">
@T("Become our first Titan")
@T("Become one of our titans")
</MudButton>
</div>
+13 -7
View File
@@ -25,13 +25,13 @@ internal sealed class Program
public static IServiceProvider SERVICE_PROVIDER = null!;
public static ILoggerFactory LOGGER_FACTORY = null!;
public static async Task Main(string[] args)
public static async Task Main()
{
if(args.Length == 0)
{
Console.WriteLine("Error: Please provide the port of the runtime API.");
return;
}
#if DEBUG
// Read the environment variables from the .env file:
var envFilePath = Path.Combine("..", "..", "startup.env");
await EnvFile.Apply(envFilePath);
#endif
// Read the secret key for the IPC from the AI_STUDIO_SECRET_KEY environment variable:
var secretPasswordEncoded = Environment.GetEnvironmentVariable("AI_STUDIO_SECRET_PASSWORD");
@@ -58,6 +58,13 @@ internal sealed class Program
return;
}
var rustApiPort = Environment.GetEnvironmentVariable("AI_STUDIO_API_PORT");
if(string.IsNullOrWhiteSpace(rustApiPort))
{
Console.WriteLine("Error: The AI_STUDIO_API_PORT environment variable is not set.");
return;
}
var apiToken = Environment.GetEnvironmentVariable("AI_STUDIO_API_TOKEN");
if(string.IsNullOrWhiteSpace(apiToken))
{
@@ -67,7 +74,6 @@ internal sealed class Program
API_TOKEN = apiToken;
var rustApiPort = args[0];
using var rust = new RustService(rustApiPort, certificateFingerprint);
var appPort = await rust.GetAppPort();
if(appPort == 0)
@@ -140,6 +140,77 @@ public sealed class ProviderAlibabaCloud(ILogger logger) : BaseProvider("https:/
return this.LoadModels(["text-embedding-"], token, apiKeyProvisional).ContinueWith(t => t.Result.Concat(additionalModels).OrderBy(x => x.Id).AsEnumerable(), token);
}
/// <inheritdoc />
public override IReadOnlyCollection<Capability> GetModelCapabilities(Model model)
{
var modelName = model.Id.ToLowerInvariant().AsSpan();
// Qwen models:
if (modelName.StartsWith("qwen"))
{
// Check for omni models:
if (modelName.IndexOf("omni") is not -1)
return
[
Capability.TEXT_INPUT, Capability.MULTIPLE_IMAGE_INPUT,
Capability.AUDIO_INPUT, Capability.SPEECH_INPUT,
Capability.VIDEO_INPUT,
Capability.TEXT_OUTPUT, Capability.SPEECH_OUTPUT
];
// Check for Qwen 3:
if(modelName.StartsWith("qwen3"))
return
[
Capability.TEXT_INPUT,
Capability.TEXT_OUTPUT,
Capability.OPTIONAL_REASONING, Capability.FUNCTION_CALLING
];
if(modelName.IndexOf("-vl-") is not -1)
return
[
Capability.TEXT_INPUT, Capability.MULTIPLE_IMAGE_INPUT,
Capability.TEXT_OUTPUT,
];
}
// QwQ models:
if (modelName.StartsWith("qwq"))
{
return
[
Capability.TEXT_INPUT,
Capability.TEXT_OUTPUT,
Capability.ALWAYS_REASONING, Capability.FUNCTION_CALLING
];
}
// QVQ models:
if (modelName.StartsWith("qvq"))
{
return
[
Capability.TEXT_INPUT, Capability.MULTIPLE_IMAGE_INPUT,
Capability.TEXT_OUTPUT,
Capability.ALWAYS_REASONING
];
}
// Default to text input and output:
return
[
Capability.TEXT_INPUT,
Capability.TEXT_OUTPUT,
Capability.FUNCTION_CALLING
];
}
#endregion
private async Task<IEnumerable<Model>> LoadModels(string[] prefixes, CancellationToken token, string? apiKeyProvisional = null)
@@ -110,6 +110,33 @@ public sealed class ProviderAnthropic(ILogger logger) : BaseProvider("https://ap
return Task.FromResult(Enumerable.Empty<Model>());
}
public override IReadOnlyCollection<Capability> GetModelCapabilities(Model model)
{
var modelName = model.Id.ToLowerInvariant().AsSpan();
// Claude 3.7 is able to do reasoning:
if(modelName.StartsWith("claude-3-7"))
return [
Capability.TEXT_INPUT, Capability.MULTIPLE_IMAGE_INPUT,
Capability.TEXT_OUTPUT,
Capability.OPTIONAL_REASONING, Capability.FUNCTION_CALLING];
// All other 3.x models are able to process text and images as input:
if(modelName.StartsWith("claude-3-"))
return [
Capability.TEXT_INPUT, Capability.MULTIPLE_IMAGE_INPUT,
Capability.TEXT_OUTPUT,
Capability.FUNCTION_CALLING];
// Any other model is able to process text only:
return [
Capability.TEXT_INPUT,
Capability.TEXT_OUTPUT,
Capability.FUNCTION_CALLING];
}
#endregion
private async Task<IEnumerable<Model>> LoadModels(CancellationToken token, string? apiKeyProvisional = null)
@@ -77,6 +77,9 @@ public abstract class BaseProvider : IProvider, ISecretId
/// <inheritdoc />
public abstract Task<IEnumerable<Model>> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default);
/// <inheritdoc />
public abstract IReadOnlyCollection<Capability> GetModelCapabilities(Model model);
#endregion
#region Implementation of ISecretId
@@ -0,0 +1,150 @@
namespace AIStudio.Provider;
public static class CapabilitiesOpenSource
{
public static IReadOnlyCollection<Capability> GetCapabilities(Model model)
{
var modelName = model.Id.ToLowerInvariant().AsSpan();
//
// Checking for names in the case of open source models is a hard task.
// Let's assume we want to check for the llama 3.1 405b model.
//
// Here is a not complete list of how providers name this model:
// - Fireworks: accounts/fireworks/models/llama-v3p1-405b-instruct
// - Hugging Face -> Nebius AI Studio: meta-llama/Meta-Llama-3.1-405B-Instruct
// - Groq: llama-3.1-405b-instruct
// - LM Studio: llama-3.1-405b-instruct
// - Helmholtz Blablador: 1 - Llama3 405 the best general model
// - GWDG: Llama 3.1 405B Instruct
//
//
// Meta llama models:
//
if (modelName.IndexOf("llama") is not -1)
{
if (modelName.IndexOf("llama4") is not -1 ||
modelName.IndexOf("llama 4") is not -1 ||
modelName.IndexOf("llama-4") is not -1 ||
modelName.IndexOf("llama-v4") is not -1)
return
[
Capability.TEXT_INPUT, Capability.MULTIPLE_IMAGE_INPUT,
Capability.TEXT_OUTPUT,
Capability.FUNCTION_CALLING,
];
// The old vision models cannot do function calling:
if (modelName.IndexOf("vision") is not -1)
return [Capability.TEXT_INPUT, Capability.MULTIPLE_IMAGE_INPUT, Capability.TEXT_OUTPUT];
//
// All models >= 3.1 are able to do function calling:
//
if (modelName.IndexOf("llama3.") is not -1 ||
modelName.IndexOf("llama 3.") is not -1 ||
modelName.IndexOf("llama-3.") is not -1 ||
modelName.IndexOf("llama-v3p") is not -1)
return
[
Capability.TEXT_INPUT,
Capability.TEXT_OUTPUT,
Capability.FUNCTION_CALLING,
];
// All other llama models can only do text input and output:
return [Capability.TEXT_INPUT, Capability.TEXT_OUTPUT];
}
//
// DeepSeek models:
//
if (modelName.IndexOf("deepseek") is not -1)
{
if(modelName.IndexOf("deepseek-r1") is not -1 ||
modelName.IndexOf("deepseek r1") is not -1)
return [Capability.TEXT_INPUT, Capability.TEXT_OUTPUT, Capability.ALWAYS_REASONING];
return [Capability.TEXT_INPUT, Capability.TEXT_OUTPUT];
}
//
// Qwen models:
//
if (modelName.IndexOf("qwen") is not -1 || modelName.IndexOf("qwq") is not -1)
{
if (modelName.IndexOf("qwq") is not -1)
return [Capability.TEXT_INPUT, Capability.TEXT_OUTPUT, Capability.ALWAYS_REASONING];
return [Capability.TEXT_INPUT, Capability.TEXT_OUTPUT];
}
//
// Mistral models:
//
if (modelName.IndexOf("mistral") is not -1 ||
modelName.IndexOf("pixtral") is not -1)
{
if(modelName.IndexOf("pixtral") is not -1)
return
[
Capability.TEXT_INPUT, Capability.MULTIPLE_IMAGE_INPUT,
Capability.TEXT_OUTPUT,
Capability.FUNCTION_CALLING
];
if (modelName.IndexOf("3.1") is not -1)
return
[
Capability.TEXT_INPUT, Capability.MULTIPLE_IMAGE_INPUT,
Capability.TEXT_OUTPUT,
Capability.FUNCTION_CALLING
];
// Default:
return
[
Capability.TEXT_INPUT,
Capability.TEXT_OUTPUT,
Capability.FUNCTION_CALLING
];
}
//
// Grok models:
//
if (modelName.IndexOf("grok") is not -1)
{
if(modelName.IndexOf("-vision-") is not -1)
return
[
Capability.TEXT_INPUT, Capability.MULTIPLE_IMAGE_INPUT,
Capability.TEXT_OUTPUT,
];
if(modelName.StartsWith("grok-3-mini"))
return
[
Capability.TEXT_INPUT,
Capability.TEXT_OUTPUT,
Capability.ALWAYS_REASONING, Capability.FUNCTION_CALLING,
];
if(modelName.StartsWith("grok-3"))
return
[
Capability.TEXT_INPUT,
Capability.TEXT_OUTPUT,
Capability.FUNCTION_CALLING,
];
}
// Default:
return [Capability.TEXT_INPUT, Capability.TEXT_OUTPUT];
}
}
@@ -0,0 +1,97 @@
namespace AIStudio.Provider;
/// <summary>
/// Represents the capabilities of an AI model.
/// </summary>
public enum Capability
{
/// <summary>
/// No capabilities specified.
/// </summary>
NONE,
/// <summary>
/// We don't know what the AI model can do.
/// </summary>
UNKNOWN,
/// <summary>
/// The AI model can perform text input.
/// </summary>
TEXT_INPUT,
/// <summary>
/// The AI model can perform audio input, such as music or sound.
/// </summary>
AUDIO_INPUT,
/// <summary>
/// The AI model can perform one image input, such as one photo or drawing.
/// </summary>
SINGLE_IMAGE_INPUT,
/// <summary>
/// The AI model can perform multiple images as input, such as multiple photos or drawings.
/// </summary>
MULTIPLE_IMAGE_INPUT,
/// <summary>
/// The AI model can perform speech input.
/// </summary>
SPEECH_INPUT,
/// <summary>
/// The AI model can perform video input, such as video files or streams.
/// </summary>
VIDEO_INPUT,
/// <summary>
/// The AI model can generate text output.
/// </summary>
TEXT_OUTPUT,
/// <summary>
/// The AI model can generate audio output, such as music or sound.
/// </summary>
AUDIO_OUTPUT,
/// <summary>
/// The AI model can generate image output, such as photos or drawings.
/// </summary>
IMAGE_OUTPUT,
/// <summary>
/// The AI model can generate speech output.
/// </summary>
SPEECH_OUTPUT,
/// <summary>
/// The AI model can generate video output.
/// </summary>
VIDEO_OUTPUT,
/// <summary>
/// The AI model can perform reasoning tasks.
/// </summary>
OPTIONAL_REASONING,
/// <summary>
/// The AI model always performs reasoning.
/// </summary>
ALWAYS_REASONING,
/// <summary>
/// The AI model can embed information or data.
/// </summary>
EMBEDDING,
/// <summary>
/// The AI model can perform in real-time.
/// </summary>
REALTIME,
/// <summary>
/// The AI model can perform function calling, such as invoking APIs or executing functions.
/// </summary>
FUNCTION_CALLING,
}
@@ -106,6 +106,27 @@ public sealed class ProviderDeepSeek(ILogger logger) : BaseProvider("https://api
return Task.FromResult(Enumerable.Empty<Model>());
}
public override IReadOnlyCollection<Capability> GetModelCapabilities(Model model)
{
var modelName = model.Id.ToLowerInvariant().AsSpan();
if(modelName.IndexOf("reasoner") is not -1)
return
[
Capability.TEXT_INPUT,
Capability.TEXT_OUTPUT,
Capability.ALWAYS_REASONING,
];
return
[
Capability.TEXT_INPUT,
Capability.TEXT_OUTPUT,
];
}
#endregion
private async Task<IEnumerable<Model>> LoadModels(CancellationToken token, string? apiKeyProvisional = null)
@@ -107,5 +107,7 @@ public class ProviderFireworks(ILogger logger) : BaseProvider("https://api.firew
return Task.FromResult(Enumerable.Empty<Model>());
}
public override IReadOnlyCollection<Capability> GetModelCapabilities(Model model) => CapabilitiesOpenSource.GetCapabilities(model);
#endregion
}
@@ -108,6 +108,8 @@ public sealed class ProviderGWDG(ILogger logger) : BaseProvider("https://chat-ai
return models.Where(model => model.Id.StartsWith("e5-", StringComparison.InvariantCultureIgnoreCase));
}
public override IReadOnlyCollection<Capability> GetModelCapabilities(Model model) => CapabilitiesOpenSource.GetCapabilities(model);
#endregion
private async Task<IEnumerable<Model>> LoadModels(CancellationToken token, string? apiKeyProvisional = null)
@@ -98,7 +98,7 @@ public class ProviderGoogle(ILogger logger) : BaseProvider("https://generativela
return [];
return modelResponse.Models.Where(model =>
model.Name.StartsWith("models/gemini-", StringComparison.OrdinalIgnoreCase))
model.Name.StartsWith("models/gemini-", StringComparison.OrdinalIgnoreCase) && !model.Name.Contains("embed"))
.Select(n => new Provider.Model(n.Name.Replace("models/", string.Empty), n.DisplayName));
}
@@ -115,10 +115,93 @@ public class ProviderGoogle(ILogger logger) : BaseProvider("https://generativela
return [];
return modelResponse.Models.Where(model =>
model.Name.StartsWith("models/text-embedding-", StringComparison.InvariantCultureIgnoreCase))
model.Name.StartsWith("models/text-embedding-", StringComparison.OrdinalIgnoreCase) ||
model.Name.StartsWith("models/gemini-embed", StringComparison.OrdinalIgnoreCase))
.Select(n => new Provider.Model(n.Name.Replace("models/", string.Empty), n.DisplayName));
}
public override IReadOnlyCollection<Capability> GetModelCapabilities(Provider.Model model)
{
var modelName = model.Id.ToLowerInvariant().AsSpan();
if (modelName.IndexOf("gemini-") is not -1)
{
// Reasoning models:
if (modelName.IndexOf("gemini-2.5") is not -1)
return
[
Capability.TEXT_INPUT, Capability.MULTIPLE_IMAGE_INPUT, Capability.AUDIO_INPUT,
Capability.SPEECH_INPUT, Capability.VIDEO_INPUT,
Capability.TEXT_OUTPUT,
Capability.ALWAYS_REASONING, Capability.FUNCTION_CALLING,
];
// Image generation:
if(modelName.IndexOf("-2.0-flash-preview-image-") is not -1)
return
[
Capability.TEXT_INPUT, Capability.MULTIPLE_IMAGE_INPUT, Capability.AUDIO_INPUT,
Capability.SPEECH_INPUT, Capability.VIDEO_INPUT,
Capability.TEXT_OUTPUT, Capability.IMAGE_OUTPUT,
];
// Realtime model:
if(modelName.IndexOf("-2.0-flash-live-") is not -1)
return
[
Capability.TEXT_INPUT, Capability.AUDIO_INPUT, Capability.SPEECH_INPUT,
Capability.VIDEO_INPUT,
Capability.TEXT_OUTPUT, Capability.SPEECH_OUTPUT,
Capability.FUNCTION_CALLING,
];
// The 2.0 flash models cannot call functions:
if(modelName.IndexOf("-2.0-flash-") is not -1)
return
[
Capability.TEXT_INPUT, Capability.MULTIPLE_IMAGE_INPUT, Capability.AUDIO_INPUT,
Capability.SPEECH_INPUT, Capability.VIDEO_INPUT,
Capability.TEXT_OUTPUT,
];
// The old 1.0 pro vision model:
if(modelName.IndexOf("pro-vision") is not -1)
return
[
Capability.TEXT_INPUT, Capability.MULTIPLE_IMAGE_INPUT,
Capability.TEXT_OUTPUT,
];
// Default to all other Gemini models:
return
[
Capability.TEXT_INPUT, Capability.MULTIPLE_IMAGE_INPUT, Capability.AUDIO_INPUT,
Capability.SPEECH_INPUT, Capability.VIDEO_INPUT,
Capability.TEXT_OUTPUT,
Capability.FUNCTION_CALLING,
];
}
// Default for all other models:
return
[
Capability.TEXT_INPUT, Capability.MULTIPLE_IMAGE_INPUT,
Capability.TEXT_OUTPUT,
Capability.FUNCTION_CALLING,
];
}
#endregion
private async Task<ModelsResponse> LoadModels(CancellationToken token, string? apiKeyProvisional = null)
@@ -110,6 +110,8 @@ public class ProviderGroq(ILogger logger) : BaseProvider("https://api.groq.com/o
return Task.FromResult(Enumerable.Empty<Model>());
}
public override IReadOnlyCollection<Capability> GetModelCapabilities(Model model) => CapabilitiesOpenSource.GetCapabilities(model);
#endregion
private async Task<IEnumerable<Model>> LoadModels(CancellationToken token, string? apiKeyProvisional = null)
@@ -112,6 +112,8 @@ public sealed class ProviderHelmholtz(ILogger logger) : BaseProvider("https://ap
model.Id.Contains("gritlm", StringComparison.InvariantCultureIgnoreCase));
}
public override IReadOnlyCollection<Capability> GetModelCapabilities(Model model) => CapabilitiesOpenSource.GetCapabilities(model);
#endregion
private async Task<IEnumerable<Model>> LoadModels(CancellationToken token, string? apiKeyProvisional = null)
@@ -111,5 +111,7 @@ public sealed class ProviderHuggingFace : BaseProvider
return Task.FromResult(Enumerable.Empty<Model>());
}
public override IReadOnlyCollection<Capability> GetModelCapabilities(Model model) => CapabilitiesOpenSource.GetCapabilities(model);
#endregion
}
@@ -63,4 +63,11 @@ public interface IProvider
/// <param name="token">The cancellation token.</param>
/// <returns>The list of embedding models.</returns>
public Task<IEnumerable<Model>> GetEmbeddingModels(string? apiKeyProvisional = null, CancellationToken token = default);
/// <summary>
/// Get the capabilities of a model.
/// </summary>
/// <param name="model">The model to get the capabilities for.</param>
/// <returns>The capabilities of the model.</returns>
public IReadOnlyCollection<Capability> GetModelCapabilities(Model model);
}
@@ -122,6 +122,52 @@ public sealed class ProviderMistral(ILogger logger) : BaseProvider("https://api.
return Task.FromResult(Enumerable.Empty<Provider.Model>());
}
public override IReadOnlyCollection<Capability> GetModelCapabilities(Provider.Model model)
{
var modelName = model.Id.ToLowerInvariant().AsSpan();
// Pixtral models are able to do process images:
if (modelName.IndexOf("pixtral") is not -1)
return
[
Capability.TEXT_INPUT, Capability.MULTIPLE_IMAGE_INPUT,
Capability.TEXT_OUTPUT,
Capability.FUNCTION_CALLING,
];
// Mistral medium:
if (modelName.IndexOf("mistral-medium-") is not -1)
return
[
Capability.TEXT_INPUT, Capability.MULTIPLE_IMAGE_INPUT,
Capability.TEXT_OUTPUT,
Capability.FUNCTION_CALLING,
];
// Mistral small:
if (modelName.IndexOf("mistral-small-") is not -1)
return
[
Capability.TEXT_INPUT, Capability.MULTIPLE_IMAGE_INPUT,
Capability.TEXT_OUTPUT,
Capability.FUNCTION_CALLING,
];
// Mistral saba:
if (modelName.IndexOf("mistral-saba-") is not -1)
return
[
Capability.TEXT_INPUT,
Capability.TEXT_OUTPUT,
];
// Default:
return CapabilitiesOpenSource.GetCapabilities(model);
}
#endregion
private async Task<ModelsResponse> LoadModelList(string? apiKeyProvisional, CancellationToken token)
+5 -1
View File
@@ -1,3 +1,5 @@
using AIStudio.Tools.PluginSystem;
namespace AIStudio.Provider;
/// <summary>
@@ -7,6 +9,8 @@ namespace AIStudio.Provider;
/// <param name="DisplayName">The model's display name.</param>
public readonly record struct Model(string Id, string? DisplayName)
{
private static string TB(string fallbackEN) => I18N.I.T(fallbackEN, typeof(Model).Namespace, nameof(Model));
#region Overrides of ValueType
public override string ToString()
@@ -17,7 +21,7 @@ public readonly record struct Model(string Id, string? DisplayName)
if(!string.IsNullOrWhiteSpace(this.Id))
return this.Id;
return "no model selected";
return TB("no model selected");
}
#endregion
@@ -31,5 +31,7 @@ public class NoProvider : IProvider
yield break;
}
public IReadOnlyCollection<Capability> GetModelCapabilities(Model model) => [ Capability.NONE ];
#endregion
}
@@ -143,6 +143,69 @@ public sealed class ProviderOpenAI(ILogger logger) : BaseProvider("https://api.o
return this.LoadModels(["text-embedding-"], token, apiKeyProvisional);
}
public override IReadOnlyCollection<Capability> GetModelCapabilities(Model model)
{
var modelName = model.Id.ToLowerInvariant().AsSpan();
if (modelName.StartsWith("o1-mini"))
return
[
Capability.TEXT_INPUT,
Capability.TEXT_OUTPUT,
Capability.ALWAYS_REASONING,
];
if (modelName.StartsWith("o3-mini"))
return
[
Capability.TEXT_INPUT,
Capability.TEXT_OUTPUT,
Capability.ALWAYS_REASONING, Capability.FUNCTION_CALLING
];
if (modelName.StartsWith("o4-mini") || modelName.StartsWith("o1") || modelName.StartsWith("o3"))
return
[
Capability.TEXT_INPUT, Capability.MULTIPLE_IMAGE_INPUT,
Capability.TEXT_OUTPUT,
Capability.ALWAYS_REASONING, Capability.FUNCTION_CALLING
];
if(modelName.StartsWith("gpt-3.5"))
return
[
Capability.TEXT_INPUT,
Capability.TEXT_OUTPUT,
];
if(modelName.StartsWith("gpt-4-turbo"))
return
[
Capability.TEXT_INPUT, Capability.MULTIPLE_IMAGE_INPUT,
Capability.TEXT_OUTPUT,
Capability.FUNCTION_CALLING
];
if(modelName is "gpt-4" || modelName.StartsWith("gpt-4-"))
return
[
Capability.TEXT_INPUT,
Capability.TEXT_OUTPUT,
];
return
[
Capability.TEXT_INPUT, Capability.MULTIPLE_IMAGE_INPUT,
Capability.TEXT_OUTPUT,
Capability.FUNCTION_CALLING,
];
}
#endregion
private async Task<IEnumerable<Model>> LoadModels(string[] prefixes, CancellationToken token, string? apiKeyProvisional = null)
@@ -88,7 +88,6 @@ public sealed class ProviderSelfHosted(ILogger logger, Host host, string hostnam
}
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
public override async Task<IEnumerable<Provider.Model>> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default)
{
try
@@ -140,6 +139,8 @@ public sealed class ProviderSelfHosted(ILogger logger, Host host, string hostnam
}
}
public override IReadOnlyCollection<Capability> GetModelCapabilities(Provider.Model model) => CapabilitiesOpenSource.GetCapabilities(model);
#endregion
private async Task<IEnumerable<Provider.Model>> LoadModels(string[] ignorePhrases, string[] filterPhrases, CancellationToken token, string? apiKeyProvisional = null)
@@ -111,6 +111,8 @@ public sealed class ProviderX(ILogger logger) : BaseProvider("https://api.x.ai/v
return Task.FromResult<IEnumerable<Model>>([]);
}
public override IReadOnlyCollection<Capability> GetModelCapabilities(Model model) => CapabilitiesOpenSource.GetCapabilities(model);
#endregion
private async Task<IEnumerable<Model>> LoadModels(string[] prefixes, CancellationToken token, string? apiKeyProvisional = null)
@@ -108,7 +108,7 @@ public static class ConfigurationSelectDataFactory
public static IEnumerable<ConfigurationSelectData<PreviewFeatures>> GetPreviewFeaturesData(SettingsManager settingsManager)
{
foreach (var source in settingsManager.ConfigurationData.App.PreviewVisibility.GetPreviewFeatures())
foreach (var source in settingsManager.ConfigurationData.App.PreviewVisibility.GetPreviewFeatures().Where(x => !x.IsReleased()))
yield return new(source.GetPreviewDescription(), source);
}
@@ -17,5 +17,26 @@ public static class PreviewFeaturesExtensions
_ => TB("Unknown preview feature")
};
public static bool IsEnabled(this PreviewFeatures feature, SettingsManager settingsManager) => settingsManager.ConfigurationData.App.EnabledPreviewFeatures.Contains(feature);
/// <summary>
/// Check if the preview feature is released or not.
/// </summary>
/// <remarks>
/// This metadata is necessary because we cannot remove the enum values from the list.
/// Otherwise, we could not deserialize old settings files that may contain these values.
/// </remarks>
/// <param name="feature">The preview feature to check.</param>
/// <returns>True when the preview feature is released, false otherwise.</returns>
public static bool IsReleased(this PreviewFeatures feature) => feature switch
{
PreviewFeatures.PRE_READ_PDF_2025 => true,
_ => false
};
public static bool IsEnabled(this PreviewFeatures feature, SettingsManager settingsManager)
{
if(feature.IsReleased())
return true;
return settingsManager.ConfigurationData.App.EnabledPreviewFeatures.Contains(feature);
}
}
+41
View File
@@ -0,0 +1,41 @@
#if DEBUG
using System.Text;
namespace AIStudio.Tools;
/// <summary>
/// Read environment variables for the application from an .env file.
/// </summary>
/// <remarks>
/// We consider this feature a security issue. Therefore, it is only
/// available in DEBUG mode. To ensure this, we remove the code
/// from any release build.
/// </remarks>
public static class EnvFile
{
public static async Task Apply(string filePath)
{
if(!File.Exists(filePath))
{
Console.WriteLine($"Error: The .env file '{filePath}' does not exist.");
return;
}
var lines = await File.ReadAllLinesAsync(filePath, Encoding.UTF8);
foreach (var line in lines)
{
if (string.IsNullOrWhiteSpace(line) || line.Trim().StartsWith('#'))
continue;
var parts = line.Split(['='], 2);
if (parts.Length != 2)
continue;
var key = parts[0].Trim();
var value = parts[1].Trim();
Environment.SetEnvironmentVariable(key, value);
}
}
}
#endif
+61 -61
View File
@@ -4,14 +4,14 @@
"net9.0": {
"CodeBeam.MudBlazor.Extensions": {
"type": "Direct",
"requested": "[8.1.0, )",
"resolved": "8.1.0",
"contentHash": "6f6K8Qr+cjk419URr8+b9nkSr0y300n/DaB0tJdYVRtDsWwoVCvgPwf9kENdDg1HIiYPL524+Q5a9QraL6hAvw==",
"requested": "[8.2.1, )",
"resolved": "8.2.1",
"contentHash": "qPCjpk6vSsjJUt3Kc0J0RLC3aZDYgaE+wDEfvDdWQHh4CtatWF8wr/jfG6q3EwP0V91nr03cW+KCCKZ5JMEnTg==",
"dependencies": {
"BuildBundlerMinifier": "3.2.449",
"CsvHelper": "33.0.1",
"Microsoft.AspNetCore.Components": "9.0.4",
"Microsoft.AspNetCore.Components.Web": "9.0.4",
"Microsoft.AspNetCore.Components": "9.0.5",
"Microsoft.AspNetCore.Components.Web": "9.0.5",
"MudBlazor": "8.0.0",
"ZXing.Net": "0.16.9"
}
@@ -30,18 +30,18 @@
},
"Microsoft.Extensions.FileProviders.Embedded": {
"type": "Direct",
"requested": "[9.0.4, )",
"resolved": "9.0.4",
"contentHash": "fictUnSF95D+M9iH4X6TYBjud2gbB2r6bcIi0sQknXFc2bHbNucoaK+SzfLCzb47tHSR9a5pm0F1Ioj0PgmFeQ==",
"requested": "[9.0.5, )",
"resolved": "9.0.5",
"contentHash": "tgYXJtPa72hYrQuw+pqJvvhUOOQtZuk5jhRZINxIgR0cXwe4bLCQhCGffN+Ad4+AIQOlz4YyOc+GX+unsHc9Kg==",
"dependencies": {
"Microsoft.Extensions.FileProviders.Abstractions": "9.0.4"
"Microsoft.Extensions.FileProviders.Abstractions": "9.0.5"
}
},
"Microsoft.NET.ILLink.Tasks": {
"type": "Direct",
"requested": "[9.0.4, )",
"resolved": "9.0.4",
"contentHash": "xUdlUxiFwXhTYhB4VxKg/IA0+jlZXJPo70LYuMryWbJHdonIpZjw+7DO2B0pWwpXIOs6MlH5WVXPEtfrGEcVZA=="
"requested": "[9.0.5, )",
"resolved": "9.0.5",
"contentHash": "4L7wbb3Y4BJgIeSnmAf/C6S/qCs9rT1b0dX72uj3qo0qvUH7D0U1tU5ZlC7A3Wb8E/dXK4caOi4JkdLTVJaXhw=="
},
"MudBlazor": {
"type": "Direct",
@@ -56,13 +56,13 @@
},
"MudBlazor.Markdown": {
"type": "Direct",
"requested": "[8.5.1.1, )",
"resolved": "8.5.1.1",
"contentHash": "3FQaHlpLV54W77aVl58JCH2HX0p2QcqGNamtxyVzMKeMa92x+MYm4DFcqqhfbEvNEWTRZ6q22ZCx/NG0UX422Q==",
"requested": "[8.6.0, )",
"resolved": "8.6.0",
"contentHash": "TIrR6QqXXBmHRx/L66Jxl9UlVtom8jK46iVpPkTi8C7TjsUUylI5zBzmKPiVYp8bKDhgLzOcQH0o01xuHlKJEw==",
"dependencies": {
"Markdig": "0.41.0",
"Markdig": "0.41.1",
"Microsoft.Extensions.Caching.Memory": "9.0.4",
"MudBlazor": "8.5.1"
"MudBlazor": "8.6.0"
}
},
"ReverseMarkdown": {
@@ -86,57 +86,57 @@
},
"Markdig": {
"type": "Transitive",
"resolved": "0.41.0",
"contentHash": "nEGSjfQ2i+MzJjvCZqoIBqW2x0iBALhhVogud48oPA/39a0n0jOhghdTYdm4xaDFBXmc4MxsVJAP5gtdvADvWQ=="
"resolved": "0.41.1",
"contentHash": "e9QXUg2agxd9iWV3hO5fA4Qp2avMuXFiElatzD8hp69yIEgESrdhKvDFf9y9NOlPe9jO7AA6W1JAnntLmqwRwA=="
},
"Microsoft.AspNetCore.Authorization": {
"type": "Transitive",
"resolved": "9.0.4",
"contentHash": "QIV3Jk/zb5wsBNkxj4eK4Qoja6f0hO2pUhWrG8NOmgnclA7zbQvzLLKaGnva7/PtglS8VS5HusZfrnnO8FTpSA==",
"resolved": "9.0.5",
"contentHash": "HPd+7PG+nMZSP13bTJuM5+q0vdzoBtjoBUMP9iuUGwG/kFHkeBOZ9QrZuSHHGmz/IgYdLbqbdJGEykSI5biIPw==",
"dependencies": {
"Microsoft.AspNetCore.Metadata": "9.0.4",
"Microsoft.Extensions.Logging.Abstractions": "9.0.4",
"Microsoft.Extensions.Options": "9.0.4"
"Microsoft.AspNetCore.Metadata": "9.0.5",
"Microsoft.Extensions.Logging.Abstractions": "9.0.5",
"Microsoft.Extensions.Options": "9.0.5"
}
},
"Microsoft.AspNetCore.Components": {
"type": "Transitive",
"resolved": "9.0.4",
"contentHash": "imeT4r1PLXqI3kzf7b06Tzj5H4od8o/u4ZE5JgyrlvwABSZlBQzWN8AgT/lPLqs2VJVxNwcAolx+vxLMl9Dn+g==",
"resolved": "9.0.5",
"contentHash": "qwA9bx7nWayh1ogWe1H13vyGL64pNjHrukHmnZ9WZbQPtHeY9QTuXkvF/Uue0tzGbxChBf9v2LH5+KeVaRJIqw==",
"dependencies": {
"Microsoft.AspNetCore.Authorization": "9.0.4",
"Microsoft.AspNetCore.Components.Analyzers": "9.0.4"
"Microsoft.AspNetCore.Authorization": "9.0.5",
"Microsoft.AspNetCore.Components.Analyzers": "9.0.5"
}
},
"Microsoft.AspNetCore.Components.Analyzers": {
"type": "Transitive",
"resolved": "9.0.4",
"contentHash": "fPrKrrfT5o0dJZ4AgbGsXBl4YNTlSrRcpYMgLTsrmYo7aOkZrdWPmsxVsFRyelfmCW9qHH7ZZK/4cTC8HuS7BA=="
"resolved": "9.0.5",
"contentHash": "u9dQP0/bjkTVHFHVBV+8TYpBVerjbFfvD3nwwOfaoJ8VmyCHoBFbD8m4nxV+rfu+jVLe2FQF4skjHZLIjjFbyQ=="
},
"Microsoft.AspNetCore.Components.Forms": {
"type": "Transitive",
"resolved": "9.0.4",
"contentHash": "FvoNIyz5URP2QonItKbDBzCcdmSXHWfyh0lUAYkNittlLmFAqaIF4TE55h3/SYLI8ISsbAVd44Cax86HBhLu8w==",
"resolved": "9.0.5",
"contentHash": "KsAPGDQIa3SpP9yaIDrlETHpqprtmbbotdbGfGJ9aVvn/vi72cCTUVzpWOXIXgstVCb0ZQ940o/wfHKxBZEQMg==",
"dependencies": {
"Microsoft.AspNetCore.Components": "9.0.4"
"Microsoft.AspNetCore.Components": "9.0.5"
}
},
"Microsoft.AspNetCore.Components.Web": {
"type": "Transitive",
"resolved": "9.0.4",
"contentHash": "X02T/wGvT7vCgIXG0R4ZWQUx5/qMaKuWJklBL3eaa8b1NJHyPlox6IpI4AcmMy7r1hM8GwaxKfm34dv5b7n5VA==",
"resolved": "9.0.5",
"contentHash": "llh2dh9rR9JcnAMqvbijc9BzEnVhloQwKF8Kvfw73N2tJMnUJoxF3C9Ln1sao+nkZo3IpUuUu3k2czLOpS3NRQ==",
"dependencies": {
"Microsoft.AspNetCore.Components": "9.0.4",
"Microsoft.AspNetCore.Components.Forms": "9.0.4",
"Microsoft.Extensions.DependencyInjection": "9.0.4",
"Microsoft.Extensions.Primitives": "9.0.4",
"Microsoft.JSInterop": "9.0.4"
"Microsoft.AspNetCore.Components": "9.0.5",
"Microsoft.AspNetCore.Components.Forms": "9.0.5",
"Microsoft.Extensions.DependencyInjection": "9.0.5",
"Microsoft.Extensions.Primitives": "9.0.5",
"Microsoft.JSInterop": "9.0.5"
}
},
"Microsoft.AspNetCore.Metadata": {
"type": "Transitive",
"resolved": "9.0.4",
"contentHash": "3AhnSEW/Zw0E++itsen8YvqT7UoyF7zYD9OZPQmeGpT7YQP4W9GOiwOljfJEC8+4pP8kqgpeRN/p4LkmKLIUlQ=="
"resolved": "9.0.5",
"contentHash": "E4lbYlraZfZLvWgliUEtrqKt42++Yh9mpGKLyGHPGo1FpbZrXF/x+fOg5dbe22bnuosgWn3cj30566XKdwiodw=="
},
"Microsoft.Extensions.Caching.Abstractions": {
"type": "Transitive",
@@ -160,23 +160,23 @@
},
"Microsoft.Extensions.DependencyInjection": {
"type": "Transitive",
"resolved": "9.0.4",
"contentHash": "f2MTUaS2EQ3lX4325ytPAISZqgBfXmY0WvgD80ji6Z20AoDNiCESxsqo6mFRwHJD/jfVKRw9FsW6+86gNre3ug==",
"resolved": "9.0.5",
"contentHash": "N1Mn0T/tUBPoLL+Fzsp+VCEtneUhhxc1//Dx3BeuQ8AX+XrMlYCfnp2zgpEXnTCB7053CLdiqVWPZ7mEX6MPjg==",
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4"
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.5"
}
},
"Microsoft.Extensions.DependencyInjection.Abstractions": {
"type": "Transitive",
"resolved": "9.0.4",
"contentHash": "UI0TQPVkS78bFdjkTodmkH0Fe8lXv9LnhGFKgKrsgUJ5a5FVdFRcgjIkBVLbGgdRhxWirxH/8IXUtEyYJx6GQg=="
"resolved": "9.0.5",
"contentHash": "cjnRtsEAzU73aN6W7vkWy8Phj5t3Xm78HSqgrbh/O4Q9SK/yN73wZVa21QQY6amSLQRQ/M8N+koGnY6PuvKQsw=="
},
"Microsoft.Extensions.FileProviders.Abstractions": {
"type": "Transitive",
"resolved": "9.0.4",
"contentHash": "gQN2o/KnBfVk6Bd71E2YsvO5lsqrqHmaepDGk+FB/C4aiQY9B0XKKNKfl5/TqcNOs9OEithm4opiMHAErMFyEw==",
"resolved": "9.0.5",
"contentHash": "LLm+e8lvD+jOI+blHRSxPqywPaohOTNcVzQv548R1UpkEiNB2D+zf3RrqxBdB1LDPicRMTnfiaKJovxF8oX1bQ==",
"dependencies": {
"Microsoft.Extensions.Primitives": "9.0.4"
"Microsoft.Extensions.Primitives": "9.0.5"
}
},
"Microsoft.Extensions.Localization": {
@@ -197,30 +197,30 @@
},
"Microsoft.Extensions.Logging.Abstractions": {
"type": "Transitive",
"resolved": "9.0.4",
"contentHash": "0MXlimU4Dud6t+iNi5NEz3dO2w1HXdhoOLaYFuLPCjAsvlPQGwOT6V2KZRMLEhCAm/stSZt1AUv0XmDdkjvtbw==",
"resolved": "9.0.5",
"contentHash": "pP1PADCrIxMYJXxFmTVbAgEU7GVpjK5i0/tyfU9DiE0oXQy3JWQaOVgCkrCiePLgS8b5sghM3Fau3EeHiVWbCg==",
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4"
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.5"
}
},
"Microsoft.Extensions.Options": {
"type": "Transitive",
"resolved": "9.0.4",
"contentHash": "fiFI2+58kicqVZyt/6obqoFwHiab7LC4FkQ3mmiBJ28Yy4fAvy2+v9MRnSvvlOO8chTOjKsdafFl/K9veCPo5g==",
"resolved": "9.0.5",
"contentHash": "vPdJQU8YLOUSSK8NL0RmwcXJr2E0w8xH559PGQl4JYsglgilZr9LZnqV2zdgk+XR05+kuvhBEZKoDVd46o7NqA==",
"dependencies": {
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.4",
"Microsoft.Extensions.Primitives": "9.0.4"
"Microsoft.Extensions.DependencyInjection.Abstractions": "9.0.5",
"Microsoft.Extensions.Primitives": "9.0.5"
}
},
"Microsoft.Extensions.Primitives": {
"type": "Transitive",
"resolved": "9.0.4",
"contentHash": "SPFyMjyku1nqTFFJ928JAMd0QnRe4xjE7KeKnZMWXf3xk+6e0WiOZAluYtLdbJUXtsl2cCRSi8cBquJ408k8RA=="
"resolved": "9.0.5",
"contentHash": "b4OAv1qE1C9aM+ShWJu3rlo/WjDwa/I30aIPXqDWSKXTtKl1Wwh6BZn+glH5HndGVVn3C6ZAPQj5nv7/7HJNBQ=="
},
"Microsoft.JSInterop": {
"type": "Transitive",
"resolved": "9.0.4",
"contentHash": "iuzwm79XoRYTRhf7cOHgMgGAE8ZsAcCGp2VsIDAV05qRzceKSE1S7tQp+8GTzG7+IZpdPpvfaJUwSD/pYIog5A=="
"resolved": "9.0.5",
"contentHash": "ji4fQKbUUZiq3lmzVBMvp15VgBtEyfR51NrmEjo6qnD97Jf1R6C6QkSMjwUO8bw5NZIDRtmPCYurypB3W5AMEg=="
},
"ZXing.Net": {
"type": "Transitive",
@@ -1 +1,8 @@
# v0.9.43, build 218 (2025-05-xx xx:xx UTC)
# v0.9.43, build 218 (2025-05-11 17:22 UTC)
- Added our first titan: the German Aerospace Center (DLR) uses AI Studio in several projects and supports further development with personnel resources.
- Added the ability to select the new Google Gemini embeddings in the embedding configuration.
- Added localization for embedding configuration, bias of the day, and the agenda assistant.
- Improved the automatic German translation; thanks Peer (`peerschuett`) for contributing.
- Improved Google Gemini LLM model selection by filtering out the new embedding models.
- Improved the dialog for configuring embeddings: the API key is applied immediately, and provider formatting was enhanced.
- Upgraded .NET dependencies.
@@ -0,0 +1,12 @@
# v0.9.44, build 219 (2025-05-18 19:33 UTC)
- Added PDF import feature: we have completed the work on the PDF import feature. Importing PDF files has been successfully tested on macOS, Linux, and Windows. The feature is now enabled for everyone.
- Added more text content to the localization and added the German translation for it.
- Improved compatibility with certain Ubuntu linux versions regarding desktop integration.
- Improved the table views for providers, embeddings, and profiles by displaying actions as icons with tooltips.
- Improved localization code & German translation.
- Fixed a bug with text fields when their content was read live. This issue caused the user interface to refresh too frequently, resulting in increased energy consumption.
- Fixed an issue that caused live translation to stop working.
- Fixed a rare bug that could, under some circumstances, prevent the currently open chat from being renamed.
- Upgraded to .NET 9.0.5
- Upgraded to Rust 1.87.0
- Upgraded dependencies
@@ -0,0 +1 @@
# v0.9.45, build 220 (2025-05-xx xx:xx UTC)
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 745 622" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g id="g13322" transform="matrix(2.64969,0,0,2.64969,-5485.74,2987.91)">
<g id="layer1">
<g id="g133221" serif:id="g13322">
<g id="path13300" transform="matrix(1,0,0,1,1821.54,-1159.23)">
<path d="M434.281,67.438L380.25,121.75L334.406,121.75L290.75,165.406L344.156,165.406L344.156,219.719L398.469,165.406L444,165.406L487.656,121.75L434.281,121.75L434.281,67.438ZM423.281,93.813L423.281,121.75L395.313,121.75L423.281,93.813ZM338.813,132.75L369.281,132.75L347.594,154.406L317.156,154.406L338.813,132.75ZM384.969,132.75L415.438,132.75L393.438,154.406L363,154.406L384.969,132.75ZM430.813,132.75L461.281,132.75L439.594,154.406L409.156,154.406L430.813,132.75ZM383.094,165.094L355.156,193.031L355.156,165.406L383.094,165.094Z" style="fill:rgb(35,31,32);"/>
</g>
<g id="path13304" transform="matrix(1,0,0,1,1821.54,-1159.23)">
<path d="M398.156,198.094L398.156,230.406L407.281,230.406C417.006,230.406 425.813,227.285 425.813,214.406C425.812,201.209 417.006,198.094 407.281,198.094L398.156,198.094ZM404.75,203.094L408.531,203.094C413.885,203.094 419.188,207.171 419.188,214.406C419.188,221.323 413.885,225.406 408.531,225.406L404.75,225.406L404.75,203.094Z" style="fill:rgb(35,31,32);"/>
</g>
<g id="path13312" transform="matrix(1,0,0,1,1821.54,-1159.23)">
<path d="M470.75,198.031C469.99,198.011 469.245,198.054 468.5,198.094L456.875,198.094L456.875,230.406L463.469,230.406L463.469,218.469L468.188,218.469C470.995,218.469 471.949,219.42 472.875,221.938L476.375,230.406L484.219,230.406L478.875,219.125C477.92,217.244 477.594,216.611 476.031,215.656C476.031,215.656 481.693,214.093 481.375,207.813C481.375,200.114 476.068,198.176 470.75,198.031ZM463.469,203.406L467.875,203.406C471.319,203.406 474.148,204.368 474.438,207.813L474.438,208.719C474.148,212.192 471.319,213.125 467.875,213.125L463.469,213.125L463.469,203.406Z" style="fill:rgb(35,31,32);"/>
</g>
<path id="path13308" d="M2253.94,-928.819L2273.1,-928.819L2273.1,-934.144L2260.51,-934.144L2260.51,-961.147L2253.94,-961.147L2253.94,-928.819Z" style="fill:rgb(35,31,32);"/>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

+7 -7
View File
@@ -1,11 +1,11 @@
0.9.42
2025-05-04 13:03:03 UTC
217
9.0.105 (commit 35890ecb87)
9.0.4 (commit f57e6dc747)
1.86.0 (commit 05f9846f8)
0.9.44
2025-05-18 19:33:02 UTC
219
9.0.106 (commit 8cdf6f0897)
9.0.5 (commit e36e4d1a8f)
1.87.0 (commit 17067e9ac)
8.6.0
1.8.1
7a08baf99bb, release
9aa0a914653, release
osx-arm64
137.0.7123.0
+3 -3
View File
@@ -1119,9 +1119,9 @@ dependencies = [
[[package]]
name = "file-format"
version = "0.26.0"
version = "0.27.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7ef3d5e8ae27277c8285ac43ed153158178ef0f79567f32024ca8140a0c7cd8"
checksum = "0ff8badf6e72ff15e42c9ade15d01375837173b17d10c228ab41d821082619db"
[[package]]
name = "filetime"
@@ -2624,7 +2624,7 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "mindwork-ai-studio"
version = "0.9.42"
version = "0.9.44"
dependencies = [
"aes",
"arboard",
+2 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "mindwork-ai-studio"
version = "0.9.42"
version = "0.9.44"
edition = "2021"
description = "MindWork AI Studio"
authors = ["Thorsten Sommer"]
@@ -33,7 +33,7 @@ pbkdf2 = "0.12.2"
hmac = "0.12.1"
sha2 = "0.10.8"
rcgen = { version = "0.13.2", features = ["pem"] }
file-format = "0.26.0"
file-format = "0.27.0"
calamine = "0.27.0"
pdfium-render = "0.8.31"
sys-locale = "0.3.2"
+40 -29
View File
@@ -34,54 +34,65 @@ pub fn dotnet_port(_token: APIToken) -> String {
format!("{dotnet_server_port}")
}
/// Creates the startup environment file for the .NET server in the development
/// environment. The file is created in the root directory of the repository.
/// Creating that env file on a production environment would be a security
/// issue, since it contains the secret password and salt in plain text.
/// Anyone could read that file and decrypt the secret communication
/// between the .NET server and the Tauri app.
///
/// Therefore, we not only create the file in the development environment
/// but also remove that code from any production build.
#[cfg(debug_assertions)]
pub fn create_startup_env_file() {
// Get the secret password & salt and convert it to a base64 string:
let secret_password = BASE64_STANDARD.encode(ENCRYPTION.secret_password);
let secret_key_salt = BASE64_STANDARD.encode(ENCRYPTION.secret_key_salt);
let api_port = *API_SERVER_PORT;
warn!(Source = "Bootloader .NET"; "Development environment detected; create the startup env file at '../startup.env'.");
let env_file_path = std::path::PathBuf::from("..").join("startup.env");
let mut env_file = std::fs::File::create(env_file_path).unwrap();
let env_file_content = format!(
"AI_STUDIO_SECRET_PASSWORD={secret_password}\n\
AI_STUDIO_SECRET_KEY_SALT={secret_key_salt}\n\
AI_STUDIO_CERTIFICATE_FINGERPRINT={cert_fingerprint}\n\
AI_STUDIO_API_PORT={api_port}\n\
AI_STUDIO_API_TOKEN={api_token}",
cert_fingerprint = CERTIFICATE_FINGERPRINT.get().unwrap(),
api_token = API_TOKEN.to_hex_text()
);
std::io::Write::write_all(&mut env_file, env_file_content.as_bytes()).unwrap();
info!(Source = "Bootloader .NET"; "The startup env file was created successfully.");
}
/// Starts the .NET server in a separate process.
pub fn start_dotnet_server() {
// Get the secret password & salt and convert it to a base64 string:
let secret_password = BASE64_STANDARD.encode(ENCRYPTION.secret_password);
let secret_key_salt = BASE64_STANDARD.encode(ENCRYPTION.secret_key_salt);
let api_port = *API_SERVER_PORT;
let dotnet_server_environment = HashMap::from_iter([
(String::from("AI_STUDIO_SECRET_PASSWORD"), secret_password),
(String::from("AI_STUDIO_SECRET_KEY_SALT"), secret_key_salt),
(String::from("AI_STUDIO_CERTIFICATE_FINGERPRINT"), CERTIFICATE_FINGERPRINT.get().unwrap().to_string()),
(String::from("AI_STUDIO_API_PORT"), format!("{api_port}")),
(String::from("AI_STUDIO_API_TOKEN"), API_TOKEN.to_hex_text().to_string()),
]);
info!("Try to start the .NET server...");
let server_spawn_clone = DOTNET_SERVER.clone();
tauri::async_runtime::spawn(async move {
let api_port = *API_SERVER_PORT;
let (mut rx, child) = match is_dev() {
true => {
// We are in the development environment, so we try to start a process
// with `dotnet run` in the `../app/MindWork AI Studio` directory. But
// we cannot issue a sidecar because we cannot use any command for the
// sidecar (see Tauri configuration). Thus, we use a standard Rust process:
warn!(Source = "Bootloader .NET"; "Development environment detected; start .NET server using 'dotnet run'.");
Command::new("dotnet")
// Start the .NET server in the `../app/MindWork AI Studio` directory.
// We provide the runtime API server port to the .NET server:
.args(["run", "--project", "../app/MindWork AI Studio", "--", format!("{api_port}").as_str()])
.envs(dotnet_server_environment)
.spawn()
.expect("Failed to spawn .NET server process.")
}
false => {
Command::new_sidecar("mindworkAIStudioServer")
let (mut rx, child) = Command::new_sidecar("mindworkAIStudioServer")
.expect("Failed to create sidecar")
// Provide the runtime API server port to the .NET server:
.args([format!("{api_port}").as_str()])
.envs(dotnet_server_environment)
.spawn()
.expect("Failed to spawn .NET server process.")
}
};
.expect("Failed to spawn .NET server process.");
let server_pid = child.pid();
info!(Source = "Bootloader .NET"; "The .NET server process started with PID={server_pid}.");
@@ -140,7 +151,7 @@ pub async fn dotnet_ready(_token: APIToken) {
// held.
{
let mut initialized = DOTNET_INITIALIZED.lock().unwrap();
if *initialized {
if !is_dev() && *initialized {
error!("Anyone tried to initialize the runtime twice. This is not intended.");
return;
}
+10
View File
@@ -13,6 +13,9 @@ use mindwork_ai_studio::log::init_logging;
use mindwork_ai_studio::metadata::MetaData;
use mindwork_ai_studio::runtime_api::start_runtime_api;
#[cfg(debug_assertions)]
use mindwork_ai_studio::dotnet::create_startup_env_file;
#[tokio::main]
async fn main() {
let metadata = MetaData::init_from_string(include_str!("../../metadata.txt"));
@@ -44,6 +47,13 @@ async fn main() {
generate_certificate();
start_runtime_api();
if is_dev() {
#[cfg(debug_assertions)]
create_startup_env_file();
} else {
start_dotnet_server();
}
start_tauri();
}
+1 -1
View File
@@ -6,7 +6,7 @@
},
"package": {
"productName": "MindWork AI Studio",
"version": "0.9.42"
"version": "0.9.44"
},
"tauri": {
"allowlist": {