Prevent sampleless speaker identities
PR and Push Build/Test / build-and-test (push) Successful in 8m38s

This commit is contained in:
2026-06-02 10:39:00 +02:00
parent a3bad1bdd4
commit e3f4e87319
10 changed files with 101 additions and 50 deletions
@@ -723,36 +723,14 @@ public sealed class WorkflowRulesEditorTools
}
}
public async Task<string> CreateIdentity(
public Task<string> CreateIdentity(
string? canonicalName = null,
string[]? aliases = null,
string[]? candidateNames = null)
{
var context = await CreatePreparedIdentityContextAsync(CancellationToken.None);
if (context is null)
{
return "Speaker identity database is not configured.";
}
await using (context)
{
var now = DateTimeOffset.UtcNow;
var identity = new SpeakerIdentity
{
CanonicalName = NormalizeNullable(canonicalName),
CreatedAt = now,
UpdatedAt = now,
Aliases = NormalizeNames(aliases)
.Select(name => new SpeakerAlias { Name = name })
.ToList(),
CandidateNames = NormalizeNames(candidateNames)
.Select(name => new SpeakerCandidateName { Name = name })
.ToList()
};
context.SpeakerIdentities.Add(identity);
await context.SaveChangesAsync();
return ToJson(ToIdentityDetail(identity));
}
return Task.FromResult(
"Refused: speaker identities require at least one audio sample. " +
"Use a speaker override from an existing transcript speaker sample, or update/merge an existing sampled identity.");
}
public async Task<string> UpdateIdentity(
@@ -902,6 +880,13 @@ public sealed class WorkflowRulesEditorTools
return $"Sample {sampleId} was not found.";
}
var sampleCount = await context.SpeakerSnippets.CountAsync(candidate =>
candidate.SpeakerIdentityId == sample.SpeakerIdentityId);
if (sampleCount <= 1)
{
return $"Refused: sample {sampleId} is the last sample for identity {sample.SpeakerIdentityId}. Delete the identity instead.";
}
context.SpeakerSnippets.Remove(sample);
await context.SaveChangesAsync();
return $"Deleted sample {sampleId}.";