diff --git a/README.md b/README.md
index 79aa3526..0ad1f280 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@ Things we are currently working on:
- [x] ~~App: Add an option to show preview features (PR [#222](https://github.com/MindWorkAI/AI-Studio/pull/222))~~
- [x] ~~App: Configure embedding providers (PR [#224](https://github.com/MindWorkAI/AI-Studio/pull/224))~~
- [x] ~~App: Implement an [ERI](https://github.com/MindWorkAI/ERI) server coding assistant (PR [#231](https://github.com/MindWorkAI/AI-Studio/pull/231))~~
- - [x] ~~App: Management of data sources (local & external data via [ERI](https://github.com/MindWorkAI/ERI)) (PR [#259](https://github.com/MindWorkAI/AI-Studio/pull/259))~~
+ - [x] ~~App: Management of data sources (local & external data via [ERI](https://github.com/MindWorkAI/ERI)) (PR [#259](https://github.com/MindWorkAI/AI-Studio/pull/259), [#273](https://github.com/MindWorkAI/AI-Studio/pull/273))~~
- [ ] Runtime: Extract data from txt / md / pdf / docx / xlsx files
- [ ] (*Optional*) Runtime: Implement internal embedding provider through [fastembed-rs](https://github.com/Anush008/fastembed-rs)
- [ ] App: Implement external embedding providers
diff --git a/app/ERIClientV1/Client.Generated.cs b/app/ERIClientV1/Client.Generated.cs
deleted file mode 100644
index 96698e16..00000000
--- a/app/ERIClientV1/Client.Generated.cs
+++ /dev/null
@@ -1,1277 +0,0 @@
-//----------------------
-//
-// Generated using the NSwag toolchain v14.0.3.0 (NJsonSchema v11.0.0.0 (Newtonsoft.Json v13.0.0.0)) (http://NSwag.org)
-//
-//----------------------
-
-#pragma warning disable 108 // Disable "CS0108 '{derivedDto}.ToJson()' hides inherited member '{dtoBase}.ToJson()'. Use the new keyword if hiding was intended."
-#pragma warning disable 114 // Disable "CS0114 '{derivedDto}.RaisePropertyChanged(String)' hides inherited member 'dtoBase.RaisePropertyChanged(String)'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword."
-#pragma warning disable 472 // Disable "CS0472 The result of the expression is always 'false' since a value of type 'Int32' is never equal to 'null' of type 'Int32?'
-#pragma warning disable 612 // Disable "CS0612 '...' is obsolete"
-#pragma warning disable 1573 // Disable "CS1573 Parameter '...' has no matching param tag in the XML comment for ...
-#pragma warning disable 1591 // Disable "CS1591 Missing XML comment for publicly visible type or member ..."
-#pragma warning disable 8073 // Disable "CS8073 The result of the expression is always 'false' since a value of type 'T' is never equal to 'null' of type 'T?'"
-#pragma warning disable 3016 // Disable "CS3016 Arrays as attribute arguments is not CLS-compliant"
-#pragma warning disable 8603 // Disable "CS8603 Possible null reference return"
-#pragma warning disable 8604 // Disable "CS8604 Possible null reference argument for parameter"
-#pragma warning disable 8625 // Disable "CS8625 Cannot convert null literal to non-nullable reference type"
-
-namespace ERI_Client.V1
-{
- using System = global::System;
-
- [System.CodeDom.Compiler.GeneratedCode("NSwag", "14.0.3.0 (NJsonSchema v11.0.0.0 (Newtonsoft.Json v13.0.0.0))")]
- public partial class Client
- {
- private System.Net.Http.HttpClient _httpClient;
- private static System.Lazy _settings = new System.Lazy(CreateSerializerSettings, true);
-
- public Client(System.Net.Http.HttpClient httpClient)
- {
- _httpClient = httpClient;
- }
-
- private static System.Text.Json.JsonSerializerOptions CreateSerializerSettings()
- {
- var settings = new System.Text.Json.JsonSerializerOptions();
- UpdateJsonSerializerSettings(settings);
- return settings;
- }
-
- protected System.Text.Json.JsonSerializerOptions JsonSerializerSettings { get { return _settings.Value; } }
-
- static partial void UpdateJsonSerializerSettings(System.Text.Json.JsonSerializerOptions settings);
-
- partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, string url);
- partial void PrepareRequest(System.Net.Http.HttpClient client, System.Net.Http.HttpRequestMessage request, System.Text.StringBuilder urlBuilder);
- partial void ProcessResponse(System.Net.Http.HttpClient client, System.Net.Http.HttpResponseMessage response);
-
- ///
- /// Get the available authentication methods.
- ///
- /// OK
- /// A server side error occurred.
- public virtual System.Threading.Tasks.Task> GetAuthMethodsAsync()
- {
- return GetAuthMethodsAsync(System.Threading.CancellationToken.None);
- }
-
- /// A cancellation token that can be used by other objects or threads to receive notice of cancellation.
- ///
- /// Get the available authentication methods.
- ///
- /// OK
- /// A server side error occurred.
- public virtual async System.Threading.Tasks.Task> GetAuthMethodsAsync(System.Threading.CancellationToken cancellationToken)
- {
- var client_ = _httpClient;
- var disposeClient_ = false;
- try
- {
- using (var request_ = new System.Net.Http.HttpRequestMessage())
- {
- request_.Method = new System.Net.Http.HttpMethod("GET");
- request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json"));
-
- var urlBuilder_ = new System.Text.StringBuilder();
-
- // Operation Path: "auth/methods"
- urlBuilder_.Append("auth/methods");
-
- PrepareRequest(client_, request_, urlBuilder_);
-
- var url_ = urlBuilder_.ToString();
- request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);
-
- PrepareRequest(client_, request_, url_);
-
- var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
- var disposeResponse_ = true;
- try
- {
- var headers_ = new System.Collections.Generic.Dictionary>();
- foreach (var item_ in response_.Headers)
- headers_[item_.Key] = item_.Value;
- if (response_.Content != null && response_.Content.Headers != null)
- {
- foreach (var item_ in response_.Content.Headers)
- headers_[item_.Key] = item_.Value;
- }
-
- ProcessResponse(client_, response_);
-
- var status_ = (int)response_.StatusCode;
- if (status_ == 200)
- {
- var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false);
- if (objectResponse_.Object == null)
- {
- throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
- }
- return objectResponse_.Object;
- }
- else
- {
- var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
- throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null);
- }
- }
- finally
- {
- if (disposeResponse_)
- response_.Dispose();
- }
- }
- }
- finally
- {
- if (disposeClient_)
- client_.Dispose();
- }
- }
-
- ///
- /// Authenticate with the data source to get a token for further requests.
- ///
- /// OK
- /// A server side error occurred.
- public virtual System.Threading.Tasks.Task AuthenticateAsync(AuthMethod authMethod)
- {
- return AuthenticateAsync(authMethod, System.Threading.CancellationToken.None);
- }
-
- /// A cancellation token that can be used by other objects or threads to receive notice of cancellation.
- ///
- /// Authenticate with the data source to get a token for further requests.
- ///
- /// OK
- /// A server side error occurred.
- public virtual async System.Threading.Tasks.Task AuthenticateAsync(AuthMethod authMethod, System.Threading.CancellationToken cancellationToken)
- {
- if (authMethod == null)
- throw new System.ArgumentNullException("authMethod");
-
- var client_ = _httpClient;
- var disposeClient_ = false;
- try
- {
- using (var request_ = new System.Net.Http.HttpRequestMessage())
- {
- request_.Content = new System.Net.Http.StringContent(string.Empty, System.Text.Encoding.UTF8, "application/json");
- request_.Method = new System.Net.Http.HttpMethod("POST");
- request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json"));
-
- var urlBuilder_ = new System.Text.StringBuilder();
-
- // Operation Path: "auth"
- urlBuilder_.Append("auth");
- urlBuilder_.Append('?');
- urlBuilder_.Append(System.Uri.EscapeDataString("authMethod")).Append('=').Append(System.Uri.EscapeDataString(ConvertToString(authMethod, System.Globalization.CultureInfo.InvariantCulture))).Append('&');
- urlBuilder_.Length--;
-
- PrepareRequest(client_, request_, urlBuilder_);
-
- var url_ = urlBuilder_.ToString();
- request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);
-
- PrepareRequest(client_, request_, url_);
-
- var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
- var disposeResponse_ = true;
- try
- {
- var headers_ = new System.Collections.Generic.Dictionary>();
- foreach (var item_ in response_.Headers)
- headers_[item_.Key] = item_.Value;
- if (response_.Content != null && response_.Content.Headers != null)
- {
- foreach (var item_ in response_.Content.Headers)
- headers_[item_.Key] = item_.Value;
- }
-
- ProcessResponse(client_, response_);
-
- var status_ = (int)response_.StatusCode;
- if (status_ == 200)
- {
- var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
- if (objectResponse_.Object == null)
- {
- throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
- }
- return objectResponse_.Object;
- }
- else
- {
- var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
- throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null);
- }
- }
- finally
- {
- if (disposeResponse_)
- response_.Dispose();
- }
- }
- }
- finally
- {
- if (disposeClient_)
- client_.Dispose();
- }
- }
-
- ///
- /// Get information about the data source.
- ///
- /// OK
- /// A server side error occurred.
- public virtual System.Threading.Tasks.Task GetDataSourceInfoAsync()
- {
- return GetDataSourceInfoAsync(System.Threading.CancellationToken.None);
- }
-
- /// A cancellation token that can be used by other objects or threads to receive notice of cancellation.
- ///
- /// Get information about the data source.
- ///
- /// OK
- /// A server side error occurred.
- public virtual async System.Threading.Tasks.Task GetDataSourceInfoAsync(System.Threading.CancellationToken cancellationToken)
- {
- var client_ = _httpClient;
- var disposeClient_ = false;
- try
- {
- using (var request_ = new System.Net.Http.HttpRequestMessage())
- {
- request_.Method = new System.Net.Http.HttpMethod("GET");
- request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json"));
-
- var urlBuilder_ = new System.Text.StringBuilder();
-
- // Operation Path: "dataSource"
- urlBuilder_.Append("dataSource");
-
- PrepareRequest(client_, request_, urlBuilder_);
-
- var url_ = urlBuilder_.ToString();
- request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);
-
- PrepareRequest(client_, request_, url_);
-
- var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
- var disposeResponse_ = true;
- try
- {
- var headers_ = new System.Collections.Generic.Dictionary>();
- foreach (var item_ in response_.Headers)
- headers_[item_.Key] = item_.Value;
- if (response_.Content != null && response_.Content.Headers != null)
- {
- foreach (var item_ in response_.Content.Headers)
- headers_[item_.Key] = item_.Value;
- }
-
- ProcessResponse(client_, response_);
-
- var status_ = (int)response_.StatusCode;
- if (status_ == 200)
- {
- var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
- if (objectResponse_.Object == null)
- {
- throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
- }
- return objectResponse_.Object;
- }
- else
- {
- var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
- throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null);
- }
- }
- finally
- {
- if (disposeResponse_)
- response_.Dispose();
- }
- }
- }
- finally
- {
- if (disposeClient_)
- client_.Dispose();
- }
- }
-
- ///
- /// Get information about the used embedding(s).
- ///
- /// OK
- /// A server side error occurred.
- public virtual System.Threading.Tasks.Task> GetEmbeddingInfoAsync()
- {
- return GetEmbeddingInfoAsync(System.Threading.CancellationToken.None);
- }
-
- /// A cancellation token that can be used by other objects or threads to receive notice of cancellation.
- ///
- /// Get information about the used embedding(s).
- ///
- /// OK
- /// A server side error occurred.
- public virtual async System.Threading.Tasks.Task> GetEmbeddingInfoAsync(System.Threading.CancellationToken cancellationToken)
- {
- var client_ = _httpClient;
- var disposeClient_ = false;
- try
- {
- using (var request_ = new System.Net.Http.HttpRequestMessage())
- {
- request_.Method = new System.Net.Http.HttpMethod("GET");
- request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json"));
-
- var urlBuilder_ = new System.Text.StringBuilder();
-
- // Operation Path: "embedding/info"
- urlBuilder_.Append("embedding/info");
-
- PrepareRequest(client_, request_, urlBuilder_);
-
- var url_ = urlBuilder_.ToString();
- request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);
-
- PrepareRequest(client_, request_, url_);
-
- var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
- var disposeResponse_ = true;
- try
- {
- var headers_ = new System.Collections.Generic.Dictionary>();
- foreach (var item_ in response_.Headers)
- headers_[item_.Key] = item_.Value;
- if (response_.Content != null && response_.Content.Headers != null)
- {
- foreach (var item_ in response_.Content.Headers)
- headers_[item_.Key] = item_.Value;
- }
-
- ProcessResponse(client_, response_);
-
- var status_ = (int)response_.StatusCode;
- if (status_ == 200)
- {
- var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false);
- if (objectResponse_.Object == null)
- {
- throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
- }
- return objectResponse_.Object;
- }
- else
- {
- var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
- throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null);
- }
- }
- finally
- {
- if (disposeResponse_)
- response_.Dispose();
- }
- }
- }
- finally
- {
- if (disposeClient_)
- client_.Dispose();
- }
- }
-
- ///
- /// Get information about the retrieval processes implemented by this data source.
- ///
- /// OK
- /// A server side error occurred.
- public virtual System.Threading.Tasks.Task> GetRetrievalInfoAsync()
- {
- return GetRetrievalInfoAsync(System.Threading.CancellationToken.None);
- }
-
- /// A cancellation token that can be used by other objects or threads to receive notice of cancellation.
- ///
- /// Get information about the retrieval processes implemented by this data source.
- ///
- /// OK
- /// A server side error occurred.
- public virtual async System.Threading.Tasks.Task> GetRetrievalInfoAsync(System.Threading.CancellationToken cancellationToken)
- {
- var client_ = _httpClient;
- var disposeClient_ = false;
- try
- {
- using (var request_ = new System.Net.Http.HttpRequestMessage())
- {
- request_.Method = new System.Net.Http.HttpMethod("GET");
- request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json"));
-
- var urlBuilder_ = new System.Text.StringBuilder();
-
- // Operation Path: "retrieval/info"
- urlBuilder_.Append("retrieval/info");
-
- PrepareRequest(client_, request_, urlBuilder_);
-
- var url_ = urlBuilder_.ToString();
- request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);
-
- PrepareRequest(client_, request_, url_);
-
- var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
- var disposeResponse_ = true;
- try
- {
- var headers_ = new System.Collections.Generic.Dictionary>();
- foreach (var item_ in response_.Headers)
- headers_[item_.Key] = item_.Value;
- if (response_.Content != null && response_.Content.Headers != null)
- {
- foreach (var item_ in response_.Content.Headers)
- headers_[item_.Key] = item_.Value;
- }
-
- ProcessResponse(client_, response_);
-
- var status_ = (int)response_.StatusCode;
- if (status_ == 200)
- {
- var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false);
- if (objectResponse_.Object == null)
- {
- throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
- }
- return objectResponse_.Object;
- }
- else
- {
- var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
- throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null);
- }
- }
- finally
- {
- if (disposeResponse_)
- response_.Dispose();
- }
- }
- }
- finally
- {
- if (disposeClient_)
- client_.Dispose();
- }
- }
-
- ///
- /// Retrieve information from the data source.
- ///
- /// OK
- /// A server side error occurred.
- public virtual System.Threading.Tasks.Task> RetrieveAsync(RetrievalRequest body)
- {
- return RetrieveAsync(body, System.Threading.CancellationToken.None);
- }
-
- /// A cancellation token that can be used by other objects or threads to receive notice of cancellation.
- ///
- /// Retrieve information from the data source.
- ///
- /// OK
- /// A server side error occurred.
- public virtual async System.Threading.Tasks.Task> RetrieveAsync(RetrievalRequest body, System.Threading.CancellationToken cancellationToken)
- {
- if (body == null)
- throw new System.ArgumentNullException("body");
-
- var client_ = _httpClient;
- var disposeClient_ = false;
- try
- {
- using (var request_ = new System.Net.Http.HttpRequestMessage())
- {
- var json_ = System.Text.Json.JsonSerializer.SerializeToUtf8Bytes(body, _settings.Value);
- var content_ = new System.Net.Http.ByteArrayContent(json_);
- content_.Headers.ContentType = System.Net.Http.Headers.MediaTypeHeaderValue.Parse("application/json");
- request_.Content = content_;
- request_.Method = new System.Net.Http.HttpMethod("POST");
- request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json"));
-
- var urlBuilder_ = new System.Text.StringBuilder();
-
- // Operation Path: "retrieval"
- urlBuilder_.Append("retrieval");
-
- PrepareRequest(client_, request_, urlBuilder_);
-
- var url_ = urlBuilder_.ToString();
- request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);
-
- PrepareRequest(client_, request_, url_);
-
- var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
- var disposeResponse_ = true;
- try
- {
- var headers_ = new System.Collections.Generic.Dictionary>();
- foreach (var item_ in response_.Headers)
- headers_[item_.Key] = item_.Value;
- if (response_.Content != null && response_.Content.Headers != null)
- {
- foreach (var item_ in response_.Content.Headers)
- headers_[item_.Key] = item_.Value;
- }
-
- ProcessResponse(client_, response_);
-
- var status_ = (int)response_.StatusCode;
- if (status_ == 200)
- {
- var objectResponse_ = await ReadObjectResponseAsync>(response_, headers_, cancellationToken).ConfigureAwait(false);
- if (objectResponse_.Object == null)
- {
- throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
- }
- return objectResponse_.Object;
- }
- else
- {
- var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
- throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null);
- }
- }
- finally
- {
- if (disposeResponse_)
- response_.Dispose();
- }
- }
- }
- finally
- {
- if (disposeClient_)
- client_.Dispose();
- }
- }
-
- ///
- /// Get the security requirements for this data source.
- ///
- /// OK
- /// A server side error occurred.
- public virtual System.Threading.Tasks.Task GetSecurityRequirementsAsync()
- {
- return GetSecurityRequirementsAsync(System.Threading.CancellationToken.None);
- }
-
- /// A cancellation token that can be used by other objects or threads to receive notice of cancellation.
- ///
- /// Get the security requirements for this data source.
- ///
- /// OK
- /// A server side error occurred.
- public virtual async System.Threading.Tasks.Task GetSecurityRequirementsAsync(System.Threading.CancellationToken cancellationToken)
- {
- var client_ = _httpClient;
- var disposeClient_ = false;
- try
- {
- using (var request_ = new System.Net.Http.HttpRequestMessage())
- {
- request_.Method = new System.Net.Http.HttpMethod("GET");
- request_.Headers.Accept.Add(System.Net.Http.Headers.MediaTypeWithQualityHeaderValue.Parse("application/json"));
-
- var urlBuilder_ = new System.Text.StringBuilder();
-
- // Operation Path: "security/requirements"
- urlBuilder_.Append("security/requirements");
-
- PrepareRequest(client_, request_, urlBuilder_);
-
- var url_ = urlBuilder_.ToString();
- request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);
-
- PrepareRequest(client_, request_, url_);
-
- var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
- var disposeResponse_ = true;
- try
- {
- var headers_ = new System.Collections.Generic.Dictionary>();
- foreach (var item_ in response_.Headers)
- headers_[item_.Key] = item_.Value;
- if (response_.Content != null && response_.Content.Headers != null)
- {
- foreach (var item_ in response_.Content.Headers)
- headers_[item_.Key] = item_.Value;
- }
-
- ProcessResponse(client_, response_);
-
- var status_ = (int)response_.StatusCode;
- if (status_ == 200)
- {
- var objectResponse_ = await ReadObjectResponseAsync(response_, headers_, cancellationToken).ConfigureAwait(false);
- if (objectResponse_.Object == null)
- {
- throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
- }
- return objectResponse_.Object;
- }
- else
- {
- var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
- throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null);
- }
- }
- finally
- {
- if (disposeResponse_)
- response_.Dispose();
- }
- }
- }
- finally
- {
- if (disposeClient_)
- client_.Dispose();
- }
- }
-
- protected struct ObjectResponseResult
- {
- public ObjectResponseResult(T responseObject, string responseText)
- {
- this.Object = responseObject;
- this.Text = responseText;
- }
-
- public T Object { get; }
-
- public string Text { get; }
- }
-
- public bool ReadResponseAsString { get; set; }
-
- protected virtual async System.Threading.Tasks.Task> ReadObjectResponseAsync(System.Net.Http.HttpResponseMessage response, System.Collections.Generic.IReadOnlyDictionary> headers, System.Threading.CancellationToken cancellationToken)
- {
- if (response == null || response.Content == null)
- {
- return new ObjectResponseResult(default(T), string.Empty);
- }
-
- if (ReadResponseAsString)
- {
- var responseText = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
- try
- {
- var typedBody = System.Text.Json.JsonSerializer.Deserialize(responseText, JsonSerializerSettings);
- return new ObjectResponseResult(typedBody, responseText);
- }
- catch (System.Text.Json.JsonException exception)
- {
- var message = "Could not deserialize the response body string as " + typeof(T).FullName + ".";
- throw new ApiException(message, (int)response.StatusCode, responseText, headers, exception);
- }
- }
- else
- {
- try
- {
- using (var responseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false))
- {
- var typedBody = await System.Text.Json.JsonSerializer.DeserializeAsync(responseStream, JsonSerializerSettings, cancellationToken).ConfigureAwait(false);
- return new ObjectResponseResult(typedBody, string.Empty);
- }
- }
- catch (System.Text.Json.JsonException exception)
- {
- var message = "Could not deserialize the response body stream as " + typeof(T).FullName + ".";
- throw new ApiException(message, (int)response.StatusCode, string.Empty, headers, exception);
- }
- }
- }
-
- private string ConvertToString(object value, System.Globalization.CultureInfo cultureInfo)
- {
- if (value == null)
- {
- return "";
- }
-
- if (value is System.Enum)
- {
- var name = System.Enum.GetName(value.GetType(), value);
- if (name != null)
- {
- var field = System.Reflection.IntrospectionExtensions.GetTypeInfo(value.GetType()).GetDeclaredField(name);
- if (field != null)
- {
- var attribute = System.Reflection.CustomAttributeExtensions.GetCustomAttribute(field, typeof(System.Runtime.Serialization.EnumMemberAttribute))
- as System.Runtime.Serialization.EnumMemberAttribute;
- if (attribute != null)
- {
- return attribute.Value != null ? attribute.Value : name;
- }
- }
-
- var converted = System.Convert.ToString(System.Convert.ChangeType(value, System.Enum.GetUnderlyingType(value.GetType()), cultureInfo));
- return converted == null ? string.Empty : converted;
- }
- }
- else if (value is bool)
- {
- return System.Convert.ToString((bool)value, cultureInfo).ToLowerInvariant();
- }
- else if (value is byte[])
- {
- return System.Convert.ToBase64String((byte[]) value);
- }
- else if (value is string[])
- {
- return string.Join(",", (string[])value);
- }
- else if (value.GetType().IsArray)
- {
- var valueArray = (System.Array)value;
- var valueTextArray = new string[valueArray.Length];
- for (var i = 0; i < valueArray.Length; i++)
- {
- valueTextArray[i] = ConvertToString(valueArray.GetValue(i), cultureInfo);
- }
- return string.Join(",", valueTextArray);
- }
-
- var result = System.Convert.ToString(value, cultureInfo);
- return result == null ? "" : result;
- }
- }
-
- ///
- /// An authentication field.
- ///
- [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.0.3.0 (NJsonSchema v11.0.0.0 (Newtonsoft.Json v13.0.0.0))")]
- public enum AuthField
- {
-
- [System.Runtime.Serialization.EnumMember(Value = @"NONE")]
- NONE = 0,
-
- [System.Runtime.Serialization.EnumMember(Value = @"USERNAME")]
- USERNAME = 1,
-
- [System.Runtime.Serialization.EnumMember(Value = @"PASSWORD")]
- PASSWORD = 2,
-
- [System.Runtime.Serialization.EnumMember(Value = @"TOKEN")]
- TOKEN = 3,
-
- [System.Runtime.Serialization.EnumMember(Value = @"KERBEROS_TICKET")]
- KERBEROS_TICKET = 4,
-
- }
-
- ///
- /// The mapping between an AuthField and the field name in the authentication request.
- ///
- [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.0.3.0 (NJsonSchema v11.0.0.0 (Newtonsoft.Json v13.0.0.0))")]
- public partial class AuthFieldMapping
- {
-
- [System.Text.Json.Serialization.JsonPropertyName("authField")]
- [System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.JsonStringEnumConverter))]
- public AuthField AuthField { get; set; }
-
- ///
- /// The field name in the authentication request.
- ///
-
- [System.Text.Json.Serialization.JsonPropertyName("fieldName")]
- public string FieldName { get; set; }
-
- }
-
- [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.0.3.0 (NJsonSchema v11.0.0.0 (Newtonsoft.Json v13.0.0.0))")]
- public enum AuthMethod
- {
-
- [System.Runtime.Serialization.EnumMember(Value = @"NONE")]
- NONE = 0,
-
- [System.Runtime.Serialization.EnumMember(Value = @"KERBEROS")]
- KERBEROS = 1,
-
- [System.Runtime.Serialization.EnumMember(Value = @"USERNAME_PASSWORD")]
- USERNAME_PASSWORD = 2,
-
- [System.Runtime.Serialization.EnumMember(Value = @"TOKEN")]
- TOKEN = 3,
-
- }
-
- ///
- /// The response to an authentication request.
- ///
- [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.0.3.0 (NJsonSchema v11.0.0.0 (Newtonsoft.Json v13.0.0.0))")]
- public partial class AuthResponse
- {
- ///
- /// True, when the authentication was successful.
- ///
-
- [System.Text.Json.Serialization.JsonPropertyName("success")]
- public bool Success { get; set; }
-
- ///
- /// The token to use for further requests.
- ///
-
- [System.Text.Json.Serialization.JsonPropertyName("token")]
- public string Token { get; set; }
-
- ///
- /// When the authentication was not successful, this contains the reason.
- ///
-
- [System.Text.Json.Serialization.JsonPropertyName("message")]
- public string Message { get; set; }
-
- }
-
- ///
- /// Describes one authentication scheme for this data source.
- ///
- [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.0.3.0 (NJsonSchema v11.0.0.0 (Newtonsoft.Json v13.0.0.0))")]
- public partial class AuthScheme
- {
-
- [System.Text.Json.Serialization.JsonPropertyName("authMethod")]
- [System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.JsonStringEnumConverter))]
- public AuthMethod AuthMethod { get; set; }
-
- ///
- /// A list of field mappings for the authentication method. The client must know,
- ///
e.g., how the password field is named in the request.
- ///
-
- [System.Text.Json.Serialization.JsonPropertyName("authFieldMappings")]
- public System.Collections.Generic.ICollection AuthFieldMappings { get; set; }
-
- }
-
- ///
- /// A chat thread, which is a list of content blocks.
- ///
- [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.0.3.0 (NJsonSchema v11.0.0.0 (Newtonsoft.Json v13.0.0.0))")]
- public partial class ChatThread
- {
- ///
- /// The content blocks in this chat thread.
- ///
-
- [System.Text.Json.Serialization.JsonPropertyName("contentBlocks")]
- public System.Collections.Generic.ICollection ContentBlocks { get; set; }
-
- }
-
- ///
- /// A block of content of a chat thread.
- ///
- [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.0.3.0 (NJsonSchema v11.0.0.0 (Newtonsoft.Json v13.0.0.0))")]
- public partial class ContentBlock
- {
- ///
- /// The content of the block. Remember that images and other media are base64 encoded.
- ///
-
- [System.Text.Json.Serialization.JsonPropertyName("content")]
- public string Content { get; set; }
-
- [System.Text.Json.Serialization.JsonPropertyName("role")]
- [System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.JsonStringEnumConverter))]
- public Role Role { get; set; }
-
- [System.Text.Json.Serialization.JsonPropertyName("type")]
- [System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.JsonStringEnumConverter))]
- public ContentType Type { get; set; }
-
- }
-
- ///
- /// The type of content.
- ///
- [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.0.3.0 (NJsonSchema v11.0.0.0 (Newtonsoft.Json v13.0.0.0))")]
- public enum ContentType
- {
-
- [System.Runtime.Serialization.EnumMember(Value = @"NONE")]
- NONE = 0,
-
- [System.Runtime.Serialization.EnumMember(Value = @"UNKNOWN")]
- UNKNOWN = 1,
-
- [System.Runtime.Serialization.EnumMember(Value = @"TEXT")]
- TEXT = 2,
-
- [System.Runtime.Serialization.EnumMember(Value = @"IMAGE")]
- IMAGE = 3,
-
- [System.Runtime.Serialization.EnumMember(Value = @"VIDEO")]
- VIDEO = 4,
-
- [System.Runtime.Serialization.EnumMember(Value = @"AUDIO")]
- AUDIO = 5,
-
- [System.Runtime.Serialization.EnumMember(Value = @"SPEECH")]
- SPEECH = 6,
-
- }
-
- ///
- /// Matching context returned by the data source as a result of a retrieval request.
- ///
- [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.0.3.0 (NJsonSchema v11.0.0.0 (Newtonsoft.Json v13.0.0.0))")]
- public partial class Context
- {
- ///
- /// The name of the source, e.g., a document name, database name,
- ///
collection name, etc.
- ///
-
- [System.Text.Json.Serialization.JsonPropertyName("name")]
- public string Name { get; set; }
-
- ///
- /// What are the contents of the source? For example, is it a
- ///
dictionary, a book chapter, business concept, a paper, etc.
- ///
-
- [System.Text.Json.Serialization.JsonPropertyName("category")]
- public string Category { get; set; }
-
- ///
- /// The path to the content, e.g., a URL, a file path, a path in a
- ///
graph database, etc.
- ///
-
- [System.Text.Json.Serialization.JsonPropertyName("path")]
- public string Path { get; set; }
-
- [System.Text.Json.Serialization.JsonPropertyName("type")]
- [System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.JsonStringEnumConverter))]
- public ContentType Type { get; set; }
-
- ///
- /// The content that matched the user prompt. For text, you
- ///
return the matched text and, e.g., three words before and after it.
- ///
-
- [System.Text.Json.Serialization.JsonPropertyName("matchedContent")]
- public string MatchedContent { get; set; }
-
- ///
- /// The surrounding content of the matched content.
- ///
For text, you may return, e.g., one sentence or paragraph before and after
- ///
the matched content.
- ///
-
- [System.Text.Json.Serialization.JsonPropertyName("surroundingContent")]
- public System.Collections.Generic.ICollection SurroundingContent { get; set; }
-
- ///
- /// Links to related content, e.g., links to Wikipedia articles,
- ///
links to sources, etc.
- ///
-
- [System.Text.Json.Serialization.JsonPropertyName("links")]
- public System.Collections.Generic.ICollection Links { get; set; }
-
- }
-
- ///
- /// Information about the data source.
- ///
- [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.0.3.0 (NJsonSchema v11.0.0.0 (Newtonsoft.Json v13.0.0.0))")]
- public partial class DataSourceInfo
- {
- ///
- /// The name of the data source, e.g., "Internal Organization Documents."
- ///
-
- [System.Text.Json.Serialization.JsonPropertyName("name")]
- public string Name { get; set; }
-
- ///
- /// A short description of the data source. What kind of data does it contain?
- ///
What is the data source used for?
- ///
-
- [System.Text.Json.Serialization.JsonPropertyName("description")]
- public string Description { get; set; }
-
- }
-
- ///
- /// Represents information about the used embedding for this data source. The purpose of this information is to give the
- ///
interested user an idea of what kind of embedding is used and what it does.
- ///
- [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.0.3.0 (NJsonSchema v11.0.0.0 (Newtonsoft.Json v13.0.0.0))")]
- public partial class EmbeddingInfo
- {
- ///
- /// What kind of embedding is used. For example, "Transformer Embedding," "Contextual Word
- ///
Embedding," "Graph Embedding," etc.
- ///
-
- [System.Text.Json.Serialization.JsonPropertyName("embeddingType")]
- public string EmbeddingType { get; set; }
-
- ///
- /// Name the embedding used. This can be a library, a framework, or the name of the used
- ///
algorithm.
- ///
-
- [System.Text.Json.Serialization.JsonPropertyName("embeddingName")]
- public string EmbeddingName { get; set; }
-
- ///
- /// A short description of the embedding. Describe what the embedding is doing.
- ///
-
- [System.Text.Json.Serialization.JsonPropertyName("description")]
- public string Description { get; set; }
-
- ///
- /// Describe when the embedding is used. For example, when the user prompt contains certain
- ///
keywords, or anytime?
- ///
-
- [System.Text.Json.Serialization.JsonPropertyName("usedWhen")]
- public string UsedWhen { get; set; }
-
- ///
- /// A link to the embedding's documentation or the source code. Might be null.
- ///
-
- [System.Text.Json.Serialization.JsonPropertyName("link")]
- public string Link { get; set; }
-
- }
-
- ///
- /// Known types of providers that can process data.
- ///
- [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.0.3.0 (NJsonSchema v11.0.0.0 (Newtonsoft.Json v13.0.0.0))")]
- public enum ProviderType
- {
-
- [System.Runtime.Serialization.EnumMember(Value = @"NONE")]
- NONE = 0,
-
- [System.Runtime.Serialization.EnumMember(Value = @"ANY")]
- ANY = 1,
-
- [System.Runtime.Serialization.EnumMember(Value = @"SELF_HOSTED")]
- SELF_HOSTED = 2,
-
- }
-
- ///
- /// Information about a retrieval process, which this data source implements.
- ///
- [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.0.3.0 (NJsonSchema v11.0.0.0 (Newtonsoft.Json v13.0.0.0))")]
- public partial class RetrievalInfo
- {
- ///
- /// A unique identifier for the retrieval process. This can be a GUID, a unique name, or an increasing integer.
- ///
-
- [System.Text.Json.Serialization.JsonPropertyName("id")]
- public string Id { get; set; }
-
- ///
- /// The name of the retrieval process, e.g., "Keyword-Based Wikipedia Article Retrieval".
- ///
-
- [System.Text.Json.Serialization.JsonPropertyName("name")]
- public string Name { get; set; }
-
- ///
- /// A short description of the retrieval process. What kind of retrieval process is it?
- ///
-
- [System.Text.Json.Serialization.JsonPropertyName("description")]
- public string Description { get; set; }
-
- ///
- /// A link to the retrieval process's documentation, paper, Wikipedia article, or the source code. Might be null.
- ///
-
- [System.Text.Json.Serialization.JsonPropertyName("link")]
- public string Link { get; set; }
-
- ///
- /// A dictionary that describes the parameters of the retrieval process. The key is the parameter name,
- ///
and the value is a description of the parameter. Although each parameter will be sent as a string, the description should indicate the
- ///
expected type and range, e.g., 0.0 to 1.0 for a float parameter.
- ///
-
- [System.Text.Json.Serialization.JsonPropertyName("parametersDescription")]
- public System.Collections.Generic.IDictionary ParametersDescription { get; set; }
-
- ///
- /// A list of embeddings used in this retrieval process. It might be empty in case no embedding is used.
- ///
-
- [System.Text.Json.Serialization.JsonPropertyName("embeddings")]
- public System.Collections.Generic.ICollection Embeddings { get; set; }
-
- }
-
- ///
- /// The retrieval request sent by AI Studio.
- ///
- [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.0.3.0 (NJsonSchema v11.0.0.0 (Newtonsoft.Json v13.0.0.0))")]
- public partial class RetrievalRequest
- {
- ///
- /// The latest user prompt that AI Studio received.
- ///
-
- [System.Text.Json.Serialization.JsonPropertyName("latestUserPrompt")]
- public string LatestUserPrompt { get; set; }
-
- [System.Text.Json.Serialization.JsonPropertyName("latestUserPromptType")]
- [System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.JsonStringEnumConverter))]
- public ContentType LatestUserPromptType { get; set; }
-
- [System.Text.Json.Serialization.JsonPropertyName("thread")]
- public ChatThread Thread { get; set; }
-
- ///
- /// Optional. The ID of the retrieval process that the data source should use.
- ///
When null, the data source chooses an appropriate retrieval process. Selecting a retrieval process is optional
- ///
for AI Studio users. Most users do not specify a retrieval process.
- ///
-
- [System.Text.Json.Serialization.JsonPropertyName("retrievalProcessId")]
- public string RetrievalProcessId { get; set; }
-
- ///
- /// A dictionary of parameters that the data source should use for the retrieval process.
- ///
Although each parameter will be sent as a string, the retrieval process specifies the expected type and range.
- ///
-
- [System.Text.Json.Serialization.JsonPropertyName("parameters")]
- public System.Collections.Generic.IDictionary Parameters { get; set; }
-
- ///
- /// The maximum number of matches that the data source should return. AI Studio uses
- ///
any value below 1 to indicate that the data source should return as many matches as appropriate.
- ///
-
- [System.Text.Json.Serialization.JsonPropertyName("maxMatches")]
- public int MaxMatches { get; set; }
-
- }
-
- ///
- /// Possible roles of any chat thread.
- ///
- [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.0.3.0 (NJsonSchema v11.0.0.0 (Newtonsoft.Json v13.0.0.0))")]
- public enum Role
- {
-
- [System.Runtime.Serialization.EnumMember(Value = @"NONE")]
- NONE = 0,
-
- [System.Runtime.Serialization.EnumMember(Value = @"UNKNOW")]
- UNKNOW = 1,
-
- [System.Runtime.Serialization.EnumMember(Value = @"SYSTEM")]
- SYSTEM = 2,
-
- [System.Runtime.Serialization.EnumMember(Value = @"USER")]
- USER = 3,
-
- [System.Runtime.Serialization.EnumMember(Value = @"AI")]
- AI = 4,
-
- [System.Runtime.Serialization.EnumMember(Value = @"AGENT")]
- AGENT = 5,
-
- }
-
- ///
- /// Represents the security requirements for this data source.
- ///
- [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.0.3.0 (NJsonSchema v11.0.0.0 (Newtonsoft.Json v13.0.0.0))")]
- public partial class SecurityRequirements
- {
-
- [System.Text.Json.Serialization.JsonPropertyName("allowedProviderType")]
- [System.Text.Json.Serialization.JsonConverter(typeof(System.Text.Json.Serialization.JsonStringEnumConverter))]
- public ProviderType AllowedProviderType { get; set; }
-
- }
-
-
-
- [System.CodeDom.Compiler.GeneratedCode("NSwag", "14.0.3.0 (NJsonSchema v11.0.0.0 (Newtonsoft.Json v13.0.0.0))")]
- public partial class ApiException : System.Exception
- {
- public int StatusCode { get; private set; }
-
- public string Response { get; private set; }
-
- public System.Collections.Generic.IReadOnlyDictionary> Headers { get; private set; }
-
- public ApiException(string message, int statusCode, string response, System.Collections.Generic.IReadOnlyDictionary> headers, System.Exception innerException)
- : base(message + "\n\nStatus: " + statusCode + "\nResponse: \n" + ((response == null) ? "(null)" : response.Substring(0, response.Length >= 512 ? 512 : response.Length)), innerException)
- {
- StatusCode = statusCode;
- Response = response;
- Headers = headers;
- }
-
- public override string ToString()
- {
- return string.Format("HTTP Response: \n\n{0}\n\n{1}", Response, base.ToString());
- }
- }
-
- [System.CodeDom.Compiler.GeneratedCode("NSwag", "14.0.3.0 (NJsonSchema v11.0.0.0 (Newtonsoft.Json v13.0.0.0))")]
- public partial class ApiException : ApiException
- {
- public TResult Result { get; private set; }
-
- public ApiException(string message, int statusCode, string response, System.Collections.Generic.IReadOnlyDictionary> headers, TResult result, System.Exception innerException)
- : base(message, statusCode, response, headers, innerException)
- {
- Result = result;
- }
- }
-
-}
-
-#pragma warning restore 108
-#pragma warning restore 114
-#pragma warning restore 472
-#pragma warning restore 612
-#pragma warning restore 1573
-#pragma warning restore 1591
-#pragma warning restore 8073
-#pragma warning restore 3016
-#pragma warning restore 8603
-#pragma warning restore 8604
-#pragma warning restore 8625
\ No newline at end of file
diff --git a/app/ERIClientV1/ERIClientV1.csproj b/app/ERIClientV1/ERIClientV1.csproj
deleted file mode 100644
index 8ca0662e..00000000
--- a/app/ERIClientV1/ERIClientV1.csproj
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
- net8.0
- latest
- enable
- enable
-
-
-
diff --git a/app/MindWork AI Studio.sln b/app/MindWork AI Studio.sln
index 6bf20b24..ef02b517 100644
--- a/app/MindWork AI Studio.sln
+++ b/app/MindWork AI Studio.sln
@@ -2,10 +2,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MindWork AI Studio", "MindWork AI Studio\MindWork AI Studio.csproj", "{059FDFCC-7D0B-474E-9F20-B9C437DF1CDD}"
EndProject
-Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ERIClients", "ERIClients", "{5C2AF789-287B-4FCB-B675-7273D8CD4579}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ERIClientV1", "ERIClientV1\ERIClientV1.csproj", "{9E35A273-0FA6-4BD5-8880-A1DDAC106926}"
-EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -16,12 +12,7 @@ Global
{059FDFCC-7D0B-474E-9F20-B9C437DF1CDD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{059FDFCC-7D0B-474E-9F20-B9C437DF1CDD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{059FDFCC-7D0B-474E-9F20-B9C437DF1CDD}.Release|Any CPU.Build.0 = Release|Any CPU
- {9E35A273-0FA6-4BD5-8880-A1DDAC106926}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {9E35A273-0FA6-4BD5-8880-A1DDAC106926}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {9E35A273-0FA6-4BD5-8880-A1DDAC106926}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {9E35A273-0FA6-4BD5-8880-A1DDAC106926}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
- {9E35A273-0FA6-4BD5-8880-A1DDAC106926} = {5C2AF789-287B-4FCB-B675-7273D8CD4579}
EndGlobalSection
EndGlobal
diff --git a/app/MindWork AI Studio/Assistants/ERI/ERIVersionExtensions.cs b/app/MindWork AI Studio/Assistants/ERI/ERIVersionExtensions.cs
index c7ef7c16..99631e14 100644
--- a/app/MindWork AI Studio/Assistants/ERI/ERIVersionExtensions.cs
+++ b/app/MindWork AI Studio/Assistants/ERI/ERIVersionExtensions.cs
@@ -7,7 +7,7 @@ public static class ERIVersionExtensions
try
{
var url = version.SpecificationURL();
- var response = await httpClient.GetAsync(url);
+ using var response = await httpClient.GetAsync(url);
return await response.Content.ReadAsStringAsync();
}
catch
diff --git a/app/MindWork AI Studio/Components/Changelog.razor.cs b/app/MindWork AI Studio/Components/Changelog.razor.cs
index c90a46c6..bba8b8a5 100644
--- a/app/MindWork AI Studio/Components/Changelog.razor.cs
+++ b/app/MindWork AI Studio/Components/Changelog.razor.cs
@@ -23,7 +23,7 @@ public partial class Changelog : ComponentBase
private async Task ReadLogAsync()
{
- var response = await this.HttpClient.GetAsync($"changelog/{this.SelectedLog.Filename}");
+ using var response = await this.HttpClient.GetAsync($"changelog/{this.SelectedLog.Filename}");
this.LogContent = await response.Content.ReadAsStringAsync();
}
}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelDataSources.razor b/app/MindWork AI Studio/Components/Settings/SettingsPanelDataSources.razor
index 3f19bf9f..506c63b9 100644
--- a/app/MindWork AI Studio/Components/Settings/SettingsPanelDataSources.razor
+++ b/app/MindWork AI Studio/Components/Settings/SettingsPanelDataSources.razor
@@ -1,4 +1,3 @@
-@using AIStudio.Settings
@using AIStudio.Settings.DataModel
@inherits SettingsPanelBase
@@ -37,12 +36,7 @@
@this.GetEmbeddingName(context)
- @if (context is IERIDataSource)
- {
- @* *@
- @* Show Information *@
- @* *@
- }
+
Edit
diff --git a/app/MindWork AI Studio/Components/Settings/SettingsPanelDataSources.razor.cs b/app/MindWork AI Studio/Components/Settings/SettingsPanelDataSources.razor.cs
index 03124527..6f7e74b8 100644
--- a/app/MindWork AI Studio/Components/Settings/SettingsPanelDataSources.razor.cs
+++ b/app/MindWork AI Studio/Components/Settings/SettingsPanelDataSources.razor.cs
@@ -1,8 +1,7 @@
using AIStudio.Dialogs;
using AIStudio.Settings;
using AIStudio.Settings.DataModel;
-
-using ERI_Client.V1;
+using AIStudio.Tools.ERIClient.DataModel;
using Microsoft.AspNetCore.Components;
@@ -216,10 +215,37 @@ public partial class SettingsPanelDataSources : SettingsPanelBase
}
}
- private Task ShowInformation(IDataSource dataSource)
+ private async Task ShowInformation(IDataSource dataSource)
{
- #warning Implement the information dialog for ERI data sources.
- return Task.CompletedTask;
+ switch (dataSource)
+ {
+ case DataSourceLocalFile localFile:
+ var localFileDialogParameters = new DialogParameters
+ {
+ { x => x.DataSource, localFile },
+ };
+
+ await this.DialogService.ShowAsync("Local File Data Source Information", localFileDialogParameters, DialogOptions.FULLSCREEN);
+ break;
+
+ case DataSourceLocalDirectory localDirectory:
+ var localDirectoryDialogParameters = new DialogParameters
+ {
+ { x => x.DataSource, localDirectory },
+ };
+
+ await this.DialogService.ShowAsync("Local Directory Data Source Information", localDirectoryDialogParameters, DialogOptions.FULLSCREEN);
+ break;
+
+ case DataSourceERI_V1 eriV1DataSource:
+ var eriV1DialogParameters = new DialogParameters
+ {
+ { x => x.DataSource, eriV1DataSource },
+ };
+
+ await this.DialogService.ShowAsync("ERI v1 Data Source Information", eriV1DialogParameters, DialogOptions.FULLSCREEN);
+ break;
+ }
}
private async Task UpdateDataSources()
diff --git a/app/MindWork AI Studio/Components/TextInfoLine.razor b/app/MindWork AI Studio/Components/TextInfoLine.razor
new file mode 100644
index 00000000..5e5de4da
--- /dev/null
+++ b/app/MindWork AI Studio/Components/TextInfoLine.razor
@@ -0,0 +1,19 @@
+
+
+
+ @if (this.ShowingCopyButton)
+ {
+
+
+
+ }
+
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Components/TextInfoLine.razor.cs b/app/MindWork AI Studio/Components/TextInfoLine.razor.cs
new file mode 100644
index 00000000..4c4e6409
--- /dev/null
+++ b/app/MindWork AI Studio/Components/TextInfoLine.razor.cs
@@ -0,0 +1,50 @@
+using AIStudio.Settings;
+
+using Microsoft.AspNetCore.Components;
+
+namespace AIStudio.Components;
+
+public partial class TextInfoLine : ComponentBase
+{
+ [Parameter]
+ public string Label { get; set; } = string.Empty;
+
+ [Parameter]
+ public string Icon { get; set; } = Icons.Material.Filled.Info;
+
+ [Parameter]
+ public string Value { get; set; } = string.Empty;
+
+ [Parameter]
+ public string ClipboardTooltipSubject { get; set; } = "the text";
+
+ [Parameter]
+ public bool ShowingCopyButton { get; set; } = true;
+
+ [Inject]
+ private RustService RustService { get; init; } = null!;
+
+ [Inject]
+ private ISnackbar Snackbar { get; init; } = null!;
+
+ [Inject]
+ private SettingsManager SettingsManager { get; init; } = null!;
+
+ #region Overrides of ComponentBase
+
+ protected override async Task OnInitializedAsync()
+ {
+ // Configure the spellchecking for the user input:
+ this.SettingsManager.InjectSpellchecking(USER_INPUT_ATTRIBUTES);
+
+ await base.OnInitializedAsync();
+ }
+
+ #endregion
+
+ private static readonly Dictionary USER_INPUT_ATTRIBUTES = new();
+
+ private string ClipboardTooltip => $"Copy {this.ClipboardTooltipSubject} to the clipboard";
+
+ private async Task CopyToClipboard(string content) => await this.RustService.CopyText2Clipboard(this.Snackbar, content);
+}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Components/TextInfoLines.razor b/app/MindWork AI Studio/Components/TextInfoLines.razor
new file mode 100644
index 00000000..02dadb48
--- /dev/null
+++ b/app/MindWork AI Studio/Components/TextInfoLines.razor
@@ -0,0 +1,20 @@
+
+
+
+ @if (this.ShowingCopyButton)
+ {
+
+
+
+ }
+
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Components/TextInfoLines.razor.cs b/app/MindWork AI Studio/Components/TextInfoLines.razor.cs
new file mode 100644
index 00000000..64d5924d
--- /dev/null
+++ b/app/MindWork AI Studio/Components/TextInfoLines.razor.cs
@@ -0,0 +1,50 @@
+using AIStudio.Settings;
+
+using Microsoft.AspNetCore.Components;
+
+namespace AIStudio.Components;
+
+public partial class TextInfoLines : ComponentBase
+{
+ [Parameter]
+ public string Label { get; set; } = string.Empty;
+
+ [Parameter]
+ public string Value { get; set; } = string.Empty;
+
+ [Parameter]
+ public string ClipboardTooltipSubject { get; set; } = "the text";
+
+ [Parameter]
+ public int MaxLines { get; set; } = 30;
+
+ [Parameter]
+ public bool ShowingCopyButton { get; set; } = true;
+
+ [Inject]
+ private RustService RustService { get; init; } = null!;
+
+ [Inject]
+ private ISnackbar Snackbar { get; init; } = null!;
+
+ [Inject]
+ private SettingsManager SettingsManager { get; init; } = null!;
+
+ #region Overrides of ComponentBase
+
+ protected override async Task OnInitializedAsync()
+ {
+ // Configure the spellchecking for the user input:
+ this.SettingsManager.InjectSpellchecking(USER_INPUT_ATTRIBUTES);
+
+ await base.OnInitializedAsync();
+ }
+
+ #endregion
+
+ private static readonly Dictionary USER_INPUT_ATTRIBUTES = new();
+
+ private string ClipboardTooltip => $"Copy {this.ClipboardTooltipSubject} to the clipboard";
+
+ private async Task CopyToClipboard(string content) => await this.RustService.CopyText2Clipboard(this.Snackbar, content);
+}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Dialogs/DataSourceERI-V1InfoDialog.razor b/app/MindWork AI Studio/Dialogs/DataSourceERI-V1InfoDialog.razor
new file mode 100644
index 00000000..a1ebe82d
--- /dev/null
+++ b/app/MindWork AI Studio/Dialogs/DataSourceERI-V1InfoDialog.razor
@@ -0,0 +1,101 @@
+@using AIStudio.Tools.ERIClient.DataModel
+
+
+
+
+ Common data source information
+
+
+
+
+
+
+ @if (!this.IsConnectionEncrypted())
+ {
+
+ Please note: the connection to the ERI v1 server is not encrypted. This means that all
+ data sent to the server is transmitted in plain text. Please ask the ERI server administrator
+ to enable encryption.
+
+ }
+
+ @if (this.DataSource.AuthMethod is AuthMethod.USERNAME_PASSWORD)
+ {
+
+ }
+
+
+
+
+
+ Retrieval information
+
+ @if (!this.retrievalInfoformation.Any())
+ {
+
+ The data source does not provide any retrieval information.
+
+ }
+ else
+ {
+
+ @for (var index = 0; index < this.retrievalInfoformation.Count; index++)
+ {
+ var info = this.retrievalInfoformation[index];
+
+
+
+
+ @if (!string.IsNullOrWhiteSpace(info.Link))
+ {
+
+ Open web link, show more information
+
+ }
+
+
+ Embeddings
+
+ @if (!info.Embeddings.Any())
+ {
+
+ The data source does not provide any embedding information.
+
+ }
+ else
+ {
+
+ @for (var embeddingIndex = 0; embeddingIndex < info.Embeddings.Count; embeddingIndex++)
+ {
+ var embedding = info.Embeddings[embeddingIndex];
+
+
+
+
+
+ @if (!string.IsNullOrWhiteSpace(embedding.Link))
+ {
+
+ Open web link, show more information
+
+ }
+
+ }
+
+ }
+
+ }
+
+ }
+
+
+
+
+ @if (this.IsOperationInProgress)
+ {
+
+ }
+ Reload
+ Close
+
+
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Dialogs/DataSourceERI-V1InfoDialog.razor.cs b/app/MindWork AI Studio/Dialogs/DataSourceERI-V1InfoDialog.razor.cs
new file mode 100644
index 00000000..304c4f67
--- /dev/null
+++ b/app/MindWork AI Studio/Dialogs/DataSourceERI-V1InfoDialog.razor.cs
@@ -0,0 +1,178 @@
+// ReSharper disable InconsistentNaming
+
+using System.Text;
+
+using AIStudio.Assistants.ERI;
+using AIStudio.Settings.DataModel;
+using AIStudio.Tools.ERIClient;
+using AIStudio.Tools.ERIClient.DataModel;
+
+using Microsoft.AspNetCore.Components;
+
+using RetrievalInfo = AIStudio.Tools.ERIClient.DataModel.RetrievalInfo;
+
+namespace AIStudio.Dialogs;
+
+public partial class DataSourceERI_V1InfoDialog : ComponentBase, IAsyncDisposable, ISecretId
+{
+ [CascadingParameter]
+ private MudDialogInstance MudDialog { get; set; } = null!;
+
+ [Parameter]
+ public DataSourceERI_V1 DataSource { get; set; }
+
+ [Inject]
+ private RustService RustService { get; init; } = null!;
+
+ #region Overrides of ComponentBase
+
+ protected override async Task OnInitializedAsync()
+ {
+ this.eriServerTasks.Add(this.GetERIMetadata());
+ await base.OnInitializedAsync();
+ }
+
+ #endregion
+
+ private readonly CancellationTokenSource cts = new();
+ private readonly List eriServerTasks = new();
+ private readonly List dataIssues = [];
+
+ private string serverDescription = string.Empty;
+ private ProviderType securityRequirements = ProviderType.NONE;
+ private IReadOnlyList retrievalInfoformation = [];
+
+ private bool IsOperationInProgress { get; set; } = true;
+
+ private bool IsConnectionEncrypted() => this.DataSource.Hostname.StartsWith("https://", StringComparison.InvariantCultureIgnoreCase);
+
+ private string Port => this.DataSource.Port == 0 ? string.Empty : $"{this.DataSource.Port}";
+
+ private string RetrievalName(RetrievalInfo retrievalInfo)
+ {
+ var hasId = !string.IsNullOrWhiteSpace(retrievalInfo.Id);
+ var hasName = !string.IsNullOrWhiteSpace(retrievalInfo.Name);
+
+ if (hasId && hasName)
+ return $"[{retrievalInfo.Id}] {retrievalInfo.Name}";
+
+ if (hasId)
+ return $"[{retrievalInfo.Id}] Unnamed retrieval process";
+
+ return hasName ? retrievalInfo.Name : "Unnamed retrieval process";
+ }
+
+ private string RetrievalParameters(RetrievalInfo retrievalInfo)
+ {
+ var parameters = retrievalInfo.ParametersDescription;
+ if (parameters is null || parameters.Count == 0)
+ return "This retrieval process has no parameters.";
+
+ var sb = new StringBuilder();
+ foreach (var (paramName, description) in parameters)
+ {
+ sb.Append("Parameter: ");
+ sb.AppendLine(paramName);
+ sb.AppendLine(description);
+ sb.AppendLine();
+ }
+
+ return sb.ToString();
+ }
+
+ private async Task GetERIMetadata()
+ {
+ this.dataIssues.Clear();
+
+ try
+ {
+ this.IsOperationInProgress = true;
+ this.StateHasChanged();
+
+ using var client = ERIClientFactory.Get(ERIVersion.V1, this.DataSource);
+ if(client is null)
+ {
+ this.dataIssues.Add("Failed to connect to the ERI v1 server. The server is not supported.");
+ return;
+ }
+
+ var loginResult = await client.AuthenticateAsync(this.DataSource, this.RustService);
+ if (!loginResult.Successful)
+ {
+ this.dataIssues.Add(loginResult.Message);
+ return;
+ }
+
+ var dataSourceInfo = await client.GetDataSourceInfoAsync(this.cts.Token);
+ if (!dataSourceInfo.Successful)
+ {
+ this.dataIssues.Add(dataSourceInfo.Message);
+ return;
+ }
+
+ this.serverDescription = dataSourceInfo.Data.Description;
+
+ var securityRequirementsResult = await client.GetSecurityRequirementsAsync(this.cts.Token);
+ if (!securityRequirementsResult.Successful)
+ {
+ this.dataIssues.Add(securityRequirementsResult.Message);
+ return;
+ }
+
+ this.securityRequirements = securityRequirementsResult.Data.AllowedProviderType;
+
+ var retrievalInfoResult = await client.GetRetrievalInfoAsync(this.cts.Token);
+ if (!retrievalInfoResult.Successful)
+ {
+ this.dataIssues.Add(retrievalInfoResult.Message);
+ return;
+ }
+
+ this.retrievalInfoformation = retrievalInfoResult.Data ?? [];
+
+ this.StateHasChanged();
+ }
+ catch (Exception e)
+ {
+ this.dataIssues.Add($"Failed to connect to the ERI v1 server. The message was: {e.Message}");
+ }
+ finally
+ {
+ this.IsOperationInProgress = false;
+ this.StateHasChanged();
+ }
+ }
+
+ private void Close()
+ {
+ this.cts.Cancel();
+ this.MudDialog.Close();
+ }
+
+ #region Implementation of ISecretId
+
+ public string SecretId => this.DataSource.Id;
+
+ public string SecretName => this.DataSource.Name;
+
+ #endregion
+
+ #region Implementation of IDisposable
+
+ public async ValueTask DisposeAsync()
+ {
+ try
+ {
+ await this.cts.CancelAsync();
+ await Task.WhenAll(this.eriServerTasks);
+
+ this.cts.Dispose();
+ }
+ catch
+ {
+ // ignored
+ }
+ }
+
+ #endregion
+}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Dialogs/DataSourceERI_V1Dialog.razor b/app/MindWork AI Studio/Dialogs/DataSourceERI_V1Dialog.razor
index 64e307b9..ac40ffa5 100644
--- a/app/MindWork AI Studio/Dialogs/DataSourceERI_V1Dialog.razor
+++ b/app/MindWork AI Studio/Dialogs/DataSourceERI_V1Dialog.razor
@@ -1,4 +1,4 @@
-@using ERI_Client.V1
+@using AIStudio.Tools.ERIClient.DataModel
diff --git a/app/MindWork AI Studio/Dialogs/DataSourceERI_V1Dialog.razor.cs b/app/MindWork AI Studio/Dialogs/DataSourceERI_V1Dialog.razor.cs
index 1e4eaf7d..aaa4dac1 100644
--- a/app/MindWork AI Studio/Dialogs/DataSourceERI_V1Dialog.razor.cs
+++ b/app/MindWork AI Studio/Dialogs/DataSourceERI_V1Dialog.razor.cs
@@ -1,9 +1,10 @@
+using AIStudio.Assistants.ERI;
using AIStudio.Settings;
using AIStudio.Settings.DataModel;
+using AIStudio.Tools.ERIClient;
+using AIStudio.Tools.ERIClient.DataModel;
using AIStudio.Tools.Validation;
-using ERI_Client.V1;
-
using Microsoft.AspNetCore.Components;
// ReSharper disable InconsistentNaming
@@ -43,7 +44,6 @@ public partial class DataSourceERI_V1Dialog : ComponentBase, ISecretId
private string[] dataIssues = [];
private string dataSecretStorageIssue = string.Empty;
private string dataEditingPreviousInstanceName = string.Empty;
- private HttpClient? httpClient;
private List availableAuthMethods = [];
private bool connectionTested;
private bool connectionSuccessfulTested;
@@ -167,31 +167,38 @@ public partial class DataSourceERI_V1Dialog : ComponentBase, ISecretId
{
try
{
- this.httpClient = new HttpClient
+ var cts = new CancellationTokenSource(TimeSpan.FromSeconds(14));
+ var dataSource = new DataSourceERI_V1
{
- BaseAddress = new Uri($"{this.dataHostname}:{this.dataPort}"),
- Timeout = TimeSpan.FromSeconds(5),
+ Hostname = this.dataHostname,
+ Port = this.dataPort
};
-
- using (this.httpClient)
+
+ using var client = ERIClientFactory.Get(ERIVersion.V1, dataSource);
+ if(client is null)
{
- var client = new Client(this.httpClient);
- var authSchemes = await client.GetAuthMethodsAsync();
- if (authSchemes is null)
- {
- await this.form.Validate();
-
- Array.Resize(ref this.dataIssues, this.dataIssues.Length + 1);
- this.dataIssues[^1] = "Failed to connect to the ERI v1 server. The server did not respond.";
- return;
- }
-
- this.availableAuthMethods = authSchemes.Select(n => n.AuthMethod).ToList();
-
- this.connectionTested = true;
- this.connectionSuccessfulTested = true;
- this.Logger.LogInformation("Connection to the ERI v1 server was successful tested.");
+ await this.form.Validate();
+
+ Array.Resize(ref this.dataIssues, this.dataIssues.Length + 1);
+ this.dataIssues[^1] = "Failed to connect to the ERI v1 server. The server is not supported.";
+ return;
}
+
+ var authSchemes = await client.GetAuthMethodsAsync(cts.Token);
+ if (!authSchemes.Successful)
+ {
+ await this.form.Validate();
+
+ Array.Resize(ref this.dataIssues, this.dataIssues.Length + 1);
+ this.dataIssues[^1] = authSchemes.Message;
+ return;
+ }
+
+ this.availableAuthMethods = authSchemes.Data!.Select(n => n.AuthMethod).ToList();
+
+ this.connectionTested = true;
+ this.connectionSuccessfulTested = true;
+ this.Logger.LogInformation("Connection to the ERI v1 server was successful tested.");
}
catch (Exception e)
{
diff --git a/app/MindWork AI Studio/Dialogs/DataSourceLocalDirectoryInfoDialog.razor b/app/MindWork AI Studio/Dialogs/DataSourceLocalDirectoryInfoDialog.razor
new file mode 100644
index 00000000..cbd951dc
--- /dev/null
+++ b/app/MindWork AI Studio/Dialogs/DataSourceLocalDirectoryInfoDialog.razor
@@ -0,0 +1,52 @@
+
+
+
+
+
+ @if (!this.IsDirectoryAvailable)
+ {
+
+ The directory chosen for the data source does not exist anymore. Please edit the data source and correct the path.
+
+ }
+ else
+ {
+
+ The directory chosen for the data source exists.
+
+ }
+
+
+ @if (this.IsCloudEmbedding)
+ {
+
+ The embedding runs in the cloud. All your data from the folder '@this.DataSource.Path' and all its subdirectories
+ will be sent to the cloud.
+
+ }
+ else
+ {
+
+ The embedding runs locally or in your organization. Your data is not sent to the cloud.
+
+ }
+
+
+
+ @if (this.directorySizeNumFiles > 100)
+ {
+
+ For performance reasons, only the first 100 files are shown. The directory contains @this.NumberFilesInDirectory files in total.
+
+ }
+
+
+
+
+ @if (this.IsOperationInProgress)
+ {
+
+ }
+ Close
+
+
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Dialogs/DataSourceLocalDirectoryInfoDialog.razor.cs b/app/MindWork AI Studio/Dialogs/DataSourceLocalDirectoryInfoDialog.razor.cs
new file mode 100644
index 00000000..ee525e01
--- /dev/null
+++ b/app/MindWork AI Studio/Dialogs/DataSourceLocalDirectoryInfoDialog.razor.cs
@@ -0,0 +1,112 @@
+using System.Text;
+
+using AIStudio.Settings;
+using AIStudio.Settings.DataModel;
+
+using Microsoft.AspNetCore.Components;
+
+using Timer = System.Timers.Timer;
+
+namespace AIStudio.Dialogs;
+
+public partial class DataSourceLocalDirectoryInfoDialog : ComponentBase, IAsyncDisposable
+{
+ [CascadingParameter]
+ private MudDialogInstance MudDialog { get; set; } = null!;
+
+ [Parameter]
+ public DataSourceLocalDirectory DataSource { get; set; }
+
+ [Inject]
+ private SettingsManager SettingsManager { get; init; } = null!;
+
+ private readonly Timer refreshTimer = new(TimeSpan.FromSeconds(1.6))
+ {
+ AutoReset = true,
+ };
+
+ #region Overrides of ComponentBase
+
+ protected override async Task OnInitializedAsync()
+ {
+ this.embeddingProvider = this.SettingsManager.ConfigurationData.EmbeddingProviders.FirstOrDefault(x => x.Id == this.DataSource.EmbeddingId);
+ this.directoryInfo = new DirectoryInfo(this.DataSource.Path);
+
+ if (this.directoryInfo.Exists)
+ {
+ this.directorySizeTask = this.directoryInfo.DetermineContentSize(this.UpdateDirectorySize, this.UpdateDirectoryFiles, this.UpdateFileList, MAX_FILES_TO_SHOW, this.DirectoryOperationDone, this.cts.Token);
+ this.refreshTimer.Elapsed += (_, _) => this.InvokeAsync(this.StateHasChanged);
+ this.refreshTimer.Start();
+ }
+
+ await base.OnInitializedAsync();
+ }
+
+ #endregion
+
+ private const int MAX_FILES_TO_SHOW = 100;
+
+ private readonly CancellationTokenSource cts = new();
+
+ private EmbeddingProvider embeddingProvider;
+ private DirectoryInfo directoryInfo = null!;
+ private long directorySizeBytes;
+ private long directorySizeNumFiles;
+ private readonly StringBuilder directoryFiles = new();
+ private Task directorySizeTask = Task.CompletedTask;
+
+ private bool IsOperationInProgress { get; set; } = true;
+
+ private bool IsCloudEmbedding => !this.embeddingProvider.IsSelfHosted;
+
+ private bool IsDirectoryAvailable => this.directoryInfo.Exists;
+
+ private void UpdateFileList(string file)
+ {
+ this.directoryFiles.Append("- ");
+ this.directoryFiles.AppendLine(file);
+ }
+
+ private void UpdateDirectorySize(long size)
+ {
+ this.directorySizeBytes = size;
+ }
+
+ private void UpdateDirectoryFiles(long numFiles) => this.directorySizeNumFiles = numFiles;
+
+ private void DirectoryOperationDone()
+ {
+ this.refreshTimer.Stop();
+ this.IsOperationInProgress = false;
+ this.InvokeAsync(this.StateHasChanged);
+ }
+
+ private string NumberFilesInDirectory => $"{this.directorySizeNumFiles:###,###,###,###}";
+
+ private void Close()
+ {
+ this.cts.Cancel();
+ this.MudDialog.Close();
+ }
+
+ #region Implementation of IDisposable
+
+ public async ValueTask DisposeAsync()
+ {
+ try
+ {
+ await this.cts.CancelAsync();
+ await this.directorySizeTask;
+
+ this.cts.Dispose();
+ this.refreshTimer.Stop();
+ this.refreshTimer.Dispose();
+ }
+ catch
+ {
+ // ignored
+ }
+ }
+
+ #endregion
+}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Dialogs/DataSourceLocalFileDialog.razor.cs b/app/MindWork AI Studio/Dialogs/DataSourceLocalFileDialog.razor.cs
index feaf5a79..6a2cd14d 100644
--- a/app/MindWork AI Studio/Dialogs/DataSourceLocalFileDialog.razor.cs
+++ b/app/MindWork AI Studio/Dialogs/DataSourceLocalFileDialog.razor.cs
@@ -23,7 +23,6 @@ public partial class DataSourceLocalFileDialog : ComponentBase
[Inject]
private SettingsManager SettingsManager { get; init; } = null!;
-
private static readonly Dictionary SPELLCHECK_ATTRIBUTES = new();
private readonly DataSourceValidation dataSourceValidation;
diff --git a/app/MindWork AI Studio/Dialogs/DataSourceLocalFileInfoDialog.razor b/app/MindWork AI Studio/Dialogs/DataSourceLocalFileInfoDialog.razor
new file mode 100644
index 00000000..6fb4b12c
--- /dev/null
+++ b/app/MindWork AI Studio/Dialogs/DataSourceLocalFileInfoDialog.razor
@@ -0,0 +1,39 @@
+
+
+
+
+
+ @if (!this.IsFileAvailable)
+ {
+
+ The file chosen for the data source does not exist anymore. Please edit the data source and choose another file or correct the path.
+
+ }
+ else
+ {
+
+ The file chosen for the data source exists.
+
+ }
+
+
+ @if (this.IsCloudEmbedding)
+ {
+
+ The embedding runs in the cloud. All your data within the
+ file '@this.DataSource.FilePath' will be sent to the cloud.
+
+ }
+ else
+ {
+
+ The embedding runs locally or in your organization. Your data is not sent to the cloud.
+
+ }
+
+
+
+
+ Close
+
+
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Dialogs/DataSourceLocalFileInfoDialog.razor.cs b/app/MindWork AI Studio/Dialogs/DataSourceLocalFileInfoDialog.razor.cs
new file mode 100644
index 00000000..a4a5c4a3
--- /dev/null
+++ b/app/MindWork AI Studio/Dialogs/DataSourceLocalFileInfoDialog.razor.cs
@@ -0,0 +1,40 @@
+using AIStudio.Settings;
+using AIStudio.Settings.DataModel;
+
+using Microsoft.AspNetCore.Components;
+
+namespace AIStudio.Dialogs;
+
+public partial class DataSourceLocalFileInfoDialog : ComponentBase
+{
+ [CascadingParameter]
+ private MudDialogInstance MudDialog { get; set; } = null!;
+
+ [Parameter]
+ public DataSourceLocalFile DataSource { get; set; }
+
+ [Inject]
+ private SettingsManager SettingsManager { get; init; } = null!;
+
+ #region Overrides of ComponentBase
+
+ protected override async Task OnInitializedAsync()
+ {
+ this.embeddingProvider = this.SettingsManager.ConfigurationData.EmbeddingProviders.FirstOrDefault(x => x.Id == this.DataSource.EmbeddingId);
+ this.fileInfo = new FileInfo(this.DataSource.FilePath);
+ await base.OnInitializedAsync();
+ }
+
+ #endregion
+
+ private EmbeddingProvider embeddingProvider;
+ private FileInfo fileInfo = null!;
+
+ private bool IsCloudEmbedding => !this.embeddingProvider.IsSelfHosted;
+
+ private bool IsFileAvailable => this.fileInfo.Exists;
+
+ private string FileSize => this.fileInfo.FileSize();
+
+ private void Close() => this.MudDialog.Close();
+}
\ 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 ccc96dff..0d6fc288 100644
--- a/app/MindWork AI Studio/MindWork AI Studio.csproj
+++ b/app/MindWork AI Studio/MindWork AI Studio.csproj
@@ -53,10 +53,6 @@
-
-
-
-
diff --git a/app/MindWork AI Studio/Pages/Home.razor.cs b/app/MindWork AI Studio/Pages/Home.razor.cs
index 2b2aa4a8..5b8d63e4 100644
--- a/app/MindWork AI Studio/Pages/Home.razor.cs
+++ b/app/MindWork AI Studio/Pages/Home.razor.cs
@@ -26,7 +26,7 @@ public partial class Home : ComponentBase
private async Task ReadLastChangeAsync()
{
var latest = Changelog.LOGS.MaxBy(n => n.Build);
- var response = await this.HttpClient.GetAsync($"changelog/{latest.Filename}");
+ using var response = await this.HttpClient.GetAsync($"changelog/{latest.Filename}");
this.LastChangeContent = await response.Content.ReadAsStringAsync();
}
diff --git a/app/MindWork AI Studio/Provider/BaseProvider.cs b/app/MindWork AI Studio/Provider/BaseProvider.cs
index 25143834..2899e4f5 100644
--- a/app/MindWork AI Studio/Provider/BaseProvider.cs
+++ b/app/MindWork AI Studio/Provider/BaseProvider.cs
@@ -103,9 +103,14 @@ public abstract class BaseProvider : IProvider, ISecretId
{
using var request = await requestBuilder();
+ //
// Send the request with the ResponseHeadersRead option.
// This allows us to read the stream as soon as the headers are received.
// This is important because we want to stream the responses.
+ //
+ // Please notice: We do not dispose the response here. The caller is responsible
+ // for disposing the response object. This is important because the response
+ // object is used to read the stream.
var nextResponse = await this.httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, token);
if (nextResponse.IsSuccessStatusCode)
{
diff --git a/app/MindWork AI Studio/Provider/Google/ProviderGoogle.cs b/app/MindWork AI Studio/Provider/Google/ProviderGoogle.cs
index e0d5da77..942cb245 100644
--- a/app/MindWork AI Studio/Provider/Google/ProviderGoogle.cs
+++ b/app/MindWork AI Studio/Provider/Google/ProviderGoogle.cs
@@ -136,8 +136,8 @@ public class ProviderGoogle(ILogger logger) : BaseProvider("https://generativela
if (secretKey is null)
return default;
- var request = new HttpRequestMessage(HttpMethod.Get, $"models?key={secretKey}");
- var response = await this.httpClient.SendAsync(request, token);
+ using var request = new HttpRequestMessage(HttpMethod.Get, $"models?key={secretKey}");
+ using var response = await this.httpClient.SendAsync(request, token);
if(!response.IsSuccessStatusCode)
return default;
diff --git a/app/MindWork AI Studio/Provider/Groq/ProviderGroq.cs b/app/MindWork AI Studio/Provider/Groq/ProviderGroq.cs
index 2fc7e88f..f32a31b5 100644
--- a/app/MindWork AI Studio/Provider/Groq/ProviderGroq.cs
+++ b/app/MindWork AI Studio/Provider/Groq/ProviderGroq.cs
@@ -127,10 +127,10 @@ public class ProviderGroq(ILogger logger) : BaseProvider("https://api.groq.com/o
if (secretKey is null)
return [];
- var request = new HttpRequestMessage(HttpMethod.Get, "models");
+ using var request = new HttpRequestMessage(HttpMethod.Get, "models");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", secretKey);
- var response = await this.httpClient.SendAsync(request, token);
+ using var response = await this.httpClient.SendAsync(request, token);
if(!response.IsSuccessStatusCode)
return [];
diff --git a/app/MindWork AI Studio/Provider/Mistral/ProviderMistral.cs b/app/MindWork AI Studio/Provider/Mistral/ProviderMistral.cs
index ebde4c7b..024f60d3 100644
--- a/app/MindWork AI Studio/Provider/Mistral/ProviderMistral.cs
+++ b/app/MindWork AI Studio/Provider/Mistral/ProviderMistral.cs
@@ -138,10 +138,10 @@ public sealed class ProviderMistral(ILogger logger) : BaseProvider("https://api.
if (secretKey is null)
return default;
- var request = new HttpRequestMessage(HttpMethod.Get, "models");
+ using var request = new HttpRequestMessage(HttpMethod.Get, "models");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", secretKey);
- var response = await this.httpClient.SendAsync(request, token);
+ using var response = await this.httpClient.SendAsync(request, token);
if(!response.IsSuccessStatusCode)
return default;
diff --git a/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs b/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs
index 6ddeeabc..ed092174 100644
--- a/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs
+++ b/app/MindWork AI Studio/Provider/OpenAI/ProviderOpenAI.cs
@@ -154,10 +154,10 @@ public sealed class ProviderOpenAI(ILogger logger) : BaseProvider("https://api.o
if (secretKey is null)
return [];
- var request = new HttpRequestMessage(HttpMethod.Get, "models");
+ using var request = new HttpRequestMessage(HttpMethod.Get, "models");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", secretKey);
- var response = await this.httpClient.SendAsync(request, token);
+ using var response = await this.httpClient.SendAsync(request, token);
if(!response.IsSuccessStatusCode)
return [];
diff --git a/app/MindWork AI Studio/Provider/SelfHosted/ProviderSelfHosted.cs b/app/MindWork AI Studio/Provider/SelfHosted/ProviderSelfHosted.cs
index 3abda28c..4ba45c6b 100644
--- a/app/MindWork AI Studio/Provider/SelfHosted/ProviderSelfHosted.cs
+++ b/app/MindWork AI Studio/Provider/SelfHosted/ProviderSelfHosted.cs
@@ -154,11 +154,11 @@ public sealed class ProviderSelfHosted(ILogger logger, Host host, string hostnam
}
};
- var lmStudioRequest = new HttpRequestMessage(HttpMethod.Get, "models");
+ using var lmStudioRequest = new HttpRequestMessage(HttpMethod.Get, "models");
if(secretKey is not null)
lmStudioRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", apiKeyProvisional);
- var lmStudioResponse = await this.httpClient.SendAsync(lmStudioRequest, token);
+ using var lmStudioResponse = await this.httpClient.SendAsync(lmStudioRequest, token);
if(!lmStudioResponse.IsSuccessStatusCode)
return [];
diff --git a/app/MindWork AI Studio/Provider/X/ProviderX.cs b/app/MindWork AI Studio/Provider/X/ProviderX.cs
index c915c964..a8334c8d 100644
--- a/app/MindWork AI Studio/Provider/X/ProviderX.cs
+++ b/app/MindWork AI Studio/Provider/X/ProviderX.cs
@@ -127,10 +127,10 @@ public sealed class ProviderX(ILogger logger) : BaseProvider("https://api.x.ai/v
if (secretKey is null)
return [];
- var request = new HttpRequestMessage(HttpMethod.Get, "models");
+ using var request = new HttpRequestMessage(HttpMethod.Get, "models");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", secretKey);
- var response = await this.httpClient.SendAsync(request, token);
+ using var response = await this.httpClient.SendAsync(request, token);
if(!response.IsSuccessStatusCode)
return [];
diff --git a/app/MindWork AI Studio/Settings/DataModel/DataSourceERI_V1.cs b/app/MindWork AI Studio/Settings/DataModel/DataSourceERI_V1.cs
index 387accf0..addec256 100644
--- a/app/MindWork AI Studio/Settings/DataModel/DataSourceERI_V1.cs
+++ b/app/MindWork AI Studio/Settings/DataModel/DataSourceERI_V1.cs
@@ -1,6 +1,7 @@
-using ERI_Client.V1;
-
// ReSharper disable InconsistentNaming
+
+using AIStudio.Tools.ERIClient.DataModel;
+
namespace AIStudio.Settings.DataModel;
///
@@ -24,23 +25,15 @@ public readonly record struct DataSourceERI_V1 : IERIDataSource
///
public DataSourceType Type { get; init; } = DataSourceType.NONE;
- ///
- /// The hostname of the ERI server.
- ///
+ ///
public string Hostname { get; init; } = string.Empty;
- ///
- /// The port of the ERI server.
- ///
+ ///
public int Port { get; init; }
- ///
- /// The authentication method to use.
- ///
+ ///
public AuthMethod AuthMethod { get; init; } = AuthMethod.NONE;
- ///
- /// The username to use for authentication, when the auth. method is USERNAME_PASSWORD.
- ///
+ ///
public string Username { get; init; } = string.Empty;
}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Settings/IERIDataSource.cs b/app/MindWork AI Studio/Settings/IERIDataSource.cs
index 34874fcc..0d917d18 100644
--- a/app/MindWork AI Studio/Settings/IERIDataSource.cs
+++ b/app/MindWork AI Studio/Settings/IERIDataSource.cs
@@ -1,12 +1,26 @@
-using ERI_Client.V1;
+using AIStudio.Tools.ERIClient.DataModel;
namespace AIStudio.Settings;
public interface IERIDataSource : IExternalDataSource
{
+ ///
+ /// The hostname of the ERI server.
+ ///
public string Hostname { get; init; }
+ ///
+ /// The port of the ERI server.
+ ///
public int Port { get; init; }
+ ///
+ /// The authentication method to use.
+ ///
public AuthMethod AuthMethod { get; init; }
+
+ ///
+ /// The username to use for authentication, when the auth. method is USERNAME_PASSWORD.
+ ///
+ public string Username { get; init; }
}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Tools/AuthMethodsV1Extensions.cs b/app/MindWork AI Studio/Tools/AuthMethodsV1Extensions.cs
index e26a20b5..4d11017a 100644
--- a/app/MindWork AI Studio/Tools/AuthMethodsV1Extensions.cs
+++ b/app/MindWork AI Studio/Tools/AuthMethodsV1Extensions.cs
@@ -1,4 +1,4 @@
-using ERI_Client.V1;
+using AIStudio.Tools.ERIClient.DataModel;
namespace AIStudio.Tools;
diff --git a/app/MindWork AI Studio/Tools/DirectoryInfoExtensions.cs b/app/MindWork AI Studio/Tools/DirectoryInfoExtensions.cs
new file mode 100644
index 00000000..70adcab7
--- /dev/null
+++ b/app/MindWork AI Studio/Tools/DirectoryInfoExtensions.cs
@@ -0,0 +1,66 @@
+namespace AIStudio.Tools;
+
+public static class DirectoryInfoExtensions
+{
+ private static readonly EnumerationOptions ENUMERATION_OPTIONS = new()
+ {
+ IgnoreInaccessible = true,
+ RecurseSubdirectories = true,
+ ReturnSpecialDirectories = false,
+ };
+
+ ///
+ /// Determines the size of the directory and all its subdirectories, as well as the number of files. When desired,
+ /// it can report the found files up to a certain limit.
+ ///
+ ///
+ /// You might set reportMaxFiles to a negative value to report all files. Any positive value will limit the number
+ /// of reported files. The cancellation token can be used to stop the operation. The cancellation operation is also able
+ /// to cancel slow operations, e.g., when the directory is on a slow network drive.
+ ///
+ /// After stopping the operation, the total size and number of files are reported as they were at the time of cancellation.
+ ///
+ /// Please note that the entire operation is done on a background thread. Thus, when reporting the found files or the
+ /// current total size, you need to use the appropriate dispatcher to update the UI. Usually, you can use the InvokeAsync
+ /// method to update the UI from a background thread.
+ ///
+ /// The root directory to determine the size of.
+ /// The callback to report the current total size of the directory.
+ /// The callback to report the current number of files found.
+ /// The callback to report the next file found. The file name is relative to the root directory.
+ /// The maximum number of files to report. A negative value reports all files.
+ /// The callback to report that the operation is done.
+ /// The cancellation token to stop the operation.
+ public static async Task DetermineContentSize(this DirectoryInfo directoryInfo, Action reportCurrentTotalSize, Action reportCurrentNumFiles, Action reportNextFile, int reportMaxFiles = -1, Action? done = null, CancellationToken cancellationToken = default)
+ {
+ var rootDirectoryLen = directoryInfo.FullName.Length;
+ long totalSize = 0;
+ long numFiles = 0;
+
+ await Task.Factory.StartNew(() => {
+ foreach (var file in directoryInfo.EnumerateFiles("*", ENUMERATION_OPTIONS))
+ {
+ if (cancellationToken.IsCancellationRequested)
+ return;
+
+ totalSize += file.Length;
+ numFiles++;
+
+ if (numFiles % 100 == 0)
+ {
+ reportCurrentTotalSize(totalSize);
+ reportCurrentNumFiles(numFiles);
+ }
+
+ if (reportMaxFiles < 0 || numFiles <= reportMaxFiles)
+ reportNextFile(file.FullName[rootDirectoryLen..]);
+ }
+ }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Default);
+
+ reportCurrentTotalSize(totalSize);
+ reportCurrentNumFiles(numFiles);
+
+ if(done is not null)
+ done();
+ }
+}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Tools/ERIClient/APIResponse.cs b/app/MindWork AI Studio/Tools/ERIClient/APIResponse.cs
new file mode 100644
index 00000000..1f82914c
--- /dev/null
+++ b/app/MindWork AI Studio/Tools/ERIClient/APIResponse.cs
@@ -0,0 +1,19 @@
+namespace AIStudio.Tools.ERIClient;
+
+public sealed class APIResponse
+{
+ ///
+ /// Was the API call successful?
+ ///
+ public bool Successful { get; set; }
+
+ ///
+ /// When the API call was not successful, this will contain the error message.
+ ///
+ public string Message { get; set; } = string.Empty;
+
+ ///
+ /// The data returned by the API call.
+ ///
+ public T? Data { get; set; }
+}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Tools/ERIClient/DataModel/AuthField.cs b/app/MindWork AI Studio/Tools/ERIClient/DataModel/AuthField.cs
new file mode 100644
index 00000000..89727430
--- /dev/null
+++ b/app/MindWork AI Studio/Tools/ERIClient/DataModel/AuthField.cs
@@ -0,0 +1,13 @@
+namespace AIStudio.Tools.ERIClient.DataModel;
+
+///
+/// An authentication field.
+///
+public enum AuthField
+{
+ NONE,
+ USERNAME,
+ PASSWORD,
+ TOKEN,
+ KERBEROS_TICKET,
+}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Tools/ERIClient/DataModel/AuthFieldMapping.cs b/app/MindWork AI Studio/Tools/ERIClient/DataModel/AuthFieldMapping.cs
new file mode 100644
index 00000000..e11c3d9f
--- /dev/null
+++ b/app/MindWork AI Studio/Tools/ERIClient/DataModel/AuthFieldMapping.cs
@@ -0,0 +1,8 @@
+namespace AIStudio.Tools.ERIClient.DataModel;
+
+///
+/// The mapping between an AuthField and the field name in the authentication request.
+///
+/// The AuthField that is mapped to the field name.
+/// The field name in the authentication request.
+public record AuthFieldMapping(AuthField AuthField, string FieldName);
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Tools/ERIClient/DataModel/AuthMethod.cs b/app/MindWork AI Studio/Tools/ERIClient/DataModel/AuthMethod.cs
new file mode 100644
index 00000000..7494ce9b
--- /dev/null
+++ b/app/MindWork AI Studio/Tools/ERIClient/DataModel/AuthMethod.cs
@@ -0,0 +1,9 @@
+namespace AIStudio.Tools.ERIClient.DataModel;
+
+public enum AuthMethod
+{
+ NONE,
+ KERBEROS,
+ USERNAME_PASSWORD,
+ TOKEN,
+}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Tools/ERIClient/DataModel/AuthResponse.cs b/app/MindWork AI Studio/Tools/ERIClient/DataModel/AuthResponse.cs
new file mode 100644
index 00000000..cdc325dd
--- /dev/null
+++ b/app/MindWork AI Studio/Tools/ERIClient/DataModel/AuthResponse.cs
@@ -0,0 +1,9 @@
+namespace AIStudio.Tools.ERIClient.DataModel;
+
+///
+/// The response to an authentication request.
+///
+/// True, when the authentication was successful.
+/// The token to use for further requests.
+/// When the authentication was not successful, this contains the reason.
+public readonly record struct AuthResponse(bool Success, string? Token, string? Message);
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Tools/ERIClient/DataModel/AuthScheme.cs b/app/MindWork AI Studio/Tools/ERIClient/DataModel/AuthScheme.cs
new file mode 100644
index 00000000..bde0175b
--- /dev/null
+++ b/app/MindWork AI Studio/Tools/ERIClient/DataModel/AuthScheme.cs
@@ -0,0 +1,9 @@
+namespace AIStudio.Tools.ERIClient.DataModel;
+
+///
+/// Describes one authentication scheme for this data source.
+///
+/// The method used for authentication, e.g., "API Key," "Username/Password," etc.
+/// A list of field mappings for the authentication method. The client must know,
+/// e.g., how the password field is named in the request.
+public readonly record struct AuthScheme(AuthMethod AuthMethod, List AuthFieldMappings);
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Tools/ERIClient/DataModel/ChatThread.cs b/app/MindWork AI Studio/Tools/ERIClient/DataModel/ChatThread.cs
new file mode 100644
index 00000000..8d6a0983
--- /dev/null
+++ b/app/MindWork AI Studio/Tools/ERIClient/DataModel/ChatThread.cs
@@ -0,0 +1,7 @@
+namespace AIStudio.Tools.ERIClient.DataModel;
+
+///
+/// A chat thread, which is a list of content blocks.
+///
+/// The content blocks in this chat thread.
+public readonly record struct ChatThread(List ContentBlocks);
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Tools/ERIClient/DataModel/ContentBlock.cs b/app/MindWork AI Studio/Tools/ERIClient/DataModel/ContentBlock.cs
new file mode 100644
index 00000000..0a46d3b5
--- /dev/null
+++ b/app/MindWork AI Studio/Tools/ERIClient/DataModel/ContentBlock.cs
@@ -0,0 +1,12 @@
+namespace AIStudio.Tools.ERIClient.DataModel;
+
+///
+/// A block of content of a chat thread.
+///
+///
+/// Images and other media are base64 encoded.
+///
+/// The content of the block. Remember that images and other media are base64 encoded.
+/// The role of the content in the chat thread.
+/// The type of the content, e.g., text, image, video, etc.
+public readonly record struct ContentBlock(string Content, Role Role, ContentType Type);
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Tools/ERIClient/DataModel/ContentType.cs b/app/MindWork AI Studio/Tools/ERIClient/DataModel/ContentType.cs
new file mode 100644
index 00000000..14203728
--- /dev/null
+++ b/app/MindWork AI Studio/Tools/ERIClient/DataModel/ContentType.cs
@@ -0,0 +1,16 @@
+namespace AIStudio.Tools.ERIClient.DataModel;
+
+///
+/// The type of content.
+///
+public enum ContentType
+{
+ NONE,
+ UNKNOWN,
+
+ TEXT,
+ IMAGE,
+ VIDEO,
+ AUDIO,
+ SPEECH,
+}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Tools/ERIClient/DataModel/Context.cs b/app/MindWork AI Studio/Tools/ERIClient/DataModel/Context.cs
new file mode 100644
index 00000000..fb5c13f5
--- /dev/null
+++ b/app/MindWork AI Studio/Tools/ERIClient/DataModel/Context.cs
@@ -0,0 +1,27 @@
+namespace AIStudio.Tools.ERIClient.DataModel;
+
+///
+/// Matching context returned by the data source as a result of a retrieval request.
+///
+/// The name of the source, e.g., a document name, database name,
+/// collection name, etc.
+/// What are the contents of the source? For example, is it a
+/// dictionary, a book chapter, business concept, a paper, etc.
+/// The path to the content, e.g., a URL, a file path, a path in a
+/// graph database, etc.
+/// The type of the content, e.g., text, image, video, audio, speech, etc.
+/// The content that matched the user prompt. For text, you
+/// return the matched text and, e.g., three words before and after it.
+/// The surrounding content of the matched content.
+/// For text, you may return, e.g., one sentence or paragraph before and after
+/// the matched content.
+/// Links to related content, e.g., links to Wikipedia articles,
+/// links to sources, etc.
+public readonly record struct Context(
+ string Name,
+ string Category,
+ string? Path,
+ ContentType Type,
+ string MatchedContent,
+ string[] SurroundingContent,
+ string[] Links);
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Tools/ERIClient/DataModel/DataSourceInfo.cs b/app/MindWork AI Studio/Tools/ERIClient/DataModel/DataSourceInfo.cs
new file mode 100644
index 00000000..07a8f92e
--- /dev/null
+++ b/app/MindWork AI Studio/Tools/ERIClient/DataModel/DataSourceInfo.cs
@@ -0,0 +1,9 @@
+namespace AIStudio.Tools.ERIClient.DataModel;
+
+///
+/// Information about the data source.
+///
+/// The name of the data source, e.g., "Internal Organization Documents."
+/// A short description of the data source. What kind of data does it contain?
+/// What is the data source used for?
+public readonly record struct DataSourceInfo(string Name, string Description);
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Tools/ERIClient/DataModel/EmbeddingInfo.cs b/app/MindWork AI Studio/Tools/ERIClient/DataModel/EmbeddingInfo.cs
new file mode 100644
index 00000000..47285126
--- /dev/null
+++ b/app/MindWork AI Studio/Tools/ERIClient/DataModel/EmbeddingInfo.cs
@@ -0,0 +1,20 @@
+namespace AIStudio.Tools.ERIClient.DataModel;
+
+///
+/// Represents information about the used embedding for this data source. The purpose of this information is to give the
+/// interested user an idea of what kind of embedding is used and what it does.
+///
+/// What kind of embedding is used. For example, "Transformer Embedding," "Contextual Word
+/// Embedding," "Graph Embedding," etc.
+/// Name the embedding used. This can be a library, a framework, or the name of the used
+/// algorithm.
+/// A short description of the embedding. Describe what the embedding is doing.
+/// Describe when the embedding is used. For example, when the user prompt contains certain
+/// keywords, or anytime?
+/// A link to the embedding's documentation or the source code. Might be null.
+public readonly record struct EmbeddingInfo(
+ string EmbeddingType,
+ string EmbeddingName,
+ string Description,
+ string UsedWhen,
+ string? Link);
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Tools/ERIClient/DataModel/ProviderType.cs b/app/MindWork AI Studio/Tools/ERIClient/DataModel/ProviderType.cs
new file mode 100644
index 00000000..008811f8
--- /dev/null
+++ b/app/MindWork AI Studio/Tools/ERIClient/DataModel/ProviderType.cs
@@ -0,0 +1,22 @@
+namespace AIStudio.Tools.ERIClient.DataModel;
+
+///
+/// Known types of providers that can process data.
+///
+public enum ProviderType
+{
+ ///
+ /// The related data is not allowed to be sent to any provider.
+ ///
+ NONE,
+
+ ///
+ /// The related data can be sent to any provider.
+ ///
+ ANY,
+
+ ///
+ /// The related data can be sent to a provider that is hosted by the same organization, either on-premises or locally.
+ ///
+ SELF_HOSTED,
+}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Tools/ERIClient/DataModel/ProviderTypeExtensions.cs b/app/MindWork AI Studio/Tools/ERIClient/DataModel/ProviderTypeExtensions.cs
new file mode 100644
index 00000000..ecdbcc19
--- /dev/null
+++ b/app/MindWork AI Studio/Tools/ERIClient/DataModel/ProviderTypeExtensions.cs
@@ -0,0 +1,13 @@
+namespace AIStudio.Tools.ERIClient.DataModel;
+
+public static class ProviderTypeExtensions
+{
+ public static string Explain(this ProviderType providerType) => providerType switch
+ {
+ ProviderType.NONE => "The related data is not allowed to be sent to any LLM provider. This means that this data source cannot be used at the moment.",
+ ProviderType.ANY => "The related data can be sent to any provider, regardless of where it is hosted (cloud or self-hosted).",
+ ProviderType.SELF_HOSTED => "The related data can be sent to a provider that is hosted by the same organization, either on-premises or locally. Cloud-based providers are not allowed.",
+
+ _ => "Unknown configuration. This data source cannot be used at the moment.",
+ };
+}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Tools/ERIClient/DataModel/RetrievalInfo.cs b/app/MindWork AI Studio/Tools/ERIClient/DataModel/RetrievalInfo.cs
new file mode 100644
index 00000000..cdd71a6b
--- /dev/null
+++ b/app/MindWork AI Studio/Tools/ERIClient/DataModel/RetrievalInfo.cs
@@ -0,0 +1,20 @@
+namespace AIStudio.Tools.ERIClient.DataModel;
+
+///
+/// Information about a retrieval process, which this data source implements.
+///
+/// A unique identifier for the retrieval process. This can be a GUID, a unique name, or an increasing integer.
+/// The name of the retrieval process, e.g., "Keyword-Based Wikipedia Article Retrieval".
+/// A short description of the retrieval process. What kind of retrieval process is it?
+/// A link to the retrieval process's documentation, paper, Wikipedia article, or the source code. Might be null.
+/// A dictionary that describes the parameters of the retrieval process. The key is the parameter name,
+/// and the value is a description of the parameter. Although each parameter will be sent as a string, the description should indicate the
+/// expected type and range, e.g., 0.0 to 1.0 for a float parameter.
+/// A list of embeddings used in this retrieval process. It might be empty in case no embedding is used.
+public readonly record struct RetrievalInfo(
+ string Id,
+ string Name,
+ string Description,
+ string? Link,
+ Dictionary? ParametersDescription,
+ List Embeddings);
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Tools/ERIClient/DataModel/RetrievalRequest.cs b/app/MindWork AI Studio/Tools/ERIClient/DataModel/RetrievalRequest.cs
new file mode 100644
index 00000000..abeac50b
--- /dev/null
+++ b/app/MindWork AI Studio/Tools/ERIClient/DataModel/RetrievalRequest.cs
@@ -0,0 +1,25 @@
+namespace AIStudio.Tools.ERIClient.DataModel;
+
+///
+/// The retrieval request sent by AI Studio.
+///
+///
+/// Images and other media are base64 encoded.
+///
+/// The latest user prompt that AI Studio received.
+/// The type of the latest user prompt, e.g., text, image, etc.
+/// The chat thread that the user is currently in.
+/// Optional. The ID of the retrieval process that the data source should use.
+/// When null, the data source chooses an appropriate retrieval process. Selecting a retrieval process is optional
+/// for AI Studio users. Most users do not specify a retrieval process.
+/// A dictionary of parameters that the data source should use for the retrieval process.
+/// Although each parameter will be sent as a string, the retrieval process specifies the expected type and range.
+/// The maximum number of matches that the data source should return. AI Studio uses
+/// any value below 1 to indicate that the data source should return as many matches as appropriate.
+public readonly record struct RetrievalRequest(
+ string LatestUserPrompt,
+ ContentType LatestUserPromptType,
+ ChatThread Thread,
+ string? RetrievalProcessId,
+ Dictionary? Parameters,
+ int MaxMatches);
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Tools/ERIClient/DataModel/Role.cs b/app/MindWork AI Studio/Tools/ERIClient/DataModel/Role.cs
new file mode 100644
index 00000000..f19376f7
--- /dev/null
+++ b/app/MindWork AI Studio/Tools/ERIClient/DataModel/Role.cs
@@ -0,0 +1,15 @@
+namespace AIStudio.Tools.ERIClient.DataModel;
+
+///
+/// Possible roles of any chat thread.
+///
+public enum Role
+{
+ NONE,
+ UNKNOW,
+
+ SYSTEM,
+ USER,
+ AI,
+ AGENT,
+}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Tools/ERIClient/DataModel/SecurityRequirements.cs b/app/MindWork AI Studio/Tools/ERIClient/DataModel/SecurityRequirements.cs
new file mode 100644
index 00000000..aa4df1f5
--- /dev/null
+++ b/app/MindWork AI Studio/Tools/ERIClient/DataModel/SecurityRequirements.cs
@@ -0,0 +1,7 @@
+namespace AIStudio.Tools.ERIClient.DataModel;
+
+///
+/// Represents the security requirements for this data source.
+///
+/// Which provider types are allowed to process the data?
+public readonly record struct SecurityRequirements(ProviderType AllowedProviderType);
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Tools/ERIClient/ERIClientBase.cs b/app/MindWork AI Studio/Tools/ERIClient/ERIClientBase.cs
new file mode 100644
index 00000000..1906a0d5
--- /dev/null
+++ b/app/MindWork AI Studio/Tools/ERIClient/ERIClientBase.cs
@@ -0,0 +1,36 @@
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace AIStudio.Tools.ERIClient;
+
+public abstract class ERIClientBase(string baseAddress) : IDisposable
+{
+ protected static readonly JsonSerializerOptions JSON_OPTIONS = new()
+ {
+ WriteIndented = true,
+ AllowTrailingCommas = true,
+ PropertyNamingPolicy = null,
+ DictionaryKeyPolicy = JsonNamingPolicy.CamelCase,
+ PropertyNameCaseInsensitive = true,
+ Converters =
+ {
+ new JsonStringEnumConverter(JsonNamingPolicy.SnakeCaseUpper),
+ }
+ };
+
+ protected readonly HttpClient httpClient = new()
+ {
+ BaseAddress = new Uri(baseAddress),
+ };
+
+ protected string securityToken = string.Empty;
+
+ #region Implementation of IDisposable
+
+ public void Dispose()
+ {
+ this.httpClient.Dispose();
+ }
+
+ #endregion
+}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Tools/ERIClient/ERIClientFactory.cs b/app/MindWork AI Studio/Tools/ERIClient/ERIClientFactory.cs
new file mode 100644
index 00000000..28581646
--- /dev/null
+++ b/app/MindWork AI Studio/Tools/ERIClient/ERIClientFactory.cs
@@ -0,0 +1,14 @@
+using AIStudio.Assistants.ERI;
+using AIStudio.Settings;
+
+namespace AIStudio.Tools.ERIClient;
+
+public static class ERIClientFactory
+{
+ public static IERIClient? Get(ERIVersion version, IERIDataSource dataSource) => version switch
+ {
+ ERIVersion.V1 => new ERIClientV1($"{dataSource.Hostname}:{dataSource.Port}"),
+
+ _ => null
+ };
+}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Tools/ERIClient/ERIClientV1.cs b/app/MindWork AI Studio/Tools/ERIClient/ERIClientV1.cs
new file mode 100644
index 00000000..62e3f556
--- /dev/null
+++ b/app/MindWork AI Studio/Tools/ERIClient/ERIClientV1.cs
@@ -0,0 +1,344 @@
+using System.Text;
+using System.Text.Json;
+
+using AIStudio.Settings;
+using AIStudio.Tools.ERIClient.DataModel;
+
+namespace AIStudio.Tools.ERIClient;
+
+public class ERIClientV1(string baseAddress) : ERIClientBase(baseAddress), IERIClient
+{
+ #region Implementation of IERIClient
+
+ public async Task>> GetAuthMethodsAsync(CancellationToken cancellationToken = default)
+ {
+ using var response = await this.httpClient.GetAsync("/auth/methods", cancellationToken);
+ if(!response.IsSuccessStatusCode)
+ {
+ return new()
+ {
+ Successful = false,
+ Message = $"Failed to retrieve the authentication methods: there was an issue communicating with the ERI server. Code: {response.StatusCode}, Reason: {response.ReasonPhrase}"
+ };
+ }
+
+ var authMethods = await response.Content.ReadFromJsonAsync>(JSON_OPTIONS, cancellationToken);
+ if(authMethods is null)
+ {
+ return new()
+ {
+ Successful = false,
+ Message = "Failed to retrieve the authentication methods: the ERI server did not return a valid response."
+ };
+ }
+
+ return new()
+ {
+ Successful = true,
+ Data = authMethods
+ };
+ }
+
+ public async Task> AuthenticateAsync(IERIDataSource dataSource, RustService rustService, CancellationToken cancellationToken = default)
+ {
+ var authMethod = dataSource.AuthMethod;
+ var username = dataSource.Username;
+ switch (dataSource.AuthMethod)
+ {
+ case AuthMethod.NONE:
+ using (var request = new HttpRequestMessage(HttpMethod.Post, $"auth?authMethod={authMethod}"))
+ {
+ using var noneAuthResponse = await this.httpClient.SendAsync(request, cancellationToken);
+ if(!noneAuthResponse.IsSuccessStatusCode)
+ {
+ return new()
+ {
+ Successful = false,
+ Message = $"Failed to authenticate with the ERI server. Code: {noneAuthResponse.StatusCode}, Reason: {noneAuthResponse.ReasonPhrase}"
+ };
+ }
+
+ var noneAuthResult = await noneAuthResponse.Content.ReadFromJsonAsync(JSON_OPTIONS, cancellationToken);
+ if(noneAuthResult == default)
+ {
+ return new()
+ {
+ Successful = false,
+ Message = "Failed to authenticate with the ERI server: the response was invalid."
+ };
+ }
+
+ this.securityToken = noneAuthResult.Token ?? string.Empty;
+ return new()
+ {
+ Successful = true,
+ Data = noneAuthResult
+ };
+ }
+
+ case AuthMethod.USERNAME_PASSWORD:
+ var passwordResponse = await rustService.GetSecret(dataSource);
+ if (!passwordResponse.Success)
+ {
+ return new()
+ {
+ Successful = false,
+ Message = "Failed to retrieve the password."
+ };
+ }
+
+ var password = await passwordResponse.Secret.Decrypt(Program.ENCRYPTION);
+ using (var request = new HttpRequestMessage(HttpMethod.Post, $"auth?authMethod={authMethod}"))
+ {
+ // We must send both values inside the header. The username field is named 'user'.
+ // The password field is named 'password'.
+ request.Headers.Add("user", username);
+ request.Headers.Add("password", password);
+
+ using var usernamePasswordAuthResponse = await this.httpClient.SendAsync(request, cancellationToken);
+ if(!usernamePasswordAuthResponse.IsSuccessStatusCode)
+ {
+ return new()
+ {
+ Successful = false,
+ Message = $"Failed to authenticate with the ERI server. Code: {usernamePasswordAuthResponse.StatusCode}, Reason: {usernamePasswordAuthResponse.ReasonPhrase}"
+ };
+ }
+
+ var usernamePasswordAuthResult = await usernamePasswordAuthResponse.Content.ReadFromJsonAsync(JSON_OPTIONS, cancellationToken);
+ if(usernamePasswordAuthResult == default)
+ {
+ return new()
+ {
+ Successful = false,
+ Message = "Failed to authenticate with the server: the response was invalid."
+ };
+ }
+
+ this.securityToken = usernamePasswordAuthResult.Token ?? string.Empty;
+ return new()
+ {
+ Successful = true,
+ Data = usernamePasswordAuthResult
+ };
+ }
+
+ case AuthMethod.TOKEN:
+ var tokenResponse = await rustService.GetSecret(dataSource);
+ if (!tokenResponse.Success)
+ {
+ return new()
+ {
+ Successful = false,
+ Message = "Failed to retrieve the access token."
+ };
+ }
+
+ var token = await tokenResponse.Secret.Decrypt(Program.ENCRYPTION);
+ using (var request = new HttpRequestMessage(HttpMethod.Post, $"auth?authMethod={authMethod}"))
+ {
+ request.Headers.Add("Authorization", $"Bearer {token}");
+
+ using var tokenAuthResponse = await this.httpClient.SendAsync(request, cancellationToken);
+ if(!tokenAuthResponse.IsSuccessStatusCode)
+ {
+ return new()
+ {
+ Successful = false,
+ Message = $"Failed to authenticate with the ERI server. Code: {tokenAuthResponse.StatusCode}, Reason: {tokenAuthResponse.ReasonPhrase}"
+ };
+ }
+
+ var tokenAuthResult = await tokenAuthResponse.Content.ReadFromJsonAsync(JSON_OPTIONS, cancellationToken);
+ if(tokenAuthResult == default)
+ {
+ return new()
+ {
+ Successful = false,
+ Message = "Failed to authenticate with the ERI server: the response was invalid."
+ };
+ }
+
+ this.securityToken = tokenAuthResult.Token ?? string.Empty;
+ return new()
+ {
+ Successful = true,
+ Data = tokenAuthResult
+ };
+ }
+
+ default:
+ this.securityToken = string.Empty;
+ return new()
+ {
+ Successful = false,
+ Message = "The authentication method is not supported yet."
+ };
+ }
+ }
+
+ public async Task> GetDataSourceInfoAsync(CancellationToken cancellationToken = default)
+ {
+ using var request = new HttpRequestMessage(HttpMethod.Get, "/dataSource");
+ request.Headers.Add("token", this.securityToken);
+
+ using var response = await this.httpClient.SendAsync(request, cancellationToken);
+ if(!response.IsSuccessStatusCode)
+ {
+ return new()
+ {
+ Successful = false,
+ Message = $"Failed to retrieve the data source information: there was an issue communicating with the ERI server. Code: {response.StatusCode}, Reason: {response.ReasonPhrase}"
+ };
+ }
+
+ var dataSourceInfo = await response.Content.ReadFromJsonAsync(JSON_OPTIONS, cancellationToken);
+ if(dataSourceInfo == default)
+ {
+ return new()
+ {
+ Successful = false,
+ Message = "Failed to retrieve the data source information: the ERI server did not return a valid response."
+ };
+ }
+
+ return new()
+ {
+ Successful = true,
+ Data = dataSourceInfo
+ };
+ }
+
+ public async Task>> GetEmbeddingInfoAsync(CancellationToken cancellationToken = default)
+ {
+ using var request = new HttpRequestMessage(HttpMethod.Get, "/embedding/info");
+ request.Headers.Add("token", this.securityToken);
+
+ using var response = await this.httpClient.SendAsync(request, cancellationToken);
+ if(!response.IsSuccessStatusCode)
+ {
+ return new()
+ {
+ Successful = false,
+ Message = $"Failed to retrieve the embedding information: there was an issue communicating with the ERI server. Code: {response.StatusCode}, Reason: {response.ReasonPhrase}"
+ };
+ }
+
+ var embeddingInfo = await response.Content.ReadFromJsonAsync>(JSON_OPTIONS, cancellationToken);
+ if(embeddingInfo is null)
+ {
+ return new()
+ {
+ Successful = false,
+ Message = "Failed to retrieve the embedding information: the ERI server did not return a valid response."
+ };
+ }
+
+ return new()
+ {
+ Successful = true,
+ Data = embeddingInfo
+ };
+ }
+
+ public async Task>> GetRetrievalInfoAsync(CancellationToken cancellationToken = default)
+ {
+ using var request = new HttpRequestMessage(HttpMethod.Get, "/retrieval/info");
+ request.Headers.Add("token", this.securityToken);
+
+ using var response = await this.httpClient.SendAsync(request, cancellationToken);
+ if(!response.IsSuccessStatusCode)
+ {
+ return new()
+ {
+ Successful = false,
+ Message = $"Failed to retrieve the retrieval information: there was an issue communicating with the ERI server. Code: {response.StatusCode}, Reason: {response.ReasonPhrase}"
+ };
+ }
+
+ var retrievalInfo = await response.Content.ReadFromJsonAsync>(JSON_OPTIONS, cancellationToken);
+ if(retrievalInfo is null)
+ {
+ return new()
+ {
+ Successful = false,
+ Message = "Failed to retrieve the retrieval information: the ERI server did not return a valid response."
+ };
+ }
+
+ return new()
+ {
+ Successful = true,
+ Data = retrievalInfo
+ };
+ }
+
+ public async Task>> ExecuteRetrievalAsync(RetrievalRequest request, CancellationToken cancellationToken = default)
+ {
+ using var requestMessage = new HttpRequestMessage(HttpMethod.Post, "/retrieval");
+ requestMessage.Headers.Add("token", this.securityToken);
+
+ using var content = new StringContent(JsonSerializer.Serialize(request, JSON_OPTIONS), Encoding.UTF8, "application/json");
+ requestMessage.Content = content;
+
+ using var response = await this.httpClient.SendAsync(requestMessage, cancellationToken);
+ if(!response.IsSuccessStatusCode)
+ {
+ return new()
+ {
+ Successful = false,
+ Message = $"Failed to execute the retrieval request: there was an issue communicating with the ERI server. Code: {response.StatusCode}, Reason: {response.ReasonPhrase}"
+ };
+ }
+
+ var contexts = await response.Content.ReadFromJsonAsync>(JSON_OPTIONS, cancellationToken);
+ if(contexts is null)
+ {
+ return new()
+ {
+ Successful = false,
+ Message = "Failed to execute the retrieval request: the ERI server did not return a valid response."
+ };
+ }
+
+ return new()
+ {
+ Successful = true,
+ Data = contexts
+ };
+ }
+
+ public async Task> GetSecurityRequirementsAsync(CancellationToken cancellationToken = default)
+ {
+ using var request = new HttpRequestMessage(HttpMethod.Get, "/security/requirements");
+ request.Headers.Add("token", this.securityToken);
+
+ using var response = await this.httpClient.SendAsync(request, cancellationToken);
+ if(!response.IsSuccessStatusCode)
+ {
+ return new()
+ {
+ Successful = false,
+ Message = $"Failed to retrieve the security requirements: there was an issue communicating with the ERI server. Code: {response.StatusCode}, Reason: {response.ReasonPhrase}"
+ };
+ }
+
+ var securityRequirements = await response.Content.ReadFromJsonAsync(JSON_OPTIONS, cancellationToken);
+ if(securityRequirements == default)
+ {
+ return new()
+ {
+ Successful = false,
+ Message = "Failed to retrieve the security requirements: the ERI server did not return a valid response."
+ };
+ }
+
+ return new()
+ {
+ Successful = true,
+ Data = securityRequirements
+ };
+ }
+
+ #endregion
+}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Tools/ERIClient/IERIClient.cs b/app/MindWork AI Studio/Tools/ERIClient/IERIClient.cs
new file mode 100644
index 00000000..9cf27fbf
--- /dev/null
+++ b/app/MindWork AI Studio/Tools/ERIClient/IERIClient.cs
@@ -0,0 +1,62 @@
+using AIStudio.Settings;
+using AIStudio.Tools.ERIClient.DataModel;
+
+namespace AIStudio.Tools.ERIClient;
+
+public interface IERIClient : IDisposable
+{
+ ///
+ /// Retrieves the available authentication methods from the ERI server.
+ ///
+ ///
+ /// No authentication is required to retrieve the available authentication methods.
+ ///
+ /// The cancellation token.
+ /// The available authentication methods.
+ public Task>> GetAuthMethodsAsync(CancellationToken cancellationToken = default);
+
+ ///
+ /// Authenticate the user to the ERI server.
+ ///
+ /// The data source to use.
+ /// The Rust service.
+ /// The cancellation token.
+ /// The authentication response.
+ public Task> AuthenticateAsync(IERIDataSource dataSource, RustService rustService, CancellationToken cancellationToken = default);
+
+ ///
+ /// Retrieves the data source information from the ERI server.
+ ///
+ /// The cancellation token.
+ /// The data source information.
+ public Task> GetDataSourceInfoAsync(CancellationToken cancellationToken = default);
+
+ ///
+ /// Retrieves the embedding information from the ERI server.
+ ///
+ /// The cancellation token.
+ /// A list of embedding information.
+ public Task>> GetEmbeddingInfoAsync(CancellationToken cancellationToken = default);
+
+ ///
+ /// Retrieves the retrieval information from the ERI server.
+ ///
+ /// The cancellation token.
+ /// A list of retrieval information.
+ public Task>> GetRetrievalInfoAsync(CancellationToken cancellationToken = default);
+
+ ///
+ /// Executes a retrieval request on the ERI server.
+ ///
+ /// The retrieval request.
+ /// The cancellation token.
+ /// The retrieved contexts to use for augmentation and generation.
+ public Task>> ExecuteRetrievalAsync(RetrievalRequest request, CancellationToken cancellationToken = default);
+
+ ///
+ /// Retrieves the security requirements from the ERI server.
+ ///
+ /// The cancellation token.
+ /// The security requirements.
+ public Task> GetSecurityRequirementsAsync(CancellationToken cancellationToken = default);
+}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Tools/FileInfoExtensions.cs b/app/MindWork AI Studio/Tools/FileInfoExtensions.cs
new file mode 100644
index 00000000..c3002624
--- /dev/null
+++ b/app/MindWork AI Studio/Tools/FileInfoExtensions.cs
@@ -0,0 +1,17 @@
+namespace AIStudio.Tools;
+
+public static class FileInfoExtensions
+{
+ ///
+ /// Returns the file size in human-readable format.
+ ///
+ /// The file info object.
+ /// The file size in human-readable format.
+ public static string FileSize(this FileInfo fileInfo)
+ {
+ if (!fileInfo.Exists)
+ return "N/A";
+
+ return fileInfo.Length.FileSize();
+ }
+}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Tools/LongExtensions.cs b/app/MindWork AI Studio/Tools/LongExtensions.cs
new file mode 100644
index 00000000..3209e47a
--- /dev/null
+++ b/app/MindWork AI Studio/Tools/LongExtensions.cs
@@ -0,0 +1,22 @@
+namespace AIStudio.Tools;
+
+public static class LongExtensions
+{
+ ///
+ /// Formats the file size in a human-readable format.
+ ///
+ /// The size in bytes.
+ /// The formatted file size.
+ public static string FileSize(this long sizeBytes)
+ {
+ string[] sizes = { "B", "kB", "MB", "GB", "TB" };
+ var order = 0;
+ while (sizeBytes >= 1024 && order < sizes.Length - 1)
+ {
+ order++;
+ sizeBytes /= 1024;
+ }
+
+ return $"{sizeBytes:0.##} {sizes[order]}";
+ }
+}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/Tools/Validation/DataSourceValidation.cs b/app/MindWork AI Studio/Tools/Validation/DataSourceValidation.cs
index f55f906a..cdd2f131 100644
--- a/app/MindWork AI Studio/Tools/Validation/DataSourceValidation.cs
+++ b/app/MindWork AI Studio/Tools/Validation/DataSourceValidation.cs
@@ -1,4 +1,4 @@
-using ERI_Client.V1;
+using AIStudio.Tools.ERIClient.DataModel;
namespace AIStudio.Tools.Validation;
diff --git a/app/MindWork AI Studio/packages.lock.json b/app/MindWork AI Studio/packages.lock.json
index 17d7a388..2941da3d 100644
--- a/app/MindWork AI Studio/packages.lock.json
+++ b/app/MindWork AI Studio/packages.lock.json
@@ -210,6 +210,6 @@
"type": "Project"
}
},
- "net8.0/osx-x64": {}
+ "net8.0/osx-arm64": {}
}
}
\ No newline at end of file
diff --git a/app/MindWork AI Studio/wwwroot/changelog/v0.9.28.md b/app/MindWork AI Studio/wwwroot/changelog/v0.9.28.md
new file mode 100644
index 00000000..29875ade
--- /dev/null
+++ b/app/MindWork AI Studio/wwwroot/changelog/v0.9.28.md
@@ -0,0 +1,4 @@
+# v0.9.28, build 203 (2025-0x-xx xx:xx UTC)
+- Added an information view to all data sources to the data source configuration page. The data source configuration is a preview feature behind the RAG feature flag.
+- Added a ERI ((E)xternal (R)etrieval (I)nterface) client for communication with any ERI server.
+- Improved the resource handling when loading models.
\ No newline at end of file