Compare commits
10 Commits
b5545bb5c8
...
89f8aa6591
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
89f8aa6591 | ||
| a3cc5115fc | |||
| 8b36c65437 | |||
| ee12986fef | |||
| fd76da265b | |||
| 4aa3015954 | |||
| fb1766b2b5 | |||
| 6fe96d41a2 | |||
| fc5e8ceeee | |||
| 33502640a2 |
@@ -55,10 +55,7 @@ public class EntityController : ControllerBase
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
List<Entity>? entities = _searchdomainHelper.EntitiesFromJSON(
|
List<Entity>? entities = _searchdomainHelper.EntitiesFromJSON(
|
||||||
[],
|
_domainManager,
|
||||||
_domainManager.embeddingCache,
|
|
||||||
_domainManager.aIProvider,
|
|
||||||
_domainManager.helper,
|
|
||||||
_logger,
|
_logger,
|
||||||
JsonSerializer.Serialize(jsonEntities));
|
JsonSerializer.Serialize(jsonEntities));
|
||||||
if (entities is not null && jsonEntities is not null)
|
if (entities is not null && jsonEntities is not null)
|
||||||
@@ -92,8 +89,13 @@ public class EntityController : ControllerBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("List")]
|
[HttpGet("List")]
|
||||||
public ActionResult<EntityListResults> List(string searchdomain, bool returnEmbeddings = false)
|
public ActionResult<EntityListResults> List(string searchdomain, bool returnModels = false, bool returnEmbeddings = false)
|
||||||
{
|
{
|
||||||
|
if (returnEmbeddings && !returnModels)
|
||||||
|
{
|
||||||
|
_logger.LogError("Invalid request for {searchdomain} - embeddings return requested but without models - not possible!", [searchdomain]);
|
||||||
|
return Ok(new EntityQueryResults() {Results = [], Success = false, Message = "Invalid request" });
|
||||||
|
}
|
||||||
Searchdomain searchdomain_;
|
Searchdomain searchdomain_;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -118,12 +120,12 @@ public class EntityController : ControllerBase
|
|||||||
List<DatapointResult> datapointResults = [];
|
List<DatapointResult> datapointResults = [];
|
||||||
foreach (Datapoint datapoint in entity.datapoints)
|
foreach (Datapoint datapoint in entity.datapoints)
|
||||||
{
|
{
|
||||||
if (returnEmbeddings)
|
if (returnModels)
|
||||||
{
|
{
|
||||||
List<EmbeddingResult> embeddingResults = [];
|
List<EmbeddingResult> embeddingResults = [];
|
||||||
foreach ((string, float[]) embedding in datapoint.embeddings)
|
foreach ((string, float[]) embedding in datapoint.embeddings)
|
||||||
{
|
{
|
||||||
embeddingResults.Add(new EmbeddingResult() {Model = embedding.Item1, Embeddings = embedding.Item2});
|
embeddingResults.Add(new EmbeddingResult() {Model = embedding.Item1, Embeddings = returnEmbeddings ? embedding.Item2 : []});
|
||||||
}
|
}
|
||||||
datapointResults.Add(new DatapointResult() {Name = datapoint.name, ProbMethod = datapoint.probMethod.name, SimilarityMethod = datapoint.similarityMethod.name, Embeddings = embeddingResults});
|
datapointResults.Add(new DatapointResult() {Name = datapoint.name, ProbMethod = datapoint.probMethod.name, SimilarityMethod = datapoint.similarityMethod.name, Embeddings = embeddingResults});
|
||||||
}
|
}
|
||||||
@@ -135,6 +137,7 @@ public class EntityController : ControllerBase
|
|||||||
EntityListResult entityListResult = new()
|
EntityListResult entityListResult = new()
|
||||||
{
|
{
|
||||||
Name = entity.name,
|
Name = entity.name,
|
||||||
|
ProbMethod = entity.probMethodName,
|
||||||
Attributes = attributeResults,
|
Attributes = attributeResults,
|
||||||
Datapoints = datapointResults
|
Datapoints = datapointResults
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using ElmahCore;
|
using ElmahCore;
|
||||||
|
using Microsoft.AspNetCore.Http.HttpResults;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Server.Exceptions;
|
using Server.Exceptions;
|
||||||
using Server.Helper;
|
using Server.Helper;
|
||||||
@@ -156,6 +157,64 @@ public class SearchdomainController : ControllerBase
|
|||||||
return Ok(new SearchdomainSearchesResults() { Searches = searchCache, Success = true });
|
return Ok(new SearchdomainSearchesResults() { Searches = searchCache, Success = true });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpDelete("Searches")]
|
||||||
|
public ActionResult<SearchdomainDeleteSearchResult> DeleteSearch(string searchdomain, string query)
|
||||||
|
{
|
||||||
|
Searchdomain searchdomain_;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
searchdomain_ = _domainManager.GetSearchdomain(searchdomain);
|
||||||
|
}
|
||||||
|
catch (SearchdomainNotFoundException)
|
||||||
|
{
|
||||||
|
_logger.LogError("Unable to retrieve the searchdomain {searchdomain} - it likely does not exist yet", [searchdomain]);
|
||||||
|
return Ok(new SearchdomainDeleteSearchResult() { Success = false, Message = "Searchdomain not found" });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError("Unable to retrieve the searchdomain {searchdomain} - {ex.Message} - {ex.StackTrace}", [searchdomain, ex.Message, ex.StackTrace]);
|
||||||
|
return Ok(new SearchdomainDeleteSearchResult() { Success = false, Message = ex.Message });
|
||||||
|
}
|
||||||
|
Dictionary<string, DateTimedSearchResult> searchCache = searchdomain_.searchCache;
|
||||||
|
bool containsKey = searchCache.ContainsKey(query);
|
||||||
|
if (containsKey)
|
||||||
|
{
|
||||||
|
searchCache.Remove(query);
|
||||||
|
return Ok(new SearchdomainDeleteSearchResult() {Success = true});
|
||||||
|
}
|
||||||
|
return Ok(new SearchdomainDeleteSearchResult() {Success = false, Message = "Query not found in search cache"});
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPatch("Searches")]
|
||||||
|
public ActionResult<SearchdomainUpdateSearchResult> UpdateSearch(string searchdomain, string query, [FromBody]List<ResultItem> results)
|
||||||
|
{
|
||||||
|
Searchdomain searchdomain_;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
searchdomain_ = _domainManager.GetSearchdomain(searchdomain);
|
||||||
|
}
|
||||||
|
catch (SearchdomainNotFoundException)
|
||||||
|
{
|
||||||
|
_logger.LogError("Unable to retrieve the searchdomain {searchdomain} - it likely does not exist yet", [searchdomain]);
|
||||||
|
return Ok(new SearchdomainUpdateSearchResult() { Success = false, Message = "Searchdomain not found" });
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError("Unable to retrieve the searchdomain {searchdomain} - {ex.Message} - {ex.StackTrace}", [searchdomain, ex.Message, ex.StackTrace]);
|
||||||
|
return Ok(new SearchdomainUpdateSearchResult() { Success = false, Message = ex.Message });
|
||||||
|
}
|
||||||
|
Dictionary<string, DateTimedSearchResult> searchCache = searchdomain_.searchCache;
|
||||||
|
bool containsKey = searchCache.ContainsKey(query);
|
||||||
|
if (containsKey)
|
||||||
|
{
|
||||||
|
DateTimedSearchResult element = searchCache[query];
|
||||||
|
element.Results = results;
|
||||||
|
searchCache[query] = element;
|
||||||
|
return Ok(new SearchdomainUpdateSearchResult() {Success = true});
|
||||||
|
}
|
||||||
|
return Ok(new SearchdomainUpdateSearchResult() {Success = false, Message = "Query not found in search cache"});
|
||||||
|
}
|
||||||
|
|
||||||
[HttpGet("GetSettings")]
|
[HttpGet("GetSettings")]
|
||||||
public ActionResult<SearchdomainSettingsResults> GetSettings(string searchdomain)
|
public ActionResult<SearchdomainSettingsResults> GetSettings(string searchdomain)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
namespace Server;
|
namespace Server;
|
||||||
|
|
||||||
public class Entity(Dictionary<string, string> attributes, Probmethods.probMethodDelegate probMethod, List<Datapoint> datapoints, string name)
|
public class Entity(Dictionary<string, string> attributes, Probmethods.probMethodDelegate probMethod, string probMethodName, List<Datapoint> datapoints, string name)
|
||||||
{
|
{
|
||||||
public Dictionary<string, string> attributes = attributes;
|
public Dictionary<string, string> attributes = attributes;
|
||||||
public Probmethods.probMethodDelegate probMethod = probMethod;
|
public Probmethods.probMethodDelegate probMethod = probMethod;
|
||||||
|
public string probMethodName = probMethodName;
|
||||||
public List<Datapoint> datapoints = datapoints;
|
public List<Datapoint> datapoints = datapoints;
|
||||||
public int id;
|
public int id;
|
||||||
public string name = name;
|
public string name = name;
|
||||||
|
|||||||
@@ -44,8 +44,12 @@ public class SearchdomainHelper(ILogger<SearchdomainHelper> logger, DatabaseHelp
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Entity>? EntitiesFromJSON(List<Entity> entityCache, Dictionary<string, Dictionary<string, float[]>> embeddingCache, AIProvider aIProvider, SQLHelper helper, ILogger logger, string json)
|
public List<Entity>? EntitiesFromJSON(SearchdomainManager searchdomainManager, ILogger logger, string json)
|
||||||
{
|
{
|
||||||
|
Dictionary<string, Dictionary<string, float[]>> embeddingCache = searchdomainManager.embeddingCache;
|
||||||
|
AIProvider aIProvider = searchdomainManager.aIProvider;
|
||||||
|
SQLHelper helper = searchdomainManager.helper;
|
||||||
|
|
||||||
List<JSONEntity>? jsonEntities = JsonSerializer.Deserialize<List<JSONEntity>>(json);
|
List<JSONEntity>? jsonEntities = JsonSerializer.Deserialize<List<JSONEntity>>(json);
|
||||||
if (jsonEntities is null)
|
if (jsonEntities is null)
|
||||||
{
|
{
|
||||||
@@ -72,8 +76,7 @@ public class SearchdomainHelper(ILogger<SearchdomainHelper> logger, DatabaseHelp
|
|||||||
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 =>
|
||||||
{
|
{
|
||||||
using var tempHelper = helper.DuplicateConnection();
|
var entity = EntityFromJSON(searchdomainManager, logger, jSONEntity);
|
||||||
var entity = EntityFromJSON(entityCache, embeddingCache, aIProvider, tempHelper, logger, jSONEntity);
|
|
||||||
if (entity is not null)
|
if (entity is not null)
|
||||||
{
|
{
|
||||||
retVal.Enqueue(entity);
|
retVal.Enqueue(entity);
|
||||||
@@ -82,37 +85,133 @@ public class SearchdomainHelper(ILogger<SearchdomainHelper> logger, DatabaseHelp
|
|||||||
return [.. retVal];
|
return [.. retVal];
|
||||||
}
|
}
|
||||||
|
|
||||||
public Entity? EntityFromJSON(List<Entity> entityCache, Dictionary<string, Dictionary<string, float[]>> embeddingCache, AIProvider aIProvider, SQLHelper helper, ILogger logger, JSONEntity jsonEntity) //string json)
|
public Entity? EntityFromJSON(SearchdomainManager searchdomainManager, ILogger logger, JSONEntity jsonEntity) //string json)
|
||||||
|
{
|
||||||
|
SQLHelper helper = searchdomainManager.helper.DuplicateConnection();
|
||||||
|
Searchdomain searchdomain = searchdomainManager.GetSearchdomain(jsonEntity.Searchdomain);
|
||||||
|
List<Entity> entityCache = searchdomain.entityCache;
|
||||||
|
AIProvider aIProvider = searchdomain.aIProvider;
|
||||||
|
Dictionary<string, Dictionary<string, float[]>> embeddingCache = searchdomain.embeddingCache;
|
||||||
|
Entity? preexistingEntity = entityCache.FirstOrDefault(entity => entity.name == jsonEntity.Name);
|
||||||
|
|
||||||
|
if (preexistingEntity is not null)
|
||||||
{
|
{
|
||||||
Dictionary<string, Dictionary<string, float[]>> embeddingsLUT = []; // embeddingsLUT: hash -> model -> [embeddingValues * n]
|
|
||||||
int? preexistingEntityID = _databaseHelper.GetEntityID(helper, jsonEntity.Name, jsonEntity.Searchdomain);
|
int? preexistingEntityID = _databaseHelper.GetEntityID(helper, jsonEntity.Name, jsonEntity.Searchdomain);
|
||||||
if (preexistingEntityID is not null)
|
if (preexistingEntityID is null)
|
||||||
{
|
{
|
||||||
lock (helper.connection) // TODO change this to helper and do A/B tests (i.e. before/after)
|
_logger.LogCritical("Unable to index entity {jsonEntity.Name} because it already exists in the searchdomain but not in the database.", [jsonEntity.Name]);
|
||||||
|
throw new Exception($"Unable to index entity {jsonEntity.Name} because it already exists in the searchdomain but not in the database.");
|
||||||
|
}
|
||||||
|
Dictionary<string, string> attributes = jsonEntity.Attributes;
|
||||||
|
|
||||||
|
// Attribute
|
||||||
|
foreach (KeyValuePair<string, string> attributesKV in preexistingEntity.attributes.ToList())
|
||||||
{
|
{
|
||||||
|
string oldAttributeKey = attributesKV.Key;
|
||||||
|
string oldAttribute = attributesKV.Value;
|
||||||
|
bool newHasAttribute = jsonEntity.Attributes.TryGetValue(oldAttributeKey, out string? newAttribute);
|
||||||
|
if (newHasAttribute && newAttribute is not null && newAttribute != oldAttribute)
|
||||||
|
{
|
||||||
|
// Attribute - Updated
|
||||||
Dictionary<string, dynamic> parameters = new()
|
Dictionary<string, dynamic> parameters = new()
|
||||||
{
|
{
|
||||||
{ "id", preexistingEntityID }
|
{ "newValue", newAttribute },
|
||||||
|
{ "entityId", preexistingEntityID },
|
||||||
|
{ "attribute", oldAttributeKey}
|
||||||
};
|
};
|
||||||
System.Data.Common.DbDataReader reader = helper.ExecuteSQLCommand("SELECT e.model, e.embedding, d.hash FROM datapoint d JOIN embedding e ON d.id = e.id_datapoint WHERE d.id_entity = @id", parameters);
|
helper.ExecuteSQLNonQuery("UPDATE attribute SET value=@newValue WHERE id_entity=@entityId AND attribute=@attribute", parameters);
|
||||||
while (reader.Read())
|
preexistingEntity.attributes[oldAttributeKey] = newAttribute;
|
||||||
|
} else if (!newHasAttribute)
|
||||||
{
|
{
|
||||||
string model = reader.GetString(0);
|
// Attribute - Deleted
|
||||||
long length = reader.GetBytes(1, 0, null, 0, 0);
|
Dictionary<string, dynamic> parameters = new()
|
||||||
byte[] embeddingBytes = new byte[length];
|
|
||||||
reader.GetBytes(1, 0, embeddingBytes, 0, (int)length);
|
|
||||||
float[] embeddingValues = FloatArrayFromBytes(embeddingBytes);
|
|
||||||
string hash = reader.GetString(2);
|
|
||||||
if (!embeddingsLUT.ContainsKey(hash))
|
|
||||||
{
|
{
|
||||||
embeddingsLUT[hash] = [];
|
{ "entityId", preexistingEntityID },
|
||||||
|
{ "attribute", oldAttributeKey}
|
||||||
|
};
|
||||||
|
helper.ExecuteSQLNonQuery("DELETE FROM attribute WHERE id_entity=@entityId AND attribute=@attribute", parameters);
|
||||||
|
preexistingEntity.attributes.Remove(oldAttributeKey);
|
||||||
}
|
}
|
||||||
embeddingsLUT[hash].TryAdd(model, embeddingValues);
|
|
||||||
}
|
}
|
||||||
reader.Close();
|
foreach (var attributesKV in jsonEntity.Attributes)
|
||||||
|
{
|
||||||
|
string newAttributeKey = attributesKV.Key;
|
||||||
|
string newAttribute = attributesKV.Value;
|
||||||
|
bool preexistingHasAttribute = preexistingEntity.attributes.TryGetValue(newAttributeKey, out string? preexistingAttribute);
|
||||||
|
if (!preexistingHasAttribute)
|
||||||
|
{
|
||||||
|
// Attribute - New
|
||||||
|
DatabaseHelper.DatabaseInsertAttribute(helper, newAttributeKey, newAttribute, (int)preexistingEntityID);
|
||||||
|
preexistingEntity.attributes.Add(newAttributeKey, newAttribute);
|
||||||
}
|
}
|
||||||
_databaseHelper.RemoveEntity(entityCache, helper, jsonEntity.Name, jsonEntity.Searchdomain); // TODO only remove entity if there is actually a change somewhere. Perhaps create 3 datapoint lists to operate with: 1. delete, 2. update, 3. create
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Datapoint
|
||||||
|
foreach (Datapoint datapoint in preexistingEntity.datapoints.ToList())
|
||||||
|
{
|
||||||
|
bool newEntityHasDatapoint = jsonEntity.Datapoints.Any(x => x.Name == datapoint.name);
|
||||||
|
if (!newEntityHasDatapoint)
|
||||||
|
{
|
||||||
|
// Datapoint - Deleted
|
||||||
|
Dictionary<string, dynamic> parameters = new()
|
||||||
|
{
|
||||||
|
{ "datapointName", datapoint.name },
|
||||||
|
{ "entityId", preexistingEntityID}
|
||||||
|
};
|
||||||
|
helper.ExecuteSQLNonQuery("DELETE e FROM embedding e JOIN datapoint d ON e.id_datapoint=d.id WHERE d.name=@datapointName AND d.id_entity=@entityId", parameters);
|
||||||
|
helper.ExecuteSQLNonQuery("DELETE FROM datapoint WHERE id_entity=@entityId AND name=@datapointName", parameters);
|
||||||
|
preexistingEntity.datapoints.Remove(datapoint);
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
JSONDatapoint? newEntityDatapoint = jsonEntity.Datapoints.FirstOrDefault(x => x.Name == datapoint.name);
|
||||||
|
if (newEntityDatapoint is not null && newEntityDatapoint.Text is not null)
|
||||||
|
{
|
||||||
|
// Datapoint - Updated (text)
|
||||||
|
Dictionary<string, dynamic> parameters = new()
|
||||||
|
{
|
||||||
|
{ "datapointName", datapoint.name },
|
||||||
|
{ "entityId", preexistingEntityID}
|
||||||
|
};
|
||||||
|
helper.ExecuteSQLNonQuery("DELETE e FROM embedding e JOIN datapoint d ON e.id_datapoint=d.id WHERE d.name=@datapointName AND d.id_entity=@entityId", parameters);
|
||||||
|
helper.ExecuteSQLNonQuery("DELETE FROM datapoint WHERE id_entity=@entityId AND name=@datapointName", parameters);
|
||||||
|
preexistingEntity.datapoints.Remove(datapoint);
|
||||||
|
Datapoint newDatapoint = DatabaseInsertDatapointWithEmbeddings(helper, searchdomain, newEntityDatapoint, (int)preexistingEntityID);
|
||||||
|
preexistingEntity.datapoints.Add(newDatapoint);
|
||||||
|
|
||||||
|
}
|
||||||
|
if (newEntityDatapoint is not null && (newEntityDatapoint.Probmethod_embedding != datapoint.probMethod.name || newEntityDatapoint.SimilarityMethod != datapoint.similarityMethod.name))
|
||||||
|
{
|
||||||
|
// Datapoint - Updated (probmethod or similaritymethod)
|
||||||
|
Dictionary<string, dynamic> parameters = new()
|
||||||
|
{
|
||||||
|
{ "probmethod", newEntityDatapoint.Probmethod_embedding },
|
||||||
|
{ "similaritymethod", newEntityDatapoint.SimilarityMethod },
|
||||||
|
{ "datapointName", datapoint.name },
|
||||||
|
{ "entityId", preexistingEntityID}
|
||||||
|
};
|
||||||
|
helper.ExecuteSQLNonQuery("UPDATE datapoint SET probmethod_embedding=@probmethod, similaritymethod=@similaritymethod WHERE id_entity=@entityId AND name=@datapointName", parameters);
|
||||||
|
Datapoint preexistingDatapoint = preexistingEntity.datapoints.First(x => x == datapoint); // The for loop is a copy. This retrieves the original such that it can be updated.
|
||||||
|
preexistingDatapoint.probMethod = datapoint.probMethod;
|
||||||
|
preexistingDatapoint.similarityMethod = datapoint.similarityMethod;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach (JSONDatapoint jsonDatapoint in jsonEntity.Datapoints)
|
||||||
|
{
|
||||||
|
bool oldEntityHasDatapoint = preexistingEntity.datapoints.Any(x => x.name == jsonDatapoint.Name);
|
||||||
|
if (!oldEntityHasDatapoint)
|
||||||
|
{
|
||||||
|
// Datapoint - New
|
||||||
|
Datapoint datapoint = DatabaseInsertDatapointWithEmbeddings(helper, searchdomain, jsonDatapoint, (int)preexistingEntityID);
|
||||||
|
preexistingEntity.datapoints.Add(datapoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return preexistingEntity;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
int id_entity = DatabaseHelper.DatabaseInsertEntity(helper, jsonEntity.Name, jsonEntity.Probmethod, _databaseHelper.GetSearchdomainID(helper, jsonEntity.Searchdomain));
|
int id_entity = DatabaseHelper.DatabaseInsertEntity(helper, jsonEntity.Name, jsonEntity.Probmethod, _databaseHelper.GetSearchdomainID(helper, jsonEntity.Searchdomain));
|
||||||
foreach (KeyValuePair<string, string> attribute in jsonEntity.Attributes)
|
foreach (KeyValuePair<string, string> attribute in jsonEntity.Attributes)
|
||||||
{
|
{
|
||||||
@@ -123,42 +222,12 @@ public class SearchdomainHelper(ILogger<SearchdomainHelper> logger, DatabaseHelp
|
|||||||
foreach (JSONDatapoint jsonDatapoint in jsonEntity.Datapoints)
|
foreach (JSONDatapoint jsonDatapoint in jsonEntity.Datapoints)
|
||||||
{
|
{
|
||||||
string hash = Convert.ToBase64String(SHA256.HashData(Encoding.UTF8.GetBytes(jsonDatapoint.Text)));
|
string hash = Convert.ToBase64String(SHA256.HashData(Encoding.UTF8.GetBytes(jsonDatapoint.Text)));
|
||||||
Dictionary<string, float[]> embeddings = [];
|
Datapoint datapoint = DatabaseInsertDatapointWithEmbeddings(helper, searchdomain, jsonDatapoint, id_entity, hash);
|
||||||
if (embeddingsLUT.ContainsKey(hash))
|
|
||||||
{
|
|
||||||
Dictionary<string, float[]> hashLUT = embeddingsLUT[hash];
|
|
||||||
foreach (string model in jsonDatapoint.Model)
|
|
||||||
{
|
|
||||||
if (hashLUT.ContainsKey(model))
|
|
||||||
{
|
|
||||||
embeddings.Add(model, hashLUT[model]);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var additionalEmbeddings = Datapoint.GenerateEmbeddings(jsonDatapoint.Text, [model], aIProvider, embeddingCache);
|
|
||||||
embeddings.Add(model, additionalEmbeddings.First().Value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
embeddings = Datapoint.GenerateEmbeddings(jsonDatapoint.Text, [.. jsonDatapoint.Model], aIProvider, embeddingCache);
|
|
||||||
}
|
|
||||||
var probMethod_embedding = new ProbMethod(jsonDatapoint.Probmethod_embedding, logger) ?? throw new ProbMethodNotFoundException(jsonDatapoint.Probmethod_embedding);
|
|
||||||
var similarityMethod = new SimilarityMethod(jsonDatapoint.SimilarityMethod, logger) ?? throw new SimilarityMethodNotFoundException(jsonDatapoint.SimilarityMethod);
|
|
||||||
Datapoint datapoint = new(jsonDatapoint.Name, probMethod_embedding, similarityMethod, hash, [.. embeddings.Select(kv => (kv.Key, kv.Value))]);
|
|
||||||
int id_datapoint = DatabaseHelper.DatabaseInsertDatapoint(helper, jsonDatapoint.Name, jsonDatapoint.Probmethod_embedding, jsonDatapoint.SimilarityMethod, hash, id_entity); // TODO make this a bulk add action to reduce number of queries
|
|
||||||
List<(string model, byte[] embedding)> data = [];
|
|
||||||
foreach ((string, float[]) embedding in datapoint.embeddings)
|
|
||||||
{
|
|
||||||
data.Add((embedding.Item1, BytesFromFloatArray(embedding.Item2)));
|
|
||||||
}
|
|
||||||
DatabaseHelper.DatabaseInsertEmbeddingBulk(helper, id_datapoint, data);
|
|
||||||
datapoints.Add(datapoint);
|
datapoints.Add(datapoint);
|
||||||
}
|
}
|
||||||
|
|
||||||
var probMethod = Probmethods.GetMethod(jsonEntity.Probmethod) ?? throw new ProbMethodNotFoundException(jsonEntity.Probmethod);
|
var probMethod = Probmethods.GetMethod(jsonEntity.Probmethod) ?? throw new ProbMethodNotFoundException(jsonEntity.Probmethod);
|
||||||
Entity entity = new(jsonEntity.Attributes, probMethod, datapoints, jsonEntity.Name)
|
Entity entity = new(jsonEntity.Attributes, probMethod, jsonEntity.Probmethod, datapoints, jsonEntity.Name)
|
||||||
{
|
{
|
||||||
id = id_entity
|
id = id_entity
|
||||||
};
|
};
|
||||||
@@ -166,3 +235,38 @@ public class SearchdomainHelper(ILogger<SearchdomainHelper> logger, DatabaseHelp
|
|||||||
return entity;
|
return entity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Datapoint DatabaseInsertDatapointWithEmbeddings(SQLHelper helper, Searchdomain searchdomain, JSONDatapoint jsonDatapoint, int id_entity, string? hash = null)
|
||||||
|
{
|
||||||
|
if (jsonDatapoint.Text is null)
|
||||||
|
{
|
||||||
|
throw new Exception("jsonDatapoint.Text must not be null at this point");
|
||||||
|
}
|
||||||
|
hash ??= Convert.ToBase64String(SHA256.HashData(Encoding.UTF8.GetBytes(jsonDatapoint.Text)));
|
||||||
|
Datapoint datapoint = BuildDatapointFromJsonDatapoint(jsonDatapoint, id_entity, searchdomain, hash);
|
||||||
|
int id_datapoint = DatabaseHelper.DatabaseInsertDatapoint(helper, jsonDatapoint.Name, jsonDatapoint.Probmethod_embedding, jsonDatapoint.SimilarityMethod, hash, id_entity); // TODO make this a bulk add action to reduce number of queries
|
||||||
|
List<(string model, byte[] embedding)> data = [];
|
||||||
|
foreach ((string, float[]) embedding in datapoint.embeddings)
|
||||||
|
{
|
||||||
|
data.Add((embedding.Item1, BytesFromFloatArray(embedding.Item2)));
|
||||||
|
}
|
||||||
|
DatabaseHelper.DatabaseInsertEmbeddingBulk(helper, id_datapoint, data);
|
||||||
|
return datapoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Datapoint BuildDatapointFromJsonDatapoint(JSONDatapoint jsonDatapoint, int entityId, Searchdomain searchdomain, string? hash = null)
|
||||||
|
{
|
||||||
|
if (jsonDatapoint.Text is null)
|
||||||
|
{
|
||||||
|
throw new Exception("jsonDatapoint.Text must not be null at this point");
|
||||||
|
}
|
||||||
|
using SQLHelper helper = searchdomain.helper.DuplicateConnection();
|
||||||
|
Dictionary<string, Dictionary<string, float[]>> embeddingCache = searchdomain.embeddingCache;
|
||||||
|
hash ??= Convert.ToBase64String(SHA256.HashData(Encoding.UTF8.GetBytes(jsonDatapoint.Text)));
|
||||||
|
DatabaseHelper.DatabaseInsertDatapoint(helper, jsonDatapoint.Name, jsonDatapoint.Probmethod_embedding, jsonDatapoint.SimilarityMethod, hash, entityId);
|
||||||
|
Dictionary<string, float[]> embeddings = Datapoint.GenerateEmbeddings(jsonDatapoint.Text, [.. jsonDatapoint.Model], searchdomain.aIProvider, embeddingCache);
|
||||||
|
var probMethod_embedding = new ProbMethod(jsonDatapoint.Probmethod_embedding, logger) ?? throw new ProbMethodNotFoundException(jsonDatapoint.Probmethod_embedding);
|
||||||
|
var similarityMethod = new SimilarityMethod(jsonDatapoint.SimilarityMethod, logger) ?? throw new SimilarityMethodNotFoundException(jsonDatapoint.SimilarityMethod);
|
||||||
|
return new Datapoint(jsonDatapoint.Name, probMethod_embedding, similarityMethod, hash, [.. embeddings.Select(kv => (kv.Key, kv.Value))]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -142,7 +142,7 @@ public class Searchdomain
|
|||||||
Probmethods.probMethodDelegate? probmethod = Probmethods.GetMethod(probmethodString);
|
Probmethods.probMethodDelegate? probmethod = Probmethods.GetMethod(probmethodString);
|
||||||
if (datapoint_unassigned.TryGetValue(id, out List<Datapoint>? datapoints) && probmethod is not null)
|
if (datapoint_unassigned.TryGetValue(id, out List<Datapoint>? datapoints) && probmethod is not null)
|
||||||
{
|
{
|
||||||
Entity entity = new(attributes, probmethod, datapoints, name)
|
Entity entity = new(attributes, probmethod, probmethodString, datapoints, name)
|
||||||
{
|
{
|
||||||
id = id
|
id = id
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -207,26 +207,70 @@
|
|||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
|
|
||||||
<!-- Access times -->
|
<!-- Access times -->
|
||||||
<h3>Access times</h3>
|
<h3>@T["Access times"]</h3>
|
||||||
<ul id="queryAccessTimes" class="list-group mb-4"></ul>
|
<ul id="queryDetailsAccessTimes" class="list-group mb-4"></ul>
|
||||||
|
|
||||||
<!-- Results -->
|
<!-- Results -->
|
||||||
<h3>Results</h3>
|
<h3>@T["Results"]</h3>
|
||||||
<table class="table table-sm table-striped">
|
<table class="table table-sm table-striped">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Score</th>
|
<th>@T["Score"]</th>
|
||||||
<th>Name</th>
|
<th>@T["Name"]</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="queryResultsBody"></tbody>
|
<tbody id="queryDetailsResultsBody"></tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||||
Close
|
@T["Close"]
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Query Update Modal -->
|
||||||
|
<div class="modal fade" id="queryUpdateModal" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-xl modal-dialog-scrollable">
|
||||||
|
<div class="modal-content">
|
||||||
|
|
||||||
|
<div class="modal-header bg-warning">
|
||||||
|
<h2 class="modal-title" id="queryUpdateTitle">@T["Query Update"] - <span id="queryUpdateQueryName"></span></h2>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-body">
|
||||||
|
|
||||||
|
<!-- Access times -->
|
||||||
|
<h3>@T["Access times"]</h3>
|
||||||
|
<ul id="queryUpdateAccessTimes" class="list-group mb-4"></ul>
|
||||||
|
|
||||||
|
<!-- Results -->
|
||||||
|
<h3>@T["Results"]</h3>
|
||||||
|
<table class="table table-sm table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th style="width: 85px;">@T["Score"]</th>
|
||||||
|
<th>@T["Name"]</th>
|
||||||
|
<th>@T["Action"]</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="queryUpdateResultsBody"></tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-warning" id="queryUpdateConfirm" data-bs-dismiss="modal">
|
||||||
|
@T["Update"]
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||||
|
@T["Close"]
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -281,10 +325,10 @@
|
|||||||
|
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="button" id="searchdomainConfirmDelete" class="btn btn-danger" data-bs-dismiss="modal">
|
<button type="button" id="searchdomainConfirmDelete" class="btn btn-danger" data-bs-dismiss="modal">
|
||||||
Delete
|
@T["Delete"]
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||||
Close
|
@T["Close"]
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -426,6 +470,109 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Update entity Modal -->
|
||||||
|
<div class="modal fade" id="updateEntityModal" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-xl modal-dialog-scrollable">
|
||||||
|
<div class="modal-content">
|
||||||
|
|
||||||
|
<div class="modal-header bg-warning text">
|
||||||
|
<h2 class="modal-title" id="updateEntityTitle">@T["Update entity"]</h2>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-body">
|
||||||
|
<!-- Entity base -->
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label for="updateEntityName" class="form-label">@T["Entity name"]</label>
|
||||||
|
<input type="text" class="form-control mb-3" id="updateEntityName" placeholder="@T["Entity name"]" />
|
||||||
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label for="updateEntityProbMethod" class="form-label">@T["Probmethod"]</label>
|
||||||
|
<input list="probMethods" id="updateEntityProbMethod" class="form-control" aria-label="@T["Probmethod"]">
|
||||||
|
<datalist id="probMethods">
|
||||||
|
@foreach (string probMethod in probMethods)
|
||||||
|
{
|
||||||
|
<option value=@probMethod>@probMethod</option>
|
||||||
|
}
|
||||||
|
</datalist>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Attributes -->
|
||||||
|
<div class="row g-3">
|
||||||
|
<h3 class="fs-5">@T["Attributes"]</h3>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td>@T["Name"]</td>
|
||||||
|
<td>@T["Value"]</td>
|
||||||
|
<td class="visually-hidden">@T["Action"]</td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="updateEntityAttributesContainer"></tbody>
|
||||||
|
</table>
|
||||||
|
<button type="button" id="updateEntityAttributesAdd" class="btn btn-primary">@T["Add attribute"]</button>
|
||||||
|
</div>
|
||||||
|
<!-- Datapoints -->
|
||||||
|
<div class="row g-3 mt-3">
|
||||||
|
<h3 class="fs-5">@T["Datapoints"]</h3>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td>@T["Name"]</td>
|
||||||
|
<td>@T["Value"]</td>
|
||||||
|
<td>@T["Probmethod_embedding"]</td>
|
||||||
|
<td>@T["Similarity method"]</td>
|
||||||
|
<td>@T["Model"]</td>
|
||||||
|
<td class="visually-hidden">@T["Action"]</td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="updateEntityDatapointsContainer"></tbody>
|
||||||
|
</table>
|
||||||
|
<button type="button" id="updateEntityDatapointsAdd" class="btn btn-primary">@T["Add datapoint"]</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" id="EntityConfirmUpdate" class="btn btn-warning" data-bs-dismiss="modal">
|
||||||
|
@T["Update"]
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||||
|
@T["Close"]
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Delete query Modal -->
|
||||||
|
<div class="modal fade" id="deleteQueryModal" tabindex="-1" aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-m modal-dialog-scrollable">
|
||||||
|
<div class="modal-content">
|
||||||
|
|
||||||
|
<div class="modal-header bg-danger text-white">
|
||||||
|
<h2 class="modal-title" id="deleteQueryTitle">@T["Delete query"]</h2>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>@Html.Raw(T["GenericDeleteConfirmation", "deleteQueryConfirmationModalName"])</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" id="queryConfirmDelete" class="btn btn-danger" data-bs-dismiss="modal">
|
||||||
|
@T["Delete"]
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
||||||
|
@T["Close"]
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
var domains = JSON.parse('@Html.Raw(System.Text.Json.JsonSerializer.Serialize(domains))');
|
var domains = JSON.parse('@Html.Raw(System.Text.Json.JsonSerializer.Serialize(domains))');
|
||||||
var probMethods = JSON.parse('@Html.Raw(System.Text.Json.JsonSerializer.Serialize(probMethods))');
|
var probMethods = JSON.parse('@Html.Raw(System.Text.Json.JsonSerializer.Serialize(probMethods))');
|
||||||
@@ -434,6 +581,60 @@
|
|||||||
var queries = null;
|
var queries = null;
|
||||||
var models = JSON.parse('@Html.Raw(System.Text.Json.JsonSerializer.Serialize(models))');
|
var models = JSON.parse('@Html.Raw(System.Text.Json.JsonSerializer.Serialize(models))');
|
||||||
|
|
||||||
|
let draggedRow = null;
|
||||||
|
document.addEventListener("dragstart", e => {
|
||||||
|
if (e.target.tagName === "TR") {
|
||||||
|
draggedRow = e.target;
|
||||||
|
e.target.classList.add("table-warning");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener("dragend", e => {
|
||||||
|
if (e.target.tagName === "TR") {
|
||||||
|
e.target.classList.remove("table-warning");
|
||||||
|
}
|
||||||
|
document.querySelectorAll(".table-warning").forEach(x => x.classList.remove("table-warning"));
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener("dragenter", e => {
|
||||||
|
if (e.target.tagName === "TR") {
|
||||||
|
e.target.classList.add("table-warning");
|
||||||
|
} else if (e.target.tagName == "TD") {
|
||||||
|
e.target.parentElement.classList.add("table-warning");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener("dragleave", e => {
|
||||||
|
if (e.target.tagName === "TR") {
|
||||||
|
e.target.classList.remove("table-warning");
|
||||||
|
} else if (e.target.tagName == "TD") {
|
||||||
|
e.target.parentElement.classList.remove("table-warning");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
document.addEventListener("dragover", e => {
|
||||||
|
if (e.target.closest("tr")) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener("drop", e => {
|
||||||
|
const targetRow = e.target.closest("tr");
|
||||||
|
if (draggedRow && targetRow && draggedRow !== targetRow && targetRow.parentElement.tagName !== "THEAD") {
|
||||||
|
if (!draggedRow || !targetRow || draggedRow === targetRow) return;
|
||||||
|
|
||||||
|
const rect = targetRow.getBoundingClientRect();
|
||||||
|
const isAfter = e.clientY > rect.top + rect.height / 2;
|
||||||
|
|
||||||
|
const parent = targetRow.parentNode;
|
||||||
|
|
||||||
|
if (isAfter) {
|
||||||
|
parent.insertBefore(draggedRow, targetRow.nextSibling);
|
||||||
|
} else {
|
||||||
|
parent.insertBefore(draggedRow, targetRow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
const filterInput = document.getElementById('entitiesFilter');
|
const filterInput = document.getElementById('entitiesFilter');
|
||||||
|
|
||||||
@@ -519,113 +720,14 @@
|
|||||||
.getElementById('createEntityAttributesAdd')
|
.getElementById('createEntityAttributesAdd')
|
||||||
.addEventListener('click', () => {
|
.addEventListener('click', () => {
|
||||||
var uuid = crypto.randomUUID();
|
var uuid = crypto.randomUUID();
|
||||||
var entityAttributesContainer = document.getElementById('createEntityAttributesContainer');
|
addEntityAttribute('createEntityAttributesContainer', uuid);
|
||||||
var tr = document.createElement('tr');
|
|
||||||
tr.setAttribute('data-uid', uuid);
|
|
||||||
var tdKey = document.createElement('td');
|
|
||||||
var keyInput = document.createElement('input');
|
|
||||||
keyInput.classList.add('form-control')
|
|
||||||
keyInput.ariaLabel = '@T["Key"]';
|
|
||||||
tdKey.append(keyInput);
|
|
||||||
var tdValue = document.createElement('td');
|
|
||||||
var ValueInput = document.createElement('textarea');
|
|
||||||
ValueInput.classList.add('form-control')
|
|
||||||
ValueInput.ariaLabel = '@T["Value"]';
|
|
||||||
tdValue.append(ValueInput);
|
|
||||||
var tdAction = document.createElement('td');
|
|
||||||
var tdButton = document.createElement('button');
|
|
||||||
tdButton.classList.add('btn', 'btn-danger');
|
|
||||||
tdButton.textContent = '@T["Remove"]';
|
|
||||||
tdButton.ariaLabel = '@T["Remove attribute"]';
|
|
||||||
tdAction.append(tdButton);
|
|
||||||
tdButton.onclick = function() {
|
|
||||||
tr.remove();
|
|
||||||
};
|
|
||||||
tr.append(tdKey);
|
|
||||||
tr.append(tdValue);
|
|
||||||
tr.append(tdAction);
|
|
||||||
entityAttributesContainer.append(tr);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
document
|
document
|
||||||
.getElementById('createEntityDatapointsAdd')
|
.getElementById('createEntityDatapointsAdd')
|
||||||
.addEventListener('click', () => {
|
.addEventListener('click', () => {
|
||||||
var uuid = crypto.randomUUID();
|
var uuid = crypto.randomUUID();
|
||||||
var enntityDatapointsContainer = document.getElementById('createEntityDatapointsContainer');
|
addEntityDatapoint('createEntityDatapointsContainer', uuid, "", "");
|
||||||
var tr = document.createElement('tr');
|
|
||||||
tr.setAttribute('data-uid', uuid);
|
|
||||||
var tdName = document.createElement('td');
|
|
||||||
var nameInput = document.createElement('input');
|
|
||||||
nameInput.classList.add('form-control')
|
|
||||||
nameInput.ariaLabel = '@T["Name"]';
|
|
||||||
tdName.append(nameInput);
|
|
||||||
var tdValue = document.createElement('td');
|
|
||||||
var ValueInput = document.createElement('textarea');
|
|
||||||
ValueInput.classList.add('form-control')
|
|
||||||
ValueInput.ariaLabel = '@T["Value"]';
|
|
||||||
tdValue.append(ValueInput);
|
|
||||||
|
|
||||||
var tdProbmethodEmbedding = document.createElement('td');
|
|
||||||
var probMethodSelect = document.createElement('select');
|
|
||||||
probMethodSelect.classList.add('probmethod');
|
|
||||||
for (i in probMethods)
|
|
||||||
{
|
|
||||||
let probMethodSelectElement = document.createElement('option');
|
|
||||||
probMethodSelectElement.value = probMethods[i];
|
|
||||||
probMethodSelectElement.text = probMethods[i];
|
|
||||||
probMethodSelect.append(probMethodSelectElement);
|
|
||||||
}
|
|
||||||
probMethodSelect.classList.add('form-control')
|
|
||||||
probMethodSelect.ariaLabel = '@T["Probmethod_embedding"]';
|
|
||||||
tdProbmethodEmbedding.append(probMethodSelect);
|
|
||||||
|
|
||||||
var tdSimilarityMethod = document.createElement('td');
|
|
||||||
var similarityMethodSelect = document.createElement('select');
|
|
||||||
similarityMethodSelect.classList.add('similarityMethod');
|
|
||||||
for (i in similarityMethods)
|
|
||||||
{
|
|
||||||
let similarityMethodSelectElement = document.createElement('option');
|
|
||||||
similarityMethodSelectElement.value = similarityMethods[i];
|
|
||||||
similarityMethodSelectElement.text = similarityMethods[i];
|
|
||||||
similarityMethodSelect.append(similarityMethodSelectElement);
|
|
||||||
}
|
|
||||||
similarityMethodSelect.classList.add('form-control')
|
|
||||||
similarityMethodSelect.ariaLabel = '@T["Similarity method"]';
|
|
||||||
tdSimilarityMethod.append(similarityMethodSelect);
|
|
||||||
|
|
||||||
var tdModel = document.createElement('td');
|
|
||||||
var modelSelect = document.createElement('select');
|
|
||||||
modelSelect.classList.add('model');
|
|
||||||
modelSelect.multiple = true;
|
|
||||||
for (i in models)
|
|
||||||
{
|
|
||||||
let modelSelectElement = document.createElement('option');
|
|
||||||
modelSelectElement.value = models[i];
|
|
||||||
modelSelectElement.text = models[i];
|
|
||||||
modelSelect.append(modelSelectElement);
|
|
||||||
}
|
|
||||||
modelSelect.classList.add('form-control')
|
|
||||||
modelSelect.ariaLabel = '@T["Probmethod_embedding"]';
|
|
||||||
tdModel.append(modelSelect);
|
|
||||||
|
|
||||||
|
|
||||||
var tdAction = document.createElement('td');
|
|
||||||
var tdButton = document.createElement('button');
|
|
||||||
tdButton.classList.add('btn', 'btn-danger');
|
|
||||||
tdButton.textContent = '@T["Remove"]';
|
|
||||||
tdButton.ariaLabel = '@T["Remove attribute"]';
|
|
||||||
tdAction.append(tdButton);
|
|
||||||
tdButton.onclick = function() {
|
|
||||||
tr.remove();
|
|
||||||
};
|
|
||||||
tr.append(tdName);
|
|
||||||
tr.append(tdValue);
|
|
||||||
tr.append(tdProbmethodEmbedding);
|
|
||||||
tr.append(tdSimilarityMethod);
|
|
||||||
tr.append(tdModel);
|
|
||||||
tr.append(tdAction);
|
|
||||||
enntityDatapointsContainer.append(tr);
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
document
|
document
|
||||||
@@ -734,6 +836,109 @@
|
|||||||
console.error('Error deleting entity:', error);
|
console.error('Error deleting entity:', error);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
document
|
||||||
|
.getElementById('updateEntityAttributesAdd')
|
||||||
|
.addEventListener('click', () => {
|
||||||
|
var uuid = crypto.randomUUID();
|
||||||
|
addEntityAttribute('updateEntityAttributesContainer', uuid);
|
||||||
|
});
|
||||||
|
|
||||||
|
document
|
||||||
|
.getElementById('updateEntityDatapointsAdd')
|
||||||
|
.addEventListener('click', () => {
|
||||||
|
var uuid = crypto.randomUUID();
|
||||||
|
addEntityDatapoint('updateEntityDatapointsContainer', uuid, "", "");
|
||||||
|
});
|
||||||
|
|
||||||
|
document
|
||||||
|
.getElementById('EntityConfirmUpdate')
|
||||||
|
.addEventListener('click', () => {
|
||||||
|
var name = document.getElementById('updateEntityName').value;
|
||||||
|
var probMethod = document.getElementById('updateEntityProbMethod').value;
|
||||||
|
var attributes = getEntityAttributes('updateEntityAttributesContainer');
|
||||||
|
var datapoints = getEntityDatapoints('updateEntityDatapointsContainer');
|
||||||
|
var data = [{
|
||||||
|
"name": name,
|
||||||
|
"probmethod": probMethod,
|
||||||
|
"searchdomain": encodeURIComponent(domains[getSelectedDomainKey()]),
|
||||||
|
"attributes": attributes,
|
||||||
|
"datapoints": datapoints
|
||||||
|
}];
|
||||||
|
console.log(data);
|
||||||
|
// TODO add throbber to indicate loading
|
||||||
|
fetch(`/Entity/Index`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
}).then(async response => {
|
||||||
|
result = await response.json();
|
||||||
|
if (response.ok && result.Success) {
|
||||||
|
// TODO add toast
|
||||||
|
console.log('Entity was created successfully');
|
||||||
|
selectDomain(getSelectedDomainKey());
|
||||||
|
} else {
|
||||||
|
// TODO add toast
|
||||||
|
console.error('Failed to create entity:', result.Message);
|
||||||
|
}
|
||||||
|
}).catch(error => {
|
||||||
|
console.error('Error creating entity:', error);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
document
|
||||||
|
.getElementById('queryConfirmDelete')
|
||||||
|
.addEventListener('click', () => {
|
||||||
|
let searchdomain = domains[getSelectedDomainKey()];
|
||||||
|
let query = document.getElementById('deleteQueryConfirmationModalName').textContent;
|
||||||
|
fetch(`/Searchdomain/Searches?searchdomain=${searchdomain}&query=${query}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
}).then(async response => {
|
||||||
|
result = await response.json();
|
||||||
|
if (response.ok && result.Success) {
|
||||||
|
// TODO add toast
|
||||||
|
console.log('Search query was deleted successfully');
|
||||||
|
selectDomain(getSelectedDomainKey());
|
||||||
|
} else {
|
||||||
|
// TODO add toast
|
||||||
|
console.error('Failed to delete query:', result.Message);
|
||||||
|
}
|
||||||
|
}).catch(error => {
|
||||||
|
console.error('Error creating entity:', error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
document
|
||||||
|
.getElementById('queryUpdateConfirm')
|
||||||
|
.addEventListener('click', () => {
|
||||||
|
let searchdomain = domains[getSelectedDomainKey()];
|
||||||
|
let query = document.getElementById('queryUpdateQueryName').textContent;
|
||||||
|
let data = getQueryUpdateTableData();
|
||||||
|
console.log()
|
||||||
|
fetch(`/Searchdomain/Searches?searchdomain=${searchdomain}&query=${query}`, {
|
||||||
|
method: 'PATCH',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(data)
|
||||||
|
}).then(async response => {
|
||||||
|
result = await response.json();
|
||||||
|
if (response.ok && result.Success) {
|
||||||
|
// TODO add toast
|
||||||
|
console.log('Search query was deleted successfully');
|
||||||
|
selectDomain(getSelectedDomainKey());
|
||||||
|
} else {
|
||||||
|
// TODO add toast
|
||||||
|
console.error('Failed to delete query:', result.Message);
|
||||||
|
}
|
||||||
|
}).catch(error => {
|
||||||
|
console.error('Error creating entity:', error);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function deleteSearchdomain(domainKey) {
|
function deleteSearchdomain(domainKey) {
|
||||||
@@ -837,7 +1042,7 @@
|
|||||||
let databaseUtilizationPromise = getSearchdomainDatabaseUtilization(getSelectedDomainKey());
|
let databaseUtilizationPromise = getSearchdomainDatabaseUtilization(getSelectedDomainKey());
|
||||||
|
|
||||||
/* ---------- ENTITIES ---------- */
|
/* ---------- ENTITIES ---------- */
|
||||||
let entitiesUrl = `/Entity/List?searchdomain=${encodeURIComponent(domainName)}&returnEmbeddings=false`;
|
let entitiesUrl = `/Entity/List?searchdomain=${encodeURIComponent(domainName)}&returnEmbeddings=false&returnModels=true`;
|
||||||
let entitiesCard = document.querySelector("#entitiesTable").parentElement;
|
let entitiesCard = document.querySelector("#entitiesTable").parentElement;
|
||||||
clearEntitiesTable();
|
clearEntitiesTable();
|
||||||
showEntitiesLoading(entitiesCard);
|
showEntitiesLoading(entitiesCard);
|
||||||
@@ -955,7 +1160,30 @@
|
|||||||
updateButton.textContent = '@T["Update"]';
|
updateButton.textContent = '@T["Update"]';
|
||||||
updateButton.setAttribute("data-index", entities.findIndex(en => en == entity));
|
updateButton.setAttribute("data-index", entities.findIndex(en => en == entity));
|
||||||
updateButton.addEventListener('click', () => {
|
updateButton.addEventListener('click', () => {
|
||||||
updateEntity(entity);
|
const modal = new bootstrap.Modal(
|
||||||
|
document.getElementById('updateEntityModal')
|
||||||
|
);
|
||||||
|
modal.show();
|
||||||
|
|
||||||
|
let updateEntityName = document.getElementById('updateEntityName');
|
||||||
|
updateEntityName.value = entity.Name;
|
||||||
|
let updateEntityProbMethod = document.getElementById('updateEntityProbMethod');
|
||||||
|
updateEntityProbMethod.value = entity.ProbMethod;
|
||||||
|
|
||||||
|
let updateEntityAttributesContainer = document.getElementById('updateEntityAttributesContainer');
|
||||||
|
updateEntityAttributesContainer.textContent = "";
|
||||||
|
for (i in entity.Attributes)
|
||||||
|
{
|
||||||
|
let attribute = entity.Attributes[i];
|
||||||
|
addEntityAttribute('updateEntityAttributesContainer', attribute.Name, attribute.Name, attribute.Value);
|
||||||
|
}
|
||||||
|
let updateEntityDatapointsContainer = document.getElementById('updateEntityDatapointsContainer');
|
||||||
|
updateEntityDatapointsContainer.textContent = "";
|
||||||
|
for (i in entity.Datapoints)
|
||||||
|
{
|
||||||
|
let datapoint = entity.Datapoints[i];
|
||||||
|
addEntityDatapoint('updateEntityDatapointsContainer', datapoint.Name, datapoint.Name, null, datapoint.ProbMethod, datapoint.SimilarityMethod, Array.from(datapoint.Embeddings, x => x.Model));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
var deleteButton = document.createElement('button');
|
var deleteButton = document.createElement('button');
|
||||||
@@ -1002,14 +1230,37 @@
|
|||||||
row.appendChild(nameCell);
|
row.appendChild(nameCell);
|
||||||
|
|
||||||
const actionCell = document.createElement('td');
|
const actionCell = document.createElement('td');
|
||||||
const btn = document.createElement('button');
|
actionCell.classList.add('btn-group');
|
||||||
btn.className = 'btn btn-sm btn-info';
|
const btnDetails = document.createElement('button');
|
||||||
btn.textContent = '@T["Details"]';
|
btnDetails.className = 'btn btn-sm btn-info';
|
||||||
btn.addEventListener('click', () => {
|
btnDetails.textContent = '@T["Details"]';
|
||||||
|
btnDetails.addEventListener('click', () => {
|
||||||
showQueryDetails(query);
|
showQueryDetails(query);
|
||||||
});
|
});
|
||||||
|
const btnUpdate = document.createElement('button');
|
||||||
|
btnUpdate.className = 'btn btn-sm btn-warning';
|
||||||
|
btnUpdate.textContent = '@T["Update"]';
|
||||||
|
btnUpdate.addEventListener('click', () => {
|
||||||
|
showQueryUpdate(query);
|
||||||
|
});
|
||||||
|
|
||||||
actionCell.appendChild(btn);
|
const btnDelete = document.createElement('button');
|
||||||
|
btnDelete.className = 'btn btn-sm btn-danger';
|
||||||
|
btnDelete.textContent = '@T["Delete"]';
|
||||||
|
btnDelete.addEventListener('click', () => {
|
||||||
|
const modal = new bootstrap.Modal(
|
||||||
|
document.getElementById('deleteQueryModal')
|
||||||
|
);
|
||||||
|
modal.show();
|
||||||
|
let queryConfirmDelete = document.getElementById('queryConfirmDelete');
|
||||||
|
queryConfirmDelete.setAttribute('data-name', query.Name);
|
||||||
|
let deleteQueryConfirmationModalName = document.getElementById('deleteQueryConfirmationModalName');
|
||||||
|
deleteQueryConfirmationModalName.textContent = query.Name;
|
||||||
|
});
|
||||||
|
|
||||||
|
actionCell.appendChild(btnDetails);
|
||||||
|
actionCell.appendChild(btnUpdate);
|
||||||
|
actionCell.appendChild(btnDelete);
|
||||||
row.appendChild(actionCell);
|
row.appendChild(actionCell);
|
||||||
|
|
||||||
tbody.appendChild(row);
|
tbody.appendChild(row);
|
||||||
@@ -1098,7 +1349,7 @@
|
|||||||
document.getElementById('queryDetailsQueryName').innerText = query.Name;
|
document.getElementById('queryDetailsQueryName').innerText = query.Name;
|
||||||
|
|
||||||
/* ---------- Access times ---------- */
|
/* ---------- Access times ---------- */
|
||||||
const accessList = document.getElementById('queryAccessTimes');
|
const accessList = document.getElementById('queryDetailsAccessTimes');
|
||||||
accessList.innerHTML = '';
|
accessList.innerHTML = '';
|
||||||
|
|
||||||
if (!query.AccessDateTimes || query.AccessDateTimes.length === 0) {
|
if (!query.AccessDateTimes || query.AccessDateTimes.length === 0) {
|
||||||
@@ -1116,7 +1367,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* ---------- Results ---------- */
|
/* ---------- Results ---------- */
|
||||||
const resultsBody = document.getElementById('queryResultsBody');
|
const resultsBody = document.getElementById('queryDetailsResultsBody');
|
||||||
resultsBody.innerHTML = '';
|
resultsBody.innerHTML = '';
|
||||||
|
|
||||||
if (!query.Results || query.Results.length === 0) {
|
if (!query.Results || query.Results.length === 0) {
|
||||||
@@ -1144,6 +1395,73 @@
|
|||||||
modal.show();
|
modal.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showQueryUpdate(query) {
|
||||||
|
// Title
|
||||||
|
document.getElementById('queryUpdateQueryName').innerText = query.Name;
|
||||||
|
|
||||||
|
/* ---------- Access times ---------- */
|
||||||
|
const accessList = document.getElementById('queryUpdateAccessTimes');
|
||||||
|
accessList.innerHTML = '';
|
||||||
|
|
||||||
|
if (!query.AccessDateTimes || query.AccessDateTimes.length === 0) {
|
||||||
|
accessList.innerHTML = `
|
||||||
|
<li class="list-group-item text-muted text-center">
|
||||||
|
No access times
|
||||||
|
</li>`;
|
||||||
|
} else {
|
||||||
|
query.AccessDateTimes.forEach(dt => {
|
||||||
|
const li = document.createElement('li');
|
||||||
|
li.className = 'list-group-item';
|
||||||
|
li.textContent = new Date(dt).toLocaleString();
|
||||||
|
accessList.appendChild(li);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- Results ---------- */
|
||||||
|
const resultsBody = document.getElementById('queryUpdateResultsBody');
|
||||||
|
resultsBody.innerHTML = '';
|
||||||
|
|
||||||
|
if (!query.Results || query.Results.length === 0) {
|
||||||
|
resultsBody.innerHTML = `
|
||||||
|
<tr>
|
||||||
|
<td colspan="2" class="text-muted text-center">
|
||||||
|
No results
|
||||||
|
</td>
|
||||||
|
</tr>`;
|
||||||
|
} else {
|
||||||
|
query.Results.forEach(r => {
|
||||||
|
const row = document.createElement('tr');
|
||||||
|
row.setAttribute("draggable", true);
|
||||||
|
const tdScore = document.createElement('td');
|
||||||
|
const scoreInput = document.createElement('input');
|
||||||
|
scoreInput.classList.add('form-control');
|
||||||
|
scoreInput.value = r.Score.toFixed(4);
|
||||||
|
tdScore.append(scoreInput);
|
||||||
|
const tdName = document.createElement('td');
|
||||||
|
tdName.classList.add("text-break");
|
||||||
|
tdName.innerText = r.Name;
|
||||||
|
const tdAction = document.createElement('td');
|
||||||
|
const deleteButton = document.createElement('button');
|
||||||
|
deleteButton.classList.add('btn', 'btn-danger', 'btn-sm');
|
||||||
|
deleteButton.innerText = '@T["Delete"]';
|
||||||
|
deleteButton.onclick = function() {
|
||||||
|
row.remove();
|
||||||
|
};
|
||||||
|
tdAction.append(deleteButton);
|
||||||
|
row.append(tdScore);
|
||||||
|
row.append(tdName);
|
||||||
|
row.append(tdAction);
|
||||||
|
resultsBody.appendChild(row);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show modal
|
||||||
|
const modal = new bootstrap.Modal(
|
||||||
|
document.getElementById('queryUpdateModal')
|
||||||
|
);
|
||||||
|
modal.show();
|
||||||
|
}
|
||||||
|
|
||||||
function NumberOfBytesAsHumanReadable(bytes, decimals = 2) {
|
function NumberOfBytesAsHumanReadable(bytes, decimals = 2) {
|
||||||
if (bytes === 0) return '0 B';
|
if (bytes === 0) return '0 B';
|
||||||
if (bytes > 1.20892581961*(10**27)) return "∞ B";
|
if (bytes > 1.20892581961*(10**27)) return "∞ B";
|
||||||
@@ -1191,7 +1509,10 @@
|
|||||||
|
|
||||||
if (keyInput && valueTextarea && probmethodSelect && modelSelect) {
|
if (keyInput && valueTextarea && probmethodSelect && modelSelect) {
|
||||||
const key = keyInput.value.trim();
|
const key = keyInput.value.trim();
|
||||||
const value = valueTextarea.value.trim();
|
var value = valueTextarea.value.trim();
|
||||||
|
if (value != null && value.length == 0) {
|
||||||
|
value = null;
|
||||||
|
}
|
||||||
const probMethod = probmethodSelect.selectedOptions[0].value;
|
const probMethod = probmethodSelect.selectedOptions[0].value;
|
||||||
const similarityMethod = similaritymethodSelect.selectedOptions[0].value;
|
const similarityMethod = similaritymethodSelect.selectedOptions[0].value;
|
||||||
const modelSelection = Array.from(modelSelect.selectedOptions)
|
const modelSelection = Array.from(modelSelect.selectedOptions)
|
||||||
@@ -1213,4 +1534,148 @@
|
|||||||
return datapoints;
|
return datapoints;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addEntityAttribute(containerName = 'createEntityAttributesContainer', uid, key = '', value = '')
|
||||||
|
{
|
||||||
|
var container = document.getElementById(containerName);
|
||||||
|
var tr = document.createElement('tr');
|
||||||
|
tr.setAttribute('data-uid', uid);
|
||||||
|
var tdKey = document.createElement('td');
|
||||||
|
var keyInput = document.createElement('input');
|
||||||
|
keyInput.classList.add('form-control');
|
||||||
|
keyInput.ariaLabel = '@T["Key"]';
|
||||||
|
keyInput.value = key;
|
||||||
|
tdKey.append(keyInput);
|
||||||
|
var tdValue = document.createElement('td');
|
||||||
|
var ValueInput = document.createElement('textarea');
|
||||||
|
ValueInput.classList.add('form-control');
|
||||||
|
ValueInput.ariaLabel = '@T["Value"]';
|
||||||
|
ValueInput.value = value;
|
||||||
|
tdValue.append(ValueInput);
|
||||||
|
var tdAction = document.createElement('td');
|
||||||
|
var tdButton = document.createElement('button');
|
||||||
|
tdButton.classList.add('btn', 'btn-danger');
|
||||||
|
tdButton.textContent = '@T["Remove"]';
|
||||||
|
tdButton.ariaLabel = '@T["Remove attribute"]';
|
||||||
|
tdAction.append(tdButton);
|
||||||
|
tdButton.onclick = function() {
|
||||||
|
tr.remove();
|
||||||
|
};
|
||||||
|
tr.append(tdKey);
|
||||||
|
tr.append(tdValue);
|
||||||
|
tr.append(tdAction);
|
||||||
|
container.append(tr);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addEntityDatapoint(containerName = 'createEntityDatapointsContainer', uid, name = null, value = null, probmethod_embedding = null, similarityMethod = null, selectedModels = [])
|
||||||
|
{
|
||||||
|
var entityDatapointsContainer = document.getElementById(containerName);
|
||||||
|
var tr = document.createElement('tr');
|
||||||
|
tr.setAttribute('data-uid', uid);
|
||||||
|
var tdName = document.createElement('td');
|
||||||
|
var nameInput = document.createElement('input');
|
||||||
|
nameInput.classList.add('form-control')
|
||||||
|
nameInput.ariaLabel = '@T["Name"]';
|
||||||
|
if (name != null) nameInput.value = name;
|
||||||
|
tdName.append(nameInput);
|
||||||
|
var tdValue = document.createElement('td');
|
||||||
|
var ValueInput = document.createElement('textarea');
|
||||||
|
ValueInput.classList.add('form-control')
|
||||||
|
ValueInput.ariaLabel = '@T["Value"]';
|
||||||
|
if (value != null) {
|
||||||
|
ValueInput.value = value;
|
||||||
|
} else {
|
||||||
|
nameInput.readOnly = true;
|
||||||
|
ValueInput.readOnly = true;
|
||||||
|
}
|
||||||
|
tdValue.append(ValueInput);
|
||||||
|
|
||||||
|
var tdProbmethodEmbedding = document.createElement('td');
|
||||||
|
var probMethodSelect = document.createElement('select');
|
||||||
|
probMethodSelect.classList.add('probmethod');
|
||||||
|
for (i in probMethods)
|
||||||
|
{
|
||||||
|
let probMethodSelectElement = document.createElement('option');
|
||||||
|
probMethodSelectElement.value = probMethods[i];
|
||||||
|
probMethodSelectElement.text = probMethods[i];
|
||||||
|
probMethodSelect.append(probMethodSelectElement);
|
||||||
|
}
|
||||||
|
probMethodSelect.classList.add('form-control')
|
||||||
|
probMethodSelect.ariaLabel = '@T["Probmethod_embedding"]';
|
||||||
|
if (probmethod_embedding != null) probMethodSelect.value = probmethod_embedding;
|
||||||
|
tdProbmethodEmbedding.append(probMethodSelect);
|
||||||
|
|
||||||
|
var tdSimilarityMethod = document.createElement('td');
|
||||||
|
var similarityMethodSelect = document.createElement('select');
|
||||||
|
similarityMethodSelect.classList.add('similarityMethod');
|
||||||
|
for (i in similarityMethods)
|
||||||
|
{
|
||||||
|
let similarityMethodSelectElement = document.createElement('option');
|
||||||
|
similarityMethodSelectElement.value = similarityMethods[i];
|
||||||
|
similarityMethodSelectElement.text = similarityMethods[i];
|
||||||
|
similarityMethodSelect.append(similarityMethodSelectElement);
|
||||||
|
}
|
||||||
|
similarityMethodSelect.classList.add('form-control')
|
||||||
|
similarityMethodSelect.ariaLabel = '@T["Similarity method"]';
|
||||||
|
if (similarityMethod != null) similarityMethodSelect.value = similarityMethod;
|
||||||
|
tdSimilarityMethod.append(similarityMethodSelect);
|
||||||
|
|
||||||
|
var tdModel = document.createElement('td');
|
||||||
|
var modelSelect = document.createElement('select');
|
||||||
|
modelSelect.classList.add('model');
|
||||||
|
modelSelect.multiple = true;
|
||||||
|
for (i in models)
|
||||||
|
{
|
||||||
|
let modelSelectElement = document.createElement('option');
|
||||||
|
modelSelectElement.value = models[i];
|
||||||
|
modelSelectElement.text = models[i];
|
||||||
|
modelSelectElement.selected = selectedModels.findIndex(x => x === models[i] || (x + ":latest") === models[i]) >= 0;
|
||||||
|
modelSelect.append(modelSelectElement);
|
||||||
|
}
|
||||||
|
modelSelect.classList.add('form-control')
|
||||||
|
modelSelect.ariaLabel = '@T["Probmethod_embedding"]';
|
||||||
|
if (value == null) {
|
||||||
|
modelSelect.disabled = true;
|
||||||
|
}
|
||||||
|
tdModel.append(modelSelect);
|
||||||
|
|
||||||
|
|
||||||
|
var tdAction = document.createElement('td');
|
||||||
|
var tdButton = document.createElement('button');
|
||||||
|
tdButton.classList.add('btn', 'btn-danger');
|
||||||
|
tdButton.textContent = '@T["Remove"]';
|
||||||
|
tdButton.ariaLabel = '@T["Remove attribute"]';
|
||||||
|
tdAction.append(tdButton);
|
||||||
|
tdButton.onclick = function() {
|
||||||
|
tr.remove();
|
||||||
|
};
|
||||||
|
tr.append(tdName);
|
||||||
|
tr.append(tdValue);
|
||||||
|
tr.append(tdProbmethodEmbedding);
|
||||||
|
tr.append(tdSimilarityMethod);
|
||||||
|
tr.append(tdModel);
|
||||||
|
tr.append(tdAction);
|
||||||
|
entityDatapointsContainer.append(tr);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getQueryUpdateTableData() {
|
||||||
|
const tbody = document.getElementById('queryUpdateResultsBody');
|
||||||
|
const rows = tbody.getElementsByTagName('tr');
|
||||||
|
const result = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < rows.length; i++) {
|
||||||
|
const row = rows[i];
|
||||||
|
const cells = row.getElementsByTagName('td');
|
||||||
|
|
||||||
|
// Get the text content from the second cell (index 1) which contains the path
|
||||||
|
const score = parseFloat(cells[0].firstChild.value);
|
||||||
|
const name = cells[1].textContent.trim();
|
||||||
|
|
||||||
|
result.push({
|
||||||
|
"Score": score,
|
||||||
|
"Name": name
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
@@ -41,6 +41,8 @@ public class EntityListResult
|
|||||||
{
|
{
|
||||||
[JsonPropertyName("Name")]
|
[JsonPropertyName("Name")]
|
||||||
public required string Name { get; set; }
|
public required string Name { get; set; }
|
||||||
|
[JsonPropertyName("ProbMethod")]
|
||||||
|
public required string ProbMethod { get; set; }
|
||||||
[JsonPropertyName("Attributes")]
|
[JsonPropertyName("Attributes")]
|
||||||
public required List<AttributeResult> Attributes { get; set; }
|
public required List<AttributeResult> Attributes { get; set; }
|
||||||
[JsonPropertyName("Datapoints")]
|
[JsonPropertyName("Datapoints")]
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ public class JSONEntity
|
|||||||
public class JSONDatapoint
|
public class JSONDatapoint
|
||||||
{
|
{
|
||||||
public required string Name { get; set; }
|
public required string Name { get; set; }
|
||||||
public required string Text { get; set; }
|
public required string? Text { get; set; }
|
||||||
public required string Probmethod_embedding { get; set; }
|
public required string Probmethod_embedding { get; set; }
|
||||||
public required string SimilarityMethod { get; set; }
|
public required string SimilarityMethod { get; set; }
|
||||||
public required string[] Model { get; set; }
|
public required string[] Model { get; set; }
|
||||||
|
|||||||
@@ -2,12 +2,19 @@
|
|||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
namespace Shared.Models;
|
namespace Shared.Models;
|
||||||
public readonly struct ResultItem(float score, string name)
|
public readonly struct ResultItem
|
||||||
{
|
{
|
||||||
[JsonPropertyName("Score")]
|
[JsonPropertyName("Score")]
|
||||||
public readonly float Score { get; } = score;
|
public readonly float Score { get; }
|
||||||
[JsonPropertyName("Name")]
|
[JsonPropertyName("Name")]
|
||||||
public readonly string Name { get; } = name;
|
public readonly string Name { get; }
|
||||||
|
|
||||||
|
[JsonConstructor]
|
||||||
|
public ResultItem(float score, string name)
|
||||||
|
{
|
||||||
|
Score = score;
|
||||||
|
Name = name;
|
||||||
|
}
|
||||||
|
|
||||||
public static long EstimateSize(ResultItem item)
|
public static long EstimateSize(ResultItem item)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -55,6 +55,24 @@ public class SearchdomainSearchesResults
|
|||||||
public required Dictionary<string, DateTimedSearchResult> Searches { get; set; }
|
public required Dictionary<string, DateTimedSearchResult> Searches { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class SearchdomainDeleteSearchResult
|
||||||
|
{
|
||||||
|
[JsonPropertyName("Success")]
|
||||||
|
public required bool Success { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("Message")]
|
||||||
|
public string? Message { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SearchdomainUpdateSearchResult
|
||||||
|
{
|
||||||
|
[JsonPropertyName("Success")]
|
||||||
|
public required bool Success { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("Message")]
|
||||||
|
public string? Message { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
public class SearchdomainSettingsResults
|
public class SearchdomainSettingsResults
|
||||||
{
|
{
|
||||||
[JsonPropertyName("Success")]
|
[JsonPropertyName("Success")]
|
||||||
|
|||||||
Reference in New Issue
Block a user