diff --git a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua index f825d229..d8658ebe 100644 --- a/app/MindWork AI Studio/Assistants/I18N/allTexts.lua +++ b/app/MindWork AI Studio/Assistants/I18N/allTexts.lua @@ -2140,9 +2140,15 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T817101267"] -- Would you like to set one provider as the default for the entire app? When you configure a different provider for an assistant, it will always take precedence. UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T844514734"] = "Would you like to set one provider as the default for the entire app? When you configure a different provider for an assistant, it will always take precedence." +-- Embedding Result +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1387042335"] = "Embedding Result" + -- Delete UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1469573738"] = "Delete" +-- Test Embedding Provider +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1655784761"] = "Test Embedding Provider" + -- Add Embedding UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1738753945"] = "Add Embedding" @@ -2155,6 +2161,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T18253 -- Add Embedding Provider UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T190634634"] = "Add Embedding Provider" +-- Embedding Vector (one dimension per line) +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T2174876961"] = "Embedding Vector (one dimension per line)" + -- Model UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T2189814010"] = "Model" @@ -2164,6 +2173,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T24199 -- Name UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T266367750"] = "Name" +-- No embedding was returned. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T291969"] = "No embedding was returned." + -- Configured Embedding Providers UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T305753126"] = "Configured Embedding Providers" @@ -2173,6 +2185,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T32512 -- Edit UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T3267849393"] = "Edit" +-- Close +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T3448155331"] = "Close" + -- Actions UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T3865031940"] = "Actions" @@ -2194,6 +2209,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T51130 -- Open Dashboard UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T78223861"] = "Open Dashboard" +-- Test +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T805092869"] = "Test" + -- Provider UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T900237532"] = "Provider" @@ -3274,6 +3292,12 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T900237532"] = "Pro -- Cancel UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T900713019"] = "Cancel" +-- Embedding Vector +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGRESULTDIALOG::T1173984541"] = "Embedding Vector" + +-- Close +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGRESULTDIALOG::T3448155331"] = "Close" + -- Unfortunately, Pandoc's GPL license isn't compatible with the AI Studios licenses. However, software under the GPL is free to use and free of charge. You'll need to accept the GPL license before we can download and install Pandoc for you automatically (recommended). Alternatively, you might download it yourself using the instructions below or install it otherwise, e.g., by using a package manager of your operating system. UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PANDOCDIALOG::T1001483402"] = "Unfortunately, Pandoc's GPL license isn't compatible with the AI Studios licenses. However, software under the GPL is free to use and free of charge. You'll need to accept the GPL license before we can download and install Pandoc for you automatically (recommended). Alternatively, you might download it yourself using the instructions below or install it otherwise, e.g., by using a package manager of your operating system." diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelEmbeddings.razor b/app/MindWork AI Studio/Components/Settings/SettingsPanelEmbeddings.razor index 874bc3c9..958abd8f 100644 --- a/app/MindWork AI Studio/Components/Settings/SettingsPanelEmbeddings.razor +++ b/app/MindWork AI Studio/Components/Settings/SettingsPanelEmbeddings.razor @@ -56,6 +56,9 @@ + + + } diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelEmbeddings.razor.cs b/app/MindWork AI Studio/Components/Settings/SettingsPanelEmbeddings.razor.cs index 94878987..cc9ef694 100644 --- a/app/MindWork AI Studio/Components/Settings/SettingsPanelEmbeddings.razor.cs +++ b/app/MindWork AI Studio/Components/Settings/SettingsPanelEmbeddings.razor.cs @@ -1,4 +1,6 @@ +using System.Globalization; using AIStudio.Dialogs; +using AIStudio.Provider; using AIStudio.Settings; using Microsoft.AspNetCore.Components; @@ -123,4 +125,48 @@ private async Task UpdateEmbeddingProviders() await this.AvailableEmbeddingProvidersChanged.InvokeAsync(this.AvailableEmbeddingProviders); } -} \ No newline at end of file + + private async Task TestEmbeddingProvider(EmbeddingProvider provider) + { + var dialogParameters = new DialogParameters + { + { x => x.ConfirmText, "Embed text" }, + { x => x.InputHeaderText, "Add text that should be embedded:" }, + { x => x.UserInput, "Add text here"} + }; + + var dialogReference = await this.DialogService.ShowAsync(T("Test Embedding Provider"), dialogParameters, DialogOptions.FULLSCREEN); + var dialogResult = await dialogReference.Result; + if (dialogResult is null || dialogResult.Canceled) + return; + + var inputText = dialogResult.Data as string; + if (string.IsNullOrWhiteSpace(inputText)) + return; + + var embeddingProvider = provider.CreateProvider(); + var embeddings = await embeddingProvider.EmbedTextAsync(provider.Model, this.SettingsManager, default, new List { inputText }); + + if (embeddings.Count == 0) + { + await this.DialogService.ShowMessageBox(T("Embedding Result"), T("No embedding was returned."), T("Close")); + return; + } + + var vector = embeddings.FirstOrDefault(); + if (vector is null || vector.Count == 0) + { + await this.DialogService.ShowMessageBox(T("Embedding Result"), T("No embedding was returned."), T("Close")); + return; + } + + var resultText = string.Join(Environment.NewLine, vector.Select(value => value.ToString("G9", CultureInfo.InvariantCulture))); + var resultParameters = new DialogParameters + { + { x => x.ResultText, resultText }, + { x => x.ResultLabel, T("Embedding Vector (one dimension per line)") }, + }; + + await this.DialogService.ShowAsync(T("Embedding Result"), resultParameters, DialogOptions.FULLSCREEN); + } +} diff --git a/app/MindWork AI Studio/Dialogs/EmbeddingResultDialog.razor b/app/MindWork AI Studio/Dialogs/EmbeddingResultDialog.razor new file mode 100644 index 00000000..8e1408ef --- /dev/null +++ b/app/MindWork AI Studio/Dialogs/EmbeddingResultDialog.razor @@ -0,0 +1,22 @@ +@inherits MSGComponentBase + + + + + + + + @T("Close") + + + diff --git a/app/MindWork AI Studio/Dialogs/EmbeddingResultDialog.razor.cs b/app/MindWork AI Studio/Dialogs/EmbeddingResultDialog.razor.cs new file mode 100644 index 00000000..96830edf --- /dev/null +++ b/app/MindWork AI Studio/Dialogs/EmbeddingResultDialog.razor.cs @@ -0,0 +1,21 @@ +using AIStudio.Components; + +using Microsoft.AspNetCore.Components; + +namespace AIStudio.Dialogs; + +public partial class EmbeddingResultDialog : MSGComponentBase +{ + [CascadingParameter] + private IMudDialogInstance MudDialog { get; set; } = null!; + + [Parameter] + public string ResultText { get; set; } = string.Empty; + + [Parameter] + public string ResultLabel { get; set; } = string.Empty; + + private string ResultLabelText => string.IsNullOrWhiteSpace(this.ResultLabel) ? T("Embedding Vector") : this.ResultLabel; + + private void Close() => this.MudDialog.Close(); +} 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 5f4adf5d..ff4c33d8 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 @@ -2142,9 +2142,15 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T817101267"] -- Would you like to set one provider as the default for the entire app? When you configure a different provider for an assistant, it will always take precedence. UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T844514734"] = "Möchten Sie einen Anbieter als Standard für die gesamte App festlegen? Wenn Sie einen anderen Anbieter für einen Assistenten konfigurieren, hat dieser immer Vorrang." +-- Embedding Result +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1387042335"] = "Einbettungsergebnis" + -- Delete UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1469573738"] = "Löschen" +-- Test Embedding Provider +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1655784761"] = "Embedding-Anbieter testen" + -- Add Embedding UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1738753945"] = "Einbettung hinzufügen" @@ -2157,6 +2163,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T18253 -- Add Embedding Provider UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T190634634"] = "Einbettungsanbieter hinzufügen" +-- Embedding Vector (one dimension per line) +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T2174876961"] = "Einbettungsvektor (eine Dimension pro Zeile)" + -- Model UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T2189814010"] = "Modell" @@ -2166,6 +2175,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T24199 -- Name UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T266367750"] = "Name" +-- No embedding was returned. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T291969"] = "Es wurde keine Einbettung zurückgegeben." + -- Configured Embedding Providers UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T305753126"] = "Konfigurierte Anbieter für Einbettungen" @@ -2175,6 +2187,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T32512 -- Edit UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T3267849393"] = "Bearbeiten" +-- Close +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T3448155331"] = "Schließen" + -- Actions UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T3865031940"] = "Aktionen" @@ -2196,6 +2211,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T51130 -- Open Dashboard UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T78223861"] = "Dashboard öffnen" +-- Test +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T805092869"] = "Testen" + -- Provider UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T900237532"] = "Anbieter" @@ -3276,6 +3294,12 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T900237532"] = "Anb -- Cancel UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T900713019"] = "Abbrechen" +-- Embedding Vector +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGRESULTDIALOG::T1173984541"] = "Einbettungsvektor" + +-- Close +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGRESULTDIALOG::T3448155331"] = "Schließen" + -- Unfortunately, Pandoc's GPL license isn't compatible with the AI Studios licenses. However, software under the GPL is free to use and free of charge. You'll need to accept the GPL license before we can download and install Pandoc for you automatically (recommended). Alternatively, you might download it yourself using the instructions below or install it otherwise, e.g., by using a package manager of your operating system. UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PANDOCDIALOG::T1001483402"] = "Leider ist die GPL-Lizenz von Pandoc nicht mit der Lizenz von AI Studio kompatibel. Software unter der GPL-Lizenz ist jedoch kostenlos und frei nutzbar. Sie müssen die GPL-Lizenz akzeptieren, bevor wir Pandoc automatisch für Sie herunterladen und installieren können (empfohlen). Alternativ können Sie Pandoc auch selbst herunterladen – entweder mit den untenstehenden Anweisungen oder auf anderem Weg, zum Beispiel über den Paketmanager Ihres Betriebssystems." 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 5a4f9f78..95fbd090 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 @@ -2142,9 +2142,15 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T817101267"] -- Would you like to set one provider as the default for the entire app? When you configure a different provider for an assistant, it will always take precedence. UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELAPP::T844514734"] = "Would you like to set one provider as the default for the entire app? When you configure a different provider for an assistant, it will always take precedence." +-- Embedding Result +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1387042335"] = "Embedding Result" + -- Delete UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1469573738"] = "Delete" +-- Test Embedding Provider +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1655784761"] = "Test Embedding Provider" + -- Add Embedding UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T1738753945"] = "Add Embedding" @@ -2157,6 +2163,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T18253 -- Add Embedding Provider UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T190634634"] = "Add Embedding Provider" +-- Embedding Vector (one dimension per line) +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T2174876961"] = "Embedding Vector (one dimension per line)" + -- Model UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T2189814010"] = "Model" @@ -2166,6 +2175,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T24199 -- Name UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T266367750"] = "Name" +-- No embedding was returned. +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T291969"] = "No embedding was returned." + -- Configured Embedding Providers UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T305753126"] = "Configured Embedding Providers" @@ -2175,6 +2187,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T32512 -- Edit UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T3267849393"] = "Edit" +-- Close +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T3448155331"] = "Close" + -- Actions UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T3865031940"] = "Actions" @@ -2196,6 +2211,9 @@ UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T51130 -- Open Dashboard UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T78223861"] = "Open Dashboard" +-- Test +UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T805092869"] = "Test" + -- Provider UI_TEXT_CONTENT["AISTUDIO::COMPONENTS::SETTINGS::SETTINGSPANELEMBEDDINGS::T900237532"] = "Provider" @@ -3276,6 +3294,12 @@ UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T900237532"] = "Pro -- Cancel UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGPROVIDERDIALOG::T900713019"] = "Cancel" +-- Embedding Vector +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGRESULTDIALOG::T1173984541"] = "Embedding Vector" + +-- Close +UI_TEXT_CONTENT["AISTUDIO::DIALOGS::EMBEDDINGRESULTDIALOG::T3448155331"] = "Close" + -- Unfortunately, Pandoc's GPL license isn't compatible with the AI Studios licenses. However, software under the GPL is free to use and free of charge. You'll need to accept the GPL license before we can download and install Pandoc for you automatically (recommended). Alternatively, you might download it yourself using the instructions below or install it otherwise, e.g., by using a package manager of your operating system. UI_TEXT_CONTENT["AISTUDIO::DIALOGS::PANDOCDIALOG::T1001483402"] = "Unfortunately, Pandoc's GPL license isn't compatible with the AI Studios licenses. However, software under the GPL is free to use and free of charge. You'll need to accept the GPL license before we can download and install Pandoc for you automatically (recommended). Alternatively, you might download it yourself using the instructions below or install it otherwise, e.g., by using a package manager of your operating system." diff --git a/app/MindWork AI Studio/Provider/AlibabaCloud/ProviderAlibabaCloud.cs b/app/MindWork AI Studio/Provider/AlibabaCloud/ProviderAlibabaCloud.cs index de46e95b..d39351b2 100644 --- a/app/MindWork AI Studio/Provider/AlibabaCloud/ProviderAlibabaCloud.cs +++ b/app/MindWork AI Studio/Provider/AlibabaCloud/ProviderAlibabaCloud.cs @@ -86,6 +86,13 @@ public override Task TranscribeAudioAsync(Model transcriptionModel, stri { return Task.FromResult(string.Empty); } + + /// + public override async Task>> EmbedTextAsync(Provider.Model embeddingModel, SettingsManager settingsManager, CancellationToken token = default, params List texts) + { + var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.EMBEDDING_PROVIDER); + return await this.PerformStandardTextEmbeddingRequest(requestedSecret, embeddingModel, token: token, texts: texts); + } /// public override Task> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) diff --git a/app/MindWork AI Studio/Provider/Anthropic/ProviderAnthropic.cs b/app/MindWork AI Studio/Provider/Anthropic/ProviderAnthropic.cs index b536ee4d..533670ad 100644 --- a/app/MindWork AI Studio/Provider/Anthropic/ProviderAnthropic.cs +++ b/app/MindWork AI Studio/Provider/Anthropic/ProviderAnthropic.cs @@ -113,6 +113,12 @@ public override Task TranscribeAudioAsync(Model transcriptionModel, stri { return Task.FromResult(string.Empty); } + + /// + public override Task>> EmbedTextAsync(Provider.Model embeddingModel, SettingsManager settingsManager, CancellationToken token = default, params List texts) + { + return Task.FromResult>>(Array.Empty>()); + } /// public override Task> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) diff --git a/app/MindWork AI Studio/Provider/BaseProvider.cs b/app/MindWork AI Studio/Provider/BaseProvider.cs index 8400e9a3..76108745 100644 --- a/app/MindWork AI Studio/Provider/BaseProvider.cs +++ b/app/MindWork AI Studio/Provider/BaseProvider.cs @@ -1,6 +1,7 @@ using System.Net; using System.Net.Http.Headers; using System.Runtime.CompilerServices; +using System.Text; using System.Text.Json; using System.Text.Json.Serialization; @@ -98,6 +99,9 @@ protected BaseProvider(LLMProviders provider, string url, ILogger logger) /// public abstract Task TranscribeAudioAsync(Model transcriptionModel, string audioFilePath, SettingsManager settingsManager, CancellationToken token = default); + /// + public abstract Task>> EmbedTextAsync(Model embeddingModel, SettingsManager settingsManager, CancellationToken token = default, params List texts); + /// public abstract Task> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default); @@ -640,6 +644,82 @@ protected async Task PerformStandardTranscriptionRequest(RequestedSecret } } + protected async Task>> PerformStandardTextEmbeddingRequest(RequestedSecret requestedSecret, Model embeddingModel, Host host = Host.NONE, CancellationToken token = default, params List texts) + { + try + { + // + // Add the model name to the form data. Ensure that a model name is always provided. + // Otherwise, the StringContent constructor will throw an exception. + // + var modelName = embeddingModel.Id; + if (string.IsNullOrWhiteSpace(modelName)) + modelName = "placeholder"; + + // Prepare the HTTP embedding request: + var payload = new + { + model = modelName, + input = texts, + encoding_format = "float" + }; + var embeddingRequest = JsonSerializer.Serialize(payload, JSON_SERIALIZER_OPTIONS); + + using var request = new HttpRequestMessage(HttpMethod.Post, host.EmbeddingURL()); + + // Handle the authorization header based on the provider: + switch (this.Provider) + { + case LLMProviders.SELF_HOSTED: + if(requestedSecret.Success) + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await requestedSecret.Secret.Decrypt(ENCRYPTION)); + + break; + + default: + if(!requestedSecret.Success) + { + this.logger.LogError("No valid API key available for embedding request."); + return Array.Empty>(); + } + + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", await requestedSecret.Secret.Decrypt(ENCRYPTION)); + break; + } + + // Set the content: + request.Content = new StringContent(embeddingRequest, Encoding.UTF8, "application/json"); + + using var response = await this.httpClient.SendAsync(request, token); + var responseBody = response.Content.ReadAsStringAsync(token).Result; + + if (!response.IsSuccessStatusCode) + { + this.logger.LogError("Embedding request failed with status code {ResponseStatusCode} and body: '{ResponseBody}'.", response.StatusCode, responseBody); + return Array.Empty>(); + } + + var embeddingResponse = JsonSerializer.Deserialize(responseBody, JSON_SERIALIZER_OPTIONS); + if (embeddingResponse is { Data: not null }) + { + return embeddingResponse.Data + .Select(d => d.Embedding?.ToArray() ?? Array.Empty()) + .Cast>() + .ToArray(); + } + else + { + this.logger.LogError("Was not able to deserialize the embedding response."); + return Array.Empty>(); + } + } + catch (Exception e) + { + this.logger.LogError("Failed to perform embedding request: '{Message}'.", e.Message); + return Array.Empty>(); + } + } + /// /// Parse and convert API parameters from a provided JSON string into a dictionary, /// optionally merging additional parameters and removing specific keys. diff --git a/app/MindWork AI Studio/Provider/DeepSeek/ProviderDeepSeek.cs b/app/MindWork AI Studio/Provider/DeepSeek/ProviderDeepSeek.cs index ce33f288..ce86ef68 100644 --- a/app/MindWork AI Studio/Provider/DeepSeek/ProviderDeepSeek.cs +++ b/app/MindWork AI Studio/Provider/DeepSeek/ProviderDeepSeek.cs @@ -86,6 +86,12 @@ public override Task TranscribeAudioAsync(Model transcriptionModel, stri { return Task.FromResult(string.Empty); } + + /// + public override Task>> EmbedTextAsync(Provider.Model embeddingModel, SettingsManager settingsManager, CancellationToken token = default, params List texts) + { + return Task.FromResult>>(Array.Empty>()); + } /// public override Task> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) diff --git a/app/MindWork AI Studio/Provider/EmbeddingResponse.cs b/app/MindWork AI Studio/Provider/EmbeddingResponse.cs new file mode 100644 index 00000000..89cf5fb8 --- /dev/null +++ b/app/MindWork AI Studio/Provider/EmbeddingResponse.cs @@ -0,0 +1,24 @@ +namespace AIStudio.Provider; + +public sealed record EmbeddingResponse +{ + public string? Id { get; set; } + public string? Object { get; set; } + public List? Data { get; set; } + public string? Model { get; set; } + public Usage? Usage { get; set; } +} + +public sealed record EmbeddingData +{ + public string? Object { get; set; } + public List? Embedding { get; set; } + public int? Index { get; set; } +} + +public sealed record Usage +{ + public int? PromptTokens { get; set; } + public int? TotalTokens { get; set; } + public int? CompletionTokens { get; set; } +} \ No newline at end of file diff --git a/app/MindWork AI Studio/Provider/Fireworks/ProviderFireworks.cs b/app/MindWork AI Studio/Provider/Fireworks/ProviderFireworks.cs index 1eb21894..cd7a5abd 100644 --- a/app/MindWork AI Studio/Provider/Fireworks/ProviderFireworks.cs +++ b/app/MindWork AI Studio/Provider/Fireworks/ProviderFireworks.cs @@ -88,6 +88,12 @@ public override async Task TranscribeAudioAsync(Model transcriptionModel var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.TRANSCRIPTION_PROVIDER); return await this.PerformStandardTranscriptionRequest(requestedSecret, transcriptionModel, audioFilePath, token: token); } + + /// + public override Task>> EmbedTextAsync(Provider.Model embeddingModel, SettingsManager settingsManager, CancellationToken token = default, params List texts) + { + return Task.FromResult>>(Array.Empty>()); + } /// public override Task> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) diff --git a/app/MindWork AI Studio/Provider/GWDG/ProviderGWDG.cs b/app/MindWork AI Studio/Provider/GWDG/ProviderGWDG.cs index 2b7e4dcb..784faa45 100644 --- a/app/MindWork AI Studio/Provider/GWDG/ProviderGWDG.cs +++ b/app/MindWork AI Studio/Provider/GWDG/ProviderGWDG.cs @@ -87,6 +87,12 @@ public override async Task TranscribeAudioAsync(Model transcriptionModel var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.TRANSCRIPTION_PROVIDER); return await this.PerformStandardTranscriptionRequest(requestedSecret, transcriptionModel, audioFilePath, token: token); } + + /// + public override Task>> EmbedTextAsync(Provider.Model embeddingModel, SettingsManager settingsManager, CancellationToken token = default, params List texts) + { + return Task.FromResult>>(Array.Empty>()); + } /// public override async Task> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) diff --git a/app/MindWork AI Studio/Provider/Google/ProviderGoogle.cs b/app/MindWork AI Studio/Provider/Google/ProviderGoogle.cs index 48dea49e..224fe5e6 100644 --- a/app/MindWork AI Studio/Provider/Google/ProviderGoogle.cs +++ b/app/MindWork AI Studio/Provider/Google/ProviderGoogle.cs @@ -87,6 +87,78 @@ public override Task TranscribeAudioAsync(Provider.Model transcriptionMo { return Task.FromResult(string.Empty); } + + /// + public override async Task>> EmbedTextAsync(Provider.Model embeddingModel, SettingsManager settingsManager, CancellationToken token = default, params List texts) + { + var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.EMBEDDING_PROVIDER); + try + { + var modelName = embeddingModel.Id; + if (string.IsNullOrWhiteSpace(modelName)) + { + LOGGER.LogError("No model name provided for embedding request."); + return Array.Empty>(); + } + + if (modelName.StartsWith("models/", StringComparison.OrdinalIgnoreCase)) + modelName = modelName.Substring("models/".Length); + + if (!requestedSecret.Success) + { + LOGGER.LogError("No valid API key available for embedding request."); + return Array.Empty>(); + } + + // Prepare the Google Gemini embedding request: + var payload = new + { + content = new + { + parts = texts.Select(text => new { text }).ToArray() + }, + taskType = "SEMANTIC_SIMILARITY" + }; + var embeddingRequest = JsonSerializer.Serialize(payload, JSON_SERIALIZER_OPTIONS); + + var embedUrl = $"https://generativelanguage.googleapis.com/v1beta/models/{modelName}:embedContent"; + using var request = new HttpRequestMessage(HttpMethod.Post, embedUrl); + request.Headers.Add("x-goog-api-key", await requestedSecret.Secret.Decrypt(ENCRYPTION)); + + // Set the content: + request.Content = new StringContent(embeddingRequest, Encoding.UTF8, "application/json"); + + using var response = await this.httpClient.SendAsync(request, token); + var responseBody = await response.Content.ReadAsStringAsync(token); + + if (!response.IsSuccessStatusCode) + { + LOGGER.LogError("Embedding request failed with status code {ResponseStatusCode} and body: '{ResponseBody}'.", response.StatusCode, responseBody); + return Array.Empty>(); + } + + var embeddingResponse = JsonSerializer.Deserialize(responseBody, JSON_SERIALIZER_OPTIONS); + + if (embeddingResponse is { Embedding: not null }) + { + return embeddingResponse.Embedding + .Select(d => d.Values?.ToArray() ?? Array.Empty()) + .Cast>() + .ToArray(); + } + else + { + LOGGER.LogError("Was not able to deserialize the embedding response."); + return Array.Empty>(); + } + + } + catch (Exception e) + { + LOGGER.LogError("Failed to perform embedding request: '{Message}'.", e.Message); + return Array.Empty>(); + } + } /// public override async Task> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) @@ -150,4 +222,14 @@ private async Task LoadModels(SecretStoreType storeType, Cancell var modelResponse = await response.Content.ReadFromJsonAsync(token); return modelResponse; } -} \ No newline at end of file + + private sealed record GoogleEmbeddingResponse + { + public List? Embedding { get; set; } + } + + private sealed record GoogleEmbedding + { + public List? Values { get; set; } + } +} diff --git a/app/MindWork AI Studio/Provider/Groq/ProviderGroq.cs b/app/MindWork AI Studio/Provider/Groq/ProviderGroq.cs index 07cdb390..1802150b 100644 --- a/app/MindWork AI Studio/Provider/Groq/ProviderGroq.cs +++ b/app/MindWork AI Studio/Provider/Groq/ProviderGroq.cs @@ -87,6 +87,12 @@ public override Task TranscribeAudioAsync(Model transcriptionModel, stri { return Task.FromResult(string.Empty); } + + /// + public override Task>> EmbedTextAsync(Provider.Model embeddingModel, SettingsManager settingsManager, CancellationToken token = default, params List texts) + { + return Task.FromResult>>(Array.Empty>()); + } /// public override Task> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) diff --git a/app/MindWork AI Studio/Provider/Helmholtz/ProviderHelmholtz.cs b/app/MindWork AI Studio/Provider/Helmholtz/ProviderHelmholtz.cs index ec5fca2c..3df909c0 100644 --- a/app/MindWork AI Studio/Provider/Helmholtz/ProviderHelmholtz.cs +++ b/app/MindWork AI Studio/Provider/Helmholtz/ProviderHelmholtz.cs @@ -86,6 +86,13 @@ public override Task TranscribeAudioAsync(Model transcriptionModel, stri { return Task.FromResult(string.Empty); } + + /// + public override async Task>> EmbedTextAsync(Provider.Model embeddingModel, SettingsManager settingsManager, CancellationToken token = default, params List texts) + { + var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.EMBEDDING_PROVIDER); + return await this.PerformStandardTextEmbeddingRequest(requestedSecret, embeddingModel, token: token, texts: texts); + } /// public override async Task> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) diff --git a/app/MindWork AI Studio/Provider/HuggingFace/ProviderHuggingFace.cs b/app/MindWork AI Studio/Provider/HuggingFace/ProviderHuggingFace.cs index a05ca11e..47db58e8 100644 --- a/app/MindWork AI Studio/Provider/HuggingFace/ProviderHuggingFace.cs +++ b/app/MindWork AI Studio/Provider/HuggingFace/ProviderHuggingFace.cs @@ -91,6 +91,12 @@ public override Task TranscribeAudioAsync(Model transcriptionModel, stri { return Task.FromResult(string.Empty); } + + /// + public override Task>> EmbedTextAsync(Provider.Model embeddingModel, SettingsManager settingsManager, CancellationToken token = default, params List texts) + { + return Task.FromResult>>(Array.Empty>()); + } /// public override Task> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) diff --git a/app/MindWork AI Studio/Provider/IProvider.cs b/app/MindWork AI Studio/Provider/IProvider.cs index 5c390074..ef15dd21 100644 --- a/app/MindWork AI Studio/Provider/IProvider.cs +++ b/app/MindWork AI Studio/Provider/IProvider.cs @@ -59,6 +59,16 @@ public interface IProvider /// The cancellation token. /// >The transcription result. public Task TranscribeAudioAsync(Model transcriptionModel, string audioFilePath, SettingsManager settingsManager, CancellationToken token = default); + + /// + /// Embed a text file. + /// + /// The model to use for embedding. + /// The settings manager instance to use. + /// The cancellation token. + /// /// A single string or a list of strings to embed. + /// >The embedded text as a single vector or as a list of vectors. + public Task>> EmbedTextAsync(Model embeddingModel, SettingsManager settingsManager, CancellationToken token = default, params List texts); /// /// Load all possible text models that can be used with this provider. diff --git a/app/MindWork AI Studio/Provider/Mistral/ProviderMistral.cs b/app/MindWork AI Studio/Provider/Mistral/ProviderMistral.cs index 6685e6d6..f4cb07f4 100644 --- a/app/MindWork AI Studio/Provider/Mistral/ProviderMistral.cs +++ b/app/MindWork AI Studio/Provider/Mistral/ProviderMistral.cs @@ -89,6 +89,13 @@ public override async Task TranscribeAudioAsync(Provider.Model transcrip return await this.PerformStandardTranscriptionRequest(requestedSecret, transcriptionModel, audioFilePath, token: token); } + /// + public override async Task>> EmbedTextAsync(Provider.Model embeddingModel, SettingsManager settingsManager, CancellationToken token = default, params List texts) + { + var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.EMBEDDING_PROVIDER); + return await this.PerformStandardTextEmbeddingRequest(requestedSecret, embeddingModel, token: token, texts: texts); + } + /// public override async Task> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) { diff --git a/app/MindWork AI Studio/Provider/NoProvider.cs b/app/MindWork AI Studio/Provider/NoProvider.cs index a650ac34..cb24e0fa 100644 --- a/app/MindWork AI Studio/Provider/NoProvider.cs +++ b/app/MindWork AI Studio/Provider/NoProvider.cs @@ -40,6 +40,8 @@ public async IAsyncEnumerable StreamImageCompletion(Model imageModel, public Task TranscribeAudioAsync(Model transcriptionModel, string audioFilePath, SettingsManager settingsManager, CancellationToken token = default) => Task.FromResult(string.Empty); + public Task>> EmbedTextAsync(Model embeddingModel, SettingsManager settingsManager, CancellationToken token = default, params List texts) => Task.FromResult>>(Array.Empty>()); + public IReadOnlyCollection GetModelCapabilities(Model model) => [ Capability.NONE ]; #endregion diff --git a/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs b/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs index d2d0b32b..90194ed7 100644 --- a/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs +++ b/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs @@ -224,6 +224,13 @@ public override async Task TranscribeAudioAsync(Model transcriptionModel var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.TRANSCRIPTION_PROVIDER); return await this.PerformStandardTranscriptionRequest(requestedSecret, transcriptionModel, audioFilePath, token: token); } + + /// + public override async Task>> EmbedTextAsync(Provider.Model embeddingModel, SettingsManager settingsManager, CancellationToken token = default, params List texts) + { + var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.EMBEDDING_PROVIDER); + return await this.PerformStandardTextEmbeddingRequest(requestedSecret, embeddingModel, token: token, texts: texts); + } /// public override async Task> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) diff --git a/app/MindWork AI Studio/Provider/OpenRouter/ProviderOpenRouter.cs b/app/MindWork AI Studio/Provider/OpenRouter/ProviderOpenRouter.cs index ca8ef155..444703c5 100644 --- a/app/MindWork AI Studio/Provider/OpenRouter/ProviderOpenRouter.cs +++ b/app/MindWork AI Studio/Provider/OpenRouter/ProviderOpenRouter.cs @@ -94,6 +94,13 @@ public override Task TranscribeAudioAsync(Model transcriptionModel, stri { return Task.FromResult(string.Empty); } + + /// + public override async Task>> EmbedTextAsync(Provider.Model embeddingModel, SettingsManager settingsManager, CancellationToken token = default, params List texts) + { + var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.EMBEDDING_PROVIDER); + return await this.PerformStandardTextEmbeddingRequest(requestedSecret, embeddingModel, token: token, texts: texts); + } /// public override Task> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) diff --git a/app/MindWork AI Studio/Provider/Perplexity/ProviderPerplexity.cs b/app/MindWork AI Studio/Provider/Perplexity/ProviderPerplexity.cs index 691dcdd5..804c8334 100644 --- a/app/MindWork AI Studio/Provider/Perplexity/ProviderPerplexity.cs +++ b/app/MindWork AI Studio/Provider/Perplexity/ProviderPerplexity.cs @@ -94,6 +94,12 @@ public override Task TranscribeAudioAsync(Model transcriptionModel, stri { return Task.FromResult(string.Empty); } + + /// + public override Task>> EmbedTextAsync(Provider.Model embeddingModel, SettingsManager settingsManager, CancellationToken token = default, params List texts) + { + return Task.FromResult>>(Array.Empty>()); + } /// public override Task> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) diff --git a/app/MindWork AI Studio/Provider/SelfHosted/HostExtensions.cs b/app/MindWork AI Studio/Provider/SelfHosted/HostExtensions.cs index 6c475273..25dc07ca 100644 --- a/app/MindWork AI Studio/Provider/SelfHosted/HostExtensions.cs +++ b/app/MindWork AI Studio/Provider/SelfHosted/HostExtensions.cs @@ -30,6 +30,11 @@ public static class HostExtensions _ => "audio/transcriptions", }; + public static string EmbeddingURL(this Host host) => host switch + { + _ => "embeddings", + }; + public static bool IsChatSupported(this Host host) { switch (host) diff --git a/app/MindWork AI Studio/Provider/SelfHosted/ProviderSelfHosted.cs b/app/MindWork AI Studio/Provider/SelfHosted/ProviderSelfHosted.cs index a1e411e1..9b3d6d67 100644 --- a/app/MindWork AI Studio/Provider/SelfHosted/ProviderSelfHosted.cs +++ b/app/MindWork AI Studio/Provider/SelfHosted/ProviderSelfHosted.cs @@ -95,6 +95,13 @@ public override async Task TranscribeAudioAsync(Provider.Model transcrip return await this.PerformStandardTranscriptionRequest(requestedSecret, transcriptionModel, audioFilePath, host, token); } + /// + public override async Task>> EmbedTextAsync(Provider.Model embeddingModel, SettingsManager settingsManager, CancellationToken token = default, params List texts) + { + var requestedSecret = await RUST_SERVICE.GetAPIKey(this, SecretStoreType.EMBEDDING_PROVIDER); + return await this.PerformStandardTextEmbeddingRequest(requestedSecret, embeddingModel, token: token, texts: texts); + } + public override async Task> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) { try diff --git a/app/MindWork AI Studio/Provider/X/ProviderX.cs b/app/MindWork AI Studio/Provider/X/ProviderX.cs index a0510dd6..22abaef0 100644 --- a/app/MindWork AI Studio/Provider/X/ProviderX.cs +++ b/app/MindWork AI Studio/Provider/X/ProviderX.cs @@ -88,6 +88,12 @@ public override Task TranscribeAudioAsync(Model transcriptionModel, stri return Task.FromResult(string.Empty); } + /// + public override Task>> EmbedTextAsync(Provider.Model embeddingModel, SettingsManager settingsManager, CancellationToken token = default, params List texts) + { + return Task.FromResult>>(Array.Empty>()); + } + /// public override async Task> GetTextModels(string? apiKeyProvisional = null, CancellationToken token = default) {