Compare commits
10 Commits
17cc8f41d5
...
59945cb523
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
59945cb523 | ||
| c13214c4e9 | |||
|
|
6e9e795a16 | ||
| 337782661e | |||
|
|
b6692770c1 | ||
| 141a567927 | |||
|
|
ba41c1cd82 | ||
| b6b812f458 | |||
|
|
9d5f53c5f4 | ||
| a9a5ee4cb6 |
@@ -31,7 +31,12 @@ public class AIProvider
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public float[] GenerateEmbeddings(string modelUri, string[] input)
|
public float[] GenerateEmbeddings(string modelUri, string input)
|
||||||
|
{
|
||||||
|
return [.. GenerateEmbeddings(modelUri, [input]).First()];
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<float[]> GenerateEmbeddings(string modelUri, string[] input)
|
||||||
{
|
{
|
||||||
Uri uri = new(modelUri);
|
Uri uri = new(modelUri);
|
||||||
string provider = uri.Scheme;
|
string provider = uri.Scheme;
|
||||||
@@ -103,13 +108,13 @@ public class AIProvider
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
JObject responseContentJson = JObject.Parse(responseContent);
|
JObject responseContentJson = JObject.Parse(responseContent);
|
||||||
JToken? responseContentTokens = responseContentJson.SelectToken(embeddingsJsonPath);
|
List<JToken>? responseContentTokens = [.. responseContentJson.SelectTokens(embeddingsJsonPath)];
|
||||||
if (responseContentTokens is null)
|
if (responseContentTokens is null)
|
||||||
{
|
{
|
||||||
_logger.LogError("Unable to select tokens using JSONPath {embeddingsJsonPath} for string: {responseContent}.", [embeddingsJsonPath, responseContent]);
|
_logger.LogError("Unable to select tokens using JSONPath {embeddingsJsonPath} for string: {responseContent}.", [embeddingsJsonPath, responseContent]);
|
||||||
throw new JSONPathSelectionException(embeddingsJsonPath, responseContent);
|
throw new JSONPathSelectionException(embeddingsJsonPath, responseContent);
|
||||||
}
|
}
|
||||||
return [.. responseContentTokens.Values<float>()];
|
return [.. responseContentTokens.Select(token => token.ToObject<float[]>() ?? throw new Exception("Unable to cast embeddings response to float[]"))];
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,12 +1,8 @@
|
|||||||
namespace Server.Controllers;
|
namespace Server.Controllers;
|
||||||
|
|
||||||
using System.Reflection;
|
|
||||||
using System.Text.Json;
|
|
||||||
using AdaptiveExpressions;
|
|
||||||
using ElmahCore;
|
using ElmahCore;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Server.Exceptions;
|
|
||||||
using Server.Helper;
|
using Server.Helper;
|
||||||
using Server.Models;
|
using Server.Models;
|
||||||
using Shared;
|
using Shared;
|
||||||
@@ -73,6 +69,7 @@ public class ServerController : ControllerBase
|
|||||||
embeddingsCount += entry.Keys.Count;
|
embeddingsCount += entry.Keys.Count;
|
||||||
}
|
}
|
||||||
var sqlHelper = DatabaseHelper.GetSQLHelper(_options.Value);
|
var sqlHelper = DatabaseHelper.GetSQLHelper(_options.Value);
|
||||||
|
var databaseTotalSize = DatabaseHelper.GetTotalDatabaseSize(sqlHelper);
|
||||||
Task<long> entityCountTask = DatabaseHelper.CountEntities(sqlHelper);
|
Task<long> entityCountTask = DatabaseHelper.CountEntities(sqlHelper);
|
||||||
long queryCacheUtilization = 0;
|
long queryCacheUtilization = 0;
|
||||||
long queryCacheElementCount = 0;
|
long queryCacheElementCount = 0;
|
||||||
@@ -95,6 +92,10 @@ public class ServerController : ControllerBase
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
long entityCount = await entityCountTask;
|
long entityCount = await entityCountTask;
|
||||||
|
GC.Collect();
|
||||||
|
GC.WaitForPendingFinalizers();
|
||||||
|
GC.Collect();
|
||||||
|
long ramTotalSize = GC.GetTotalMemory(false);
|
||||||
|
|
||||||
return new ServerGetStatsResult() {
|
return new ServerGetStatsResult() {
|
||||||
Success = true,
|
Success = true,
|
||||||
@@ -106,7 +107,9 @@ public class ServerController : ControllerBase
|
|||||||
EmbeddingCacheUtilization = size,
|
EmbeddingCacheUtilization = size,
|
||||||
EmbeddingCacheMaxElementCount = _searchdomainManager.EmbeddingCacheMaxCount,
|
EmbeddingCacheMaxElementCount = _searchdomainManager.EmbeddingCacheMaxCount,
|
||||||
EmbeddingCacheElementCount = elementCount,
|
EmbeddingCacheElementCount = elementCount,
|
||||||
EmbeddingsCount = embeddingsCount
|
EmbeddingsCount = embeddingsCount,
|
||||||
|
DatabaseTotalSize = databaseTotalSize,
|
||||||
|
RamTotalSize = ramTotalSize
|
||||||
};
|
};
|
||||||
} catch (Exception ex)
|
} catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -52,9 +52,69 @@ public class Datapoint
|
|||||||
return embeddings;
|
return embeddings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Dictionary<string, Dictionary<string, float[]>> GetEmbeddings(string[] content, List<string> models, AIProvider aIProvider, EnumerableLruCache<string, Dictionary<string, float[]>> embeddingCache)
|
||||||
|
{
|
||||||
|
Dictionary<string, Dictionary<string, float[]>> embeddings = [];
|
||||||
|
foreach (string model in models)
|
||||||
|
{
|
||||||
|
List<string> toBeGenerated = [];
|
||||||
|
embeddings[model] = [];
|
||||||
|
foreach (string value in content)
|
||||||
|
{
|
||||||
|
bool generateThisEntry = true;
|
||||||
|
bool embeddingCacheHasContent = embeddingCache.TryGetValue(value, out var embeddingCacheForContent);
|
||||||
|
if (embeddingCacheHasContent && embeddingCacheForContent is not null)
|
||||||
|
{
|
||||||
|
bool embeddingCacheHasModel = embeddingCacheForContent.TryGetValue(model, out float[]? embedding);
|
||||||
|
if (embeddingCacheHasModel && embedding is not null)
|
||||||
|
{
|
||||||
|
embeddings[model][value] = embedding;
|
||||||
|
generateThisEntry = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (generateThisEntry)
|
||||||
|
{
|
||||||
|
if (!toBeGenerated.Contains(value))
|
||||||
|
{
|
||||||
|
toBeGenerated.Add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
IEnumerable<float[]> generatedEmbeddings = GenerateEmbeddings([.. toBeGenerated], model, aIProvider, embeddingCache);
|
||||||
|
if (generatedEmbeddings.Count() != toBeGenerated.Count)
|
||||||
|
{
|
||||||
|
throw new Exception("Requested embeddings count and generated embeddings count mismatched!");
|
||||||
|
}
|
||||||
|
for (int i = 0; i < toBeGenerated.Count; i++)
|
||||||
|
{
|
||||||
|
embeddings[model][toBeGenerated.ElementAt(i)] = generatedEmbeddings.ElementAt(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return embeddings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<float[]> GenerateEmbeddings(string[] content, string model, AIProvider aIProvider, EnumerableLruCache<string, Dictionary<string, float[]>> embeddingCache)
|
||||||
|
{
|
||||||
|
IEnumerable<float[]> embeddings = aIProvider.GenerateEmbeddings(model, content);
|
||||||
|
if (embeddings.Count() != content.Length)
|
||||||
|
{
|
||||||
|
throw new Exception("Resulting embeddings count does not match up with request count");
|
||||||
|
}
|
||||||
|
for (int i = 0; i < content.Length; i++)
|
||||||
|
{
|
||||||
|
if (!embeddingCache.ContainsKey(content[i]))
|
||||||
|
{
|
||||||
|
embeddingCache[content[i]] = [];
|
||||||
|
}
|
||||||
|
embeddingCache[content[i]][model] = embeddings.ElementAt(i);
|
||||||
|
}
|
||||||
|
return embeddings;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public static float[] GenerateEmbeddings(string content, string model, AIProvider aIProvider, EnumerableLruCache<string, Dictionary<string, float[]>> embeddingCache)
|
public static float[] GenerateEmbeddings(string content, string model, AIProvider aIProvider, EnumerableLruCache<string, Dictionary<string, float[]>> embeddingCache)
|
||||||
{
|
{
|
||||||
float[] embeddings = aIProvider.GenerateEmbeddings(model, [content]);
|
float[] embeddings = aIProvider.GenerateEmbeddings(model, content);
|
||||||
if (!embeddingCache.ContainsKey(content))
|
if (!embeddingCache.ContainsKey(content))
|
||||||
{
|
{
|
||||||
embeddingCache[content] = [];
|
embeddingCache[content] = [];
|
||||||
|
|||||||
@@ -224,6 +224,21 @@ public class DatabaseHelper(ILogger<DatabaseHelper> logger)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static long GetTotalDatabaseSize(SQLHelper helper)
|
||||||
|
{
|
||||||
|
Dictionary<string, dynamic> parameters = [];
|
||||||
|
DbDataReader searchdomainSumReader = helper.ExecuteSQLCommand("SELECT SUM(Data_length) FROM information_schema.tables", parameters);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
bool success = searchdomainSumReader.Read();
|
||||||
|
long result = success && !searchdomainSumReader.IsDBNull(0) ? searchdomainSumReader.GetInt64(0) : 0;
|
||||||
|
return result;
|
||||||
|
} finally
|
||||||
|
{
|
||||||
|
searchdomainSumReader.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static async Task<long> CountEntities(SQLHelper helper)
|
public static async Task<long> CountEntities(SQLHelper helper)
|
||||||
{
|
{
|
||||||
DbDataReader searchdomainSumReader = helper.ExecuteSQLCommand("SELECT COUNT(*) FROM entity;", []);
|
DbDataReader searchdomainSumReader = helper.ExecuteSQLCommand("SELECT COUNT(*) FROM entity;", []);
|
||||||
|
|||||||
@@ -58,22 +58,42 @@ public class SearchdomainHelper(ILogger<SearchdomainHelper> logger, DatabaseHelp
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// toBeCached: model -> [datapoint.text * n]
|
// Prefetch embeddings
|
||||||
Dictionary<string, List<string>> toBeCached = [];
|
Dictionary<string, List<string>> toBeCached = [];
|
||||||
|
Dictionary<string, List<string>> toBeCachedParallel = [];
|
||||||
foreach (JSONEntity jSONEntity in jsonEntities)
|
foreach (JSONEntity jSONEntity in jsonEntities)
|
||||||
{
|
{
|
||||||
|
Dictionary<string, List<string>> targetDictionary = toBeCached;
|
||||||
|
if (searchdomainManager.GetSearchdomain(jSONEntity.Searchdomain).settings.ParallelEmbeddingsPrefetch)
|
||||||
|
{
|
||||||
|
targetDictionary = toBeCachedParallel;
|
||||||
|
}
|
||||||
foreach (JSONDatapoint datapoint in jSONEntity.Datapoints)
|
foreach (JSONDatapoint datapoint in jSONEntity.Datapoints)
|
||||||
{
|
{
|
||||||
foreach (string model in datapoint.Model)
|
foreach (string model in datapoint.Model)
|
||||||
{
|
{
|
||||||
if (!toBeCached.ContainsKey(model))
|
if (!targetDictionary.ContainsKey(model))
|
||||||
{
|
{
|
||||||
toBeCached[model] = [];
|
targetDictionary[model] = [];
|
||||||
}
|
}
|
||||||
toBeCached[model].Add(datapoint.Text);
|
targetDictionary[model].Add(datapoint.Text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach (var toBeCachedKV in toBeCached)
|
||||||
|
{
|
||||||
|
string model = toBeCachedKV.Key;
|
||||||
|
List<string> uniqueStrings = [.. toBeCachedKV.Value.Distinct()];
|
||||||
|
Datapoint.GetEmbeddings([.. uniqueStrings], [model], aIProvider, embeddingCache);
|
||||||
|
}
|
||||||
|
Parallel.ForEach(toBeCachedParallel, toBeCachedParallelKV =>
|
||||||
|
{
|
||||||
|
string model = toBeCachedParallelKV.Key;
|
||||||
|
List<string> uniqueStrings = [.. toBeCachedParallelKV.Value.Distinct()];
|
||||||
|
Datapoint.GetEmbeddings([.. uniqueStrings], [model], aIProvider, embeddingCache);
|
||||||
|
});
|
||||||
|
// Index/parse the entities
|
||||||
ConcurrentQueue<Entity> retVal = [];
|
ConcurrentQueue<Entity> retVal = [];
|
||||||
ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = 16 }; // <-- This is needed! Otherwise if we try to index 100+ entities at once, it spawns 100 threads, exploding the SQL pool
|
ParallelOptions parallelOptions = new() { MaxDegreeOfParallelism = 16 }; // <-- This is needed! Otherwise if we try to index 100+ entities at once, it spawns 100 threads, exploding the SQL pool
|
||||||
Parallel.ForEach(jsonEntities, parallelOptions, jSONEntity =>
|
Parallel.ForEach(jsonEntities, parallelOptions, jSONEntity =>
|
||||||
|
|||||||
@@ -12,6 +12,11 @@ public static class DatabaseMigrations
|
|||||||
int initialDatabaseVersion = DatabaseGetVersion(helper);
|
int initialDatabaseVersion = DatabaseGetVersion(helper);
|
||||||
int databaseVersion = initialDatabaseVersion;
|
int databaseVersion = initialDatabaseVersion;
|
||||||
|
|
||||||
|
if (databaseVersion == 0)
|
||||||
|
{
|
||||||
|
databaseVersion = Create(helper);
|
||||||
|
}
|
||||||
|
|
||||||
var updateMethods = typeof(DatabaseMigrations)
|
var updateMethods = typeof(DatabaseMigrations)
|
||||||
.GetMethods(BindingFlags.Public | BindingFlags.Static)
|
.GetMethods(BindingFlags.Public | BindingFlags.Static)
|
||||||
.Where(m => m.Name.StartsWith("UpdateFrom") && m.ReturnType == typeof(int))
|
.Where(m => m.Name.StartsWith("UpdateFrom") && m.ReturnType == typeof(int))
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ using Shared.Models;
|
|||||||
using Microsoft.AspNetCore.ResponseCompression;
|
using Microsoft.AspNetCore.ResponseCompression;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using Server.Migrations;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
@@ -31,10 +32,13 @@ builder.Services.AddControllersWithViews()
|
|||||||
// Add Configuration
|
// Add Configuration
|
||||||
IConfigurationSection configurationSection = builder.Configuration.GetSection("Embeddingsearch");
|
IConfigurationSection configurationSection = builder.Configuration.GetSection("Embeddingsearch");
|
||||||
EmbeddingSearchOptions configuration = configurationSection.Get<EmbeddingSearchOptions>() ?? throw new ConfigurationErrorsException("Unable to start server due to an invalid configration");
|
EmbeddingSearchOptions configuration = configurationSection.Get<EmbeddingSearchOptions>() ?? throw new ConfigurationErrorsException("Unable to start server due to an invalid configration");
|
||||||
|
|
||||||
builder.Services.Configure<EmbeddingSearchOptions>(configurationSection);
|
builder.Services.Configure<EmbeddingSearchOptions>(configurationSection);
|
||||||
builder.Services.Configure<ApiKeyOptions>(configurationSection);
|
builder.Services.Configure<ApiKeyOptions>(configurationSection);
|
||||||
|
|
||||||
|
// Migrate database
|
||||||
|
var helper = new SQLHelper(new MySql.Data.MySqlClient.MySqlConnection(configuration.ConnectionStrings.SQL), configuration.ConnectionStrings.SQL);
|
||||||
|
DatabaseMigrations.Migrate(helper);
|
||||||
|
|
||||||
// Add Localization
|
// Add Localization
|
||||||
builder.Services.AddLocalization(options => options.ResourcesPath = "Resources");
|
builder.Services.AddLocalization(options => options.ResourcesPath = "Resources");
|
||||||
builder.Services.Configure<RequestLocalizationOptions>(options =>
|
builder.Services.Configure<RequestLocalizationOptions>(options =>
|
||||||
|
|||||||
@@ -315,4 +315,13 @@
|
|||||||
<data name="queryCacheEntryCountLoadedInfo" xml:space="preserve">
|
<data name="queryCacheEntryCountLoadedInfo" xml:space="preserve">
|
||||||
<value>Anzahl der Einträge, die insgesamt in den Query-Cache der geladenen Searchdomains passen.</value>
|
<value>Anzahl der Einträge, die insgesamt in den Query-Cache der geladenen Searchdomains passen.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Query cache size" xml:space="preserve">
|
||||||
|
<value>Query Cache Größe</value>
|
||||||
|
</data>
|
||||||
|
<data name="Embeddings parallel prefetching" xml:space="preserve">
|
||||||
|
<value>Embeddings parallel prefetchen</value>
|
||||||
|
</data>
|
||||||
|
<data name="parallelEmbeddingsPrefetchInfo" xml:space="preserve">
|
||||||
|
<value>Wenn diese Einstellung aktiv ist, wird das Abrufen von Embeddings beim Indizieren von Entities parallelisiert. Deaktiviere diese Einstellung, falls Model-unloading ein Problem ist.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -315,4 +315,13 @@
|
|||||||
<data name="queryCacheEntryCountLoadedInfo" xml:space="preserve">
|
<data name="queryCacheEntryCountLoadedInfo" xml:space="preserve">
|
||||||
<value>Number of query cache entries that can be stored in the query cache of all loaded searchdomains.</value>
|
<value>Number of query cache entries that can be stored in the query cache of all loaded searchdomains.</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Query cache size" xml:space="preserve">
|
||||||
|
<value>Query Cache size</value>
|
||||||
|
</data>
|
||||||
|
<data name="Embeddings parallel prefetching" xml:space="preserve">
|
||||||
|
<value>Embeddings parallel prefetching</value>
|
||||||
|
</data>
|
||||||
|
<data name="parallelEmbeddingsPrefetchInfo" xml:space="preserve">
|
||||||
|
<value>With this setting activated the embeddings retrieval will be parallelized when indexing entities. Disable this setting if model unloading is an issue.</value>
|
||||||
|
</data>
|
||||||
</root>
|
</root>
|
||||||
@@ -37,15 +37,6 @@ public class SearchdomainManager
|
|||||||
connection = new MySqlConnection(connectionString);
|
connection = new MySqlConnection(connectionString);
|
||||||
connection.Open();
|
connection.Open();
|
||||||
helper = new SQLHelper(connection, connectionString);
|
helper = new SQLHelper(connection, connectionString);
|
||||||
try
|
|
||||||
{
|
|
||||||
DatabaseMigrations.Migrate(helper);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogCritical("Unable to migrate the database due to the exception: {ex}", [ex.Message]);
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Searchdomain GetSearchdomain(string searchdomain)
|
public Searchdomain GetSearchdomain(string searchdomain)
|
||||||
|
|||||||
@@ -24,6 +24,24 @@
|
|||||||
|
|
||||||
<div class="row g-4">
|
<div class="row g-4">
|
||||||
|
|
||||||
|
<!-- Server -->
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="card shadow-sm h-100">
|
||||||
|
<div class="card-body">
|
||||||
|
<h2 class="card-title fs-5">@T["Server"]</h2>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-between mt-2">
|
||||||
|
<span>@T["Total RAM usage"]</span>
|
||||||
|
<strong id="serverMemorySize"></strong>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-content-between mt-2">
|
||||||
|
<span>@T["Total Database size"]</span>
|
||||||
|
<strong id="serverDatabaseSize"></strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Embedding Cache -->
|
<!-- Embedding Cache -->
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="card shadow-sm h-100">
|
<div class="card shadow-sm h-100">
|
||||||
@@ -152,13 +170,6 @@
|
|||||||
var searchdomains = null;
|
var searchdomains = null;
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', async () => {
|
document.addEventListener('DOMContentLoaded', async () => {
|
||||||
// Initialize all tooltips
|
|
||||||
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
|
|
||||||
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
|
|
||||||
return new bootstrap.Tooltip(tooltipTriggerEl)
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
let searchdomainCount = document.getElementById("searchdomainCount");
|
let searchdomainCount = document.getElementById("searchdomainCount");
|
||||||
showThrobber(searchdomainCount);
|
showThrobber(searchdomainCount);
|
||||||
let searchdomainEntityCount = document.getElementById("searchdomainEntityCount");
|
let searchdomainEntityCount = document.getElementById("searchdomainEntityCount");
|
||||||
@@ -182,6 +193,10 @@
|
|||||||
showThrobber(querycacheLoadedMaxElementCount);
|
showThrobber(querycacheLoadedMaxElementCount);
|
||||||
let querycacheLoadedElementCountProgressBar = document.getElementById("querycacheLoadedElementCountProgressBar");
|
let querycacheLoadedElementCountProgressBar = document.getElementById("querycacheLoadedElementCountProgressBar");
|
||||||
|
|
||||||
|
let serverMemorySize = document.getElementById("serverMemorySize");
|
||||||
|
showThrobber(serverMemorySize);
|
||||||
|
let serverDatabaseSize = document.getElementById("serverDatabaseSize");
|
||||||
|
showThrobber(serverDatabaseSize);
|
||||||
|
|
||||||
let healthchecksServer = document.getElementById("healthchecksServer");
|
let healthchecksServer = document.getElementById("healthchecksServer");
|
||||||
let healthchecksAiProvider = document.getElementById("healthchecksAiProvider");
|
let healthchecksAiProvider = document.getElementById("healthchecksAiProvider");
|
||||||
@@ -221,6 +236,10 @@
|
|||||||
hideThrobber(querycacheLoadedMaxElementCount);
|
hideThrobber(querycacheLoadedMaxElementCount);
|
||||||
querycacheLoadedMaxElementCount.textContent = queryCacheMaxElementCountLoadedSearchdomainsOnly.toLocaleString();
|
querycacheLoadedMaxElementCount.textContent = queryCacheMaxElementCountLoadedSearchdomainsOnly.toLocaleString();
|
||||||
querycacheLoadedMaxElementCountProgressBar.style.width = `${queryCacheElementCount / queryCacheMaxElementCountLoadedSearchdomainsOnly * 100}%`;
|
querycacheLoadedMaxElementCountProgressBar.style.width = `${queryCacheElementCount / queryCacheMaxElementCountLoadedSearchdomainsOnly * 100}%`;
|
||||||
|
serverMemorySize.textContent = NumberOfBytesAsHumanReadable(result.RamTotalSize);
|
||||||
|
hideThrobber(serverMemorySize);
|
||||||
|
serverDatabaseSize.textContent = NumberOfBytesAsHumanReadable(result.DatabaseTotalSize);
|
||||||
|
hideThrobber(serverDatabaseSize);
|
||||||
});
|
});
|
||||||
getHealthCheckStatusAndApply(healthchecksServer, "/healthz/Database");
|
getHealthCheckStatusAndApply(healthchecksServer, "/healthz/Database");
|
||||||
getHealthCheckStatusAndApply(healthchecksAiProvider, "/healthz/AIProvider");
|
getHealthCheckStatusAndApply(healthchecksAiProvider, "/healthz/AIProvider");
|
||||||
|
|||||||
@@ -66,10 +66,17 @@
|
|||||||
<label class="form-check-label" for="searchdomainConfigQueryCacheSize">@T["Query cache size"]:</label>
|
<label class="form-check-label" for="searchdomainConfigQueryCacheSize">@T["Query cache size"]:</label>
|
||||||
<input type="number" class="form-control" id="searchdomainConfigQueryCacheSize" />
|
<input type="number" class="form-control" id="searchdomainConfigQueryCacheSize" />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6 mt-3">
|
||||||
<input type="checkbox" class="form-check-input" id="searchdomainConfigCacheReconciliation" />
|
<input type="checkbox" class="form-check-input" id="searchdomainConfigCacheReconciliation" />
|
||||||
<label class="form-check-label" for="searchdomainConfigCacheReconciliation">@T["Cache reconciliation"]</label>
|
<label class="form-check-label" for="searchdomainConfigCacheReconciliation">@T["Cache reconciliation"]</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-md-6 mt-3">
|
||||||
|
<input type="checkbox" class="form-check-input" id="searchdomainConfigParallelEmbeddingsPrefetch" />
|
||||||
|
<label class="form-check-label" for="searchdomainConfigParallelEmbeddingsPrefetch">@T["Embeddings parallel prefetching"]</label>
|
||||||
|
<i class="bi bi-info-circle-fill text-info"
|
||||||
|
data-bs-toggle="tooltip"
|
||||||
|
title="@T["parallelEmbeddingsPrefetchInfo"]"></i>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row align-items-center mb-3">
|
<div class="row align-items-center mb-3">
|
||||||
<div class="col-md-2 mt-md-0">
|
<div class="col-md-2 mt-md-0">
|
||||||
@@ -362,10 +369,14 @@
|
|||||||
<label class="form-check-label mb-2" for="createSearchdomainQueryCacheSize">@T["Query cache size"]:</label>
|
<label class="form-check-label mb-2" for="createSearchdomainQueryCacheSize">@T["Query cache size"]:</label>
|
||||||
<input type="number" class="form-control" id="createSearchdomainQueryCacheSize" />
|
<input type="number" class="form-control" id="createSearchdomainQueryCacheSize" />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-7">
|
<div class="col-md-7 mt-3">
|
||||||
<input type="checkbox" class="form-check-input" id="createSearchdomainWithCacheReconciliation" />
|
<input type="checkbox" class="form-check-input" id="createSearchdomainWithCacheReconciliation" />
|
||||||
<label class="form-check-label" for="createSearchdomainWithCacheReconciliation">@T["Enable cache reconciliation"]</label>
|
<label class="form-check-label" for="createSearchdomainWithCacheReconciliation">@T["Enable cache reconciliation"]</label>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-md-6 mt-3">
|
||||||
|
<input type="checkbox" class="form-check-input" id="createSearchdomainConfigParallelEmbeddingsPrefetch" />
|
||||||
|
<label class="form-check-label" for="createSearchdomainConfigParallelEmbeddingsPrefetch">@T["Embeddings parallel prefetching"]</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -672,7 +683,10 @@
|
|||||||
queriesFilter.addEventListener('input', () => {
|
queriesFilter.addEventListener('input', () => {
|
||||||
populateQueriesTable(queriesFilter.value);
|
populateQueriesTable(queriesFilter.value);
|
||||||
});
|
});
|
||||||
selectDomain(0);
|
try
|
||||||
|
{
|
||||||
|
selectDomain(0);
|
||||||
|
} catch (error) {}
|
||||||
|
|
||||||
document
|
document
|
||||||
.getElementById('searchdomainRename')
|
.getElementById('searchdomainRename')
|
||||||
@@ -711,7 +725,8 @@
|
|||||||
const domainKey = getSelectedDomainKey();
|
const domainKey = getSelectedDomainKey();
|
||||||
const cacheReconciliation = document.getElementById('searchdomainConfigCacheReconciliation').checked;
|
const cacheReconciliation = document.getElementById('searchdomainConfigCacheReconciliation').checked;
|
||||||
const queryCacheSize = document.getElementById('searchdomainConfigQueryCacheSize').value;
|
const queryCacheSize = document.getElementById('searchdomainConfigQueryCacheSize').value;
|
||||||
updateSearchdomainConfig(domainKey, { CacheReconciliation: cacheReconciliation, QueryCacheSize: queryCacheSize});
|
const parallelEmbeddingsPrefetch = document.getElementById('searchdomainConfigParallelEmbeddingsPrefetch').checked;
|
||||||
|
updateSearchdomainConfig(domainKey, { CacheReconciliation: cacheReconciliation, QueryCacheSize: queryCacheSize, ParallelEmbeddingsPrefetch: parallelEmbeddingsPrefetch});
|
||||||
});
|
});
|
||||||
|
|
||||||
document
|
document
|
||||||
@@ -794,8 +809,8 @@
|
|||||||
const name = document.getElementById('createSearchdomainName').value;
|
const name = document.getElementById('createSearchdomainName').value;
|
||||||
const queryCacheSize = document.getElementById('createSearchdomainQueryCacheSize').value;
|
const queryCacheSize = document.getElementById('createSearchdomainQueryCacheSize').value;
|
||||||
const cacheReconciliation = document.getElementById('createSearchdomainWithCacheReconciliation').checked;
|
const cacheReconciliation = document.getElementById('createSearchdomainWithCacheReconciliation').checked;
|
||||||
const settings = { CacheReconciliation: cacheReconciliation, QueryCacheSize: queryCacheSize };
|
const parallelEmbeddingsPrefetch = document.getElementById('createSearchdomainConfigParallelEmbeddingsPrefetch').checked;
|
||||||
// Implement create logic here
|
const settings = { CacheReconciliation: cacheReconciliation, QueryCacheSize: queryCacheSize, ParallelEmbeddingsPrefetch: parallelEmbeddingsPrefetch };
|
||||||
fetch(`/Searchdomain?searchdomain=${encodeURIComponent(name)}`, {
|
fetch(`/Searchdomain?searchdomain=${encodeURIComponent(name)}`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
@@ -1072,9 +1087,11 @@
|
|||||||
let searchdomainConfigPromise = getSearchdomainConfig(getSelectedDomainKey());
|
let searchdomainConfigPromise = getSearchdomainConfig(getSelectedDomainKey());
|
||||||
let configElementCachereconciliation = document.getElementById('searchdomainConfigCacheReconciliation');
|
let configElementCachereconciliation = document.getElementById('searchdomainConfigCacheReconciliation');
|
||||||
let configElementCacheSize = document.getElementById('searchdomainConfigQueryCacheSize');
|
let configElementCacheSize = document.getElementById('searchdomainConfigQueryCacheSize');
|
||||||
|
let configElementParallelEmbeddingsPrefetch = document.getElementById('searchdomainConfigParallelEmbeddingsPrefetch');
|
||||||
|
|
||||||
showThrobber(document.querySelector('#searchdomainConfigQueryCacheSize'), true);
|
showThrobber(document.querySelector('#searchdomainConfigQueryCacheSize'), true);
|
||||||
showThrobber(document.querySelector('#searchdomainConfigCacheReconciliation'), true);
|
showThrobber(document.querySelector('#searchdomainConfigCacheReconciliation'), true);
|
||||||
|
showThrobber(document.querySelector('#searchdomainConfigParallelEmbeddingsPrefetch'), true);
|
||||||
let cacheUtilizationPromise = getSearchdomainCacheUtilization(getSelectedDomainKey());
|
let cacheUtilizationPromise = getSearchdomainCacheUtilization(getSelectedDomainKey());
|
||||||
let databaseUtilizationPromise = getSearchdomainDatabaseUtilization(getSelectedDomainKey());
|
let databaseUtilizationPromise = getSearchdomainDatabaseUtilization(getSelectedDomainKey());
|
||||||
|
|
||||||
@@ -1117,11 +1134,14 @@
|
|||||||
|
|
||||||
searchdomainConfigPromise.then(searchdomainConfig => {
|
searchdomainConfigPromise.then(searchdomainConfig => {
|
||||||
hideThrobber(document.querySelector('#searchdomainConfigCacheReconciliation'), true);
|
hideThrobber(document.querySelector('#searchdomainConfigCacheReconciliation'), true);
|
||||||
|
hideThrobber(document.querySelector('#searchdomainConfigParallelEmbeddingsPrefetch'), true);
|
||||||
|
|
||||||
if (searchdomainConfig != null && searchdomainConfig.Settings != null)
|
if (searchdomainConfig != null && searchdomainConfig.Settings != null)
|
||||||
{
|
{
|
||||||
configElementCacheSize.value = searchdomainConfig.Settings.QueryCacheSize;
|
configElementCacheSize.value = searchdomainConfig.Settings.QueryCacheSize;
|
||||||
configElementCachereconciliation.checked = searchdomainConfig.Settings.CacheReconciliation;
|
configElementCachereconciliation.checked = searchdomainConfig.Settings.CacheReconciliation;
|
||||||
configElementCachereconciliation.disabled = false;
|
configElementCachereconciliation.disabled = false;
|
||||||
|
configElementParallelEmbeddingsPrefetch.checked = searchdomainConfig.Settings.ParallelEmbeddingsPrefetch;
|
||||||
} else {
|
} else {
|
||||||
configElementCachereconciliation.disabled = true;
|
configElementCachereconciliation.disabled = true;
|
||||||
showToast("@T["Unable to fetch searchdomain config"]", "danger");
|
showToast("@T["Unable to fetch searchdomain config"]", "danger");
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
<meta name="description" content="Embeddingsearch server" />
|
<meta name="description" content="Embeddingsearch server" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>@ViewData["Title"] - embeddingsearch</title>
|
<title>@ViewData["Title"] - embeddingsearch</title>
|
||||||
|
<link rel="preload" href="~/fonts/bootstrap-icons.woff2" as="font" type="font/woff2" crossorigin="anonymous"/>
|
||||||
@if (!Context.Request.Query.ContainsKey("renderRaw") && !Context.Request.Query.ContainsKey("noCriticalCSS"))
|
@if (!Context.Request.Query.ContainsKey("renderRaw") && !Context.Request.Query.ContainsKey("noCriticalCSS"))
|
||||||
{
|
{
|
||||||
<link rel="preload" href="~/lib/bootstrap/dist/css/bootstrap.min.css" as="style"/>
|
<link rel="preload" href="~/lib/bootstrap/dist/css/bootstrap.min.css" as="style"/>
|
||||||
|
|||||||
@@ -49,3 +49,13 @@ function showToast(message, type) {
|
|||||||
bsToast.show();
|
bsToast.show();
|
||||||
toast.addEventListener('hidden.bs.toast', () => toast.remove());
|
toast.addEventListener('hidden.bs.toast', () => toast.remove());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', async () => {
|
||||||
|
// Initialize all tooltips
|
||||||
|
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
|
||||||
|
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
|
||||||
|
let retVal = new bootstrap.Tooltip(tooltipTriggerEl);
|
||||||
|
tooltipTriggerEl.role = "tooltip";
|
||||||
|
return retVal;
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -95,12 +95,14 @@ public struct DateTimedSearchResult(DateTime dateTime, List<ResultItem> results)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct SearchdomainSettings(bool cacheReconciliation = false, int queryCacheSize = 1_000_000)
|
public struct SearchdomainSettings(bool cacheReconciliation = false, int queryCacheSize = 1_000_000, bool parallelEmbeddingsPrefetch = false)
|
||||||
{
|
{
|
||||||
[JsonPropertyName("CacheReconciliation")]
|
[JsonPropertyName("CacheReconciliation")]
|
||||||
public bool CacheReconciliation { get; set; } = cacheReconciliation;
|
public bool CacheReconciliation { get; set; } = cacheReconciliation;
|
||||||
[JsonPropertyName("QueryCacheSize")]
|
[JsonPropertyName("QueryCacheSize")]
|
||||||
public int QueryCacheSize { get; set; } = queryCacheSize;
|
public int QueryCacheSize { get; set; } = queryCacheSize;
|
||||||
|
[JsonPropertyName("ParallelEmbeddingsPrefetch")]
|
||||||
|
public bool ParallelEmbeddingsPrefetch { get; set; } = parallelEmbeddingsPrefetch;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class MemorySizes
|
public static class MemorySizes
|
||||||
|
|||||||
@@ -28,4 +28,8 @@ public class ServerGetStatsResult : SuccesMessageBaseModel
|
|||||||
public long? QueryCacheMaxElementCountLoadedSearchdomainsOnly { get; set; }
|
public long? QueryCacheMaxElementCountLoadedSearchdomainsOnly { get; set; }
|
||||||
[JsonPropertyName("QueryCacheUtilization")]
|
[JsonPropertyName("QueryCacheUtilization")]
|
||||||
public long? QueryCacheUtilization { get; set; }
|
public long? QueryCacheUtilization { get; set; }
|
||||||
|
[JsonPropertyName("DatabaseTotalSize")]
|
||||||
|
public long? DatabaseTotalSize { get; set; }
|
||||||
|
[JsonPropertyName("RamTotalSize")]
|
||||||
|
public long? RamTotalSize { get; set; }
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user