Merge pull request #63 from LD-Reborn/59-implement-missing-endpoints-in-client

59 implement missing endpoints in client
This commit is contained in:
LD50
2025-12-29 19:51:35 +01:00
committed by GitHub
9 changed files with 316 additions and 119 deletions

View File

@@ -34,24 +34,44 @@ public class Client
this.searchdomain = searchdomain ?? ""; this.searchdomain = searchdomain ?? "";
} }
public async Task<EntityListResults> EntityListAsync(bool returnEmbeddings = false)
{
return await EntityListAsync(searchdomain, returnEmbeddings);
}
public async Task<EntityListResults> EntityListAsync(string searchdomain, bool returnEmbeddings = false)
{
var url = $"{baseUri}/Entities?apiKey={HttpUtility.UrlEncode(apiKey)}&searchdomain={HttpUtility.UrlEncode(searchdomain)}&returnEmbeddings={HttpUtility.UrlEncode(returnEmbeddings.ToString())}";
return await GetUrlAndProcessJson<EntityListResults>(url);
}
public async Task<EntityIndexResult> EntityIndexAsync(List<JSONEntity> jsonEntity)
{
return await EntityIndexAsync(JsonSerializer.Serialize(jsonEntity));
}
public async Task<EntityIndexResult> EntityIndexAsync(string jsonEntity)
{
var content = new StringContent(jsonEntity, Encoding.UTF8, "application/json");
return await PutUrlAndProcessJson<EntityIndexResult>(GetUrl($"{baseUri}", "Entities", apiKey, []), content);
}
public async Task<EntityDeleteResults> EntityDeleteAsync(string entityName)
{
return await EntityDeleteAsync(searchdomain, entityName);
}
public async Task<EntityDeleteResults> EntityDeleteAsync(string searchdomain, string entityName)
{
var url = $"{baseUri}/Entity?apiKey={HttpUtility.UrlEncode(apiKey)}&searchdomain={HttpUtility.UrlEncode(searchdomain)}&entity={HttpUtility.UrlEncode(entityName)}";
return await DeleteUrlAndProcessJson<EntityDeleteResults>(url);
}
public async Task<SearchdomainListResults> SearchdomainListAsync() public async Task<SearchdomainListResults> SearchdomainListAsync()
{ {
return await GetUrlAndProcessJson<SearchdomainListResults>(GetUrl($"{baseUri}", "Searchdomains", apiKey, [])); return await GetUrlAndProcessJson<SearchdomainListResults>(GetUrl($"{baseUri}", "Searchdomains", apiKey, []));
} }
public async Task<SearchdomainDeleteResults> SearchdomainDeleteAsync()
{
return await SearchdomainDeleteAsync(searchdomain);
}
public async Task<SearchdomainDeleteResults> SearchdomainDeleteAsync(string searchdomain)
{
return await DeleteUrlAndProcessJson<SearchdomainDeleteResults>(GetUrl($"{baseUri}", "Searchdomain", apiKey, new Dictionary<string, string>()
{
{"searchdomain", searchdomain}
}));
}
public async Task<SearchdomainCreateResults> SearchdomainCreateAsync() public async Task<SearchdomainCreateResults> SearchdomainCreateAsync()
{ {
return await SearchdomainCreateAsync(searchdomain); return await SearchdomainCreateAsync(searchdomain);
@@ -65,6 +85,19 @@ public class Client
}), new StringContent(JsonSerializer.Serialize(searchdomainSettings), Encoding.UTF8, "application/json")); }), new StringContent(JsonSerializer.Serialize(searchdomainSettings), Encoding.UTF8, "application/json"));
} }
public async Task<SearchdomainDeleteResults> SearchdomainDeleteAsync()
{
return await SearchdomainDeleteAsync(searchdomain);
}
public async Task<SearchdomainDeleteResults> SearchdomainDeleteAsync(string searchdomain)
{
return await DeleteUrlAndProcessJson<SearchdomainDeleteResults>(GetUrl($"{baseUri}", "Searchdomain", apiKey, new Dictionary<string, string>()
{
{"searchdomain", searchdomain}
}));
}
public async Task<SearchdomainUpdateResults> SearchdomainUpdateAsync(string newName, string settings = "{}") public async Task<SearchdomainUpdateResults> SearchdomainUpdateAsync(string newName, string settings = "{}")
{ {
SearchdomainUpdateResults updateResults = await SearchdomainUpdateAsync(searchdomain, newName, settings); SearchdomainUpdateResults updateResults = await SearchdomainUpdateAsync(searchdomain, newName, settings);
@@ -86,51 +119,104 @@ public class Client
}), new StringContent(settings, Encoding.UTF8, "application/json")); }), new StringContent(settings, Encoding.UTF8, "application/json"));
} }
public async Task<EntityQueryResults> EntityQueryAsync(string query) public async Task<SearchdomainSearchesResults> SearchdomainGetQueriesAsync(string searchdomain)
{ {
return await EntityQueryAsync(searchdomain, query); Dictionary<string, string> parameters = new()
{
{"searchdomain", searchdomain}
};
return await GetUrlAndProcessJson<SearchdomainSearchesResults>(GetUrl($"{baseUri}/Searchdomain", "Queries", apiKey, parameters));
} }
public async Task<EntityQueryResults> EntityQueryAsync(string searchdomain, string query) public async Task<EntityQueryResults> SearchdomainQueryAsync(string query)
{ {
return await PostUrlAndProcessJson<EntityQueryResults>(GetUrl($"{baseUri}/Searchdomain", "Query", apiKey, new Dictionary<string, string>() return await SearchdomainQueryAsync(searchdomain, query);
}
public async Task<EntityQueryResults> SearchdomainQueryAsync(string searchdomain, string query, int? topN = null, bool returnAttributes = false)
{
Dictionary<string, string> parameters = new()
{ {
{"searchdomain", searchdomain}, {"searchdomain", searchdomain},
{"query", query} {"query", query}
}), null); };
if (topN is not null) parameters.Add("topN", ((int)topN).ToString());
if (returnAttributes) parameters.Add("returnAttributes", returnAttributes.ToString());
return await PostUrlAndProcessJson<EntityQueryResults>(GetUrl($"{baseUri}/Searchdomain", "Query", apiKey, parameters), null);
} }
public async Task<EntityIndexResult> EntityIndexAsync(List<JSONEntity> jsonEntity) public async Task<SearchdomainDeleteSearchResult> SearchdomainDeleteQueryAsync(string searchdomain, string query)
{ {
return await EntityIndexAsync(JsonSerializer.Serialize(jsonEntity)); Dictionary<string, string> parameters = new()
{
{"searchdomain", searchdomain},
{"query", query}
};
return await DeleteUrlAndProcessJson<SearchdomainDeleteSearchResult>(GetUrl($"{baseUri}/Searchdomain", "Query", apiKey, parameters));
} }
public async Task<EntityIndexResult> EntityIndexAsync(string jsonEntity) public async Task<SearchdomainUpdateSearchResult> SearchdomainUpdateQueryAsync(string searchdomain, string query, List<ResultItem> results)
{ {
var content = new StringContent(jsonEntity, Encoding.UTF8, "application/json"); Dictionary<string, string> parameters = new()
return await PutUrlAndProcessJson<EntityIndexResult>(GetUrl($"{baseUri}", "Entity", apiKey, []), content); {
{"searchdomain", searchdomain},
{"query", query}
};
return await PatchUrlAndProcessJson<SearchdomainUpdateSearchResult>(
GetUrl($"{baseUri}/Searchdomain", "Query", apiKey, parameters),
new StringContent(JsonSerializer.Serialize(results), Encoding.UTF8, "application/json"));
} }
public async Task<EntityListResults> EntityListAsync(bool returnEmbeddings = false) public async Task<SearchdomainSettingsResults> SearchdomainGetSettingsAsync(string searchdomain)
{ {
return await EntityListAsync(searchdomain, returnEmbeddings); Dictionary<string, string> parameters = new()
{
{"searchdomain", searchdomain}
};
return await GetUrlAndProcessJson<SearchdomainSettingsResults>(GetUrl($"{baseUri}/Searchdomain", "Settings", apiKey, parameters));
} }
public async Task<EntityListResults> EntityListAsync(string searchdomain, bool returnEmbeddings = false) public async Task<SearchdomainUpdateResults> SearchdomainUpdateSettingsAsync(string searchdomain, SearchdomainSettings searchdomainSettings)
{ {
var url = $"{baseUri}/Entities?apiKey={HttpUtility.UrlEncode(apiKey)}&searchdomain={HttpUtility.UrlEncode(searchdomain)}&returnEmbeddings={HttpUtility.UrlEncode(returnEmbeddings.ToString())}"; Dictionary<string, string> parameters = new()
return await GetUrlAndProcessJson<EntityListResults>(url); {
{"searchdomain", searchdomain}
};
StringContent content = new(JsonSerializer.Serialize(searchdomainSettings), Encoding.UTF8, "application/json");
return await PutUrlAndProcessJson<SearchdomainUpdateResults>(GetUrl($"{baseUri}/Searchdomain", "Settings", apiKey, parameters), content);
} }
public async Task<EntityDeleteResults> EntityDeleteAsync(string entityName) public async Task<SearchdomainSearchCacheSizeResults> SearchdomainGetQueryCacheSizeAsync(string searchdomain)
{ {
return await EntityDeleteAsync(searchdomain, entityName); Dictionary<string, string> parameters = new()
{
{"searchdomain", searchdomain}
};
return await GetUrlAndProcessJson<SearchdomainSearchCacheSizeResults>(GetUrl($"{baseUri}/Searchdomain/QueryCache", "Size", apiKey, parameters));
} }
public async Task<EntityDeleteResults> EntityDeleteAsync(string searchdomain, string entityName) public async Task<SearchdomainInvalidateCacheResults> SearchdomainClearQueryCache(string searchdomain)
{ {
var url = $"{baseUri}/Entity?apiKey={HttpUtility.UrlEncode(apiKey)}&searchdomain={HttpUtility.UrlEncode(searchdomain)}&entity={HttpUtility.UrlEncode(entityName)}"; Dictionary<string, string> parameters = new()
return await DeleteUrlAndProcessJson<EntityDeleteResults>(url); {
{"searchdomain", searchdomain}
};
return await PostUrlAndProcessJson<SearchdomainInvalidateCacheResults>(GetUrl($"{baseUri}/Searchdomain/QueryCache", "Clear", apiKey, parameters), null);
}
public async Task<SearchdomainGetDatabaseSizeResult> SearchdomainGetDatabaseSizeAsync(string searchdomain)
{
Dictionary<string, string> parameters = new()
{
{"searchdomain", searchdomain}
};
return await GetUrlAndProcessJson<SearchdomainGetDatabaseSizeResult>(GetUrl($"{baseUri}/Searchdomain/Database", "Size", apiKey, parameters));
}
public async Task<ServerGetModelsResult> ServerGetModelsAsync()
{
return await GetUrlAndProcessJson<ServerGetModelsResult>(GetUrl($"{baseUri}/Server", "Models", apiKey, []));
} }
private static async Task<T> GetUrlAndProcessJson<T>(string url) private static async Task<T> GetUrlAndProcessJson<T>(string url)
@@ -163,6 +249,16 @@ public class Client
return result; return result;
} }
private static async Task<T> PatchUrlAndProcessJson<T>(string url, HttpContent content)
{
using var client = new HttpClient();
var response = await client.PatchAsync(url, content);
string responseContent = await response.Content.ReadAsStringAsync();
var result = JsonSerializer.Deserialize<T>(responseContent)
?? throw new Exception($"Failed to deserialize JSON to type {typeof(T).Name}");
return result;
}
private static async Task<T> DeleteUrlAndProcessJson<T>(string url) private static async Task<T> DeleteUrlAndProcessJson<T>(string url)
{ {
using var client = new HttpClient(); using var client = new HttpClient();

View File

@@ -24,44 +24,17 @@ public class EntityController : ControllerBase
_databaseHelper = databaseHelper; _databaseHelper = databaseHelper;
} }
[HttpPut] /// <summary>
public ActionResult<EntityIndexResult> Index([FromBody] List<JSONEntity>? jsonEntities) /// List the entities in a searchdomain
{ /// </summary>
try /// <remarks>
{ /// With returnModels = false expect: "Datapoints": [..., "Embeddings": null]<br/>
List<Entity>? entities = _searchdomainHelper.EntitiesFromJSON( /// With returnModels = true expect: "Datapoints": [..., "Embeddings": [{"Model": "...", "Embeddings": []}, ...]]<br/>
_domainManager, /// With returnEmbeddings = true expect: "Datapoints": [..., "Embeddings": [{"Model": "...", "Embeddings": [0.007384672,0.01309805,0.0012528514,...]}, ...]]
_logger, /// </remarks>
JsonSerializer.Serialize(jsonEntities)); /// <param name="searchdomain">Name of the searchdomain</param>
if (entities is not null && jsonEntities is not null) /// <param name="returnModels">Include the models in the response</param>
{ /// <param name="returnEmbeddings">Include the embeddings in the response (requires returnModels)</param>
List<string> invalidatedSearchdomains = [];
foreach (var jsonEntity in jsonEntities)
{
string jsonEntityName = jsonEntity.Name;
string jsonEntitySearchdomainName = jsonEntity.Searchdomain;
if (entities.Select(x => x.name == jsonEntityName).Any()
&& !invalidatedSearchdomains.Contains(jsonEntitySearchdomainName))
{
invalidatedSearchdomains.Add(jsonEntitySearchdomainName);
}
}
return Ok(new EntityIndexResult() { Success = true });
}
else
{
_logger.LogError("Unable to deserialize an entity");
return Ok(new EntityIndexResult() { Success = false, Message = "Unable to deserialize an entity"});
}
} catch (Exception ex)
{
if (ex.InnerException is not null) ex = ex.InnerException;
_logger.LogError("Unable to index the provided entities. {ex.Message} - {ex.StackTrace}", [ex.Message, ex.StackTrace]);
return Ok(new EntityIndexResult() { Success = false, Message = ex.Message });
}
}
[HttpGet("/Entities")] [HttpGet("/Entities")]
public ActionResult<EntityListResults> List(string searchdomain, bool returnModels = false, bool returnEmbeddings = false) public ActionResult<EntityListResults> List(string searchdomain, bool returnModels = false, bool returnEmbeddings = false)
{ {
@@ -109,6 +82,56 @@ public class EntityController : ControllerBase
return Ok(entityListResults); return Ok(entityListResults);
} }
/// <summary>
/// Index entities
/// </summary>
/// <remarks>
/// Behavior: Creates new entities, but overwrites existing entities that have the same name
/// </remarks>
/// <param name="jsonEntities">Entities to index</param>
[HttpPut("/Entities")]
public ActionResult<EntityIndexResult> Index([FromBody] List<JSONEntity>? jsonEntities)
{
try
{
List<Entity>? entities = _searchdomainHelper.EntitiesFromJSON(
_domainManager,
_logger,
JsonSerializer.Serialize(jsonEntities));
if (entities is not null && jsonEntities is not null)
{
List<string> invalidatedSearchdomains = [];
foreach (var jsonEntity in jsonEntities)
{
string jsonEntityName = jsonEntity.Name;
string jsonEntitySearchdomainName = jsonEntity.Searchdomain;
if (entities.Select(x => x.name == jsonEntityName).Any()
&& !invalidatedSearchdomains.Contains(jsonEntitySearchdomainName))
{
invalidatedSearchdomains.Add(jsonEntitySearchdomainName);
}
}
return Ok(new EntityIndexResult() { Success = true });
}
else
{
_logger.LogError("Unable to deserialize an entity");
return Ok(new EntityIndexResult() { Success = false, Message = "Unable to deserialize an entity"});
}
} catch (Exception ex)
{
if (ex.InnerException is not null) ex = ex.InnerException;
_logger.LogError("Unable to index the provided entities. {ex.Message} - {ex.StackTrace}", [ex.Message, ex.StackTrace]);
return Ok(new EntityIndexResult() { Success = false, Message = ex.Message });
}
}
/// <summary>
/// Deletes an entity
/// </summary>
/// <param name="searchdomain">Name of the searchdomain</param>
/// <param name="entityName">Name of the entity</param>
[HttpDelete] [HttpDelete]
public ActionResult<EntityDeleteResults> Delete(string searchdomain, string entityName) public ActionResult<EntityDeleteResults> Delete(string searchdomain, string entityName)
{ {

View File

@@ -1,3 +1,4 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json; using System.Text.Json;
using ElmahCore; using ElmahCore;
using Microsoft.AspNetCore.Http.HttpResults; using Microsoft.AspNetCore.Http.HttpResults;
@@ -23,6 +24,9 @@ public class SearchdomainController : ControllerBase
_domainManager = domainManager; _domainManager = domainManager;
} }
/// <summary>
/// Lists all searchdomains
/// </summary>
[HttpGet("/Searchdomains")] [HttpGet("/Searchdomains")]
public ActionResult<SearchdomainListResults> List() public ActionResult<SearchdomainListResults> List()
{ {
@@ -40,8 +44,13 @@ public class SearchdomainController : ControllerBase
return Ok(searchdomainListResults); return Ok(searchdomainListResults);
} }
/// <summary>
/// Creates a new searchdomain
/// </summary>
/// <param name="searchdomain">Name of the searchdomain</param>
/// <param name="settings">Optional initial settings</param>
[HttpPost] [HttpPost]
public ActionResult<SearchdomainCreateResults> Create(string searchdomain, [FromBody]SearchdomainSettings settings = new()) public ActionResult<SearchdomainCreateResults> Create([Required]string searchdomain, [FromBody]SearchdomainSettings settings = new())
{ {
try try
{ {
@@ -54,8 +63,12 @@ public class SearchdomainController : ControllerBase
} }
} }
/// <summary>
/// Deletes a searchdomain
/// </summary>
/// <param name="searchdomain">Name of the searchdomain</param>
[HttpDelete] [HttpDelete]
public ActionResult<SearchdomainDeleteResults> Delete(string searchdomain) public ActionResult<SearchdomainDeleteResults> Delete([Required]string searchdomain)
{ {
bool success; bool success;
int deletedEntries; int deletedEntries;
@@ -84,8 +97,14 @@ public class SearchdomainController : ControllerBase
return Ok(new SearchdomainDeleteResults(){Success = success, DeletedEntities = deletedEntries, Message = message}); return Ok(new SearchdomainDeleteResults(){Success = success, DeletedEntities = deletedEntries, Message = message});
} }
/// <summary>
/// Updates name and settings of a searchdomain
/// </summary>
/// <param name="searchdomain">Name of the searchdomain</param>
/// <param name="newName">Updated name of the searchdomain</param>
/// <param name="settings">Updated settings of searchdomain</param>
[HttpPut] [HttpPut]
public ActionResult<SearchdomainUpdateResults> Update(string searchdomain, string newName, [FromBody]string? settings = "{}") public ActionResult<SearchdomainUpdateResults> Update([Required]string searchdomain, string newName, [FromBody]SearchdomainSettings? settings)
{ {
(Searchdomain? searchdomain_, int? httpStatusCode, string? message) = SearchdomainHelper.TryGetSearchdomain(_domainManager, searchdomain, _logger); (Searchdomain? searchdomain_, int? httpStatusCode, string? message) = SearchdomainHelper.TryGetSearchdomain(_domainManager, searchdomain, _logger);
if (searchdomain_ is null || httpStatusCode is not null) return StatusCode(httpStatusCode ?? 500, new SearchdomainUpdateResults(){Success = false, Message = message}); if (searchdomain_ is null || httpStatusCode is not null) return StatusCode(httpStatusCode ?? 500, new SearchdomainUpdateResults(){Success = false, Message = message});
@@ -110,8 +129,29 @@ public class SearchdomainController : ControllerBase
return Ok(new SearchdomainUpdateResults(){Success = true}); return Ok(new SearchdomainUpdateResults(){Success = true});
} }
/// <summary>
/// Gets the query cache of a searchdomain
/// </summary>
/// <param name="searchdomain">Name of the searchdomain</param>
[HttpGet("Queries")]
public ActionResult<SearchdomainSearchesResults> GetQueries([Required]string searchdomain)
{
(Searchdomain? searchdomain_, int? httpStatusCode, string? message) = SearchdomainHelper.TryGetSearchdomain(_domainManager, searchdomain, _logger);
if (searchdomain_ is null || httpStatusCode is not null) return StatusCode(httpStatusCode ?? 500, new SearchdomainUpdateResults(){Success = false, Message = message});
Dictionary<string, DateTimedSearchResult> searchCache = searchdomain_.searchCache;
return Ok(new SearchdomainSearchesResults() { Searches = searchCache, Success = true });
}
/// <summary>
/// Executes a query in the searchdomain
/// </summary>
/// <param name="searchdomain">Name of the searchdomain</param>
/// <param name="query">Query to execute</param>
/// <param name="topN">Return only the top N results</param>
/// <param name="returnAttributes">Return the attributes of the object</param>
[HttpPost("Query")] [HttpPost("Query")]
public ActionResult<EntityQueryResults> Query(string searchdomain, string query, int? topN, bool returnAttributes = false) public ActionResult<EntityQueryResults> Query([Required]string searchdomain, [Required]string query, int? topN, bool returnAttributes = false)
{ {
(Searchdomain? searchdomain_, int? httpStatusCode, string? message) = SearchdomainHelper.TryGetSearchdomain(_domainManager, searchdomain, _logger); (Searchdomain? searchdomain_, int? httpStatusCode, string? message) = SearchdomainHelper.TryGetSearchdomain(_domainManager, searchdomain, _logger);
if (searchdomain_ is null || httpStatusCode is not null) return StatusCode(httpStatusCode ?? 500, new SearchdomainUpdateResults(){Success = false, Message = message}); if (searchdomain_ is null || httpStatusCode is not null) return StatusCode(httpStatusCode ?? 500, new SearchdomainUpdateResults(){Success = false, Message = message});
@@ -125,33 +165,13 @@ public class SearchdomainController : ControllerBase
return Ok(new EntityQueryResults(){Results = queryResults, Success = true }); return Ok(new EntityQueryResults(){Results = queryResults, Success = true });
} }
[HttpPut("Settings")] /// <summary>
public ActionResult<SearchdomainUpdateResults> UpdateSettings(string searchdomain, [FromBody] SearchdomainSettings request) /// Deletes a query from the query cache
{ /// </summary>
(Searchdomain? searchdomain_, int? httpStatusCode, string? message) = SearchdomainHelper.TryGetSearchdomain(_domainManager, searchdomain, _logger); /// <param name="searchdomain">Name of the searchdomain</param>
if (searchdomain_ is null || httpStatusCode is not null) return StatusCode(httpStatusCode ?? 500, new SearchdomainUpdateResults(){Success = false, Message = message}); /// <param name="query">Query to delete</param>
Dictionary<string, dynamic> parameters = new()
{
{"settings", JsonSerializer.Serialize(request)},
{"id", searchdomain_.id}
};
searchdomain_.helper.ExecuteSQLNonQuery("UPDATE searchdomain set settings = @settings WHERE id = @id", parameters);
searchdomain_.settings = request;
return Ok(new SearchdomainUpdateResults(){Success = true});
}
[HttpGet("Queries")]
public ActionResult<SearchdomainSearchesResults> GetQueries(string searchdomain)
{
(Searchdomain? searchdomain_, int? httpStatusCode, string? message) = SearchdomainHelper.TryGetSearchdomain(_domainManager, searchdomain, _logger);
if (searchdomain_ is null || httpStatusCode is not null) return StatusCode(httpStatusCode ?? 500, new SearchdomainUpdateResults(){Success = false, Message = message});
Dictionary<string, DateTimedSearchResult> searchCache = searchdomain_.searchCache;
return Ok(new SearchdomainSearchesResults() { Searches = searchCache, Success = true });
}
[HttpDelete("Query")] [HttpDelete("Query")]
public ActionResult<SearchdomainDeleteSearchResult> DeleteQuery(string searchdomain, string query) public ActionResult<SearchdomainDeleteSearchResult> DeleteQuery([Required]string searchdomain, [Required]string query)
{ {
(Searchdomain? searchdomain_, int? httpStatusCode, string? message) = SearchdomainHelper.TryGetSearchdomain(_domainManager, searchdomain, _logger); (Searchdomain? searchdomain_, int? httpStatusCode, string? message) = SearchdomainHelper.TryGetSearchdomain(_domainManager, searchdomain, _logger);
if (searchdomain_ is null || httpStatusCode is not null) return StatusCode(httpStatusCode ?? 500, new SearchdomainUpdateResults(){Success = false, Message = message}); if (searchdomain_ is null || httpStatusCode is not null) return StatusCode(httpStatusCode ?? 500, new SearchdomainUpdateResults(){Success = false, Message = message});
@@ -165,8 +185,14 @@ public class SearchdomainController : ControllerBase
return Ok(new SearchdomainDeleteSearchResult() {Success = false, Message = "Query not found in search cache"}); return Ok(new SearchdomainDeleteSearchResult() {Success = false, Message = "Query not found in search cache"});
} }
/// <summary>
/// Updates a query from the query cache
/// </summary>
/// <param name="searchdomain">Name of the searchdomain</param>
/// <param name="query">Query to update</param>
/// <param name="results">List of results to apply to the query</param>
[HttpPatch("Query")] [HttpPatch("Query")]
public ActionResult<SearchdomainUpdateSearchResult> UpdateQuery(string searchdomain, string query, [FromBody]List<ResultItem> results) public ActionResult<SearchdomainUpdateSearchResult> UpdateQuery([Required]string searchdomain, [Required]string query, [Required][FromBody]List<ResultItem> results)
{ {
(Searchdomain? searchdomain_, int? httpStatusCode, string? message) = SearchdomainHelper.TryGetSearchdomain(_domainManager, searchdomain, _logger); (Searchdomain? searchdomain_, int? httpStatusCode, string? message) = SearchdomainHelper.TryGetSearchdomain(_domainManager, searchdomain, _logger);
if (searchdomain_ is null || httpStatusCode is not null) return StatusCode(httpStatusCode ?? 500, new SearchdomainUpdateResults(){Success = false, Message = message}); if (searchdomain_ is null || httpStatusCode is not null) return StatusCode(httpStatusCode ?? 500, new SearchdomainUpdateResults(){Success = false, Message = message});
@@ -182,8 +208,12 @@ public class SearchdomainController : ControllerBase
return Ok(new SearchdomainUpdateSearchResult() {Success = false, Message = "Query not found in search cache"}); return Ok(new SearchdomainUpdateSearchResult() {Success = false, Message = "Query not found in search cache"});
} }
/// <summary>
/// Get the settings of a searchdomain
/// </summary>
/// <param name="searchdomain">Name of the searchdomain</param>
[HttpGet("Settings")] [HttpGet("Settings")]
public ActionResult<SearchdomainSettingsResults> GetSettings(string searchdomain) public ActionResult<SearchdomainSettingsResults> GetSettings([Required]string searchdomain)
{ {
(Searchdomain? searchdomain_, int? httpStatusCode, string? message) = SearchdomainHelper.TryGetSearchdomain(_domainManager, searchdomain, _logger); (Searchdomain? searchdomain_, int? httpStatusCode, string? message) = SearchdomainHelper.TryGetSearchdomain(_domainManager, searchdomain, _logger);
if (searchdomain_ is null || httpStatusCode is not null) return StatusCode(httpStatusCode ?? 500, new SearchdomainUpdateResults(){Success = false, Message = message}); if (searchdomain_ is null || httpStatusCode is not null) return StatusCode(httpStatusCode ?? 500, new SearchdomainUpdateResults(){Success = false, Message = message});
@@ -191,8 +221,31 @@ public class SearchdomainController : ControllerBase
return Ok(new SearchdomainSettingsResults() { Settings = settings, Success = true }); return Ok(new SearchdomainSettingsResults() { Settings = settings, Success = true });
} }
[HttpGet("SearchCache/Size")] /// <summary>
public ActionResult<SearchdomainSearchCacheSizeResults> GetSearchCacheSize(string searchdomain) /// Update the settings of a searchdomain
/// </summary>
/// <param name="searchdomain">Name of the searchdomain</param>
[HttpPut("Settings")]
public ActionResult<SearchdomainUpdateResults> UpdateSettings([Required]string searchdomain, [Required][FromBody] SearchdomainSettings request)
{
(Searchdomain? searchdomain_, int? httpStatusCode, string? message) = SearchdomainHelper.TryGetSearchdomain(_domainManager, searchdomain, _logger);
if (searchdomain_ is null || httpStatusCode is not null) return StatusCode(httpStatusCode ?? 500, new SearchdomainUpdateResults(){Success = false, Message = message});
Dictionary<string, dynamic> parameters = new()
{
{"settings", JsonSerializer.Serialize(request)},
{"id", searchdomain_.id}
};
searchdomain_.helper.ExecuteSQLNonQuery("UPDATE searchdomain set settings = @settings WHERE id = @id", parameters);
searchdomain_.settings = request;
return Ok(new SearchdomainUpdateResults(){Success = true});
}
/// <summary>
/// Get the query cache size of a searchdomain
/// </summary>
/// <param name="searchdomain">Name of the searchdomain</param>
[HttpGet("QueryCache/Size")]
public ActionResult<SearchdomainSearchCacheSizeResults> GetSearchCacheSize([Required]string searchdomain)
{ {
(Searchdomain? searchdomain_, int? httpStatusCode, string? message) = SearchdomainHelper.TryGetSearchdomain(_domainManager, searchdomain, _logger); (Searchdomain? searchdomain_, int? httpStatusCode, string? message) = SearchdomainHelper.TryGetSearchdomain(_domainManager, searchdomain, _logger);
if (searchdomain_ is null || httpStatusCode is not null) return StatusCode(httpStatusCode ?? 500, new SearchdomainUpdateResults(){Success = false, Message = message}); if (searchdomain_ is null || httpStatusCode is not null) return StatusCode(httpStatusCode ?? 500, new SearchdomainUpdateResults(){Success = false, Message = message});
@@ -204,11 +257,15 @@ public class SearchdomainController : ControllerBase
sizeInBytes += entry.Key.Length * sizeof(char); // string characters sizeInBytes += entry.Key.Length * sizeof(char); // string characters
sizeInBytes += entry.Value.EstimateSize(); sizeInBytes += entry.Value.EstimateSize();
} }
return Ok(new SearchdomainSearchCacheSizeResults() { SearchCacheSizeBytes = sizeInBytes, Success = true }); return Ok(new SearchdomainSearchCacheSizeResults() { QueryCacheSizeBytes = sizeInBytes, Success = true });
} }
[HttpPost("SearchCache/Clear")] /// <summary>
public ActionResult<SearchdomainInvalidateCacheResults> InvalidateSearchCache(string searchdomain) /// Clear the query cache of a searchdomain
/// </summary>
/// <param name="searchdomain">Name of the searchdomain</param>
[HttpPost("QueryCache/Clear")]
public ActionResult<SearchdomainInvalidateCacheResults> InvalidateSearchCache([Required]string searchdomain)
{ {
(Searchdomain? searchdomain_, int? httpStatusCode, string? message) = SearchdomainHelper.TryGetSearchdomain(_domainManager, searchdomain, _logger); (Searchdomain? searchdomain_, int? httpStatusCode, string? message) = SearchdomainHelper.TryGetSearchdomain(_domainManager, searchdomain, _logger);
if (searchdomain_ is null || httpStatusCode is not null) return StatusCode(httpStatusCode ?? 500, new SearchdomainUpdateResults(){Success = false, Message = message}); if (searchdomain_ is null || httpStatusCode is not null) return StatusCode(httpStatusCode ?? 500, new SearchdomainUpdateResults(){Success = false, Message = message});
@@ -216,8 +273,12 @@ public class SearchdomainController : ControllerBase
return Ok(new SearchdomainInvalidateCacheResults(){Success = true}); return Ok(new SearchdomainInvalidateCacheResults(){Success = true});
} }
/// <summary>
/// Get the disk size of a searchdomain in bytes
/// </summary>
/// <param name="searchdomain">Name of the searchdomain</param>
[HttpGet("Database/Size")] [HttpGet("Database/Size")]
public ActionResult<SearchdomainGetDatabaseSizeResult> GetDatabaseSize(string searchdomain) public ActionResult<SearchdomainGetDatabaseSizeResult> GetDatabaseSize([Required]string searchdomain)
{ {
(Searchdomain? searchdomain_, int? httpStatusCode, string? message) = SearchdomainHelper.TryGetSearchdomain(_domainManager, searchdomain, _logger); (Searchdomain? searchdomain_, int? httpStatusCode, string? message) = SearchdomainHelper.TryGetSearchdomain(_domainManager, searchdomain, _logger);
if (searchdomain_ is null || httpStatusCode is not null) return StatusCode(httpStatusCode ?? 500, new SearchdomainUpdateResults(){Success = false, Message = message}); if (searchdomain_ is null || httpStatusCode is not null) return StatusCode(httpStatusCode ?? 500, new SearchdomainUpdateResults(){Success = false, Message = message});

View File

@@ -22,6 +22,12 @@ public class ServerController : ControllerBase
_aIProvider = aIProvider; _aIProvider = aIProvider;
} }
/// <summary>
/// Lists the models available to the server
/// </summary>
/// <remarks>
/// Returns ALL models available to the server - not only the embedding models.
/// </remarks>
[HttpGet("Models")] [HttpGet("Models")]
public ActionResult<ServerGetModelsResult> GetModels() public ActionResult<ServerGetModelsResult> GetModels()
{ {

View File

@@ -9,6 +9,7 @@ using Server.Helper;
using Server.Models; using Server.Models;
using Server.Services; using Server.Services;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using System.Reflection;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
@@ -37,7 +38,12 @@ builder.Services.AddScoped<LocalizationService>();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer(); builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(); builder.Services.AddSwaggerGen(c =>
{
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
c.IncludeXmlComments(xmlPath);
});
Log.Logger = new LoggerConfiguration() Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(builder.Configuration) .ReadFrom.Configuration(builder.Configuration)
.CreateLogger(); .CreateLogger();

View File

@@ -6,6 +6,11 @@
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup> </PropertyGroup>
<PropertyGroup>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>
</PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="AdaptiveExpressions" Version="4.23.0" /> <PackageReference Include="AdaptiveExpressions" Version="4.23.0" />
<PackageReference Include="ElmahCore" Version="2.1.2" /> <PackageReference Include="ElmahCore" Version="2.1.2" />

View File

@@ -745,7 +745,7 @@
"datapoints": datapoints "datapoints": datapoints
}]; }];
showToast("@T["Creating entity"]", "primary"); showToast("@T["Creating entity"]", "primary");
fetch(`/Entity`, { fetch(`/Entities`, {
method: 'PUT', method: 'PUT',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
@@ -804,7 +804,7 @@
.getElementById('cacheClear') .getElementById('cacheClear')
.addEventListener('click', () => { .addEventListener('click', () => {
const domainKey = getSelectedDomainKey(); const domainKey = getSelectedDomainKey();
fetch(`/Searchdomain/SearchCache/Clear?searchdomain=${encodeURIComponent(domains[domainKey])}`, { fetch(`/Searchdomain/QueryCache/Clear?searchdomain=${encodeURIComponent(domains[domainKey])}`, {
method: 'POST' method: 'POST'
}).then(response => { }).then(response => {
if (response.ok) { if (response.ok) {
@@ -874,7 +874,7 @@
"datapoints": datapoints "datapoints": datapoints
}]; }];
showToast("@T["Updating entity"]", "primary"); showToast("@T["Updating entity"]", "primary");
fetch(`/Entity`, { fetch(`/Entities`, {
method: 'PUT', method: 'PUT',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
@@ -1031,7 +1031,7 @@
} }
function getSearchdomainCacheUtilization(domainKey) { function getSearchdomainCacheUtilization(domainKey) {
return fetch(`/Searchdomain/SearchCache/Size?searchdomain=${encodeURIComponent(domains[domainKey])}`) return fetch(`/Searchdomain/QueryCache/Size?searchdomain=${encodeURIComponent(domains[domainKey])}`)
.then(r => r.json()); .then(r => r.json());
} }
@@ -1106,10 +1106,10 @@
} }
}); });
cacheUtilizationPromise.then(cacheUtilization => { cacheUtilizationPromise.then(cacheUtilization => {
if (cacheUtilization != null && cacheUtilization.SearchCacheSizeBytes != null) if (cacheUtilization != null && cacheUtilization.QueryCacheSizeBytes != null)
{ {
document.querySelector('#cacheUtilization').innerText = document.querySelector('#cacheUtilization').innerText =
`${NumberOfBytesAsHumanReadable(cacheUtilization.SearchCacheSizeBytes)}`; `${NumberOfBytesAsHumanReadable(cacheUtilization.QueryCacheSizeBytes)}`;
} else { } else {
showToast("@T["Unable to fetch searchdomain cache utilization"]", "danger"); showToast("@T["Unable to fetch searchdomain cache utilization"]", "danger");
console.error('Failed to fetch searchdomain cache utilization'); console.error('Failed to fetch searchdomain cache utilization');

View File

@@ -24,7 +24,7 @@
"172.17.0.1" "172.17.0.1"
] ]
}, },
"EmbeddingCacheMaxCount": 5, "EmbeddingCacheMaxCount": 10000000,
"AiProviders": { "AiProviders": {
"ollama": { "ollama": {
"handler": "ollama", "handler": "ollama",

View File

@@ -43,8 +43,8 @@ public class SearchdomainSettingsResults : SuccesMessageBaseModel
public class SearchdomainSearchCacheSizeResults : SuccesMessageBaseModel public class SearchdomainSearchCacheSizeResults : SuccesMessageBaseModel
{ {
[JsonPropertyName("SearchCacheSizeBytes")] [JsonPropertyName("QueryCacheSizeBytes")]
public required long? SearchCacheSizeBytes { get; set; } public required long? QueryCacheSizeBytes { get; set; }
} }
public class SearchdomainInvalidateCacheResults : SuccesMessageBaseModel {} public class SearchdomainInvalidateCacheResults : SuccesMessageBaseModel {}