using System; using System.IO; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using Newtonsoft.Json.Linq; using SwarmUI.Accounts; using SwarmUI.Core; using SwarmUI.Utils; using SwarmUI.WebAPI; using System.Net.Http; namespace Mrleo1nid.SwarmAssistent; /// Krea 2 collaborative assistant: Ollama chat + vision + prompt/LoRA/params patches + Generate/Civitai. public partial class SwarmAssistentExtension : Extension { public static PermInfo PermUse = Permissions.Register(new( "swarm_assistent_use", "[Swarm Assistent] Use", "Allows using the Swarm Assistent chat (Ollama proxy).", PermissionDefault.USER, Permissions.GroupUser)); public static HttpClient HttpClient; public AssistentConfig Config; public AssistentMemory Memory; public override void OnPreInit() { ScriptFiles.Add("Assets/assistent.api.js"); ScriptFiles.Add("Assets/assistent.patch.js"); ScriptFiles.Add("Assets/assistent.persist.js"); ScriptFiles.Add("Assets/assistent.js"); StyleSheetFiles.Add("Assets/assistent.css"); ExtensionAuthor = "mrleo1nid"; Description = "Collaborative Krea 2 assistant: Ollama chat, persona presets, vector memory, model cards, Generate loop."; License = "MIT"; Version = "0.8.1"; Tags = ["tabs", "ui", "llm", "ollama", "krea", "inpaint", "memory"]; } public override void OnInit() { HttpClient ??= new HttpClient { Timeout = TimeSpan.FromMinutes(10) }; Config = new AssistentConfig(FilePath, DataRoot()); Memory = new AssistentMemory(DataRoot(), HttpClient, Config.LoadAssistant(Config.DefaultPersonaId())["embed_model"]?.ToString() ?? "nomic-embed-text"); API.RegisterAPICall(AssistentListModels, false, PermUse); API.RegisterAPICall(AssistentGetPacks, false, PermUse); API.RegisterAPICall(AssistentListPersonas, false, PermUse); API.RegisterAPICall(AssistentGetConfig, false, PermUse); API.RegisterAPICall(AssistentGetSettings, false, PermUse); API.RegisterAPICall(AssistentSaveSettings, true, PermUse); API.RegisterAPICall(AssistentListInventory, false, PermUse); API.RegisterAPICall(AssistentGetCard, false, PermUse); API.RegisterAPICall(AssistentSaveCard, true, PermUse); API.RegisterAPICall(AssistentEnqueueWanted, true, PermUse); API.RegisterAPICall(AssistentGetCardMeta, false, PermUse); API.RegisterAPICall(AssistentSearchCivitai, false, PermUse); API.RegisterAPICall(AssistentGetTaste, false, PermUse); API.RegisterAPICall(AssistentSaveTaste, true, PermUse); API.RegisterAPICall(AssistentChat, true, PermUse); API.RegisterAPICall(AssistentChatWS, true, PermUse); API.RegisterAPICall(AssistentListChats, false, PermUse); API.RegisterAPICall(AssistentGetChat, false, PermUse); API.RegisterAPICall(AssistentSaveChat, true, PermUse); API.RegisterAPICall(AssistentDeleteChat, true, PermUse); API.RegisterAPICall(AssistentGetUiState, false, PermUse); API.RegisterAPICall(AssistentSaveUiState, true, PermUse); API.RegisterAPICall(AssistentParkLlm, true, PermUse); API.RegisterAPICall(AssistentWarmLlm, true, PermUse); API.RegisterAPICall(AssistentListMemory, false, PermUse); API.RegisterAPICall(AssistentUpsertMemory, true, PermUse); API.RegisterAPICall(AssistentForgetMemory, true, PermUse); API.RegisterAPICall(AssistentListWanted, false, PermUse); Logs.Init("Swarm Assistent extension loaded (disk chats + park LLM + memory UI)"); } int CfgInt(string key, int fallback) { try { return Config?.LoadAssistant(Config.DefaultPersonaId())[key]?.Value() ?? fallback; } catch { return fallback; } } static string Clip(string text, int max) { if (string.IsNullOrEmpty(text) || text.Length <= max) { return text ?? ""; } return text[..max] + "…"; } static string CollapseWs(string text) { if (string.IsNullOrWhiteSpace(text)) { return ""; } return Regex.Replace(text.Trim(), @"\s+", " "); } public static string NormalizeBaseUrl(string raw) { string url = (raw ?? "").Trim(); if (string.IsNullOrWhiteSpace(url)) { url = "http://127.0.0.1:11434"; } return url.TrimEnd('/'); } static string DataRoot() { if (Directory.Exists("/mnt/swarm_data")) { return "/mnt/swarm_data"; } try { string models = Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, "Models")); if (Directory.Exists(models)) { return Path.GetDirectoryName(models) ?? Environment.CurrentDirectory; } } catch { // ignore } return Environment.CurrentDirectory; } string PersonasOverlayJsonPath() => Path.Combine(DataRoot(), "Assistent", "personas.json"); string TasteJsonPath() => Path.Combine(DataRoot(), "Assistent", "taste.json"); public string ReadPackFile(string name) { return Config?.LoadPackPrompt(Config.DefaultPersonaId(), name); } public async Task AssistentGetPacks(Session session, string persona = null) { await Task.CompletedTask; string pid = AssistentConfig.SafeId(persona) ?? Config.DefaultPersonaId(); JObject packs = new(); JArray order = []; foreach (var p in Config.ListPacks(pid)) { string text = Config.LoadPackPrompt(pid, p.id); if (text is not null) { packs[p.id] = text; } order.Add(p.id); } return new JObject { ["success"] = true, ["packs"] = packs, ["order"] = order, ["persona"] = pid }; } public async Task AssistentGetConfig(Session session, string persona = null) { await Task.CompletedTask; string pid = AssistentConfig.SafeId(persona) ?? Config.DefaultPersonaId(); return Config.BuildMergedConfigPayload(pid); } public async Task AssistentGetSettings(Session session) { await Task.CompletedTask; return new JObject { ["success"] = true, ["settings"] = Config.LoadSettings() }; } public async Task AssistentSaveSettings(Session session, JObject settings) { await Task.CompletedTask; if (settings is null) { return new JObject { ["error"] = "settings required" }; } string prevEmbed = Config.LoadSettings()["embed_model"]?.ToString(); Config.SaveSettings(settings); string nextEmbed = settings["embed_model"]?.ToString(); if (!string.IsNullOrWhiteSpace(nextEmbed) && !string.Equals(prevEmbed, nextEmbed, StringComparison.OrdinalIgnoreCase)) { try { await Memory.ReembedAllAsync(NormalizeBaseUrl(settings["base_url"]?.ToString()), nextEmbed); } catch (Exception ex) { Logs.Debug($"AssistentSaveSettings reembed: {ex.Message}"); } } return new JObject { ["success"] = true, ["path"] = Path.Combine(Config.OverlayRoot, "settings.json") }; } public async Task AssistentListPersonas(Session session) { await Task.CompletedTask; var catalog = Config.ListPersonaCatalog(); JArray list = []; foreach (var p in catalog) { list.Add(new JObject { ["id"] = p.id, ["title"] = p.title, ["accent"] = p.accent, ["prompt"] = Config.RenderIdentityBlock(p.id), ["source"] = p.source, }); } return new JObject { ["success"] = true, ["default"] = Config.DefaultPersonaId(), ["personas"] = list, }; } public async Task AssistentGetTaste(Session session) { await Task.CompletedTask; string path = TasteJsonPath(); if (!File.Exists(path)) { return new JObject { ["success"] = true, ["taste"] = null }; } try { JObject taste = JObject.Parse(File.ReadAllText(path, Encoding.UTF8)); return new JObject { ["success"] = true, ["taste"] = taste }; } catch (Exception ex) { return new JObject { ["error"] = $"taste.json: {ex.Message}" }; } } public async Task AssistentSaveTaste(Session session, JObject taste) { await Task.CompletedTask; if (taste is null) { return new JObject { ["error"] = "taste required" }; } if (taste["updated"] == null) { taste["updated"] = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); } string dir = Path.Combine(DataRoot(), "Assistent"); Directory.CreateDirectory(dir); string path = TasteJsonPath(); File.WriteAllText(path, taste.ToString(Newtonsoft.Json.Formatting.Indented), Encoding.UTF8); return new JObject { ["success"] = true, ["path"] = path }; } }