diff --git a/app/MindWork AI Studio/Assistants/DocumentAnalysis/DocumentAnalysisAssistant.razor b/app/MindWork AI Studio/Assistants/DocumentAnalysis/DocumentAnalysisAssistant.razor new file mode 100644 index 00000000..2cefb7ac --- /dev/null +++ b/app/MindWork AI Studio/Assistants/DocumentAnalysis/DocumentAnalysisAssistant.razor @@ -0,0 +1,99 @@ +@attribute [Route(Routes.ASSISTANT_DOCUMENT_ANALYSIS)] +@inherits AssistantBaseCore + +@using AIStudio.Settings.DataModel + + +
+ + + @T("Document analysis policies") + + + + @T("Here you have the option to save different policies for various document analysis assistants and switch between them.") + + +@if(this.SettingsManager.ConfigurationData.DocumentAnalysis.Policies.Count is 0) +{ + + @T("You have not yet added any document analysis policies.") + +} +else +{ + + @foreach (var policy in this.SettingsManager.ConfigurationData.DocumentAnalysis.Policies) + { + + @policy.PolicyName + + } + +} + + + + @T("Add policy") + + + @T("Delete this policy") + + + + + + + @T("Common settings") + + + + + + + + + + @T("Input and output rules") + + + + + + + + + + + + @T("Preparation for enterprise distribution") + + + + + @T("Export policy as configuration section") + + + + + + + + + @T("Description") + + + + @this.selectedPolicy?.PolicyDescription + + + + @T("Documents for the analysis") + + + + + + + + \ No newline at end of file diff --git a/app/MindWork AI Studio/Assistants/DocumentAnalysis/DocumentAnalysisAssistant.razor.cs b/app/MindWork AI Studio/Assistants/DocumentAnalysis/DocumentAnalysisAssistant.razor.cs new file mode 100644 index 00000000..e3bf3bc7 --- /dev/null +++ b/app/MindWork AI Studio/Assistants/DocumentAnalysis/DocumentAnalysisAssistant.razor.cs @@ -0,0 +1,366 @@ +using System.Text; + +using AIStudio.Chat; +using AIStudio.Dialogs; +using AIStudio.Dialogs.Settings; +using AIStudio.Settings.DataModel; + +using Microsoft.AspNetCore.Components; + +using DialogOptions = AIStudio.Dialogs.DialogOptions; + +namespace AIStudio.Assistants.DocumentAnalysis; + +public partial class DocumentAnalysisAssistant : AssistantBaseCore +{ + [Inject] + private IDialogService DialogService { get; init; } = null!; + + public override Tools.Components Component => Tools.Components.DOCUMENT_ANALYSIS_ASSISTANT; + + protected override string Title => T("Document Analysis Assistant"); + + protected override string Description => T("The document analysis assistant helps you to analyze and extract information from documents based on predefined policies. You can create, edit, and manage document analysis policies that define how documents should be processed and what information should be extracted. Some policies might be protected by your organization and cannot be modified or deleted."); + + protected override string SystemPrompt + { + get + { + var sb = new StringBuilder(); + + sb.Append("# Task description"); + sb.AppendLine(); + + if (this.loadedDocumentPaths.Count > 1) + { + sb.Append($"Your task is to analyse {this.loadedDocumentPaths.Count} documents."); + sb.Append("Different Documents are divided by a horizontal rule in markdown formatting followed by the name of the document."); + sb.AppendLine(); + } + else + { + sb.Append("Your task is to analyse a single document."); + sb.AppendLine(); + } + + var taskDescription = """ + The analysis should be done using the policy analysis rules. + The output should be formatted according to the policy output rules. + The rule sets should be followed strictly. + Only use information given in the documents or in the policy. + Never add any information of your own to it. + Keep your answers precise, professional and factual. + Only answer with the correctly formatted analysis result and do not add any opening or closing remarks. + Answer in the language that is used by the policy or is stated in the output rules. + """; + + sb.Append(taskDescription); + sb.AppendLine(); + + sb.Append(this.PromptGetActivePolicy()); + + return sb.ToString(); + } + } + + protected override IReadOnlyList FooterButtons => []; + + protected override bool ShowEntireChatThread => true; + + protected override bool ShowSendTo => true; + + protected override string SubmitText => T("Analyze documents"); + + protected override Func SubmitAction => this.Analyze; + + protected override bool SubmitDisabled => (this.IsNoPolicySelected || this.loadedDocumentPaths.Count==0); + + protected override ChatThread ConvertToChatThread => (this.chatThread ?? new()) with + { + SystemPrompt = SystemPrompts.DEFAULT, + }; + + protected override void ResetForm() + { + if (!this.MightPreselectValues()) + { + this.policyName = string.Empty; + this.policyDescription = string.Empty; + this.policyIsProtected = false; + this.policyAnalysisRules = string.Empty; + this.policyOutputRules = string.Empty; + } + } + + protected override bool MightPreselectValues() + { + if (this.selectedPolicy is not null) + { + this.policyName = this.selectedPolicy.PolicyName; + this.policyDescription = this.selectedPolicy.PolicyDescription; + this.policyIsProtected = this.selectedPolicy.IsProtected; + this.policyAnalysisRules = this.selectedPolicy.AnalysisRules; + this.policyOutputRules = this.selectedPolicy.OutputRules; + + return true; + } + + return false; + } + + protected override async Task OnFormChange() + { + await this.AutoSave(); + } + + #region Overrides of AssistantBase + + protected override async Task OnInitializedAsync() + { + this.selectedPolicy = this.SettingsManager.ConfigurationData.DocumentAnalysis.Policies.FirstOrDefault(); + if(this.selectedPolicy is null) + { + await this.AddPolicy(); + this.selectedPolicy = this.SettingsManager.ConfigurationData.DocumentAnalysis.Policies.First(); + } + + var receivedDeferredContent = MessageBus.INSTANCE.CheckDeferredMessages(Event.SEND_TO_DOCUMENT_ANALYSIS_ASSISTANT).FirstOrDefault(); + if (receivedDeferredContent is not null) + this.deferredContent = receivedDeferredContent; + + await base.OnInitializedAsync(); + } + + #endregion + + private async Task AutoSave(bool force = false) + { + if(this.selectedPolicy is null) + return; + + if(!force && this.selectedPolicy.IsProtected) + return; + + if(!force && this.policyIsProtected) + return; + + this.selectedPolicy.PreselectedProvider = this.providerSettings.Id; + + this.selectedPolicy.PolicyName = this.policyName; + this.selectedPolicy.PolicyDescription = this.policyDescription; + this.selectedPolicy.IsProtected = this.policyIsProtected; + this.selectedPolicy.AnalysisRules = this.policyAnalysisRules; + this.selectedPolicy.OutputRules = this.policyOutputRules; + + await this.SettingsManager.StoreSettings(); + } + + private DataDocumentAnalysisPolicy? selectedPolicy; + private bool policyIsProtected; + private string policyName = string.Empty; + private string policyDescription = string.Empty; + private string policyAnalysisRules = string.Empty; + private string policyOutputRules = string.Empty; +#warning Use deferred content for document analysis + private string deferredContent = string.Empty; + private HashSet loadedDocumentPaths = []; + + private bool IsNoPolicySelectedOrProtected => this.selectedPolicy is null || this.selectedPolicy.IsProtected; + + private bool IsNoPolicySelected => this.selectedPolicy is null; + + private void SelectedPolicyChanged(DataDocumentAnalysisPolicy? policy) + { + this.selectedPolicy = policy; + this.ResetForm(); + } + + private async Task AddPolicy() + { + this.SettingsManager.ConfigurationData.DocumentAnalysis.Policies.Add(new () + { + PolicyName = string.Format(T("Policy {0}"), DateTimeOffset.UtcNow), + }); + + await this.SettingsManager.StoreSettings(); + } + + private async Task RemovePolicy() + { + if(this.selectedPolicy is null) + return; + + if(this.selectedPolicy.IsProtected) + return; + + var dialogParameters = new DialogParameters + { + { x => x.Message, string.Format(T("Are you sure you want to delete the document analysis policy '{0}'?"), this.selectedPolicy.PolicyName) }, + }; + + var dialogReference = await this.DialogService.ShowAsync(T("Delete document analysis policy"), dialogParameters, DialogOptions.FULLSCREEN); + var dialogResult = await dialogReference.Result; + if (dialogResult is null || dialogResult.Canceled) + return; + + this.SettingsManager.ConfigurationData.DocumentAnalysis.Policies.Remove(this.selectedPolicy); + this.selectedPolicy = null; + this.ResetForm(); + + await this.SettingsManager.StoreSettings(); + this.form?.ResetValidation(); + } + + /// + /// Gets called when the policy name was changed by typing. + /// + /// + /// This method is used to update the policy name in the selected policy. + /// Otherwise, the users would be confused when they change the name and the changes are not reflected in the UI. + /// + private void PolicyNameWasChanged() + { + if(this.selectedPolicy is null) + return; + + if(this.selectedPolicy.IsProtected) + return; + + this.selectedPolicy.PolicyName = this.policyName; + } + + private async Task PolicyProtectionWasChanged(bool state) + { + if(this.selectedPolicy is null) + return; + + this.policyIsProtected = state; + this.selectedPolicy.IsProtected = state; + await this.AutoSave(true); + } + + private string? ValidatePolicyName(string name) + { + if(string.IsNullOrWhiteSpace(name)) + return T("Please provide a name for your policy. This name will be used to identify the policy in AI Studio."); + + if(name.Length is > 60 or < 6) + return T("The name of your policy must be between 6 and 60 characters long."); + + if(this.SettingsManager.ConfigurationData.DocumentAnalysis.Policies.Where(n => n != this.selectedPolicy).Any(n => n.PolicyName == name)) + return T("A policy with this name already exists. Please choose a different name."); + + return null; + } + + private string? ValidatePolicyDescription(string description) + { + if(string.IsNullOrWhiteSpace(description)) + return T("Please provide a description for your policy. This description will be used to inform users about the purpose of your document analysis policy."); + + if(description.Length is < 32 or > 512) + return T("The description of your policy must be between 32 and 512 characters long."); + + return null; + } + + private string? ValidateAnalysisRules(string analysisRules) + { + if(string.IsNullOrWhiteSpace(analysisRules)) + return T("Please provide a description of your analysis rules. This rules will be used to instruct the AI on how to analyze the documents."); + + return null; + } + + private string? ValidateOutputRules(string outputRules) + { + if(string.IsNullOrWhiteSpace(outputRules)) + return T("Please provide a description of your output rules. This rules will be used to instruct the AI on how to format the output of the analysis."); + + return null; + } + + private string PromptGetActivePolicy() + { + return $""" + # Policy + The policy is defined as follows: + + ## Policy name + {this.policyName} + + ## Policy description + {this.policyDescription} + + ## Policy analysis rules + {this.policyAnalysisRules} + + ## Policy output rules + {this.policyOutputRules} + """; + } + + private async Task PromptLoadDocumentsContent() + { + if (this.loadedDocumentPaths.Count == 0) + return string.Empty; + + var sb = new StringBuilder(); + var count = 1; + foreach(var documentPath in this.loadedDocumentPaths) + { + sb.Append("---"); + sb.AppendLine(); + sb.Append($"Document {count} file path: {documentPath}"); + sb.AppendLine(); + sb.Append($"Document {count} content:"); + sb.AppendLine(); + + var fileContent = await this.RustService.ReadArbitraryFileData(documentPath, int.MaxValue); + sb.Append($""" + ``` + {fileContent} + ``` + """); + sb.AppendLine(); + sb.AppendLine(); + count += 1; + } + + return sb.ToString(); + } + + private async Task Analyze() + { + // if (this.IsNoPolicySelectedOrProtected) + // return; + + await this.AutoSave(); + await this.form!.Validate(); + if (!this.inputIsValid) + return; + + this.CreateChatThread(); + + var userRequest = this.AddUserRequest( + $""" + {await this.PromptLoadDocumentsContent()} + """, hideContentFromUser:true); + + await this.AddAIResponseAsync(userRequest); + } + + private async Task ExportPolicyAsConfiguration() + { + return; + +# warning Implement the export function + // do not allow the export of a protected policy + if (this.IsNoPolicySelectedOrProtected) + return; + + await this.AutoSave(); + await this.form!.Validate(); + + } +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Assistants/ERI/AssistantERI.razor b/app/MindWork AI Studio/Assistants/ERI/AssistantERI.razor index bada49bf..997b6637 100644 --- a/app/MindWork AI Studio/Assistants/ERI/AssistantERI.razor +++ b/app/MindWork AI Studio/Assistants/ERI/AssistantERI.razor @@ -1,7 +1,8 @@ @attribute [Route(Routes.ASSISTANT_ERI)] +@inherits AssistantBaseCore + @using AIStudio.Settings.DataModel @using MudExtensions -@inherits AssistantBaseCore @T("You can imagine it like this: Hypothetically, when Wikipedia implemented the ERI, it would vectorize all pages using an embedding method. All of Wikipedia’s data would remain with Wikipedia, including the vector database (decentralized approach). Then, any AI Studio user could add Wikipedia as a data source to significantly reduce the hallucination of the LLM in knowledge questions.") diff --git a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua index 9d9e58a5..c2b1cc0b 100644 --- a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua +++ b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua @@ -382,6 +382,120 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::CODING::COMMONCODINGLANGUAGEEXTENSIONS::T -- None UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::CODING::COMMONCODINGLANGUAGEEXTENSIONS::T810547195"] = "None" +-- Please provide a brief description of your policy. Describe or explain what your policy does. This description will be shown to users in AI Studio. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T1182372158"] = "Please provide a brief description of your policy. Describe or explain what your policy does. This description will be shown to users in AI Studio." + +-- No, the policy can be edited +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T1286595725"] = "No, the policy can be edited" + +-- Please provide a description of your analysis rules. This rules will be used to instruct the AI on how to analyze the documents. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T1291179736"] = "Please provide a description of your analysis rules. This rules will be used to instruct the AI on how to analyze the documents." + +-- Not implemented yet. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T1568777658"] = "Not implemented yet." + +-- Input and output rules +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T1714738288"] = "Input and output rules" + +-- Description +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T1725856265"] = "Description" + +-- Yes, protect this policy +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T1762380857"] = "Yes, protect this policy" + +-- Please give your policy a name that provides information about the intended purpose. The name will be displayed to users in AI Studio. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T1786783201"] = "Please give your policy a name that provides information about the intended purpose. The name will be displayed to users in AI Studio." + +-- Please provide a description for your policy. This description will be used to inform users about the purpose of your document analysis policy. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T1837166236"] = "Please provide a description for your policy. This description will be used to inform users about the purpose of your document analysis policy." + +-- Common settings +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T1963959073"] = "Common settings" + +-- The document analysis assistant helps you to analyze and extract information from documents based on predefined policies. You can create, edit, and manage document analysis policies that define how documents should be processed and what information should be extracted. Some policies might be protected by your organization and cannot be modified or deleted. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T206207667"] = "The document analysis assistant helps you to analyze and extract information from documents based on predefined policies. You can create, edit, and manage document analysis policies that define how documents should be processed and what information should be extracted. Some policies might be protected by your organization and cannot be modified or deleted." + +-- Document analysis policies +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2064879144"] = "Document analysis policies" + +-- Load output rules from document +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2168201568"] = "Load output rules from document" + +-- The name of your policy must be between 6 and 60 characters long. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2435013256"] = "The name of your policy must be between 6 and 60 characters long." + +-- Policy definition +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2491168104"] = "Policy definition" + +-- Export policy as configuration section +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2556564432"] = "Export policy as configuration section" + +-- Are you sure you want to delete the document analysis policy '{0}'? +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2582525917"] = "Are you sure you want to delete the document analysis policy '{0}'?" + +-- Document analysis +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2708005534"] = "Document analysis" + +-- Policy name +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2879019438"] = "Policy name" + +-- Analyze documents +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2894951609"] = "Analyze documents" + +-- Documents for the analysis +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3030664641"] = "Documents for the analysis" + +-- Analysis rules +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3108719748"] = "Analysis rules" + +-- Delete this policy +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3119086260"] = "Delete this policy" + +-- Policy {0} +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3157740273"] = "Policy {0}" + +-- The description of your policy must be between 32 and 512 characters long. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3285636934"] = "The description of your policy must be between 32 and 512 characters long." + +-- Add policy +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3357792182"] = "Add policy" + +-- You have not yet added any document analysis policies. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3394850216"] = "You have not yet added any document analysis policies." + +-- Document Analysis Assistant +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T348883878"] = "Document Analysis Assistant" + +-- A policy with this name already exists. Please choose a different name. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3584374593"] = "A policy with this name already exists. Please choose a different name." + +-- Load analysis rules from document +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3813558135"] = "Load analysis rules from document" + +-- Output rules +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3918193587"] = "Output rules" + +-- Preparation for enterprise distribution +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T39557294"] = "Preparation for enterprise distribution" + +-- Please provide a name for your policy. This name will be used to identify the policy in AI Studio. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T4040507702"] = "Please provide a name for your policy. This name will be used to identify the policy in AI Studio." + +-- Delete document analysis policy +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T4293094335"] = "Delete document analysis policy" + +-- Please provide a description of your output rules. This rules will be used to instruct the AI on how to format the output of the analysis. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T652187065"] = "Please provide a description of your output rules. This rules will be used to instruct the AI on how to format the output of the analysis." + +-- Policy description +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T748735777"] = "Policy description" + +-- Would you like to protect this policy so that you cannot accidentally edit or delete it? +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T80597472"] = "Would you like to protect this policy so that you cannot accidentally edit or delete it?" + +-- Here you have the option to save different policies for various document analysis assistants and switch between them. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T848153710"] = "Here you have the option to save different policies for various document analysis assistants and switch between them." + -- Provide a list of bullet points and some basic information for an e-mail. The assistant will generate an e-mail based on that input. UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::EMAIL::ASSISTANTEMAIL::T1143222914"] = "Provide a list of bullet points and some basic information for an e-mail. The assistant will generate an e-mail based on that input." @@ -1369,6 +1483,27 @@ UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T861873672"] = "Export C -- Open Settings UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTBLOCK::T1172211894"] = "Open Settings" +-- Drag and drop files here or click to attach documents. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ATTACHDOCUMENTS::T1647829151"] = "Drag and drop files here or click to attach documents." + +-- Pandoc Load Document Preview +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ATTACHDOCUMENTS::T2686523471"] = "Pandoc Load Document Preview" + +-- Videos are not supported yet +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ATTACHDOCUMENTS::T2928927510"] = "Videos are not supported yet" + +-- Images are not supported yet +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ATTACHDOCUMENTS::T298062956"] = "Images are not supported yet" + +-- Clear file list +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ATTACHDOCUMENTS::T3759696136"] = "Clear file list" + +-- Executables are not allowed +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ATTACHDOCUMENTS::T4167762413"] = "Executables are not allowed" + +-- Select a file to attach +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ATTACHDOCUMENTS::T595772870"] = "Select a file to attach" + -- Changelog UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHANGELOG::T3017574265"] = "Changelog" @@ -1672,6 +1807,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READFILECONTENT::T185447014"] = "Pandoc I -- Pandoc may be required for importing files. UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READFILECONTENT::T2596465560"] = "Pandoc may be required for importing files." +-- Videos are not supported yet +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READFILECONTENT::T2928927510"] = "Videos are not supported yet" + -- Images are not supported yet UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READFILECONTENT::T298062956"] = "Images are not supported yet" @@ -2980,6 +3118,21 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PANDOCDIALOG::T504404155"] = "Accept the ter -- Pandoc is distributed under the GNU General Public License v2 (GPL). By clicking "Accept GPL and archive," you agree to the terms of the GPL license. Software under GPL is free of charge and free to use. UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PANDOCDIALOG::T523908375"] = "Pandoc is distributed under the GNU General Public License v2 (GPL). By clicking \"Accept GPL and archive,\" you agree to the terms of the GPL license. Software under GPL is free of charge and free to use." +-- Test how Pandoc loads your document. See the raw content it produces before further processing. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PANDOCDOCUMENTCHECKDIALOG::T1481857352"] = "Test how Pandoc loads your document. See the raw content it produces before further processing." + +-- Content Loaded by Pandoc +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PANDOCDOCUMENTCHECKDIALOG::T2147198279"] = "Content Loaded by Pandoc" + +-- This is the content Pandoc loaded from your document — including headings, lists, and formatting. Use this to verify your document loads as expected. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PANDOCDOCUMENTCHECKDIALOG::T2156541074"] = "This is the content Pandoc loaded from your document — including headings, lists, and formatting. Use this to verify your document loads as expected." + +-- Load document +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PANDOCDOCUMENTCHECKDIALOG::T2394358670"] = "Load document" + +-- Cancel +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PANDOCDOCUMENTCHECKDIALOG::T900713019"] = "Cancel" + -- Tell the AI what you want it to do for you. What are your goals or are you trying to achieve? Like having the AI address you informally. UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROFILEDIALOG::T1458195391"] = "Tell the AI what you want it to do for you. What are your goals or are you trying to achieve? Like having the AI address you informally." @@ -3625,6 +3778,33 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T774473 -- Local Directory UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T926703547"] = "Local Directory" +-- Assistant: Document Analysis +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDOCUMENTANALYSIS::T1372406750"] = "Assistant: Document Analysis" + +-- Most document analysis options can be customized and saved directly in the assistant. For this, the assistant has an auto-save function. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDOCUMENTANALYSIS::T1870328357"] = "Most document analysis options can be customized and saved directly in the assistant. For this, the assistant has an auto-save function." + +-- Would you like to preselect one of your profiles? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDOCUMENTANALYSIS::T2221665527"] = "Would you like to preselect one of your profiles?" + +-- Preselect document analysis options? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDOCUMENTANALYSIS::T2230062650"] = "Preselect document analysis options?" + +-- When enabled, you can preselect some document analysis options. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDOCUMENTANALYSIS::T2301091111"] = "When enabled, you can preselect some document analysis options." + +-- No document analysis options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDOCUMENTANALYSIS::T3317802895"] = "No document analysis options are preselected" + +-- Close +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDOCUMENTANALYSIS::T3448155331"] = "Close" + +-- Document analysis options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDOCUMENTANALYSIS::T3945756386"] = "Document analysis options are preselected" + +-- Preselect one of your profiles? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDOCUMENTANALYSIS::T4004501229"] = "Preselect one of your profiles?" + -- When enabled, you can preselect some ERI server options. UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGERISERVER::T1280666275"] = "When enabled, you can preselect some ERI server options." @@ -4501,6 +4681,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T986578435"] = "Install Pandoc" -- Get coding and debugging support from an LLM. UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T1243850917"] = "Get coding and debugging support from an LLM." +-- Analyze a document regarding defined rules and extract key information. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T1271524187"] = "Analyze a document regarding defined rules and extract key information." + -- Business UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T131837803"] = "Business" @@ -4546,6 +4729,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T2547582747"] = "Synonyms" -- Find synonyms for a given word or phrase. UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T2712131461"] = "Find synonyms for a given word or phrase." +-- Document Analysis +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T2770149758"] = "Document Analysis" + -- AI Studio Development UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T2830810750"] = "AI Studio Development" @@ -5068,6 +5254,9 @@ UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::PREVIEWFEATURESEXTENSIONS::T1587 -- Read PDF: Preview of our PDF reading system where you can read and extract text from PDF files UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::PREVIEWFEATURESEXTENSIONS::T1847148141"] = "Read PDF: Preview of our PDF reading system where you can read and extract text from PDF files" +-- Document Analysis: Preview of our document analysis system where you can analyze and extract information from documents +UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::PREVIEWFEATURESEXTENSIONS::T1848209619"] = "Document Analysis: Preview of our document analysis system where you can analyze and extract information from documents" + -- Plugins: Preview of our plugin system where you can extend the functionality of the app UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::PREVIEWFEATURESEXTENSIONS::T2056842933"] = "Plugins: Preview of our plugin system where you can extend the functionality of the app" @@ -5188,6 +5377,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::COMPONENTSEXTENSIONS::T2684676843"] = "Text Su -- Synonym Assistant UI_TEXT_CONTENT["AISTUDIO::TOOLS::COMPONENTSEXTENSIONS::T2921123194"] = "Synonym Assistant" +-- Document Analysis Assistant +UI_TEXT_CONTENT["AISTUDIO::TOOLS::COMPONENTSEXTENSIONS::T348883878"] = "Document Analysis Assistant" + -- Translation Assistant UI_TEXT_CONTENT["AISTUDIO::TOOLS::COMPONENTSEXTENSIONS::T3887962308"] = "Translation Assistant" @@ -5638,6 +5830,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::RAG::RAGPROCESSES::AISRCSELWITHRETCTXVAL::T377 -- Executable Files UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T2217313358"] = "Executable Files" +-- All Video Files +UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T2850789856"] = "All Video Files" + -- PDF Files UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T3108466742"] = "PDF Files" diff --git a/app/MindWork AI Studio/Components/AttachDocuments.razor b/app/MindWork AI Studio/Components/AttachDocuments.razor new file mode 100644 index 00000000..aa89ebe3 --- /dev/null +++ b/app/MindWork AI Studio/Components/AttachDocuments.razor @@ -0,0 +1,20 @@ +@inherits MSGComponentBase + +
+ + + + @T("Drag and drop files here or click to attach documents.") + + @foreach (var fileInfo in this.DocumentPaths.Select(file => new FileInfo(file))) + { + + + + } + + + + @T("Clear file list") + +
\ No newline at end of file diff --git a/app/MindWork AI Studio/Components/AttachDocuments.razor.cs b/app/MindWork AI Studio/Components/AttachDocuments.razor.cs new file mode 100644 index 00000000..7b885842 --- /dev/null +++ b/app/MindWork AI Studio/Components/AttachDocuments.razor.cs @@ -0,0 +1,185 @@ +using AIStudio.Dialogs; +using AIStudio.Tools.Rust; +using AIStudio.Tools.Services; + +using Microsoft.AspNetCore.Components; + +namespace AIStudio.Components; + +using DialogOptions = AIStudio.Dialogs.DialogOptions; + +public partial class AttachDocuments : MSGComponentBase +{ + [Parameter] + public string Name { get; set; } = string.Empty; + + [Parameter] + public HashSet DocumentPaths { get; set; } = []; + + [Parameter] + public EventCallback> DocumentPathsChanged { get; set; } + + [Parameter] + public Func, Task> OnChange { get; set; } = _ => Task.CompletedTask; + + /// + /// Catch all documents that are hovered over the AI Studio window and not only over the drop zone. + /// + [Parameter] + public bool CatchAllDocuments { get; set; } + + [Inject] + private ILogger Logger { get; set; } = null!; + + [Inject] + private RustService RustService { get; init; } = null!; + + [Inject] + private IDialogService DialogService { get; init; } = null!; + + #region Overrides of MSGComponentBase + + protected override async Task OnInitializedAsync() + { + this.ApplyFilters([], [ Event.TAURI_EVENT_RECEIVED ]); + await base.OnInitializedAsync(); + } + + protected override async Task ProcessIncomingMessage(ComponentBase? sendingComponent, Event triggeredEvent, T? data) where T : default + { + switch (triggeredEvent) + { + case Event.TAURI_EVENT_RECEIVED when data is TauriEvent { EventType: TauriEventType.FILE_DROP_HOVERED }: + if(!this.isComponentHovered && !this.CatchAllDocuments) + { + this.Logger.LogDebug("Attach documents component '{Name}' is not hovered, ignoring file drop hovered event.", this.Name); + return; + } + + this.SetDragClass(); + this.StateHasChanged(); + break; + + case Event.TAURI_EVENT_RECEIVED when data is TauriEvent { EventType: TauriEventType.FILE_DROP_DROPPED, Payload: var paths }: + if(!this.isComponentHovered && !this.CatchAllDocuments) + { + this.Logger.LogDebug("Attach documents component '{Name}' is not hovered, ignoring file drop dropped event.", this.Name); + return; + } + + foreach (var path in paths) + { + if(!await this.IsFileExtensionValid(path)) + continue; + + this.DocumentPaths.Add(path); + } + + await this.DocumentPathsChanged.InvokeAsync(this.DocumentPaths); + await this.OnChange(this.DocumentPaths); + this.StateHasChanged(); + break; + } + } + + #endregion + + private const string DEFAULT_DRAG_CLASS = "relative rounded-lg border-2 border-dashed pa-4 mt-4 mud-width-full mud-height-full"; + + private string dragClass = DEFAULT_DRAG_CLASS; + + private bool isComponentHovered; + + private async Task AddFilesManually() + { + var selectedFile = await this.RustService.SelectFile(T("Select a file to attach")); + if (selectedFile.UserCancelled) + return; + + if (!File.Exists(selectedFile.SelectedFilePath)) + return; + + if (!await this.IsFileExtensionValid(selectedFile.SelectedFilePath)) + return; + + this.DocumentPaths.Add(selectedFile.SelectedFilePath); + await this.DocumentPathsChanged.InvokeAsync(this.DocumentPaths); + await this.OnChange(this.DocumentPaths); + } + + private async Task IsFileExtensionValid(string selectedFile) + { + var ext = Path.GetExtension(selectedFile).TrimStart('.'); + if (Array.Exists(FileTypeFilter.Executables.FilterExtensions, x => x.Equals(ext, StringComparison.OrdinalIgnoreCase))) + { + await MessageBus.INSTANCE.SendError(new(Icons.Material.Filled.AppBlocking, this.T("Executables are not allowed"))); + return false; + } + + if (Array.Exists(FileTypeFilter.AllImages.FilterExtensions, x => x.Equals(ext, StringComparison.OrdinalIgnoreCase))) + { + await MessageBus.INSTANCE.SendWarning(new(Icons.Material.Filled.ImageNotSupported, this.T("Images are not supported yet"))); + return false; + } + + if (Array.Exists(FileTypeFilter.AllVideos.FilterExtensions, x => x.Equals(ext, StringComparison.OrdinalIgnoreCase))) + { + await MessageBus.INSTANCE.SendWarning(new(Icons.Material.Filled.FeaturedVideo, this.T("Videos are not supported yet"))); + return false; + } + + return true; + } + + private async Task ClearAllFiles() + { + this.DocumentPaths.Clear(); + await this.DocumentPathsChanged.InvokeAsync(this.DocumentPaths); + await this.OnChange(this.DocumentPaths); + } + + private void SetDragClass() => this.dragClass = $"{DEFAULT_DRAG_CLASS} mud-border-primary border-4"; + + private void ClearDragClass() => this.dragClass = DEFAULT_DRAG_CLASS; + + private void OnMouseEnter(EventArgs _) + { + this.Logger.LogDebug("Attach documents component '{Name}' is hovered.", this.Name); + this.isComponentHovered = true; + this.SetDragClass(); + this.StateHasChanged(); + } + + private void OnMouseLeave(EventArgs _) + { + this.Logger.LogDebug("Attach documents component '{Name}' is no longer hovered.", this.Name); + this.isComponentHovered = false; + this.ClearDragClass(); + this.StateHasChanged(); + } + + private async Task RemoveDocumentPathFromDocumentPaths(FileInfo file) + { + this.DocumentPaths.Remove(file.ToString()); + + await this.DocumentPathsChanged.InvokeAsync(this.DocumentPaths); + await this.OnChange(this.DocumentPaths); + } + + /// + /// The user might want to check what the Pandoc integration actually extracts from his file and therefore gives the LLM as input. + /// + /// The file to check. + private async Task InvestigateFile(FileInfo file) + { + # warning Implement Investigation of file + + var dialogParameters = new DialogParameters{}; + + var dialogReference = await this.DialogService.ShowAsync(T("Pandoc Load Document Preview"), dialogParameters, DialogOptions.FULLSCREEN); + var dialogResult = await dialogReference.Result; + if (dialogResult is null || dialogResult.Canceled) + return; + return; + } +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Components/ReadFileContent.razor b/app/MindWork AI Studio/Components/ReadFileContent.razor index f9e33fc8..05a63722 100644 --- a/app/MindWork AI Studio/Components/ReadFileContent.razor +++ b/app/MindWork AI Studio/Components/ReadFileContent.razor @@ -1,4 +1,11 @@ @inherits MSGComponentBase - @T("Use file content as input") + @if (string.IsNullOrWhiteSpace(this.Text)) + { + @T("Use file content as input") + } + else + { + @this.Text + } \ No newline at end of file diff --git a/app/MindWork AI Studio/Components/ReadFileContent.razor.cs b/app/MindWork AI Studio/Components/ReadFileContent.razor.cs index ff3fa1f6..c4019fa2 100644 --- a/app/MindWork AI Studio/Components/ReadFileContent.razor.cs +++ b/app/MindWork AI Studio/Components/ReadFileContent.razor.cs @@ -10,6 +10,9 @@ namespace AIStudio.Components; public partial class ReadFileContent : MSGComponentBase { + [Parameter] + public string Text { get; set; } = string.Empty; + [Parameter] public string FileContent { get; set; } = string.Empty; @@ -47,6 +50,12 @@ public partial class ReadFileContent : MSGComponentBase return; } + if (Array.Exists(FileTypeFilter.AllVideos.FilterExtensions, x => x.Equals(ext, StringComparison.OrdinalIgnoreCase))) + { + await MessageBus.INSTANCE.SendWarning(new(Icons.Material.Filled.FeaturedVideo, this.T("Videos are not supported yet"))); + return; + } + // Ensure that Pandoc is installed and ready: var pandocState = await Pandoc.CheckAvailabilityAsync(this.RustService, showSuccessMessage: false); if (!pandocState.IsAvailable) diff --git a/app/MindWork AI Studio/Dialogs/PandocDocumentCheckDialog.razor b/app/MindWork AI Studio/Dialogs/PandocDocumentCheckDialog.razor new file mode 100644 index 00000000..50e81235 --- /dev/null +++ b/app/MindWork AI Studio/Dialogs/PandocDocumentCheckDialog.razor @@ -0,0 +1,34 @@ +@inherits MSGComponentBase + + + + + + + + @T("Test how Pandoc loads your document. See the raw content it produces before further processing.") + + + + + + + + + @T("Cancel") + + + \ No newline at end of file diff --git a/app/MindWork AI Studio/Dialogs/PandocDocumentCheckDialog.razor.cs b/app/MindWork AI Studio/Dialogs/PandocDocumentCheckDialog.razor.cs new file mode 100644 index 00000000..361342d4 --- /dev/null +++ b/app/MindWork AI Studio/Dialogs/PandocDocumentCheckDialog.razor.cs @@ -0,0 +1,20 @@ +using System.Formats.Asn1; +using AIStudio.Components; + +using Microsoft.AspNetCore.Components; + +namespace AIStudio.Dialogs; + +/// +/// Check how your file will be loaded by Pandoc. +/// +public partial class PandocDocumentCheckDialog : MSGComponentBase +{ + [CascadingParameter] + private IMudDialogInstance MudDialog { get; set; } = null!; + + + private string documentContent = string.Empty; + + private void Cancel() => this.MudDialog.Cancel(); +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogDocumentAnalysis.razor b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogDocumentAnalysis.razor new file mode 100644 index 00000000..2b9744b0 --- /dev/null +++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogDocumentAnalysis.razor @@ -0,0 +1,28 @@ +@using AIStudio.Settings +@inherits SettingsDialogBase + + + + + + + @T("Assistant: Document Analysis") + + + + + + + + + + @T("Most document analysis options can be customized and saved directly in the assistant. For this, the assistant has an auto-save function.") + + + + + + @T("Close") + + + \ No newline at end of file diff --git a/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogDocumentAnalysis.razor.cs b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogDocumentAnalysis.razor.cs new file mode 100644 index 00000000..caafcb44 --- /dev/null +++ b/app/MindWork AI Studio/Dialogs/Settings/SettingsDialogDocumentAnalysis.razor.cs @@ -0,0 +1,3 @@ +namespace AIStudio.Dialogs.Settings; + +public partial class SettingsDialogDocumentAnalysis : SettingsDialogBase; \ No newline at end of file diff --git a/app/MindWork AI Studio/MindWork AI Studio.csproj b/app/MindWork AI Studio/MindWork AI Studio.csproj index 5d39cb48..b559389c 100644 --- a/app/MindWork AI Studio/MindWork AI Studio.csproj +++ b/app/MindWork AI Studio/MindWork AI Studio.csproj @@ -61,6 +61,12 @@ + + + true + + + diff --git a/app/MindWork AI Studio/Pages/Assistants.razor b/app/MindWork AI Studio/Pages/Assistants.razor index d7fe0bed..1cd72a5f 100644 --- a/app/MindWork AI Studio/Pages/Assistants.razor +++ b/app/MindWork AI Studio/Pages/Assistants.razor @@ -26,6 +26,12 @@ + + @if (PreviewFeatures.PRE_DOCUMENT_ANALYSIS_2025.IsEnabled(this.SettingsManager)) + { + + } + diff --git a/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua b/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua index 0c8cef6a..15b818e4 100644 --- a/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua +++ b/app/MindWork AI Studio/Plugins/languages/de-de-43065dbc-78d0-45b7-92be-f14c2926e2dc/plugin.lua @@ -384,6 +384,120 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::CODING::COMMONCODINGLANGUAGEEXTENSIONS::T -- None UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::CODING::COMMONCODINGLANGUAGEEXTENSIONS::T810547195"] = "Keine" +-- Please provide a brief description of your policy. Describe or explain what your policy does. This description will be shown to users in AI Studio. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T1182372158"] = "Bitte geben Sie eine kurze Beschreibung Ihres Regelwerks an. Beschreiben oder erklären Sie, was das Regelwerk bewirkt. Diese Beschreibung wird den Benutzern in AI Studio angezeigt." + +-- No, the policy can be edited +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T1286595725"] = "Nein, das Regelwerk kann bearbeitet werden." + +-- Please provide a description of your analysis rules. This rules will be used to instruct the AI on how to analyze the documents. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T1291179736"] = "Bitte geben Sie eine Beschreibung Ihrer Analysekriterien an. Diese Regeln werden verwendet, um die KI anzuweisen, wie die Dokumente analysiert werden sollen." + +-- Not implemented yet. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T1568777658"] = "Noch nicht implementiert." + +-- Analysis and output rules +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T1714738288"] = "Analyse- und Ausgaberegeln" + +-- Description +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T1725856265"] = "Beschreibung" + +-- Yes, protect this policy +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T1762380857"] = "Ja, dieses Regelwerk schützen" + +-- Please give your policy a name that provides information about the intended purpose. The name will be displayed to users in AI Studio. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T1786783201"] = "Bitte geben Sie Ihrem Regelwerk einen Namen, der den beabsichtigten Zweck beschreibt. Der Name wird den Nutzern in AI Studio angezeigt." + +-- Please provide a description for your policy. This description will be used to inform users about the purpose of your document analysis policy. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T1837166236"] = "Bitte geben Sie eine Beschreibung für Ihr Regelwerk an. Diese Beschreibung wird verwendet, um Benutzer über den Zweck Ihres Regelwerks zur Dokumentenanalyse zu informieren." + +-- Common settings +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T1963959073"] = "Allgemeine Einstellungen" + +-- The document analysis assistant helps you to analyze and extract information from documents based on predefined policies. You can create, edit, and manage document analysis policies that define how documents should be processed and what information should be extracted. Some policies might be protected by your organization and cannot be modified or deleted. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T206207667"] = "Der Assistent für Dokumentenanalysen hilft Ihnen dabei, Informationen aus Dokumenten basierend auf vordefinierten Regelwerken zu analysieren und zu extrahieren. Sie können Regelwerke für die Dokumentenanalyse erstellen, bearbeiten und verwalten, die festlegen, wie Dokumente verarbeitet werden und welche Informationen extrahiert werden sollen. Einige Regelwerke könnten durch Ihre Organisation geschützt sein und können nicht geändert oder gelöscht werden." + +-- Document analysis policies +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2064879144"] = "Regelwerke zur Dokumentenanalyse" + +-- Load output rules from document +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2168201568"] = "Regeln für die Ausgabe aus einem Dokument laden" + +-- The name of your policy must be between 6 and 60 characters long. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2435013256"] = "Der Name Ihres Regelwerks muss zwischen 6 und 60 Zeichen lang sein." + +-- Policy definition +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2491168104"] = "Definition des Regelwerks" + +-- Export policy as configuration section +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2556564432"] = "Exportieren Sie das Regelwerk als Konfigurationsabschnitt" + +-- Are you sure you want to delete the document analysis policy '{0}'? +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2582525917"] = "Möchten Sie das Regelwerk '{0}' wirklich löschen?" + +-- Document analysis +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2708005534"] = "Dokumentenanalyse" + +-- Policy name +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2879019438"] = "Name des Regelwerks" + +-- Analyze documents +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2894951609"] = "Dokumente analysieren" + +-- Documents for the analysis +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3030664641"] = "Dokumente für die Analyse" + +-- Analysis rules +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3108719748"] = "Regeln zur Analyse" + +-- Delete this policy +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3119086260"] = "Dieses Regelwerk löschen" + +-- Policy {0} +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3157740273"] = "Regelwerk {0}" + +-- The description of your policy must be between 32 and 512 characters long. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3285636934"] = "Die Beschreibung des Regelwerks muss zwischen 32 und 512 Zeichen lang sein." + +-- Add policy +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3357792182"] = "Regelwerk hinzufügen" + +-- You have not yet added any document analysis policies. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3394850216"] = "Sie haben keine Regelwerke zur Dokumentenanalyse hinzugefügt." + +-- Document Analysis Assistant +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T348883878"] = "Assistent für die Analyse von Dokumenten" + +-- A policy with this name already exists. Please choose a different name. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3584374593"] = "Ein Regelwerk mit diesem Namen existiert bereits. Bitte wählen Sie einen anderen Namen." + +-- Load analysis rules from document +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3813558135"] = "Regeln für die Analyse aus einem Dokument laden" + +-- Output rules +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3918193587"] = "Regeln für die Ausgabe" + +-- Preparation for enterprise distribution +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T39557294"] = "Vorbereitung für den Unternehmenseinsatz" + +-- Please provide a name for your policy. This name will be used to identify the policy in AI Studio. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T4040507702"] = "Bitte geben Sie einen Namen für Ihr Regelwerk an. Dieser Name wird verwendet, um das Regelwerk in AI Studio zu identifizieren." + +-- Delete document analysis policy +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T4293094335"] = "Regelwerk löschen" + +-- Please provide a description of your output rules. This rules will be used to instruct the AI on how to format the output of the analysis. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T652187065"] = "Bitte geben Sie eine Beschreibung der Regeln für die Ausgabe an. Diese Regeln werden verwendet, um die KI anzuweisen, wie die Ausgabe der Analyse formatiert werden soll." + +-- Policy description +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T748735777"] = "Regelwerkbeschreibung" + +-- Would you like to protect this policy so that you cannot accidentally edit or delete it? +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T80597472"] = "Möchten Sie dieses Regelwerk schützen, damit Sie es nicht versehentlich bearbeiten oder löschen können?" + +-- Here you have the option to save different policies for various document analysis assistants and switch between them. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T848153710"] = "Hier haben Sie die Möglichkeit, verschiedene Regelwerke für unterschiedliche Dokumentenanalysen zu speichern und zwischen ihnen zu wechseln." + -- Provide a list of bullet points and some basic information for an e-mail. The assistant will generate an e-mail based on that input. UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::EMAIL::ASSISTANTEMAIL::T1143222914"] = "Geben Sie eine Liste von Stichpunkten sowie einige Basisinformationen für eine E-Mail ein. Der Assistent erstellt anschließend eine E-Mail auf Grundlage ihrer Angaben." @@ -1371,6 +1485,27 @@ UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T861873672"] = "Chat in -- Open Settings UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTBLOCK::T1172211894"] = "Einstellungen öffnen" +-- Drag and drop files here or click to attach documents. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ATTACHDOCUMENTS::T1647829151"] = "Dateien hierher ziehen und ablegen oder klicken, um Dokumente anzuhängen." + +-- Pandoc Load Document Preview +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ATTACHDOCUMENTS::T2686523471"] = "Pandoc-Dokumentvorschau" + +-- Videos are not supported yet +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ATTACHDOCUMENTS::T2928927510"] = "Videos werden noch nicht unterstützt." + +-- Images are not supported yet +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ATTACHDOCUMENTS::T298062956"] = "Bilder werden noch nicht unterstützt." + +-- Clear file list +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ATTACHDOCUMENTS::T3759696136"] = "Dateiliste löschen" + +-- Executables are not allowed +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ATTACHDOCUMENTS::T4167762413"] = "Ausführbare Dateien sind nicht erlaubt" + +-- Select a file to attach +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ATTACHDOCUMENTS::T595772870"] = "Datei zum Anhängen auswählen" + -- Changelog UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHANGELOG::T3017574265"] = "Änderungsprotokoll" @@ -1576,7 +1711,7 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEPANDOCDEPENDENCY::T527187983"] = " UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MANAGEPANDOCDEPENDENCY::T986578435"] = "Pandoc installieren" -- Given that my employer's workplace uses both Windows and Linux, I wanted a cross-platform solution that would work seamlessly across all major operating systems, including macOS. Additionally, I wanted to demonstrate that it is possible to create modern, efficient, cross-platform applications without resorting to Electron bloatware. The combination of .NET and Rust with Tauri proved to be an excellent technology stack for building such robust applications. -UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MOTIVATION::T1057189794"] = "Da mein Arbeitgeber sowohl Windows als auch Linux am Arbeitsplatz nutzt, wollte ich eine plattformübergreifende Lösung, die nahtlos auf allen wichtigen Betriebssystemen, einschließlich macOS, funktioniert. Außerdem wollte ich zeigen, dass es möglich ist, moderne, effiziente und plattformübergreifende Anwendungen zu erstellen, ohne auf Software-Balast, wie z.B. das Electron-Framework, zurückzugreifen. Die Kombination aus .NET und Rust mit Tauri hat sich dabei als hervorragender Technologiestapel für den Bau solch robuster Anwendungen erwiesen." +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MOTIVATION::T1057189794"] = "Da mein Arbeitgeber sowohl Windows als auch Linux am Arbeitsplatz nutzt, wollte ich eine plattformübergreifende Lösung, die nahtlos auf allen wichtigen Betriebssystemen, einschließlich macOS, funktioniert. Außerdem wollte ich zeigen, dass es möglich ist, moderne, effiziente und plattformübergreifende Anwendungen zu erstellen, ohne auf Software-Ballast, wie z.B. das Electron-Framework, zurückzugreifen. Die Kombination aus .NET und Rust mit Tauri hat sich dabei als hervorragender Technologiestapel für den Bau solch robuster Anwendungen erwiesen." -- Limitations of Existing Solutions UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::MOTIVATION::T1086130692"] = "Einschränkungen bestehender Lösungen" @@ -1674,6 +1809,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READFILECONTENT::T185447014"] = "Pandoc-I -- Pandoc may be required for importing files. UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READFILECONTENT::T2596465560"] = "Pandoc wird möglicherweise zum Importieren von Dateien benötigt." +-- Videos are not supported yet +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READFILECONTENT::T2928927510"] = "Videos werden noch nicht unterstützt." + -- Images are not supported yet UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READFILECONTENT::T298062956"] = "Bilder werden derzeit nicht unterstützt" @@ -2982,6 +3120,21 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PANDOCDIALOG::T504404155"] = "Akzeptieren Si -- Pandoc is distributed under the GNU General Public License v2 (GPL). By clicking "Accept the GPL and download the archive," you agree to the terms of the GPL license. Software under GPL is free of charge and free to use. UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PANDOCDIALOG::T523908375"] = "Pandoc wird unter der GNU General Public License v2 (GPL) vertrieben. Wenn Sie auf „GPL akzeptieren und Archiv herunterladen“ klicken, stimmen Sie den Bedingungen der GPL-Lizenz zu. Software unter der GPL ist kostenlos und frei nutzbar." +-- Test how Pandoc loads your document. See the raw content it produces before further processing. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PANDOCDOCUMENTCHECKDIALOG::T1481857352"] = "Testen Sie, wie Pandoc Ihr Dokument lädt, und überprüfen Sie ob der Inhalt korrekt geladen wird." + +-- Content Loaded by Pandoc +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PANDOCDOCUMENTCHECKDIALOG::T2147198279"] = "Von Pandoc geladener Inhalt" + +-- This is the content Pandoc loaded from your document — including headings, lists, and formatting. Use this to verify your document loads as expected. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PANDOCDOCUMENTCHECKDIALOG::T2156541074"] = "Das ist der Inhalt, den Pandoc aus Ihrem Dokument geladen hat – einschließlich Überschriften, Listen und Formatierung. Überprüfen Sie damit, ob Ihr Dokument wie erwartet geladen wird." + +-- Load document +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PANDOCDOCUMENTCHECKDIALOG::T2394358670"] = "Dokument laden" + +-- Cancel +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PANDOCDOCUMENTCHECKDIALOG::T900713019"] = "Abbrechen" + -- Tell the AI what you want it to do for you. What are your goals or are you trying to achieve? Like having the AI address you informally. UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROFILEDIALOG::T1458195391"] = "Teilen Sie der KI mit, was sie machen soll. Was sind ihre Ziele oder was möchten Sie erreichen? Zum Beispiel, dass die KI Sie duzt." @@ -3627,6 +3780,33 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T774473 -- Local Directory UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T926703547"] = "Lokaler Ordner" +-- Assistant: Document Analysis +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDOCUMENTANALYSIS::T1372406750"] = "Assistent: Dokumentenanalyse" + +-- Most document analysis options can be customized and saved directly in the assistant. For this, the assistant has an auto-save function. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDOCUMENTANALYSIS::T1870328357"] = "Die meisten Optionen für die Analyse von Dokumenten können im Assistenten angepasst und direkt gespeichert werden. Dafür verfügt der Assistent über eine automatische Speicherfunktion." + +-- Would you like to preselect one of your profiles? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDOCUMENTANALYSIS::T2221665527"] = "Möchten Sie eines Ihrer Profile vorab auswählen?" + +-- Preselect document analysis options? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDOCUMENTANALYSIS::T2230062650"] = "Dokumentenanalyse-Optionen vorab auswählen?" + +-- When enabled, you can preselect some document analysis options. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDOCUMENTANALYSIS::T2301091111"] = "Wenn aktiviert, können Sie einige Dokumentanalyse-Optionen vorab auswählen." + +-- No document analysis options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDOCUMENTANALYSIS::T3317802895"] = "Keine Dokumentenanalyse-Optionen sind vorausgewählt." + +-- Close +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDOCUMENTANALYSIS::T3448155331"] = "Schließen" + +-- Document analysis options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDOCUMENTANALYSIS::T3945756386"] = "Dokumentenanalyse-Optionen sind vorausgewählt." + +-- Preselect one of your profiles? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDOCUMENTANALYSIS::T4004501229"] = "Eines Ihrer Profile vorauswählen?" + -- When enabled, you can preselect some ERI server options. UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGERISERVER::T1280666275"] = "Wenn aktiviert, können Sie einige ERI-Serveroptionen vorauswählen." @@ -4503,6 +4683,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T986578435"] = "Pandoc installieren" -- Get coding and debugging support from an LLM. UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T1243850917"] = "Erhalten Sie Unterstützung beim Programmieren und Debuggen durch ein KI-Modell." +-- Analyze a document regarding defined rules and extract key information. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T1271524187"] = "Analysiere ein Dokument hinsichtlich festgelegter Regeln und extrahiere wichtige Informationen." + -- Business UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T131837803"] = "Business" @@ -4548,6 +4731,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T2547582747"] = "Synonyme" -- Find synonyms for a given word or phrase. UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T2712131461"] = "Finde Synonyme für ein angegebenes Wort oder eine Phrase." +-- Document Analysis +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T2770149758"] = "Dokumentenanalyse" + -- AI Studio Development UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T2830810750"] = "AI Studio Entwicklung" @@ -4702,7 +4888,7 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T617579208"] = "Kostenlos" UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T649448159"] = "Unabhängigkeit" -- No bloatware -UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T858047957"] = "Keinen unnötigen Software-Balast" +UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T858047957"] = "Keinen unnötigen Software-Ballast" -- Here's what makes MindWork AI Studio stand out: UI_TEXT_CONTENT["AISTUDIO::PAGES::HOME::T873851215"] = "Das zeichnet MindWork AI Studio aus:" @@ -5070,6 +5256,9 @@ UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::PREVIEWFEATURESEXTENSIONS::T1587 -- Read PDF: Preview of our PDF reading system where you can read and extract text from PDF files UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::PREVIEWFEATURESEXTENSIONS::T1847148141"] = "PDF-Dateien einlesen: Vorschau unseres PDF-Lesesystems, mit dem Sie Text aus PDF-Dateien einlesen können" +-- Document Analysis: Preview of our document analysis system where you can analyze and extract information from documents +UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::PREVIEWFEATURESEXTENSIONS::T1848209619"] = "Dokumentenanalyse: Vorschau auf unsere Dokumentenanalyse, mit dem Sie Informationen aus Dokumenten analysieren und extrahieren können." + -- Plugins: Preview of our plugin system where you can extend the functionality of the app UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::PREVIEWFEATURESEXTENSIONS::T2056842933"] = "Plugins: Vorschau auf unser Pluginsystems, mit dem Sie die Funktionalität der App erweitern können" @@ -5190,6 +5379,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::COMPONENTSEXTENSIONS::T2684676843"] = "Texte z -- Synonym Assistant UI_TEXT_CONTENT["AISTUDIO::TOOLS::COMPONENTSEXTENSIONS::T2921123194"] = "Synonym-Assistent" +-- Document Analysis Assistant +UI_TEXT_CONTENT["AISTUDIO::TOOLS::COMPONENTSEXTENSIONS::T348883878"] = "Dokumentenanalyse-Assistent" + -- Translation Assistant UI_TEXT_CONTENT["AISTUDIO::TOOLS::COMPONENTSEXTENSIONS::T3887962308"] = "Übersetzungs-Assistent" @@ -5640,6 +5832,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::RAG::RAGPROCESSES::AISRCSELWITHRETCTXVAL::T377 -- Executable Files UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T2217313358"] = "Ausführbare Dateien" +-- All Video Files +UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T2850789856"] = "Alle Videodateien" + -- PDF Files UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T3108466742"] = "PDF-Dateien" diff --git a/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua b/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua index e8ff12fe..f962cf75 100644 --- a/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua +++ b/app/MindWork AI Studio/Plugins/languages/en-us-97dfb1ba-50c4-4440-8dfa-6575daf543c8/plugin.lua @@ -384,6 +384,120 @@ UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::CODING::COMMONCODINGLANGUAGEEXTENSIONS::T -- None UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::CODING::COMMONCODINGLANGUAGEEXTENSIONS::T810547195"] = "None" +-- Please provide a brief description of your policy. Describe or explain what your policy does. This description will be shown to users in AI Studio. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T1182372158"] = "Please provide a brief description of your policy. Describe or explain what your policy does. This description will be shown to users in AI Studio." + +-- No, the policy can be edited +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T1286595725"] = "No, the policy can be edited" + +-- Please provide a description of your analysis rules. This rules will be used to instruct the AI on how to analyze the documents. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T1291179736"] = "Please provide a description of your analysis rules. This rules will be used to instruct the AI on how to analyze the documents." + +-- Not implemented yet. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T1568777658"] = "Not implemented yet." + +-- Analysis and output rules +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T1714738288"] = "Analysis and output rules" + +-- Description +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T1725856265"] = "Description" + +-- Yes, protect this policy +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T1762380857"] = "Yes, protect this policy" + +-- Please give your policy a name that provides information about the intended purpose. The name will be displayed to users in AI Studio. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T1786783201"] = "Please give your policy a name that provides information about the intended purpose. The name will be displayed to users in AI Studio." + +-- Please provide a description for your policy. This description will be used to inform users about the purpose of your document analysis policy. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T1837166236"] = "Please provide a description for your policy. This description will be used to inform users about the purpose of your document analysis policy." + +-- Common settings +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T1963959073"] = "Common settings" + +-- The document analysis assistant helps you to analyze and extract information from documents based on predefined policies. You can create, edit, and manage document analysis policies that define how documents should be processed and what information should be extracted. Some policies might be protected by your organization and cannot be modified or deleted. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T206207667"] = "The document analysis assistant helps you to analyze and extract information from documents based on predefined policies. You can create, edit, and manage document analysis policies that define how documents should be processed and what information should be extracted. Some policies might be protected by your organization and cannot be modified or deleted." + +-- Document analysis policies +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2064879144"] = "Document analysis policies" + +-- Load output rules from document +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2168201568"] = "Load output rules from document" + +-- The name of your policy must be between 6 and 60 characters long. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2435013256"] = "The name of your policy must be between 6 and 60 characters long." + +-- Policy definition +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2491168104"] = "Policy definition" + +-- Export policy as configuration section +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2556564432"] = "Export policy as configuration section" + +-- Are you sure you want to delete the document analysis policy '{0}'? +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2582525917"] = "Are you sure you want to delete the document analysis policy '{0}'?" + +-- Document analysis +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2708005534"] = "Document analysis" + +-- Policy name +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2879019438"] = "Policy name" + +-- Analyze documents +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T2894951609"] = "Analyze documents" + +-- Documents for the analysis +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3030664641"] = "Documents for the analysis" + +-- Analysis rules +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3108719748"] = "Analysis rules" + +-- Delete this policy +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3119086260"] = "Delete this policy" + +-- Policy {0} +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3157740273"] = "Policy {0}" + +-- The description of your policy must be between 32 and 512 characters long. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3285636934"] = "The description of your policy must be between 32 and 512 characters long." + +-- Add policy +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3357792182"] = "Add policy" + +-- You have not yet added any document analysis policies. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3394850216"] = "You have not yet added any document analysis policies." + +-- Document Analysis Assistant +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T348883878"] = "Document Analysis Assistant" + +-- A policy with this name already exists. Please choose a different name. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3584374593"] = "A policy with this name already exists. Please choose a different name." + +-- Load analysis rules from document +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3813558135"] = "Load analysis rules from document" + +-- Output rules +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T3918193587"] = "Output rules" + +-- Preparation for enterprise distribution +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T39557294"] = "Preparation for enterprise distribution" + +-- Please provide a name for your policy. This name will be used to identify the policy in AI Studio. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T4040507702"] = "Please provide a name for your policy. This name will be used to identify the policy in AI Studio." + +-- Delete document analysis policy +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T4293094335"] = "Delete document analysis policy" + +-- Please provide a description of your output rules. This rules will be used to instruct the AI on how to format the output of the analysis. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T652187065"] = "Please provide a description of your output rules. This rules will be used to instruct the AI on how to format the output of the analysis." + +-- Policy description +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T748735777"] = "Policy description" + +-- Would you like to protect this policy so that you cannot accidentally edit or delete it? +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T80597472"] = "Would you like to protect this policy so that you cannot accidentally edit or delete it?" + +-- Here you have the option to save different policies for various document analysis assistants and switch between them. +UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::DOCUMENTANALYSIS::DOCUMENTANALYSISASSISTANT::T848153710"] = "Here you have the option to save different policies for various document analysis assistants and switch between them." + -- Provide a list of bullet points and some basic information for an e-mail. The assistant will generate an e-mail based on that input. UI_TEXT_CONTENT["AISTUDIO::ASSISTANTS::EMAIL::ASSISTANTEMAIL::T1143222914"] = "Provide a list of bullet points and some basic information for an e-mail. The assistant will generate an e-mail based on that input." @@ -1371,6 +1485,27 @@ UI_TEXT_CONTENT["AISTUDIO::CHAT::CONTENTBLOCKCOMPONENT::T861873672"] = "Export C -- Open Settings UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ASSISTANTBLOCK::T1172211894"] = "Open Settings" +-- Drag and drop files here or click to attach documents. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ATTACHDOCUMENTS::T1647829151"] = "Drag and drop files here or click to attach documents." + +-- Pandoc Load Document Preview +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ATTACHDOCUMENTS::T2686523471"] = "Pandoc Load Document Preview" + +-- Videos are not supported yet +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ATTACHDOCUMENTS::T2928927510"] = "Videos are not supported yet" + +-- Images are not supported yet +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ATTACHDOCUMENTS::T298062956"] = "Images are not supported yet" + +-- Clear file list +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ATTACHDOCUMENTS::T3759696136"] = "Clear file list" + +-- Executables are not allowed +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ATTACHDOCUMENTS::T4167762413"] = "Executables are not allowed" + +-- Select a file to attach +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::ATTACHDOCUMENTS::T595772870"] = "Select a file to attach" + -- Changelog UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::CHANGELOG::T3017574265"] = "Changelog" @@ -1674,6 +1809,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READFILECONTENT::T185447014"] = "Pandoc I -- Pandoc may be required for importing files. UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READFILECONTENT::T2596465560"] = "Pandoc may be required for importing files." +-- Videos are not supported yet +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READFILECONTENT::T2928927510"] = "Videos are not supported yet" + -- Images are not supported yet UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::READFILECONTENT::T298062956"] = "Images are not supported yet" @@ -2982,6 +3120,21 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PANDOCDIALOG::T504404155"] = "Accept the ter -- Pandoc is distributed under the GNU General Public License v2 (GPL). By clicking "Accept the GPL and download the archive," you agree to the terms of the GPL license. Software under GPL is free of charge and free to use. UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PANDOCDIALOG::T523908375"] = "Pandoc is distributed under the GNU General Public License v2 (GPL). By clicking \"Accept the GPL and download the archive,\" you agree to the terms of the GPL license. Software under GPL is free of charge and free to use." +-- Test how Pandoc loads your document. See the raw content it produces before further processing. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PANDOCDOCUMENTCHECKDIALOG::T1481857352"] = "Test how Pandoc loads your document. See the raw content it produces before further processing." + +-- Content Loaded by Pandoc +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PANDOCDOCUMENTCHECKDIALOG::T2147198279"] = "Content Loaded by Pandoc" + +-- This is the content Pandoc loaded from your document — including headings, lists, and formatting. Use this to verify your document loads as expected. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PANDOCDOCUMENTCHECKDIALOG::T2156541074"] = "This is the content Pandoc loaded from your document — including headings, lists, and formatting. Use this to verify your document loads as expected." + +-- Load document +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PANDOCDOCUMENTCHECKDIALOG::T2394358670"] = "Load document" + +-- Cancel +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PANDOCDOCUMENTCHECKDIALOG::T900713019"] = "Cancel" + -- Tell the AI what you want it to do for you. What are your goals or are you trying to achieve? Like having the AI address you informally. UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PROFILEDIALOG::T1458195391"] = "Tell the AI what you want it to do for you. What are your goals or are you trying to achieve? Like having the AI address you informally." @@ -3627,6 +3780,33 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T774473 -- Local Directory UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDATASOURCES::T926703547"] = "Local Directory" +-- Assistant: Document Analysis +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDOCUMENTANALYSIS::T1372406750"] = "Assistant: Document Analysis" + +-- Most document analysis options can be customized and saved directly in the assistant. For this, the assistant has an auto-save function. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDOCUMENTANALYSIS::T1870328357"] = "Most document analysis options can be customized and saved directly in the assistant. For this, the assistant has an auto-save function." + +-- Would you like to preselect one of your profiles? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDOCUMENTANALYSIS::T2221665527"] = "Would you like to preselect one of your profiles?" + +-- Preselect document analysis options? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDOCUMENTANALYSIS::T2230062650"] = "Preselect document analysis options?" + +-- When enabled, you can preselect some document analysis options. +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDOCUMENTANALYSIS::T2301091111"] = "When enabled, you can preselect some document analysis options." + +-- No document analysis options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDOCUMENTANALYSIS::T3317802895"] = "No document analysis options are preselected" + +-- Close +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDOCUMENTANALYSIS::T3448155331"] = "Close" + +-- Document analysis options are preselected +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDOCUMENTANALYSIS::T3945756386"] = "Document analysis options are preselected" + +-- Preselect one of your profiles? +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGDOCUMENTANALYSIS::T4004501229"] = "Preselect one of your profiles?" + -- When enabled, you can preselect some ERI server options. UI_TEXT_CONTENT["AISTUDIO::DIALOGS::SETTINGS::SETTINGSDIALOGERISERVER::T1280666275"] = "When enabled, you can preselect some ERI server options." @@ -4503,6 +4683,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::ABOUT::T986578435"] = "Install Pandoc" -- Get coding and debugging support from an LLM. UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T1243850917"] = "Get coding and debugging support from an LLM." +-- Analyze a document regarding defined rules and extract key information. +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T1271524187"] = "Analyze a document regarding defined rules and extract key information." + -- Business UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T131837803"] = "Business" @@ -4548,6 +4731,9 @@ UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T2547582747"] = "Synonyms" -- Find synonyms for a given word or phrase. UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T2712131461"] = "Find synonyms for a given word or phrase." +-- Document Analysis +UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T2770149758"] = "Document Analysis" + -- AI Studio Development UI_TEXT_CONTENT["AISTUDIO::PAGES::ASSISTANTS::T2830810750"] = "AI Studio Development" @@ -5070,6 +5256,9 @@ UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::PREVIEWFEATURESEXTENSIONS::T1587 -- Read PDF: Preview of our PDF reading system where you can read and extract text from PDF files UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::PREVIEWFEATURESEXTENSIONS::T1847148141"] = "Read PDF: Preview of our PDF reading system where you can read and extract text from PDF files" +-- Document Analysis: Preview of our document analysis system where you can analyze and extract information from documents +UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::PREVIEWFEATURESEXTENSIONS::T1848209619"] = "Document Analysis: Preview of our document analysis system where you can analyze and extract information from documents" + -- Plugins: Preview of our plugin system where you can extend the functionality of the app UI_TEXT_CONTENT["AISTUDIO::SETTINGS::DATAMODEL::PREVIEWFEATURESEXTENSIONS::T2056842933"] = "Plugins: Preview of our plugin system where you can extend the functionality of the app" @@ -5190,6 +5379,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::COMPONENTSEXTENSIONS::T2684676843"] = "Text Su -- Synonym Assistant UI_TEXT_CONTENT["AISTUDIO::TOOLS::COMPONENTSEXTENSIONS::T2921123194"] = "Synonym Assistant" +-- Document Analysis Assistant +UI_TEXT_CONTENT["AISTUDIO::TOOLS::COMPONENTSEXTENSIONS::T348883878"] = "Document Analysis Assistant" + -- Translation Assistant UI_TEXT_CONTENT["AISTUDIO::TOOLS::COMPONENTSEXTENSIONS::T3887962308"] = "Translation Assistant" @@ -5640,6 +5832,9 @@ UI_TEXT_CONTENT["AISTUDIO::TOOLS::RAG::RAGPROCESSES::AISRCSELWITHRETCTXVAL::T377 -- Executable Files UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T2217313358"] = "Executable Files" +-- All Video Files +UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T2850789856"] = "All Video Files" + -- PDF Files UI_TEXT_CONTENT["AISTUDIO::TOOLS::RUST::FILETYPEFILTER::T3108466742"] = "PDF Files" diff --git a/app/MindWork AI Studio/Program.cs b/app/MindWork AI Studio/Program.cs index a4d9c2b4..2514a67f 100644 --- a/app/MindWork AI Studio/Program.cs +++ b/app/MindWork AI Studio/Program.cs @@ -133,6 +133,11 @@ internal sealed class Program builder.Services.AddHostedService(); builder.Services.AddHostedService(); builder.Services.AddHostedService(); + + // ReSharper disable AccessToDisposedClosure + builder.Services.AddHostedService(_ => rust); + // ReSharper restore AccessToDisposedClosure + builder.Services.AddRazorComponents() .AddInteractiveServerComponents() .AddHubOptions(options => diff --git a/app/MindWork AI Studio/Routes.razor.cs b/app/MindWork AI Studio/Routes.razor.cs index d59bffac..836cab0e 100644 --- a/app/MindWork AI Studio/Routes.razor.cs +++ b/app/MindWork AI Studio/Routes.razor.cs @@ -27,5 +27,6 @@ public sealed partial class Routes public const string ASSISTANT_BIAS = "/assistant/bias-of-the-day"; public const string ASSISTANT_ERI = "/assistant/eri"; public const string ASSISTANT_AI_STUDIO_I18N = "/assistant/ai-studio/i18n"; + public const string ASSISTANT_DOCUMENT_ANALYSIS = "/assistant/document-analysis"; // ReSharper restore InconsistentNaming } \ No newline at end of file diff --git a/app/MindWork AI Studio/Settings/DataModel/Data.cs b/app/MindWork AI Studio/Settings/DataModel/Data.cs index 0e825aa0..c07ab3d6 100644 --- a/app/MindWork AI Studio/Settings/DataModel/Data.cs +++ b/app/MindWork AI Studio/Settings/DataModel/Data.cs @@ -84,6 +84,8 @@ public sealed class Data public DataCoding Coding { get; init; } = new(); public DataERI ERI { get; init; } = new(); + + public DataDocumentAnalysis DocumentAnalysis { get; init; } = new(); public DataTextSummarizer TextSummarizer { get; init; } = new(); diff --git a/app/MindWork AI Studio/Settings/DataModel/DataDocumentAnalysis.cs b/app/MindWork AI Studio/Settings/DataModel/DataDocumentAnalysis.cs new file mode 100644 index 00000000..c6961270 --- /dev/null +++ b/app/MindWork AI Studio/Settings/DataModel/DataDocumentAnalysis.cs @@ -0,0 +1,9 @@ +namespace AIStudio.Settings.DataModel; + +public sealed class DataDocumentAnalysis +{ + /// + /// Configured document analysis policies. + /// + public List Policies { get; set; } = []; +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Settings/DataModel/DataDocumentAnalysisPolicy.cs b/app/MindWork AI Studio/Settings/DataModel/DataDocumentAnalysisPolicy.cs new file mode 100644 index 00000000..aae8ede1 --- /dev/null +++ b/app/MindWork AI Studio/Settings/DataModel/DataDocumentAnalysisPolicy.cs @@ -0,0 +1,52 @@ +using AIStudio.Provider; + +namespace AIStudio.Settings.DataModel; + +public sealed class DataDocumentAnalysisPolicy +{ + /// + /// Preselect the policy name? + /// + public string PolicyName { get; set; } = string.Empty; + + /// + /// Preselect the policy description? + /// + public string PolicyDescription { get; set; } = string.Empty; + + /// + /// Is this policy protected? If so, it cannot be deleted or modified by the user. + /// + public bool IsProtected { get; set; } + + /// + /// Is this a managed policy? Managed policies are created and managed by the organization + /// and cannot be modified or deleted by the user. + /// + public bool IsManaged { get; set; } + + /// + /// The rules for the document analysis policy. + /// + public string AnalysisRules { get; set; } = string.Empty; + + /// + /// The rules for the output of the document analysis, e.g., the desired format, structure, etc. + /// + public string OutputRules { get; set; } = string.Empty; + + /// + /// The minimum confidence level required for a provider to be considered. + /// + public ConfidenceLevel MinimumProviderConfidence { get; set; } = ConfidenceLevel.NONE; + + /// + /// Which LLM provider should be preselected? + /// + public string PreselectedProvider { get; set; } = string.Empty; + + /// + /// Preselect a profile? + /// + public string PreselectedProfile { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Settings/DataModel/PreviewFeatures.cs b/app/MindWork AI Studio/Settings/DataModel/PreviewFeatures.cs index 85acedec..49aad8d0 100644 --- a/app/MindWork AI Studio/Settings/DataModel/PreviewFeatures.cs +++ b/app/MindWork AI Studio/Settings/DataModel/PreviewFeatures.cs @@ -11,4 +11,5 @@ public enum PreviewFeatures PRE_PLUGINS_2025, PRE_READ_PDF_2025, + PRE_DOCUMENT_ANALYSIS_2025, } \ No newline at end of file diff --git a/app/MindWork AI Studio/Settings/DataModel/PreviewFeaturesExtensions.cs b/app/MindWork AI Studio/Settings/DataModel/PreviewFeaturesExtensions.cs index 3736dd80..e80495b2 100644 --- a/app/MindWork AI Studio/Settings/DataModel/PreviewFeaturesExtensions.cs +++ b/app/MindWork AI Studio/Settings/DataModel/PreviewFeaturesExtensions.cs @@ -13,6 +13,7 @@ public static class PreviewFeaturesExtensions PreviewFeatures.PRE_PLUGINS_2025 => TB("Plugins: Preview of our plugin system where you can extend the functionality of the app"), PreviewFeatures.PRE_READ_PDF_2025 => TB("Read PDF: Preview of our PDF reading system where you can read and extract text from PDF files"), + PreviewFeatures.PRE_DOCUMENT_ANALYSIS_2025 => TB("Document Analysis: Preview of our document analysis system where you can analyze and extract information from documents"), _ => TB("Unknown preview feature") }; diff --git a/app/MindWork AI Studio/Settings/DataModel/PreviewVisibilityExtensions.cs b/app/MindWork AI Studio/Settings/DataModel/PreviewVisibilityExtensions.cs index f80939f6..bd648b24 100644 --- a/app/MindWork AI Studio/Settings/DataModel/PreviewVisibilityExtensions.cs +++ b/app/MindWork AI Studio/Settings/DataModel/PreviewVisibilityExtensions.cs @@ -20,6 +20,7 @@ public static class PreviewVisibilityExtensions if (visibility >= PreviewVisibility.PROTOTYPE) { features.Add(PreviewFeatures.PRE_RAG_2024); + features.Add(PreviewFeatures.PRE_DOCUMENT_ANALYSIS_2025); } if (visibility >= PreviewVisibility.EXPERIMENTAL) diff --git a/app/MindWork AI Studio/Settings/TolerantEnumConverter.cs b/app/MindWork AI Studio/Settings/TolerantEnumConverter.cs index c3f8fcd0..51bccea4 100644 --- a/app/MindWork AI Studio/Settings/TolerantEnumConverter.cs +++ b/app/MindWork AI Studio/Settings/TolerantEnumConverter.cs @@ -1,3 +1,4 @@ +using System.Text; using System.Text.Json; using System.Text.Json.Serialization; @@ -9,6 +10,9 @@ namespace AIStudio.Settings; /// /// When the target enum value does not exist, the value will be the default value. /// This converter handles enum values as property names and values. +///

+/// We assume that enum names are in UPPER_SNAKE_CASE, and the JSON strings may be +/// in any case style (e.g., camelCase, PascalCase, snake_case, UPPER_SNAKE_CASE, etc.) ///
public sealed class TolerantEnumConverter : JsonConverter { @@ -16,30 +20,46 @@ public sealed class TolerantEnumConverter : JsonConverter public override bool CanConvert(Type typeToConvert) => typeToConvert.IsEnum; - public override object? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override object? Read(ref Utf8JsonReader reader, Type enumType, JsonSerializerOptions options) { // Is this token a string? if (reader.TokenType == JsonTokenType.String) + { // Try to use that string as the name of the enum value: - if (Enum.TryParse(typeToConvert, reader.GetString(), out var result)) + var text = reader.GetString(); + + // Convert the text to UPPER_SNAKE_CASE: + text = ConvertToUpperSnakeCase(text); + + // Try to parse the enum value: + if (Enum.TryParse(enumType, text, out var result)) return result; + } // In any other case, we will return the default enum value: - LOG.LogWarning($"Cannot read '{reader.GetString()}' as '{typeToConvert.Name}' enum; token type: {reader.TokenType}"); - return Activator.CreateInstance(typeToConvert); + LOG.LogWarning($"Cannot read '{reader.GetString()}' as '{enumType.Name}' enum; token type: {reader.TokenType}"); + return Activator.CreateInstance(enumType); } - public override object ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override object ReadAsPropertyName(ref Utf8JsonReader reader, Type enumType, JsonSerializerOptions options) { // Is this token a property name? if (reader.TokenType == JsonTokenType.PropertyName) + { // Try to use that property name as the name of the enum value: - if (Enum.TryParse(typeToConvert, reader.GetString(), out var result)) + var text = reader.GetString(); + + // Convert the text to UPPER_SNAKE_CASE: + text = ConvertToUpperSnakeCase(text); + + // Try to parse the enum value: + if (Enum.TryParse(enumType, text, out var result)) return result; + } // In any other case, we will return the default enum value: - LOG.LogWarning($"Cannot read '{reader.GetString()}' as '{typeToConvert.Name}' enum; token type: {reader.TokenType}"); - return Activator.CreateInstance(typeToConvert)!; + LOG.LogWarning($"Cannot read '{reader.GetString()}' as '{enumType.Name}' enum; token type: {reader.TokenType}"); + return Activator.CreateInstance(enumType)!; } public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options) @@ -51,4 +71,44 @@ public sealed class TolerantEnumConverter : JsonConverter { writer.WritePropertyName(value.ToString()!); } + + /// + /// Converts a string to UPPER_SNAKE_CASE. + /// + /// The text to convert. + /// The converted text as UPPER_SNAKE_CASE. + private static string ConvertToUpperSnakeCase(string? text) + { + // Handle null or empty strings: + if (string.IsNullOrWhiteSpace(text)) + return string.Empty; + + // Create a string builder with the same length as the + // input text. We will add underscores as needed, which + // may increase the length -- we cannot predict how many + // underscores will be added, so we just start with the + // original length: + var sb = new StringBuilder(text.Length); + + // State to track if the last character was lowercase. + // This helps to determine when to add underscores: + var lastCharWasLowerCase = false; + + // Iterate through each character in the input text: + foreach(var c in text) + { + // If the current character is uppercase and the last + // character was lowercase, we need to add an underscore: + if (char.IsUpper(c) && lastCharWasLowerCase) + sb.Append('_'); + + // Append the uppercase version of the current character: + sb.Append(char.ToUpperInvariant(c)); + + // Keep track of whether the current character is lowercase: + lastCharWasLowerCase = char.IsLower(c); + } + + return sb.ToString(); + } } \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/Components.cs b/app/MindWork AI Studio/Tools/Components.cs index 94148d5e..1004188c 100644 --- a/app/MindWork AI Studio/Tools/Components.cs +++ b/app/MindWork AI Studio/Tools/Components.cs @@ -18,6 +18,7 @@ public enum Components JOB_POSTING_ASSISTANT, BIAS_DAY_ASSISTANT, ERI_ASSISTANT, + DOCUMENT_ANALYSIS_ASSISTANT, // ReSharper disable InconsistentNaming I18N_ASSISTANT, diff --git a/app/MindWork AI Studio/Tools/ComponentsExtensions.cs b/app/MindWork AI Studio/Tools/ComponentsExtensions.cs index 6c2ccb88..54eb2cfa 100644 --- a/app/MindWork AI Studio/Tools/ComponentsExtensions.cs +++ b/app/MindWork AI Studio/Tools/ComponentsExtensions.cs @@ -42,10 +42,11 @@ public static class ComponentsExtensions Components.JOB_POSTING_ASSISTANT => TB("Job Posting Assistant"), Components.ERI_ASSISTANT => TB("ERI Server"), Components.I18N_ASSISTANT => TB("Localization Assistant"), + Components.DOCUMENT_ANALYSIS_ASSISTANT => TB("Document Analysis Assistant"), Components.CHAT => TB("New Chat"), - _ => Enum.GetName(typeof(Components), component)!, + _ => Enum.GetName(component)!, }; public static ComponentsData GetData(this Components destination) => destination switch @@ -62,6 +63,7 @@ public static class ComponentsExtensions 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.DOCUMENT_ANALYSIS_ASSISTANT => new(Event.SEND_TO_DOCUMENT_ANALYSIS_ASSISTANT, Routes.ASSISTANT_DOCUMENT_ANALYSIS), Components.CHAT => new(Event.SEND_TO_CHAT, Routes.CHAT), @@ -84,6 +86,9 @@ public static class ComponentsExtensions Components.JOB_POSTING_ASSISTANT => settingsManager.ConfigurationData.JobPostings.PreselectOptions ? settingsManager.ConfigurationData.JobPostings.MinimumProviderConfidence : default, Components.BIAS_DAY_ASSISTANT => settingsManager.ConfigurationData.BiasOfTheDay.PreselectOptions ? settingsManager.ConfigurationData.BiasOfTheDay.MinimumProviderConfidence : default, Components.ERI_ASSISTANT => settingsManager.ConfigurationData.ERI.PreselectOptions ? settingsManager.ConfigurationData.ERI.MinimumProviderConfidence : default, + + #warning Add minimum confidence for DOCUMENT_ANALYSIS_ASSISTANT: + //Components.DOCUMENT_ANALYSIS_ASSISTANT => settingsManager.ConfigurationData.DocumentAnalysis.PreselectOptions ? settingsManager.ConfigurationData.DocumentAnalysis.MinimumProviderConfidence : default, _ => default, }; @@ -108,6 +113,9 @@ public static class ComponentsExtensions Components.BIAS_DAY_ASSISTANT => settingsManager.ConfigurationData.BiasOfTheDay.PreselectOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.BiasOfTheDay.PreselectedProvider) : null, Components.ERI_ASSISTANT => settingsManager.ConfigurationData.ERI.PreselectOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.ERI.PreselectedProvider) : null, Components.I18N_ASSISTANT => settingsManager.ConfigurationData.I18N.PreselectOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.I18N.PreselectedProvider) : null, + + #warning Add preselected provider for DOCUMENT_ANALYSIS_ASSISTANT: + //Components.DOCUMENT_ANALYSIS_ASSISTANT => settingsManager.ConfigurationData.DocumentAnalysis.PreselectOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.DocumentAnalysis.PreselectedProvider) : null, Components.CHAT => settingsManager.ConfigurationData.Chat.PreselectOptions ? settingsManager.ConfigurationData.Providers.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.Chat.PreselectedProvider) : null, @@ -123,6 +131,8 @@ public static class ComponentsExtensions public static Profile PreselectedProfile(this Components component, SettingsManager settingsManager) => component switch { + #warning Add preselected profile for DOCUMENT_ANALYSIS_ASSISTANT: + // Components.DOCUMENT_ANALYSIS_ASSISTANT => settingsManager.ConfigurationData.DocumentAnalysis.PreselectOptions ? settingsManager.ConfigurationData.Profiles.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.DocumentAnalysis.PreselectedProfile) : default, Components.AGENDA_ASSISTANT => settingsManager.ConfigurationData.Agenda.PreselectOptions ? settingsManager.ConfigurationData.Profiles.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.Agenda.PreselectedProfile) ?? Profile.NO_PROFILE : Profile.NO_PROFILE, Components.CODING_ASSISTANT => settingsManager.ConfigurationData.Coding.PreselectOptions ? settingsManager.ConfigurationData.Profiles.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.Coding.PreselectedProfile) ?? Profile.NO_PROFILE : Profile.NO_PROFILE, Components.EMAIL_ASSISTANT => settingsManager.ConfigurationData.EMail.PreselectOptions ? settingsManager.ConfigurationData.Profiles.FirstOrDefault(x => x.Id == settingsManager.ConfigurationData.EMail.PreselectedProfile) ?? Profile.NO_PROFILE : Profile.NO_PROFILE, diff --git a/app/MindWork AI Studio/Tools/Event.cs b/app/MindWork AI Studio/Tools/Event.cs index fcf32604..9f612f04 100644 --- a/app/MindWork AI Studio/Tools/Event.cs +++ b/app/MindWork AI Studio/Tools/Event.cs @@ -14,6 +14,7 @@ public enum Event SHOW_ERROR, SHOW_WARNING, SHOW_SUCCESS, + TAURI_EVENT_RECEIVED, // Update events: USER_SEARCH_FOR_UPDATE, @@ -47,4 +48,5 @@ public enum Event SEND_TO_SYNONYMS_ASSISTANT, SEND_TO_MY_TASKS_ASSISTANT, SEND_TO_JOB_POSTING_ASSISTANT, + SEND_TO_DOCUMENT_ANALYSIS_ASSISTANT, } \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/EventHandlers.cs b/app/MindWork AI Studio/Tools/EventHandlers.cs new file mode 100644 index 00000000..bc68af6c --- /dev/null +++ b/app/MindWork AI Studio/Tools/EventHandlers.cs @@ -0,0 +1,14 @@ +using Microsoft.AspNetCore.Components; + +namespace AIStudio.Tools; + +/// +/// Add handling for more DOM events to Blazor components. +/// +/// +/// See https://learn.microsoft.com/en-us/aspnet/core/blazor/components/event-handling. It is important +/// that this class is named EventHandlers. +/// +[EventHandler("onmouseenter", typeof(EventArgs), enableStopPropagation: true, enablePreventDefault: true)] +[EventHandler("onmouseleave", typeof(EventArgs), enableStopPropagation: true, enablePreventDefault: true)] +public static class EventHandlers; \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/Rust/FileTypeFilter.cs b/app/MindWork AI Studio/Tools/Rust/FileTypeFilter.cs index bf0ac5b2..d755184e 100644 --- a/app/MindWork AI Studio/Tools/Rust/FileTypeFilter.cs +++ b/app/MindWork AI Studio/Tools/Rust/FileTypeFilter.cs @@ -19,7 +19,9 @@ public readonly record struct FileTypeFilter(string FilterName, string[] FilterE public static FileTypeFilter AllOffice => new(TB("All Office Files"), ["docx", "xlsx", "pptx", "doc", "xls", "ppt", "pdf"]); - public static FileTypeFilter AllImages => new(TB("All Image Files"), ["jpg", "jpeg", "png", "gif", "bmp", "tiff"]); + public static FileTypeFilter AllImages => new(TB("All Image Files"), ["jpg", "jpeg", "png", "gif", "bmp", "tiff", "svg", "webp", "heic"]); + + public static FileTypeFilter AllVideos => new(TB("All Video Files"), ["mp4", "avi", "mkv", "mov", "wmv", "flv", "webm"]); public static FileTypeFilter Executables => new(TB("Executable Files"), ["exe", "app", "bin", "appimage"]); } \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/Rust/TauriEvent.cs b/app/MindWork AI Studio/Tools/Rust/TauriEvent.cs new file mode 100644 index 00000000..c060e63a --- /dev/null +++ b/app/MindWork AI Studio/Tools/Rust/TauriEvent.cs @@ -0,0 +1,8 @@ +namespace AIStudio.Tools.Rust; + +/// +/// The data structure for a Tauri event sent from the Rust backend to the C# frontend. +/// +/// The type of the Tauri event. +/// The payload of the Tauri event. +public readonly record struct TauriEvent(TauriEventType EventType, List Payload); \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/Rust/TauriEventType.cs b/app/MindWork AI Studio/Tools/Rust/TauriEventType.cs new file mode 100644 index 00000000..2cd1c792 --- /dev/null +++ b/app/MindWork AI Studio/Tools/Rust/TauriEventType.cs @@ -0,0 +1,18 @@ +namespace AIStudio.Tools.Rust; + +/// +/// The type of Tauri events we can receive. +/// +public enum TauriEventType +{ + NONE, + PING, + UNKNOWN, + + WINDOW_FOCUSED, + WINDOW_NOT_FOCUSED, + + FILE_DROP_HOVERED, + FILE_DROP_DROPPED, + FILE_DROP_CANCELED, +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/Services/RustService.Events.cs b/app/MindWork AI Studio/Tools/Services/RustService.Events.cs new file mode 100644 index 00000000..e4d72a0f --- /dev/null +++ b/app/MindWork AI Studio/Tools/Services/RustService.Events.cs @@ -0,0 +1,77 @@ +using System.Text.Json; + +using AIStudio.Tools.Rust; + +namespace AIStudio.Tools.Services; + +public partial class RustService +{ + /// + /// Consume the Tauri event stream and forward relevant events to the message bus. + /// + /// Cancellation token to stop the stream. + private async Task StartStreamTauriEvents(CancellationToken stopToken) + { + // Outer try-catch to handle cancellation: + try + { + while (!stopToken.IsCancellationRequested) + { + // Inner try-catch to handle streaming issues: + try + { + // Open the event stream: + await using var stream = await this.http.GetStreamAsync("/events", stopToken); + + + // Read events line by line: + using var reader = new StreamReader(stream); + + // Read until the end of the stream or cancellation: + while(!reader.EndOfStream && !stopToken.IsCancellationRequested) + { + // Read the next line of JSON from the stream: + var line = await reader.ReadLineAsync(stopToken); + + // Skip empty lines: + if (string.IsNullOrWhiteSpace(line)) + continue; + + // Deserialize the Tauri event: + var tauriEvent = JsonSerializer.Deserialize(line, this.jsonRustSerializerOptions); + + // Forward relevant events to the message bus: + if (tauriEvent != default && tauriEvent.EventType + is not TauriEventType.NONE + and not TauriEventType.UNKNOWN + and not TauriEventType.PING) + { + this.logger!.LogDebug("Received Tauri event {EventType} with {NumPayloadItems} payload items.", tauriEvent.EventType, tauriEvent.Payload.Count); + await MessageBus.INSTANCE.SendMessage(null, Event.TAURI_EVENT_RECEIVED, tauriEvent); + } + } + } + + // The cancellation token was triggered, exit the loop: + catch (OperationCanceledException) + { + break; + } + + // Some other error occurred, log it and retry after a delay: + catch (Exception e) + { + this.logger!.LogError("Error while streaming Tauri events: {Message}", e.Message); + await Task.Delay(TimeSpan.FromSeconds(3), stopToken); + } + } + } + + // The cancellation token was triggered, exit the method: + catch (OperationCanceledException) + { + } + + this.logger!.LogWarning("Stopped streaming Tauri events."); + } +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/Services/RustService.cs b/app/MindWork AI Studio/Tools/Services/RustService.cs index 38fab8cc..41628992 100644 --- a/app/MindWork AI Studio/Tools/Services/RustService.cs +++ b/app/MindWork AI Studio/Tools/Services/RustService.cs @@ -1,6 +1,10 @@ using System.Security.Cryptography; using System.Text.Json; +using AIStudio.Settings; + +using Version = System.Version; + // ReSharper disable NotAccessedPositionalProperty.Local namespace AIStudio.Tools.Services; @@ -8,13 +12,14 @@ namespace AIStudio.Tools.Services; /// /// Calling Rust functions. /// -public sealed partial class RustService : IDisposable +public sealed partial class RustService : BackgroundService { private readonly HttpClient http; private readonly JsonSerializerOptions jsonRustSerializerOptions = new() { PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower, + Converters = { new TolerantEnumConverter() }, }; private ILogger? logger; @@ -59,11 +64,24 @@ public sealed partial class RustService : IDisposable this.encryptor = encryptionService; } - #region IDisposable + #region Overrides of BackgroundService - public void Dispose() + /// + /// The main execution loop of the Rust service as a background thread. + /// + /// The cancellation token to stop the service. + protected override async Task ExecuteAsync(CancellationToken stopToken) + { + this.logger?.LogInformation("The Rust service was initialized."); + + // Start consuming Tauri events: + await this.StartStreamTauriEvents(stopToken); + } + + public override void Dispose() { this.http.Dispose(); + base.Dispose(); } #endregion diff --git a/app/MindWork AI Studio/Tools/Services/TemporaryChatService.cs b/app/MindWork AI Studio/Tools/Services/TemporaryChatService.cs index ab2f39e7..61a6e4c8 100644 --- a/app/MindWork AI Studio/Tools/Services/TemporaryChatService.cs +++ b/app/MindWork AI Studio/Tools/Services/TemporaryChatService.cs @@ -24,24 +24,24 @@ public sealed class TemporaryChatService(ILogger logger, S return; } - await this.StartMaintenance(); + this.StartMaintenance(); while (!stoppingToken.IsCancellationRequested) { await Task.Delay(CHECK_INTERVAL, stoppingToken); - await this.StartMaintenance(); + this.StartMaintenance(); } } #endregion - private Task StartMaintenance() + private void StartMaintenance() { logger.LogInformation("Starting maintenance of temporary chat storage."); var temporaryDirectories = Path.Join(SettingsManager.DataDirectory, "tempChats"); if(!Directory.Exists(temporaryDirectories)) { logger.LogWarning("Temporary chat storage directory does not exist. End maintenance."); - return Task.CompletedTask; + return; } foreach (var tempChatDirPath in Directory.EnumerateDirectories(temporaryDirectories)) @@ -71,7 +71,6 @@ public sealed class TemporaryChatService(ILogger logger, S } logger.LogInformation("Finished maintenance of temporary chat storage."); - return Task.CompletedTask; } public static void Initialize() diff --git a/app/MindWork AI Studio/packages.lock.json b/app/MindWork AI Studio/packages.lock.json index c36716e7..2d101c3a 100644 --- a/app/MindWork AI Studio/packages.lock.json +++ b/app/MindWork AI Studio/packages.lock.json @@ -39,9 +39,9 @@ }, "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[9.0.11, )", - "resolved": "9.0.11", - "contentHash": "vvB9rtDmWaXgYkViT00KORBVmA3pcYsHlgd9vOPqL9sf5bKy3rvLMF1+sI1uUfVj28S3itirHlHmX5/kcpZKNw==" + "requested": "[9.0.8, )", + "resolved": "9.0.8", + "contentHash": "rd1CbIsMtVPtZNTIVD6Xydue//klYOOQIDpRgu3BHtv17AlpRs74/6QFbcYgMm/jL+naVU2T3OFLxVSLV5lQLQ==" }, "MudBlazor": { "type": "Direct", @@ -210,6 +210,6 @@ "type": "Project" } }, - "net9.0/osx-arm64": {} + "net9.0/win-x64": {} } } \ No newline at end of file diff --git a/metadata.txt b/metadata.txt index 9cbca49a..c9e9c4ec 100644 --- a/metadata.txt +++ b/metadata.txt @@ -6,6 +6,6 @@ 1.91.1 (commit ed61e7d7e) 8.12.0 1.8.1 -45ac3eee2ba, release -osx-arm64 -137.0.7215.0 +bac0b49dce9, release +win-x64 +137.0.7215.0 \ No newline at end of file diff --git a/runtime/src/app_window.rs b/runtime/src/app_window.rs index 25764123..dd994415 100644 --- a/runtime/src/app_window.rs +++ b/runtime/src/app_window.rs @@ -1,14 +1,16 @@ use std::sync::Mutex; use std::time::Duration; -use log::{error, info, trace, warn}; +use log::{debug, error, info, trace, warn}; use once_cell::sync::Lazy; use rocket::{get, post}; +use rocket::response::stream::TextStream; use rocket::serde::json::Json; use rocket::serde::Serialize; use serde::Deserialize; use tauri::updater::UpdateResponse; -use tauri::{Manager, PathResolver, Window}; +use tauri::{FileDropEvent, UpdaterEvent, RunEvent, Manager, PathResolver, Window, WindowEvent}; use tauri::api::dialog::blocking::FileDialogBuilder; +use tokio::sync::broadcast; use tokio::time; use crate::api_token::APIToken; use crate::dotnet::stop_dotnet_server; @@ -22,18 +24,70 @@ static MAIN_WINDOW: Lazy>> = Lazy::new(|| Mutex::new(None)) /// The update response coming from the Tauri updater. static CHECK_UPDATE_RESPONSE: Lazy>>> = Lazy::new(|| Mutex::new(None)); +/// The event broadcast sender for Tauri events. +static EVENT_BROADCAST: Lazy>>> = Lazy::new(|| Mutex::new(None)); + /// Starts the Tauri app. pub fn start_tauri() { info!("Starting Tauri app..."); + + // Create the event broadcast channel: + let (event_sender, root_event_receiver) = broadcast::channel(100); + + // Save a copy of the event broadcast sender for later use: + *EVENT_BROADCAST.lock().unwrap() = Some(event_sender.clone()); + + // When the last receiver is dropped, we lose the ability to send events. + // Therefore, we spawn a task that keeps the root receiver alive: + tauri::async_runtime::spawn(async move { + let mut root_receiver = root_event_receiver; + loop { + match root_receiver.recv().await { + Ok(event) => { + debug!(Source = "Tauri"; "Tauri event received: location=root receiver , event={event:?}"); + }, + + Err(broadcast::error::RecvError::Lagged(skipped)) => { + warn!(Source = "Tauri"; "Root event receiver lagged, skipped {skipped} messages."); + }, + + Err(broadcast::error::RecvError::Closed) => { + warn!(Source = "Tauri"; "Root event receiver channel closed."); + return; + }, + } + } + }); + let app = tauri::Builder::default() .setup(move |app| { + + // Get the main window: let window = app.get_window("main").expect("Failed to get main window."); + + // Register a callback for window events, such as file drops. We have to use + // this handler in addition to the app event handler, because file drop events + // are only available in the window event handler (is a bug, cf. https://github.com/tauri-apps/tauri/issues/14338): + window.on_window_event(move |event| { + debug!(Source = "Tauri"; "Tauri event received: location=window event handler, event={event:?}"); + let event_to_send = Event::from_window_event(event); + let sender = event_sender.clone(); + tauri::async_runtime::spawn(async move { + match sender.send(event_to_send) { + Ok(_) => {}, + Err(error) => error!(Source = "Tauri"; "Failed to channel window event: {error}"), + } + }); + }); + + // Save the main window for later access: *MAIN_WINDOW.lock().unwrap() = Some(window); info!(Source = "Bootloader Tauri"; "Setup is running."); let data_path = app.path_resolver().app_local_data_dir().unwrap(); let data_path = data_path.join("data"); + // Get and store the data and config directories: DATA_DIRECTORY.set(data_path.to_str().unwrap().to_string()).map_err(|_| error!("Was not abe to set the data directory.")).unwrap(); CONFIG_DIRECTORY.set(app.path_resolver().app_config_dir().unwrap().to_str().unwrap().to_string()).map_err(|_| error!("Was not able to set the config directory.")).unwrap(); @@ -46,84 +100,84 @@ pub fn start_tauri() { .build(tauri::generate_context!()) .expect("Error while running Tauri application"); - app.run(|app_handle, event| match event { - - tauri::RunEvent::WindowEvent { event, label, .. } => { - match event { - tauri::WindowEvent::CloseRequested { .. } => { - warn!(Source = "Tauri"; "Window '{label}': close was requested."); - } - - tauri::WindowEvent::Destroyed => { - warn!(Source = "Tauri"; "Window '{label}': was destroyed."); - } - - tauri::WindowEvent::FileDrop(files) => { - info!(Source = "Tauri"; "Window '{label}': files were dropped: {files:?}"); - } - - _ => (), - } + // The app event handler: + app.run(|app_handle, event| { + if !matches!(event, RunEvent::MainEventsCleared) { + debug!(Source = "Tauri"; "Tauri event received: location=app event handler , event={event:?}"); } - - tauri::RunEvent::Updater(updater_event) => { - match updater_event { - - tauri::UpdaterEvent::UpdateAvailable { body, date, version } => { - let body_len = body.len(); - info!(Source = "Tauri"; "Updater: update available: body size={body_len} time={date:?} version={version}"); - } - - tauri::UpdaterEvent::Pending => { - info!(Source = "Tauri"; "Updater: update is pending!"); - } - - tauri::UpdaterEvent::DownloadProgress { chunk_length, content_length: _ } => { - trace!(Source = "Tauri"; "Updater: downloading chunk of {chunk_length} bytes"); - } - - tauri::UpdaterEvent::Downloaded => { - info!(Source = "Tauri"; "Updater: update has been downloaded!"); - warn!(Source = "Tauri"; "Try to stop the .NET server now..."); - - if is_prod() { - stop_dotnet_server(); - } else { - warn!(Source = "Tauri"; "Development environment detected; do not stop the .NET server."); - } - } - - tauri::UpdaterEvent::Updated => { - info!(Source = "Tauri"; "Updater: app has been updated"); - warn!(Source = "Tauri"; "Try to restart the app now..."); - - if is_prod() { - app_handle.restart(); - } else { - warn!(Source = "Tauri"; "Development environment detected; do not restart the app."); + + match event { + RunEvent::WindowEvent { event, label, .. } => { + match event { + WindowEvent::CloseRequested { .. } => { + warn!(Source = "Tauri"; "Window '{label}': close was requested."); } - } + WindowEvent::Destroyed => { + warn!(Source = "Tauri"; "Window '{label}': was destroyed."); + } - tauri::UpdaterEvent::AlreadyUpToDate => { - info!(Source = "Tauri"; "Updater: app is already up to date"); - } - - tauri::UpdaterEvent::Error(error) => { - warn!(Source = "Tauri"; "Updater: failed to update: {error}"); + _ => (), } } - } - tauri::RunEvent::ExitRequested { .. } => { - warn!(Source = "Tauri"; "Run event: exit was requested."); - } + RunEvent::Updater(updater_event) => { + match updater_event { + UpdaterEvent::UpdateAvailable { body, date, version } => { + let body_len = body.len(); + info!(Source = "Tauri"; "Updater: update available: body size={body_len} time={date:?} version={version}"); + } - tauri::RunEvent::Ready => { - info!(Source = "Tauri"; "Run event: Tauri app is ready."); - } + UpdaterEvent::Pending => { + info!(Source = "Tauri"; "Updater: update is pending!"); + } - _ => {} + UpdaterEvent::DownloadProgress { chunk_length, content_length: _ } => { + trace!(Source = "Tauri"; "Updater: downloading chunk of {chunk_length} bytes"); + } + + UpdaterEvent::Downloaded => { + info!(Source = "Tauri"; "Updater: update has been downloaded!"); + warn!(Source = "Tauri"; "Try to stop the .NET server now..."); + + if is_prod() { + stop_dotnet_server(); + } else { + warn!(Source = "Tauri"; "Development environment detected; do not stop the .NET server."); + } + } + + UpdaterEvent::Updated => { + info!(Source = "Tauri"; "Updater: app has been updated"); + warn!(Source = "Tauri"; "Try to restart the app now..."); + + if is_prod() { + app_handle.restart(); + } else { + warn!(Source = "Tauri"; "Development environment detected; do not restart the app."); + } + } + + UpdaterEvent::AlreadyUpToDate => { + info!(Source = "Tauri"; "Updater: app is already up to date"); + } + + UpdaterEvent::Error(error) => { + warn!(Source = "Tauri"; "Updater: failed to update: {error}"); + } + } + } + + RunEvent::ExitRequested { .. } => { + warn!(Source = "Tauri"; "Run event: exit was requested."); + } + + RunEvent::Ready => { + info!(Source = "Tauri"; "Run event: Tauri app is ready."); + } + + _ => {} + } }); warn!(Source = "Tauri"; "Tauri app was stopped."); @@ -133,6 +187,144 @@ pub fn start_tauri() { } } +/// Our event API endpoint for Tauri events. We try to send an endless stream of events to the client. +/// If no events are available for a certain time, we send a ping event to keep the connection alive. +/// When the client disconnects, the stream is closed. But we try to not lose events in between. +/// The client is expected to reconnect automatically when the connection is closed and continue +/// listening for events. +#[get("/events")] +pub async fn get_event_stream(_token: APIToken) -> TextStream![String] { + // Get the lock to the event broadcast sender: + let event_broadcast_lock = EVENT_BROADCAST.lock().unwrap(); + + // Get and subscribe to the event receiver: + let mut event_receiver = event_broadcast_lock.as_ref() + .expect("Event sender not initialized.") + .subscribe(); + + // Drop the lock to allow other access to the sender: + drop(event_broadcast_lock); + + // Create the event stream: + TextStream! { + loop { + // Wait at most 3 seconds for an event: + match time::timeout(Duration::from_secs(3), event_receiver.recv()).await { + + // Case: we received an event + Ok(Ok(event)) => { + // Serialize the event to JSON. Important is that the entire event + // is serialized as a single line so that the client can parse it + // correctly: + let event_json = serde_json::to_string(&event).unwrap(); + yield event_json; + + // The client expects a newline after each event because we are using + // a method to read the stream line-by-line: + yield "\n".to_string(); + }, + + // Case: we lagged behind and missed some events + Ok(Err(broadcast::error::RecvError::Lagged(skipped))) => { + warn!(Source = "Tauri"; "Event receiver lagged, skipped {skipped} messages."); + }, + + // Case: the event channel was closed + Ok(Err(broadcast::error::RecvError::Closed)) => { + warn!(Source = "Tauri"; "Event receiver channel closed."); + return; + }, + + // Case: timeout. We will send a ping event to keep the connection alive. + Err(_) => { + let ping_event = Event::new(TauriEventType::Ping, Vec::new()); + + // Again, we have to serialize the event as a single line: + let event_json = serde_json::to_string(&ping_event).unwrap(); + yield event_json; + + // The client expects a newline after each event because we are using + // a method to read the stream line-by-line: + yield "\n".to_string(); + }, + } + } + } +} + +/// Data structure representing a Tauri event for our event API. +#[derive(Debug, Clone, Serialize)] +pub struct Event { + pub event_type: TauriEventType, + pub payload: Vec, +} + +/// Implementation of the Event struct. +impl Event { + + /// Creates a new Event instance. + pub fn new(event_type: TauriEventType, payload: Vec) -> Self { + Event { + payload, + event_type, + } + } + + /// Creates an Event instance from a Tauri WindowEvent. + pub fn from_window_event(window_event: &WindowEvent) -> Self { + match window_event { + WindowEvent::FileDrop(drop_event) => { + match drop_event { + FileDropEvent::Hovered(files) => Event::new(TauriEventType::FileDropHovered, + files.iter().map(|f| f.to_string_lossy().to_string()).collect(), + ), + + FileDropEvent::Dropped(files) => Event::new(TauriEventType::FileDropDropped, + files.iter().map(|f| f.to_string_lossy().to_string()).collect(), + ), + + FileDropEvent::Cancelled => Event::new(TauriEventType::FileDropCanceled, + Vec::new(), + ), + + _ => Event::new(TauriEventType::Unknown, + Vec::new(), + ), + } + }, + + WindowEvent::Focused(state) => if *state { + Event::new(TauriEventType::WindowFocused, + Vec::new(), + ) + } else { + Event::new(TauriEventType::WindowNotFocused, + Vec::new(), + ) + }, + + _ => Event::new(TauriEventType::Unknown, + Vec::new(), + ), + } + } +} + +/// The types of Tauri events we can send through our event API. +#[derive(Debug, Serialize, Clone)] +pub enum TauriEventType { + None, + Ping, + Unknown, + + WindowFocused, + WindowNotFocused, + + FileDropHovered, + FileDropDropped, + FileDropCanceled, +} + /// Changes the location of the main window to the given URL. pub async fn change_location_to(url: &str) { // Try to get the main window. If it is not available yet, wait for it: diff --git a/runtime/src/file_data.rs b/runtime/src/file_data.rs index f05b18b5..969f19e1 100644 --- a/runtime/src/file_data.rs +++ b/runtime/src/file_data.rs @@ -27,6 +27,7 @@ pub struct Chunk { pub stream_id: String, pub metadata: Metadata, } + impl Chunk { pub fn new(content: String, metadata: Metadata) -> Self { Chunk { content, stream_id: String::new(), metadata } diff --git a/runtime/src/runtime_api.rs b/runtime/src/runtime_api.rs index f510b132..23fc5e33 100644 --- a/runtime/src/runtime_api.rs +++ b/runtime/src/runtime_api.rs @@ -68,6 +68,7 @@ pub fn start_runtime_api() { crate::dotnet::dotnet_port, crate::dotnet::dotnet_ready, crate::clipboard::set_clipboard, + crate::app_window::get_event_stream, crate::app_window::check_for_update, crate::app_window::install_update, crate::app_window::select_directory, diff --git a/runtime/tauri.conf.json b/runtime/tauri.conf.json index f0f5d4a4..2b9ce17f 100644 --- a/runtime/tauri.conf.json +++ b/runtime/tauri.conf.json @@ -40,7 +40,8 @@ "resizable": true, "title": "MindWork AI Studio", "width": 1920, - "height": 1080 + "height": 1080, + "fileDropEnabled": true } ], "security": {