Compare commits

...
9 Commits
40 changed files with 644 additions and 99 deletions

No files matched your search

+3 -3
View File
@@ -278,7 +278,7 @@ jobs:
if: matrix.platform != 'windows-latest'
run: |
if ! cargo tauri --version > /dev/null 2>&1; then
cargo install tauri-cli
cargo install --version 1.6.2 tauri-cli
else
echo "Tauri is already installed"
fi
@@ -287,7 +287,7 @@ jobs:
if: matrix.platform == 'windows-latest'
run: |
if (-not (cargo tauri --version 2>$null)) {
cargo install tauri-cli
cargo install --version 1.6.2 tauri-cli
} else {
Write-Output "Tauri is already installed"
}
@@ -500,7 +500,7 @@ jobs:
apt-get install --yes libwebkit2gtk-4.0-dev build-essential libssl-dev libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev
# Setup Tauri:
cargo install tauri-cli
cargo install --version 1.6.2 tauri-cli
- name: Add the built runner image to the cache
if: ${{ steps.linux_arm_cache.outputs.cache-hit != 'true' && env.SKIP != 'true' }}
+3
View File
@@ -1 +1,4 @@
*.woff2 filter=lfs diff=lfs merge=lfs -text
*.afpub filter=lfs diff=lfs merge=lfs -text
*.afdesign filter=lfs diff=lfs merge=lfs -text
*.afphoto filter=lfs diff=lfs merge=lfs -text
@@ -32,6 +32,9 @@
<div id="@RESULT_DIV_ID" class="mr-2 mt-3">
</div>
<div id="@BEFORE_RESULT_DIV_ID" class="mt-3">
</div>
@if (this.ShowResult && this.resultingContentBlock is not null)
{
<ContentBlockComponent Role="@(this.resultingContentBlock.Role)" Type="@(this.resultingContentBlock.ContentType)" Time="@(this.resultingContentBlock.Time)" Content="@(this.resultingContentBlock.Content)"/>
@@ -37,8 +37,9 @@ public abstract partial class AssistantBase : ComponentBase, IMessageBusReceiver
[Inject]
private MessageBus MessageBus { get; init; } = null!;
internal const string AFTER_RESULT_DIV_ID = "afterAssistantResult";
internal const string RESULT_DIV_ID = "assistantResult";
internal const string BEFORE_RESULT_DIV_ID = "beforeAssistantResult";
internal const string AFTER_RESULT_DIV_ID = "afterAssistantResult";
protected abstract string Title { get; }
@@ -24,8 +24,6 @@ public partial class AssistantGrammarSpelling : AssistantBaseCore
""";
protected override bool AllowProfiles => false;
protected override bool ShowResult => false;
protected override bool ShowDedicatedProgress => true;
@@ -0,0 +1,15 @@
@attribute [Route(Routes.ASSISTANT_JOB_POSTING)]
@inherits AssistantBaseCore
<MudTextField T="string" @bind-Text="@this.inputCompanyName" Label="(Optional) The company name" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Warehouse" Variant="Variant.Outlined" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES" Class="mb-3"/>
<MudTextField T="string" @bind-Text="@this.inputCountryLegalFramework" Label="Provide the country, where the company is located" Validation="@this.ValidateCountryLegalFramework" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Flag" Variant="Variant.Outlined" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES" Class="mb-3" HelperText="This is important to consider the legal framework of the country."/>
<MudTextField T="string" @bind-Text="@this.inputMandatoryInformation" Label="(Optional) Provide mandatory information" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.TextSnippet" Variant="Variant.Outlined" Lines="6" AutoGrow="@true" MaxLines="12" Class="mb-3" UserAttributes="@USER_INPUT_ATTRIBUTES" HelperText="Mandatory information that your company requires for all job postings. This can include the company description, etc." />
<MudTextField T="string" @bind-Text="@this.inputJobDescription" Label="Job description" Validation="@this.ValidateJobDescription" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Settings" Variant="Variant.Outlined" Lines="6" AutoGrow="@true" MaxLines="12" Class="mb-3" UserAttributes="@USER_INPUT_ATTRIBUTES" HelperText="Describe what the person is supposed to do in the company. This might be just short bullet points." />
<MudTextField T="string" @bind-Text="@this.inputQualifications" Label="(Optional) Provide necessary job qualifications" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Settings" Variant="Variant.Outlined" Lines="6" AutoGrow="@true" MaxLines="12" Class="mb-3" UserAttributes="@USER_INPUT_ATTRIBUTES" HelperText="Describe what the person should bring to the table. This might be just short bullet points." />
<MudTextField T="string" @bind-Text="@this.inputResponsibilities" Label="(Optional) Provide job responsibilities" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.Settings" Variant="Variant.Outlined" Lines="6" AutoGrow="@true" MaxLines="12" Class="mb-3" UserAttributes="@USER_INPUT_ATTRIBUTES" HelperText="Describe the responsibilities the person should take on in the company." />
<MudTextField T="string" @bind-Text="@this.inputWorkLocation" Label="(Optional) Provide the work location" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.MyLocation" Variant="Variant.Outlined" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES" Class="mb-3"/>
<MudTextField T="string" @bind-Text="@this.inputEntryDate" Label="(Optional) Provide the entry date" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.DateRange" Variant="Variant.Outlined" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES" Class="mb-3"/>
<MudTextField T="string" @bind-Text="@this.inputValidUntil" Label="(Optional) Provide the date until the job posting is valid" Adornment="Adornment.Start" AdornmentIcon="@Icons.Material.Filled.DateRange" Variant="Variant.Outlined" Margin="Margin.Dense" UserAttributes="@USER_INPUT_ATTRIBUTES" Class="mb-3"/>
<EnumSelection T="CommonLanguages" NameFunc="@(language => language.NameSelectingOptional())" @bind-Value="@this.selectedTargetLanguage" Icon="@Icons.Material.Filled.Translate" Label="Target language" AllowOther="@true" OtherValue="CommonLanguages.OTHER" @bind-OtherInput="@this.customTargetLanguage" ValidateOther="@this.ValidateCustomLanguage" LabelOther="Custom target language" />
<ProviderSelection @bind-ProviderSettings="@this.providerSettings" ValidateProvider="@this.ValidatingProvider"/>
@@ -0,0 +1,290 @@
using AIStudio.Chat;
namespace AIStudio.Assistants.JobPosting;
public partial class AssistantJobPostings : AssistantBaseCore
{
public override Tools.Components Component => Tools.Components.JOB_POSTING_ASSISTANT;
protected override string Title => "Job Posting";
protected override string Description =>
"""
Provide some key points about the job you want to post. The AI will then
formulate a suggestion that you can finalize.
""";
protected override string SystemPrompt =>
$"""
You are an experienced specialist in personnel matters. You write job postings.
You follow the usual guidelines in the country of the posting. You ensure that
no individuals are discriminated against or excluded by the job posting. You do
not ask any questions and you do not repeat the task. You provide your response
in a Markdown format. Missing information necessary for the job posting, you try
to derive from the other details provided.
Structure of your response:
---
# <TITLE>
<Description of the job and the company>
# <TASKS>
<Describe what takes the job entails>
# <QUALIFICATIONS>
<What qualifications are required>
# <RESPONSIBILITIES>
<What responsibilities are associated with the job>
<When available, closing details such as the entry date etc.>
---
You write the job posting in the following language: {this.SystemPromptLanguage()}.
""";
protected override IReadOnlyList<IButtonData> FooterButtons => [];
protected override string SubmitText => "Create the job posting";
protected override Func<Task> SubmitAction => this.CreateJobPosting;
protected override bool SubmitDisabled => false;
protected override bool AllowProfiles => false;
protected override ChatThread ConvertToChatThread => (this.chatThread ?? new()) with
{
SystemPrompt = SystemPrompts.DEFAULT,
};
protected override void ResetFrom()
{
this.inputEntryDate = string.Empty;
this.inputValidUntil = string.Empty;
if (!this.MightPreselectValues())
{
this.selectedTargetLanguage = CommonLanguages.AS_IS;
this.customTargetLanguage = string.Empty;
this.inputMandatoryInformation = string.Empty;
this.inputJobDescription = string.Empty;
this.inputQualifications = string.Empty;
this.inputResponsibilities = string.Empty;
this.inputCompanyName = string.Empty;
this.inputWorkLocation = string.Empty;
this.inputCountryLegalFramework = string.Empty;
}
}
protected override bool MightPreselectValues()
{
if (this.SettingsManager.ConfigurationData.JobPostings.PreselectOptions)
{
this.inputMandatoryInformation = this.SettingsManager.ConfigurationData.JobPostings.PreselectedMandatoryInformation;
if(string.IsNullOrWhiteSpace(this.inputJobDescription))
this.inputJobDescription = this.SettingsManager.ConfigurationData.JobPostings.PreselectedJobDescription;
this.inputQualifications = this.SettingsManager.ConfigurationData.JobPostings.PreselectedQualifications;
this.inputResponsibilities = this.SettingsManager.ConfigurationData.JobPostings.PreselectedResponsibilities;
this.inputCompanyName = this.SettingsManager.ConfigurationData.JobPostings.PreselectedCompanyName;
this.inputWorkLocation = this.SettingsManager.ConfigurationData.JobPostings.PreselectedWorkLocation;
this.inputCountryLegalFramework = this.SettingsManager.ConfigurationData.JobPostings.PreselectedCountryLegalFramework;
this.selectedTargetLanguage = this.SettingsManager.ConfigurationData.JobPostings.PreselectedTargetLanguage;
this.customTargetLanguage = this.SettingsManager.ConfigurationData.JobPostings.PreselectOtherLanguage;
return true;
}
return false;
}
private string inputMandatoryInformation = string.Empty;
private string inputJobDescription = string.Empty;
private string inputQualifications = string.Empty;
private string inputResponsibilities = string.Empty;
private string inputCompanyName = string.Empty;
private string inputEntryDate = string.Empty;
private string inputValidUntil = string.Empty;
private string inputWorkLocation = string.Empty;
private string inputCountryLegalFramework = string.Empty;
private CommonLanguages selectedTargetLanguage = CommonLanguages.AS_IS;
private string customTargetLanguage = string.Empty;
#region Overrides of ComponentBase
protected override async Task OnInitializedAsync()
{
var deferredContent = MessageBus.INSTANCE.CheckDeferredMessages<string>(Event.SEND_TO_JOB_POSTING_ASSISTANT).FirstOrDefault();
if (deferredContent is not null)
this.inputJobDescription = deferredContent;
await base.OnInitializedAsync();
}
#endregion
private string? ValidateCustomLanguage(string language)
{
if(this.selectedTargetLanguage == CommonLanguages.OTHER && string.IsNullOrWhiteSpace(language))
return "Please provide a custom target language.";
return null;
}
private string? ValidateJobDescription(string jobDescription)
{
if(string.IsNullOrWhiteSpace(jobDescription))
return "Please provide a job description.";
return null;
}
private string? ValidateCountryLegalFramework(string countryLegalFramework)
{
if(string.IsNullOrWhiteSpace(countryLegalFramework))
return "Please provide the country where the job is posted (legal framework).";
return null;
}
private string SystemPromptLanguage()
{
if(this.selectedTargetLanguage is CommonLanguages.AS_IS)
return "Use the same language as the input";
if(this.selectedTargetLanguage is CommonLanguages.OTHER)
return this.customTargetLanguage;
return this.selectedTargetLanguage.Name();
}
private string UserPromptMandatoryInformation()
{
if(string.IsNullOrWhiteSpace(this.inputMandatoryInformation))
return string.Empty;
return $"""
# Mandatory Information
{this.inputMandatoryInformation}
""";
}
private string UserPromptJobDescription()
{
if(string.IsNullOrWhiteSpace(this.inputJobDescription))
return string.Empty;
return $"""
# Job Description
{this.inputJobDescription}
""";
}
private string UserPromptQualifications()
{
if(string.IsNullOrWhiteSpace(this.inputQualifications))
return string.Empty;
return $"""
# Qualifications
{this.inputQualifications}
""";
}
private string UserPromptResponsibilities()
{
if(string.IsNullOrWhiteSpace(this.inputResponsibilities))
return string.Empty;
return $"""
# Responsibilities
{this.inputResponsibilities}
""";
}
private string UserPromptCompanyName()
{
if(string.IsNullOrWhiteSpace(this.inputCompanyName))
return string.Empty;
return $"""
# Company Name
{this.inputCompanyName}
""";
}
private string UserPromptEntryDate()
{
if(string.IsNullOrWhiteSpace(this.inputEntryDate))
return string.Empty;
return $"""
# Entry Date
{this.inputEntryDate}
""";
}
private string UserPromptValidUntil()
{
if(string.IsNullOrWhiteSpace(this.inputValidUntil))
return string.Empty;
return $"""
# Job Posting Valid Until
{this.inputValidUntil}
""";
}
private string UserPromptWorkLocation()
{
if(string.IsNullOrWhiteSpace(this.inputWorkLocation))
return string.Empty;
return $"""
# Work Location
{this.inputWorkLocation}
""";
}
private string UserPromptCountryLegalFramework()
{
if(string.IsNullOrWhiteSpace(this.inputCountryLegalFramework))
return string.Empty;
return $"""
# Country where the job is posted (legal framework)
{this.inputCountryLegalFramework}
""";
}
private async Task CreateJobPosting()
{
await this.form!.Validate();
if (!this.inputIsValid)
return;
this.CreateChatThread();
var time = this.AddUserRequest(
$"""
{this.UserPromptCompanyName()}
{this.UserPromptCountryLegalFramework()}
{this.UserPromptMandatoryInformation()}
{this.UserPromptJobDescription()}
{this.UserPromptQualifications()}
{this.UserPromptResponsibilities()}
{this.UserPromptWorkLocation()}
{this.UserPromptEntryDate()}
{this.UserPromptValidUntil()}
""");
await this.AddAIResponseAsync(time);
}
}
@@ -1,3 +1,5 @@
using AIStudio.Chat;
namespace AIStudio.Assistants.LegalCheck;
public partial class AssistantLegalCheck : AssistantBaseCore
@@ -30,6 +32,11 @@ public partial class AssistantLegalCheck : AssistantBaseCore
protected override bool SubmitDisabled => this.isAgentRunning;
protected override ChatThread ConvertToChatThread => (this.chatThread ?? new()) with
{
SystemPrompt = SystemPrompts.DEFAULT,
};
protected override void ResetFrom()
{
this.inputLegalDocument = string.Empty;
@@ -1,3 +1,4 @@
using AIStudio.Chat;
using AIStudio.Settings;
namespace AIStudio.Assistants.MyTasks;
@@ -34,6 +35,11 @@ public partial class AssistantMyTasks : AssistantBaseCore
protected override Func<Task> SubmitAction => this.AnalyzeText;
protected override bool ShowProfileSelection => false;
protected override ChatThread ConvertToChatThread => (this.chatThread ?? new()) with
{
SystemPrompt = SystemPrompts.DEFAULT,
};
protected override void ResetFrom()
{
@@ -26,8 +26,6 @@ public partial class AssistantRewriteImprove : AssistantBaseCore
protected override bool AllowProfiles => false;
protected override bool ShowResult => false;
protected override bool ShowDedicatedProgress => true;
protected override Func<string> Result2Copy => () => this.rewrittenText;
@@ -13,6 +13,8 @@ public partial class Changelog
public static readonly Log[] LOGS =
[
new (188, "v0.9.13, build 188 (2024-10-07 11:18 UTC)", "v0.9.13.md"),
new (187, "v0.9.12, build 187 (2024-09-15 20:49 UTC)", "v0.9.12.md"),
new (186, "v0.9.11, build 186 (2024-09-15 10:33 UTC)", "v0.9.11.md"),
new (185, "v0.9.10, build 185 (2024-09-12 20:52 UTC)", "v0.9.10.md"),
new (184, "v0.9.9, build 184 (2024-09-11 21:10 UTC)", "v0.9.9.md"),
@@ -3,16 +3,17 @@
<MudTextField
T="string"
Text="@this.Text()"
TextChanged="@this.OptionChanged"
TextChanged="@this.InternalUpdate"
Label="@this.OptionDescription"
Disabled="@this.Disabled()"
Class="mb-3"
Class="@MARGIN_CLASS"
Adornment="Adornment.Start"
AdornmentIcon="@this.Icon"
AdornmentColor="@this.IconColor"
UserAttributes="@SPELLCHECK_ATTRIBUTES"
Lines="1"
Immediate="@false"
DebounceInterval="500"
Lines="@this.NumLines"
AutoGrow="@this.AutoGrow"
MaxLines="@this.GetMaxLines"
Immediate="@true"
Variant="Variant.Outlined"
/>
@@ -1,5 +1,7 @@
using Microsoft.AspNetCore.Components;
using Timer = System.Timers.Timer;
namespace AIStudio.Components;
public partial class ConfigurationText : ConfigurationBase
@@ -11,7 +13,7 @@ public partial class ConfigurationText : ConfigurationBase
public Func<string> Text { get; set; } = () => string.Empty;
/// <summary>
/// An action which is called when the option is changed.
/// An action which is called when the text was changed.
/// </summary>
[Parameter]
public Action<string> TextUpdate { get; set; } = _ => { };
@@ -27,6 +29,55 @@ public partial class ConfigurationText : ConfigurationBase
/// </summary>
[Parameter]
public Color IconColor { get; set; } = Color.Default;
/// <summary>
/// How many lines should the textfield have?
/// </summary>
[Parameter]
public int NumLines { get; set; } = 1;
/// <summary>
/// What is the maximum number of lines?
/// </summary>
[Parameter]
public int MaxLines { get; set; } = 12;
private string internalText = string.Empty;
private Timer timer = new(TimeSpan.FromMilliseconds(500))
{
AutoReset = false
};
#region Overrides of ConfigurationBase
protected override async Task OnInitializedAsync()
{
this.timer.Elapsed += async (_, _) => await this.InvokeAsync(async () => await this.OptionChanged(this.internalText));
await base.OnInitializedAsync();
}
#region Overrides of ComponentBase
protected override async Task OnParametersSetAsync()
{
this.internalText = this.Text();
await base.OnParametersSetAsync();
}
#endregion
#endregion
private bool AutoGrow => this.NumLines > 1;
private int GetMaxLines => this.AutoGrow ? this.MaxLines : 1;
private void InternalUpdate(string text)
{
this.timer.Stop();
this.internalText = text;
this.timer.Start();
}
private async Task OptionChanged(string updatedText)
{
@@ -19,7 +19,7 @@
<MudTextField
T="string"
@bind-Text="@this.dataAPIKey"
Label="API Key"
Label="@this.APIKeyText"
Disabled="@(!this.NeedAPIKey)"
Class="mb-3"
Adornment="Adornment.Start"
@@ -133,7 +133,7 @@ public partial class ProviderDialog : ComponentBase
//
// We cannot load the API key for self-hosted providers:
//
if (this.DataLLMProvider is LLMProviders.SELF_HOSTED)
if (this.DataLLMProvider is LLMProviders.SELF_HOSTED && this.DataHost is not Host.OLLAMA)
{
await this.ReloadModels();
await base.OnInitializedAsync();
@@ -149,7 +149,7 @@ public partial class ProviderDialog : ComponentBase
}
// Load the API key:
var requestedSecret = await this.RustService.GetAPIKey(provider);
var requestedSecret = await this.RustService.GetAPIKey(provider, isTrying: this.DataLLMProvider is LLMProviders.SELF_HOSTED);
if(requestedSecret.Success)
{
this.dataAPIKey = await requestedSecret.Secret.Decrypt(this.encryption);
@@ -159,8 +159,15 @@ public partial class ProviderDialog : ComponentBase
}
else
{
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.";
await this.form.Validate();
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.";
await this.form.Validate();
}
// We still try to load the models. Some local hosts don't need an API key:
await this.ReloadModels();
}
}
@@ -192,7 +199,7 @@ public partial class ProviderDialog : ComponentBase
// Use the data model to store the provider.
// We just return this data to the parent component:
var addedProviderSettings = this.CreateProviderSettings();
if (addedProviderSettings.UsedLLMProvider != LLMProviders.SELF_HOSTED)
if (!string.IsNullOrWhiteSpace(this.dataAPIKey))
{
// We need to instantiate the provider to store the API key:
var provider = addedProviderSettings.CreateProvider(this.Logger);
@@ -363,9 +370,16 @@ public partial class ProviderDialog : ComponentBase
LLMProviders.ANTHROPIC => true,
LLMProviders.FIREWORKS => true,
LLMProviders.SELF_HOSTED => this.DataHost is Host.OLLAMA,
_ => false,
};
private string APIKeyText => this.DataLLMProvider switch
{
LLMProviders.SELF_HOSTED => "(Optional) API Key",
_ => "API Key",
};
private bool NeedHostname => this.DataLLMProvider switch
{
@@ -24,6 +24,7 @@
<AssistantBlock Name="E-Mail" Description="Generate an e-mail for a given context." Icon="@Icons.Material.Filled.Email" Link="@Routes.ASSISTANT_EMAIL"/>
<AssistantBlock Name="My Tasks" Description="Analyze a text or an email for tasks you need to complete." Icon="@Icons.Material.Filled.Task" Link="@Routes.ASSISTANT_MY_TASKS"/>
<AssistantBlock Name="Agenda Planner" Description="Generate an agenda for a given meeting, seminar, etc." Icon="@Icons.Material.Filled.CalendarToday" Link="@Routes.ASSISTANT_AGENDA"/>
<AssistantBlock Name="Job Posting" Description="Generate a job posting for a given job description." Icon="@Icons.Material.Filled.Work" Link="@Routes.ASSISTANT_JOB_POSTING"/>
<AssistantBlock Name="Legal Check" Description="Ask a question about a legal document." Icon="@Icons.Material.Filled.Gavel" Link="@Routes.ASSISTANT_LEGAL_CHECK"/>
<AssistantBlock Name="Icon Finder" Description="Using a LLM to find an icon for a given context." Icon="@Icons.Material.Filled.FindInPage" Link="@Routes.ASSISTANT_ICON_FINDER"/>
</MudStack>
@@ -342,6 +342,26 @@
</MudPaper>
</ExpansionPanel>
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Work" HeaderText="Assistant: Job Postings">
<MudPaper Class="pa-3 mb-8 border-dashed border rounded-lg">
<ConfigurationOption OptionDescription="Preselect job posting options?" LabelOn="Job posting options are preselected" LabelOff="No job posting options are preselected" State="@(() => this.SettingsManager.ConfigurationData.JobPostings.PreselectOptions)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.JobPostings.PreselectOptions = updatedState)" OptionHelp="When enabled, you can preselect some job posting options. This is might be useful when you prefer a specific LLM model."/>
<ConfigurationText OptionDescription="Preselect the company name?" Disabled="@(() => !this.SettingsManager.ConfigurationData.JobPostings.PreselectOptions)" Icon="@Icons.Material.Filled.Warehouse" Text="@(() => this.SettingsManager.ConfigurationData.JobPostings.PreselectedCompanyName)" TextUpdate="@(updatedText => this.SettingsManager.ConfigurationData.JobPostings.PreselectedCompanyName = updatedText)" />
<ConfigurationText OptionDescription="Preselect some mandatory information about the job posting?" NumLines="6" Disabled="@(() => !this.SettingsManager.ConfigurationData.JobPostings.PreselectOptions)" Icon="@Icons.Material.Filled.TextSnippet" Text="@(() => this.SettingsManager.ConfigurationData.JobPostings.PreselectedMandatoryInformation)" TextUpdate="@(updatedText => this.SettingsManager.ConfigurationData.JobPostings.PreselectedMandatoryInformation = updatedText)" />
<ConfigurationText OptionDescription="Preselect the job description?" NumLines="3" MaxLines="6" Disabled="@(() => !this.SettingsManager.ConfigurationData.JobPostings.PreselectOptions)" Icon="@Icons.Material.Filled.Settings" Text="@(() => this.SettingsManager.ConfigurationData.JobPostings.PreselectedJobDescription)" TextUpdate="@(updatedText => this.SettingsManager.ConfigurationData.JobPostings.PreselectedJobDescription = updatedText)" />
<ConfigurationText OptionDescription="Preselect the job qualifications?" NumLines="3" MaxLines="6" Disabled="@(() => !this.SettingsManager.ConfigurationData.JobPostings.PreselectOptions)" Icon="@Icons.Material.Filled.Settings" Text="@(() => this.SettingsManager.ConfigurationData.JobPostings.PreselectedQualifications)" TextUpdate="@(updatedText => this.SettingsManager.ConfigurationData.JobPostings.PreselectedQualifications = updatedText)" />
<ConfigurationText OptionDescription="Preselect the job responsibilities?" NumLines="3" MaxLines="6" Disabled="@(() => !this.SettingsManager.ConfigurationData.JobPostings.PreselectOptions)" Icon="@Icons.Material.Filled.Settings" Text="@(() => this.SettingsManager.ConfigurationData.JobPostings.PreselectedResponsibilities)" TextUpdate="@(updatedText => this.SettingsManager.ConfigurationData.JobPostings.PreselectedResponsibilities = updatedText)" />
<ConfigurationText OptionDescription="Preselect the work location?" Disabled="@(() => !this.SettingsManager.ConfigurationData.JobPostings.PreselectOptions)" Icon="@Icons.Material.Filled.MyLocation" Text="@(() => this.SettingsManager.ConfigurationData.JobPostings.PreselectedWorkLocation)" TextUpdate="@(updatedText => this.SettingsManager.ConfigurationData.JobPostings.PreselectedWorkLocation = updatedText)" />
<ConfigurationText OptionDescription="Preselect the country (legal framework)?" Disabled="@(() => !this.SettingsManager.ConfigurationData.JobPostings.PreselectOptions)" Icon="@Icons.Material.Filled.Flag" Text="@(() => this.SettingsManager.ConfigurationData.JobPostings.PreselectedCountryLegalFramework)" TextUpdate="@(updatedText => this.SettingsManager.ConfigurationData.JobPostings.PreselectedCountryLegalFramework = updatedText)" />
<ConfigurationSelect OptionDescription="Preselect the language" Disabled="@(() => !this.SettingsManager.ConfigurationData.JobPostings.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.JobPostings.PreselectedTargetLanguage)" Data="@ConfigurationSelectDataFactory.GetCommonLanguagesOptionalData()" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.JobPostings.PreselectedTargetLanguage = selectedValue)" OptionHelp="Which target language should be preselected?"/>
@if (this.SettingsManager.ConfigurationData.JobPostings.PreselectedTargetLanguage is CommonLanguages.OTHER)
{
<ConfigurationText OptionDescription="Preselect another target language" Disabled="@(() => !this.SettingsManager.ConfigurationData.JobPostings.PreselectOptions)" Icon="@Icons.Material.Filled.Translate" Text="@(() => this.SettingsManager.ConfigurationData.JobPostings.PreselectOtherLanguage)" TextUpdate="@(updatedText => this.SettingsManager.ConfigurationData.JobPostings.PreselectOtherLanguage = updatedText)"/>
}
<ConfigurationMinConfidenceSelection Disabled="@(() => !this.SettingsManager.ConfigurationData.JobPostings.PreselectOptions)" RestrictToGlobalMinimumConfidence="@true" SelectedValue="@(() => this.SettingsManager.ConfigurationData.JobPostings.MinimumProviderConfidence)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.JobPostings.MinimumProviderConfidence = selectedValue)"/>
<ConfigurationProviderSelection Component="Components.JOB_POSTING_ASSISTANT" Data="@this.availableProviders" Disabled="@(() => !this.SettingsManager.ConfigurationData.JobPostings.PreselectOptions)" SelectedValue="@(() => this.SettingsManager.ConfigurationData.JobPostings.PreselectedProvider)" SelectionUpdate="@(selectedValue => this.SettingsManager.ConfigurationData.JobPostings.PreselectedProvider = selectedValue)"/>
</MudPaper>
</ExpansionPanel>
<ExpansionPanel HeaderIcon="@Icons.Material.Filled.Gavel" HeaderText="Assistant: Legal Check">
<ConfigurationOption OptionDescription="Hide the web content reader?" LabelOn="Web content reader is hidden" LabelOff="Web content reader is shown" State="@(() => this.SettingsManager.ConfigurationData.LegalCheck.HideWebContentReader)" StateUpdate="@(updatedState => this.SettingsManager.ConfigurationData.LegalCheck.HideWebContentReader = updatedState)" OptionHelp="When activated, the web content reader is hidden and cannot be used. As a result, the user interface becomes a bit easier to use."/>
<MudPaper Class="pa-3 mb-8 border-dashed border rounded-lg">
@@ -7,7 +7,7 @@ using AIStudio.Settings;
namespace AIStudio.Provider;
public static class ProvidersExtensions
public static class LLMProvidersExtensions
{
/// <summary>
/// Returns the human-readable name of the provider.
@@ -1,3 +1,4 @@
using System.Net.Http.Headers;
using System.Runtime.CompilerServices;
using System.Text;
using System.Text.Json;
@@ -23,6 +24,9 @@ public sealed class ProviderSelfHosted(ILogger logger, Settings.Provider provide
/// <inheritdoc />
public async IAsyncEnumerable<string> StreamChatCompletion(Provider.Model chatModel, ChatThread chatThread, [EnumeratorCancellation] CancellationToken token = default)
{
// Get the API key:
var requestedSecret = await RUST_SERVICE.GetAPIKey(this, isTrying: true);
// Prepare the system prompt:
var systemPrompt = new Message
{
@@ -62,68 +66,83 @@ public sealed class ProviderSelfHosted(ILogger logger, Settings.Provider provide
MaxTokens = -1,
}, JSON_SERIALIZER_OPTIONS);
// Build the HTTP post request:
var request = new HttpRequestMessage(HttpMethod.Post, provider.Host.ChatURL());
// Set the content:
request.Content = new StringContent(providerChatRequest, Encoding.UTF8, "application/json");
// Send the request with the ResponseHeadersRead option.
// This allows us to read the stream as soon as the headers are received.
// This is important because we want to stream the responses.
var response = await this.httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, token);
// Open the response stream:
var providerStream = await response.Content.ReadAsStreamAsync(token);
// Add a stream reader to read the stream, line by line:
var streamReader = new StreamReader(providerStream);
// Read the stream, line by line:
while(!streamReader.EndOfStream)
StreamReader? streamReader = default;
try
{
// Check if the token is canceled:
if(token.IsCancellationRequested)
yield break;
// Read the next line:
var line = await streamReader.ReadLineAsync(token);
// Skip empty lines:
if(string.IsNullOrWhiteSpace(line))
continue;
// Skip lines that do not start with "data: ". Regard
// to the specification, we only want to read the data lines:
if(!line.StartsWith("data: ", StringComparison.InvariantCulture))
continue;
// Build the HTTP post request:
var request = new HttpRequestMessage(HttpMethod.Post, provider.Host.ChatURL());
// Check if the line is the end of the stream:
if (line.StartsWith("data: [DONE]", StringComparison.InvariantCulture))
yield break;
// Set the authorization header:
if (requestedSecret.Success)
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await requestedSecret.Secret.Decrypt(ENCRYPTION));
ResponseStreamLine providerResponse;
try
// Set the content:
request.Content = new StringContent(providerChatRequest, Encoding.UTF8, "application/json");
// Send the request with the ResponseHeadersRead option.
// This allows us to read the stream as soon as the headers are received.
// This is important because we want to stream the responses.
var response = await this.httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, token);
// Open the response stream:
var providerStream = await response.Content.ReadAsStreamAsync(token);
// Add a stream reader to read the stream, line by line:
streamReader = new StreamReader(providerStream);
}
catch(Exception e)
{
this.logger.LogError($"Failed to stream chat completion from self-hosted provider '{this.InstanceName}': {e.Message}");
}
if (streamReader is not null)
{
// Read the stream, line by line:
while (!streamReader.EndOfStream)
{
// We know that the line starts with "data: ". Hence, we can
// skip the first 6 characters to get the JSON data after that.
var jsonData = line[6..];
// Deserialize the JSON data:
providerResponse = JsonSerializer.Deserialize<ResponseStreamLine>(jsonData, JSON_SERIALIZER_OPTIONS);
// Check if the token is canceled:
if (token.IsCancellationRequested)
yield break;
// Read the next line:
var line = await streamReader.ReadLineAsync(token);
// Skip empty lines:
if (string.IsNullOrWhiteSpace(line))
continue;
// Skip lines that do not start with "data: ". Regard
// to the specification, we only want to read the data lines:
if (!line.StartsWith("data: ", StringComparison.InvariantCulture))
continue;
// Check if the line is the end of the stream:
if (line.StartsWith("data: [DONE]", StringComparison.InvariantCulture))
yield break;
ResponseStreamLine providerResponse;
try
{
// We know that the line starts with "data: ". Hence, we can
// skip the first 6 characters to get the JSON data after that.
var jsonData = line[6..];
// Deserialize the JSON data:
providerResponse = JsonSerializer.Deserialize<ResponseStreamLine>(jsonData, JSON_SERIALIZER_OPTIONS);
}
catch
{
// Skip invalid JSON data:
continue;
}
// Skip empty responses:
if (providerResponse == default || providerResponse.Choices.Count == 0)
continue;
// Yield the response:
yield return providerResponse.Choices[0].Delta.Content;
}
catch
{
// Skip invalid JSON data:
continue;
}
// Skip empty responses:
if(providerResponse == default || providerResponse.Choices.Count == 0)
continue;
// Yield the response:
yield return providerResponse.Choices[0].Delta.Content;
}
}
@@ -149,7 +168,21 @@ public sealed class ProviderSelfHosted(ILogger logger, Settings.Provider provide
case Host.LM_STUDIO:
case Host.OLLAMA:
var secretKey = apiKeyProvisional switch
{
not null => apiKeyProvisional,
_ => await RUST_SERVICE.GetAPIKey(this, isTrying: true) switch
{
{ Success: true } result => await result.Secret.Decrypt(ENCRYPTION),
_ => null,
}
};
var lmStudioRequest = new HttpRequestMessage(HttpMethod.Get, "models");
if(secretKey is not null)
lmStudioRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", apiKeyProvisional);
var lmStudioResponse = await this.httpClient.SendAsync(lmStudioRequest, token);
if(!lmStudioResponse.IsSuccessStatusCode)
return [];
+1
View File
@@ -21,5 +21,6 @@ public sealed partial class Routes
public const string ASSISTANT_LEGAL_CHECK = "/assistant/legal-check";
public const string ASSISTANT_SYNONYMS = "/assistant/synonyms";
public const string ASSISTANT_MY_TASKS = "/assistant/my-tasks";
public const string ASSISTANT_JOB_POSTING = "/assistant/job-posting";
// ReSharper restore InconsistentNaming
}
@@ -58,11 +58,13 @@ public sealed class Data
public DataRewriteImprove RewriteImprove { get; init; } = new();
public DataEMail EMail { get; set; } = new();
public DataEMail EMail { get; init; } = new();
public DataLegalCheck LegalCheck { get; set; } = new();
public DataLegalCheck LegalCheck { get; init; } = new();
public DataSynonyms Synonyms { get; set; } = new();
public DataSynonyms Synonyms { get; init; } = new();
public DataMyTasks MyTasks { get; set; } = new();
public DataMyTasks MyTasks { get; init; } = new();
public DataJobPostings JobPostings { get; init; } = new();
}
@@ -0,0 +1,66 @@
using AIStudio.Provider;
namespace AIStudio.Settings.DataModel;
public sealed class DataJobPostings
{
/// <summary>
/// Do we want to preselect any translator options?
/// </summary>
public bool PreselectOptions { get; set; }
/// <summary>
/// The mandatory information for the job posting.
/// </summary>
public string PreselectedMandatoryInformation { get; set; } = string.Empty;
/// <summary>
/// The job description.
/// </summary>
public string PreselectedJobDescription { get; set; } = string.Empty;
/// <summary>
/// The qualifications required for the job.
/// </summary>
public string PreselectedQualifications { get; set; } = string.Empty;
/// <summary>
/// What are the responsibilities of the job?
/// </summary>
public string PreselectedResponsibilities { get; set; } = string.Empty;
/// <summary>
/// Which company name should be preselected?
/// </summary>
public string PreselectedCompanyName { get; set; } = string.Empty;
/// <summary>
/// Where should the work be done?
/// </summary>
public string PreselectedWorkLocation { get; set; } = string.Empty;
/// <summary>
/// The preselected country legal framework.
/// </summary>
public string PreselectedCountryLegalFramework { get; set; } = string.Empty;
/// <summary>
/// Preselect the target language?
/// </summary>
public CommonLanguages PreselectedTargetLanguage { get; set; } = CommonLanguages.EN_US;
/// <summary>
/// Preselect any other language?
/// </summary>
public string PreselectOtherLanguage { get; set; } = string.Empty;
/// <summary>
/// The minimum confidence level required for a provider to be considered.
/// </summary>
public ConfidenceLevel MinimumProviderConfidence { get; set; } = ConfidenceLevel.NONE;
/// <summary>
/// The preselected translator provider.
/// </summary>
public string PreselectedProvider { get; set; } = string.Empty;
}
@@ -15,6 +15,7 @@ public enum Components
LEGAL_CHECK_ASSISTANT,
SYNONYMS_ASSISTANT,
MY_TASKS_ASSISTANT,
JOB_POSTING_ASSISTANT,
CHAT,
}
@@ -18,6 +18,7 @@ public static class ComponentsExtensions
Components.LEGAL_CHECK_ASSISTANT => "Legal Check Assistant",
Components.SYNONYMS_ASSISTANT => "Synonym Assistant",
Components.MY_TASKS_ASSISTANT => "My Tasks Assistant",
Components.JOB_POSTING_ASSISTANT => "Job Posting Assistant",
Components.CHAT => "New Chat",
@@ -37,6 +38,7 @@ public static class ComponentsExtensions
Components.LEGAL_CHECK_ASSISTANT => new(Event.SEND_TO_LEGAL_CHECK_ASSISTANT, Routes.ASSISTANT_LEGAL_CHECK),
Components.SYNONYMS_ASSISTANT => new(Event.SEND_TO_SYNONYMS_ASSISTANT, Routes.ASSISTANT_SYNONYMS),
Components.MY_TASKS_ASSISTANT => new(Event.SEND_TO_MY_TASKS_ASSISTANT, Routes.ASSISTANT_MY_TASKS),
Components.JOB_POSTING_ASSISTANT => new(Event.SEND_TO_JOB_POSTING_ASSISTANT, Routes.ASSISTANT_JOB_POSTING),
Components.CHAT => new(Event.SEND_TO_CHAT, Routes.CHAT),
@@ -56,6 +58,7 @@ public static class ComponentsExtensions
Components.LEGAL_CHECK_ASSISTANT => settingsManager.ConfigurationData.LegalCheck.PreselectOptions ? settingsManager.ConfigurationData.LegalCheck.MinimumProviderConfidence : default,
Components.SYNONYMS_ASSISTANT => settingsManager.ConfigurationData.Synonyms.PreselectOptions ? settingsManager.ConfigurationData.Synonyms.MinimumProviderConfidence : default,
Components.MY_TASKS_ASSISTANT => settingsManager.ConfigurationData.MyTasks.PreselectOptions ? settingsManager.ConfigurationData.MyTasks.MinimumProviderConfidence : default,
Components.JOB_POSTING_ASSISTANT => settingsManager.ConfigurationData.JobPostings.PreselectOptions ? settingsManager.ConfigurationData.JobPostings.MinimumProviderConfidence : default,
_ => default,
};
@@ -73,6 +76,7 @@ public static class ComponentsExtensions
Components.LEGAL_CHECK_ASSISTANT => settingsManager.ConfigurationData.LegalCheck.PreselectOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.LegalCheck.PreselectedProvider) : default,
Components.SYNONYMS_ASSISTANT => settingsManager.ConfigurationData.Synonyms.PreselectOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.Synonyms.PreselectedProvider) : default,
Components.MY_TASKS_ASSISTANT => settingsManager.ConfigurationData.MyTasks.PreselectOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.MyTasks.PreselectedProvider) : default,
Components.JOB_POSTING_ASSISTANT => settingsManager.ConfigurationData.JobPostings.PreselectOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.JobPostings.PreselectedProvider) : default,
Components.CHAT => settingsManager.ConfigurationData.Chat.PreselectOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.Chat.PreselectedProvider) : default,
+1
View File
@@ -30,4 +30,5 @@ public enum Event
SEND_TO_LEGAL_CHECK_ASSISTANT,
SEND_TO_SYNONYMS_ASSISTANT,
SEND_TO_MY_TASKS_ASSISTANT,
SEND_TO_JOB_POSTING_ASSISTANT,
}
@@ -6,7 +6,7 @@ public static class JsRuntimeExtensions
{
public static async Task GenerateAndShowDiff(this IJSRuntime jsRuntime, string text1, string text2)
{
await jsRuntime.InvokeVoidAsync("generateDiff", text1, text2, AssistantBase.RESULT_DIV_ID, AssistantBase.AFTER_RESULT_DIV_ID);
await jsRuntime.InvokeVoidAsync("generateDiff", text1, text2, AssistantBase.RESULT_DIV_ID, AssistantBase.BEFORE_RESULT_DIV_ID);
}
public static async Task ClearDiv(this IJSRuntime jsRuntime, string divId)
@@ -1,3 +1,3 @@
namespace AIStudio.Tools.Rust;
public readonly record struct SelectSecretRequest(string Destination, string UserName);
public readonly record struct SelectSecretRequest(string Destination, string UserName, bool IsTrying);
+8 -6
View File
@@ -253,24 +253,26 @@ public sealed class RustService : IDisposable
throw;
}
}
/// <summary>
/// Try to get the API key for the given provider.
/// </summary>
/// <param name="provider">The provider to get the API key for.</param>
/// <param name="isTrying">Indicates if we are trying to get the API key. In that case, we don't log errors.</param>
/// <returns>The requested secret.</returns>
public async Task<RequestedSecret> GetAPIKey(IProvider provider)
public async Task<RequestedSecret> GetAPIKey(IProvider provider, bool isTrying = false)
{
var secretRequest = new SelectSecretRequest($"provider::{provider.Id}::{provider.InstanceName}::api_key", Environment.UserName);
var secretRequest = new SelectSecretRequest($"provider::{provider.Id}::{provider.InstanceName}::api_key", Environment.UserName, isTrying);
var result = await this.http.PostAsJsonAsync("/secrets/get", secretRequest, this.jsonRustSerializerOptions);
if (!result.IsSuccessStatusCode)
{
this.logger!.LogError($"Failed to get the API key for provider '{provider.Id}' due to an API issue: '{result.StatusCode}'");
if(!isTrying)
this.logger!.LogError($"Failed to get the API key for provider '{provider.Id}' due to an API issue: '{result.StatusCode}'");
return new RequestedSecret(false, new EncryptedText(string.Empty), "Failed to get the API key due to an API issue.");
}
var secret = await result.Content.ReadFromJsonAsync<RequestedSecret>(this.jsonRustSerializerOptions);
if (!secret.Success)
if (!secret.Success && !isTrying)
this.logger!.LogError($"Failed to get the API key for provider '{provider.Id}': '{secret.Issue}'");
return secret;
@@ -307,7 +309,7 @@ public sealed class RustService : IDisposable
/// <returns>The delete secret response.</returns>
public async Task<DeleteSecretResponse> DeleteAPIKey(IProvider provider)
{
var request = new SelectSecretRequest($"provider::{provider.Id}::{provider.InstanceName}::api_key", Environment.UserName);
var request = new SelectSecretRequest($"provider::{provider.Id}::{provider.InstanceName}::api_key", Environment.UserName, false);
var result = await this.http.PostAsJsonAsync("/secrets/delete", request, this.jsonRustSerializerOptions);
if (!result.IsSuccessStatusCode)
{
+11
View File
@@ -54,4 +54,15 @@
.custom-icon-color > a > svg {
color: var(--custom-icon-color) !important;
}
.improvedDiff .wikEdDiffFragment {
background-color: initial !important;
}
.improvedDiff .wikEdDiffDelete {
text-decoration-line: line-through;
text-decoration-style: double;
text-decoration-color: red;
text-decoration-thickness: 2px;
}
+1 -1
View File
@@ -2,7 +2,7 @@ window.generateDiff = function (text1, text2, divDiff, divLegend) {
let wikEdDiff = new WikEdDiff();
let targetDiv = document.getElementById(divDiff)
targetDiv.innerHTML = wikEdDiff.diff(text1, text2);
targetDiv.classList.add('mud-typography-body1');
targetDiv.classList.add('mud-typography-body1', 'improvedDiff');
let legend = document.getElementById(divLegend);
legend.innerHTML = `
@@ -0,0 +1,8 @@
# v0.9.12, build 187 (2024-09-15 20:49 UTC)
- Added a job posting assistant to the business category.
- Improved text diff viewer by line-through deleted text.
- Improved grammar & spelling check and rewrite assistants by showing the final text as well.
- Fixed the dark theme for the text diff viewer, e.g., in the grammar check assistant.
- Fixed margin-related issue in the `ConfigurationText` component.
- Fixed chat conversion of job posting, legal check, and my task assistants.
- Refactored the `ConfigurationText` component to debounce the input field to prevent unnecessary configuration updates. The component now also supports multiline text.
@@ -0,0 +1,2 @@
# v0.9.13, build 188 (2024-10-07 11:18 UTC)
- Allow the use of an API key for self-hosted `ollama` instances. Useful when using `ollama` with, e.g., Open WebUI.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+4 -4
View File
@@ -1,9 +1,9 @@
0.9.11
2024-09-15 10:33:24 UTC
186
0.9.13
2024-10-07 11:18:05 UTC
188
8.0.108 (commit 665a05cea7)
8.0.8 (commit 08338fcaa5)
1.81.0 (commit eeb90cda1)
7.8.0
1.7.1
e9998348c5a, release
580ca9850b1, release
+1 -1
View File
@@ -2130,7 +2130,7 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "mindwork-ai-studio"
version = "0.9.11"
version = "0.9.13"
dependencies = [
"aes",
"arboard",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "mindwork-ai-studio"
version = "0.9.11"
version = "0.9.13"
edition = "2021"
description = "MindWork AI Studio"
authors = ["Thorsten Sommer"]
+5 -1
View File
@@ -966,7 +966,10 @@ fn get_secret(_token: APIToken, request: Json<RequestSecret>) -> Json<RequestedS
},
Err(e) => {
error!(Source = "Secret Store"; "Failed to retrieve secret for '{service}' and user '{user_name}': {e}.");
if !request.is_trying {
error!(Source = "Secret Store"; "Failed to retrieve secret for '{service}' and user '{user_name}': {e}.");
}
Json(RequestedSecret {
success: false,
secret: EncryptedText::new(String::from("")),
@@ -980,6 +983,7 @@ fn get_secret(_token: APIToken, request: Json<RequestSecret>) -> Json<RequestedS
struct RequestSecret {
destination: String,
user_name: String,
is_trying: bool,
}
#[derive(Serialize)]
+1 -1
View File
@@ -6,7 +6,7 @@
},
"package": {
"productName": "MindWork AI Studio",
"version": "0.9.11"
"version": "0.9.13"
},
"tauri": {
"allowlist": {