Ship Assistent 0.12.1: training tab, dataset pipeline, and heard RAG.

Restructure UI with app-level tabs and chat history drawer; add dataset curation,
HF import, Modelfile/QLoRA hooks, and link approved samples to the agent immediately
via heard vector memory without waiting for fine-tuning.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Leonid Pershin
2026-08-22 14:27:59 +03:00
co-authored by Cursor
parent e8bb012885
commit 1a03c3178f
21 changed files with 4919 additions and 638 deletions
+174
View File
@@ -0,0 +1,174 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Newtonsoft.Json.Linq;
using SwarmUI.Accounts;
using SwarmUI.Utils;
namespace Mrleo1nid.SwarmAssistent;
/// <summary>Link training dataset samples to the live agent as retrievable &quot;heard&quot; examples.</summary>
public partial class SwarmAssistentExtension
{
static string MemoryEmbedForTraining(string personaId = null)
{
string pid = AssistentConfig.SafeId(personaId) ?? Config.DefaultPersonaId();
return Config.LoadSettings()["embed_model"]?.ToString()
?? Config.LoadAssistant(pid)["embed_model"]?.ToString()
?? "nomic-embed-text";
}
static string MemoryBaseForTraining(JObject raw = null)
=> MemoryBaseUrl(raw?["base_url"]?.ToString());
public async Task<JObject> AssistentGetDatasetAgentSettings(Session session)
{
await Task.CompletedTask;
JObject settings = Config.LoadTrainingAgent();
return new JObject
{
["success"] = true,
["settings"] = settings,
["linked"] = Memory.CountAgentLinkedTrainSamples(),
["approved"] = Memory.CountTrainSamples("approved"),
};
}
public async Task<JObject> AssistentSaveDatasetAgentSettings(Session session, JObject settings)
{
await Task.CompletedTask;
if (settings is null)
{
return new JObject { ["error"] = "settings required" };
}
Config.SaveTrainingAgent(settings);
return new JObject { ["success"] = true, ["settings"] = Config.LoadTrainingAgent() };
}
public async Task<JObject> AssistentLinkTrainSampleToAgent(Session session, string id, JObject raw = null)
{
if (string.IsNullOrWhiteSpace(id))
{
return new JObject { ["error"] = "id required" };
}
JObject sample = Memory.GetTrainSample(id.Trim());
if (sample is null)
{
return new JObject { ["error"] = "sample not found" };
}
if (!string.Equals(sample["status"]?.ToString(), "approved", StringComparison.OrdinalIgnoreCase))
{
return new JObject { ["error"] = "only approved samples can be linked to the agent" };
}
try
{
string embed = MemoryEmbedForTraining(sample["persona"]?.ToString());
bool ok = await Memory.LinkTrainSampleToAgentAsync(MemoryBaseForTraining(raw), sample, embed);
return new JObject { ["success"] = ok, ["linked"] = Memory.CountAgentLinkedTrainSamples() };
}
catch (Exception ex)
{
return new JObject { ["error"] = ex.Message };
}
}
public async Task<JObject> AssistentUnlinkTrainSampleFromAgent(Session session, string id)
{
await Task.CompletedTask;
if (string.IsNullOrWhiteSpace(id))
{
return new JObject { ["error"] = "id required" };
}
JObject sample = Memory.GetTrainSample(id.Trim());
if (sample is null)
{
return new JObject { ["error"] = "sample not found" };
}
Memory.UnlinkTrainSampleFromAgent(sample);
return new JObject { ["success"] = true, ["linked"] = Memory.CountAgentLinkedTrainSamples() };
}
public async Task<JObject> AssistentSyncDatasetToAgent(Session session, JObject raw = null)
{
bool approvedOnly = raw?["approved_only"]?.Value<bool?>() ?? true;
bool relink = raw?["relink"]?.Value<bool?>() ?? false;
string persona = raw?["persona"]?.ToString();
string status = approvedOnly ? "approved" : "all";
List<JObject> samples = Memory.ListTrainSamples(status, persona, null, 2000);
string baseUrl = MemoryBaseForTraining(raw);
int linked = 0;
int skipped = 0;
List<string> errors = [];
foreach (JObject sample in samples)
{
bool already = sample["agent_linked"]?.Value<bool?>() ?? false;
if (already && !relink)
{
skipped++;
continue;
}
if (!string.Equals(sample["status"]?.ToString(), "approved", StringComparison.OrdinalIgnoreCase))
{
skipped++;
continue;
}
try
{
string embed = MemoryEmbedForTraining(sample["persona"]?.ToString());
if (await Memory.LinkTrainSampleToAgentAsync(baseUrl, sample, embed))
{
linked++;
}
else
{
skipped++;
}
}
catch (Exception ex)
{
errors.Add($"{sample["id"]}: {ex.Message}");
}
}
return new JObject
{
["success"] = true,
["linked_now"] = linked,
["skipped"] = skipped,
["total_linked"] = Memory.CountAgentLinkedTrainSamples(),
["errors"] = new JArray(errors.Take(8)),
};
}
async Task TryAutoLinkTrainSample(Session session, JObject sample, JObject raw = null)
{
if (sample is null || Memory is null)
{
return;
}
JObject agent = Config.LoadTrainingAgent();
if (agent["enabled"]?.Value<bool?>() == false)
{
return;
}
if (agent["auto_link_on_approve"]?.Value<bool?>() == false)
{
return;
}
string status = sample["status"]?.ToString() ?? "";
if (!string.Equals(status, "approved", StringComparison.OrdinalIgnoreCase))
{
Memory.UnlinkTrainSampleFromAgent(sample);
return;
}
try
{
string embed = MemoryEmbedForTraining(sample["persona"]?.ToString());
await Memory.LinkTrainSampleToAgentAsync(MemoryBaseForTraining(raw), sample, embed);
}
catch (Exception ex)
{
Logs.Debug($"TryAutoLinkTrainSample: {ex.Message}");
}
}
}