Ship Assistent 0.11.3: variants grid, EN Krea prep, and stream fence fix.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -5,6 +5,7 @@
|
|||||||
min-height: 28rem;
|
min-height: 28rem;
|
||||||
padding: 0.5rem 0.65rem 0.65rem;
|
padding: 0.5rem 0.65rem 0.65rem;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sa-gate {
|
.sa-gate {
|
||||||
@@ -1847,6 +1848,137 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sa-board.sa-board-variants {
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
grid-auto-rows: minmax(7.5rem, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sa-slot.sa-slot-gen-result .sa-slot-open {
|
||||||
|
appearance: none;
|
||||||
|
border: 1px solid color-mix(in srgb, currentColor 28%, transparent);
|
||||||
|
background: color-mix(in srgb, currentColor 10%, transparent);
|
||||||
|
color: inherit;
|
||||||
|
border-radius: 999px;
|
||||||
|
padding: 0.05rem 0.45rem;
|
||||||
|
font-size: 0.68rem;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-left: auto;
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sa-slot.sa-slot-gen-result .sa-slot-open:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sa-lightbox {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 80;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sa-lightbox[hidden] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sa-lightbox-backdrop {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: color-mix(in srgb, #000 62%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sa-lightbox-panel {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.45rem;
|
||||||
|
width: min(96%, 52rem);
|
||||||
|
max-height: 94%;
|
||||||
|
padding: 0.55rem 0.65rem 0.65rem;
|
||||||
|
border-radius: 0.65rem;
|
||||||
|
border: 1px solid color-mix(in srgb, currentColor 28%, transparent);
|
||||||
|
background: color-mix(in srgb, Canvas 92%, transparent);
|
||||||
|
box-shadow: 0 12px 40px color-mix(in srgb, #000 35%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sa-lightbox-head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.45rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sa-lightbox-title {
|
||||||
|
font-weight: 650;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sa-lightbox-idx {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sa-lightbox-body {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 12rem;
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sa-lightbox-body img {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: min(70vh, 36rem);
|
||||||
|
object-fit: contain;
|
||||||
|
border-radius: 0.35rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sa-lightbox-nav {
|
||||||
|
appearance: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 2rem;
|
||||||
|
height: 2.4rem;
|
||||||
|
border: 1px solid color-mix(in srgb, currentColor 30%, transparent);
|
||||||
|
border-radius: 0.4rem;
|
||||||
|
background: color-mix(in srgb, Canvas 80%, transparent);
|
||||||
|
color: inherit;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
line-height: 1;
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sa-lightbox-nav:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sa-lightbox-prev {
|
||||||
|
left: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sa-lightbox-next {
|
||||||
|
right: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sa-lightbox-actions {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.35rem;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 900px) {
|
@media (max-width: 900px) {
|
||||||
.sa-layout {
|
.sa-layout {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -1872,4 +2004,8 @@
|
|||||||
border-right: none;
|
border-right: none;
|
||||||
border-bottom: 1px solid color-mix(in srgb, currentColor 18%, transparent);
|
border-bottom: 1px solid color-mix(in srgb, currentColor 18%, transparent);
|
||||||
}
|
}
|
||||||
|
.sa-lightbox-panel {
|
||||||
|
width: 98%;
|
||||||
|
max-height: 96%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+9391
-8671
File diff suppressed because it is too large
Load Diff
@@ -16,6 +16,7 @@ window.SA = window.SA || {};
|
|||||||
'clear_prompt_images', 'slot_to_prompt_image', 'pack',
|
'clear_prompt_images', 'slot_to_prompt_image', 'pack',
|
||||||
'memories', 'memory_query', 'memory_kind', 'tag_query', 'user_prefs',
|
'memories', 'memory_query', 'memory_kind', 'tag_query', 'user_prefs',
|
||||||
'controls', 'persona_clone', 'persona_shelves', 'persona', 'notes',
|
'controls', 'persona_clone', 'persona_shelves', 'persona', 'notes',
|
||||||
|
'variants',
|
||||||
];
|
];
|
||||||
|
|
||||||
const FENCE_RE = /```(?:json)?\s*([\s\S]*?)```/gi;
|
const FENCE_RE = /```(?:json)?\s*([\s\S]*?)```/gi;
|
||||||
@@ -92,10 +93,53 @@ window.SA = window.SA || {};
|
|||||||
return { prose, patch: lastPatch };
|
return { prose, patch: lastPatch };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closed fence worth freezing the stream / stopping Ollama early.
|
||||||
|
* Weak fences (pack / creativity / empty) must NOT stop — model often continues with the real patch.
|
||||||
|
*/
|
||||||
|
function isTerminalStreamPatch(obj) {
|
||||||
|
if (!obj || typeof obj !== 'object') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (isCardObject(obj)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (Array.isArray(obj.variants) && obj.variants.length) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (obj.look_at != null || obj.vision_from != null || obj.vision_slots != null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (obj.search_query != null || obj.civitai_query != null
|
||||||
|
|| obj.memory_query != null || obj.tag_query != null || obj.inventory_query != null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const acts = Array.isArray(obj.actions) ? obj.actions.map(String) : [];
|
||||||
|
const hopOrGen = [
|
||||||
|
'skill_load', 'persona_read', 'memory_get', 'memory_search', 'lookup_tags',
|
||||||
|
'list_inventory', 'search_civitai', 'interrupt', 'generate',
|
||||||
|
'memory_upsert', 'user_pref_upsert',
|
||||||
|
];
|
||||||
|
if (acts.some((a) => hopOrGen.includes(a))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (String(obj.prompt || '').trim().length >= 48) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (obj.loras != null || obj.aspect != null || obj.steps != null
|
||||||
|
|| obj.width != null || obj.height != null || obj.cfg != null
|
||||||
|
|| obj.seed != null || obj.controls != null
|
||||||
|
|| obj.memories != null || obj.user_prefs != null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
SA.PATCH_KEYS = PATCH_KEYS;
|
SA.PATCH_KEYS = PATCH_KEYS;
|
||||||
SA.isCardObject = isCardObject;
|
SA.isCardObject = isCardObject;
|
||||||
SA.isPatchObject = isPatchObject;
|
SA.isPatchObject = isPatchObject;
|
||||||
|
SA.isTerminalStreamPatch = isTerminalStreamPatch;
|
||||||
SA.normalizePatch = normalizePatch;
|
SA.normalizePatch = normalizePatch;
|
||||||
SA.extractPatch = extractPatch;
|
SA.extractPatch = extractPatch;
|
||||||
})();
|
})();
|
||||||
␍
|
|
||||||
|
|||||||
+2
-1
@@ -530,7 +530,8 @@ public sealed class AssistentConfig
|
|||||||
}
|
}
|
||||||
if (patch["prompt"] != null || patch["loras"] != null || patch["aspect"] != null
|
if (patch["prompt"] != null || patch["loras"] != null || patch["aspect"] != null
|
||||||
|| patch["width"] != null || patch["height"] != null || patch["steps"] != null
|
|| patch["width"] != null || patch["height"] != null || patch["steps"] != null
|
||||||
|| patch["cfg"] != null || patch["seed"] != null)
|
|| patch["cfg"] != null || patch["seed"] != null
|
||||||
|
|| patch["variants"] != null)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -166,6 +166,12 @@ public partial class SwarmAssistentExtension
|
|||||||
{
|
{
|
||||||
int numCtx = Config.LoadAssistant(AssistentConfig.SafeId(personaId) ?? Config.DefaultPersonaId())["num_ctx"]?.Value<int?>()
|
int numCtx = Config.LoadAssistant(AssistentConfig.SafeId(personaId) ?? Config.DefaultPersonaId())["num_ctx"]?.Value<int?>()
|
||||||
?? DefaultNumCtxFallback;
|
?? DefaultNumCtxFallback;
|
||||||
|
int numPredict = Config.LoadAssistant(AssistentConfig.SafeId(personaId) ?? Config.DefaultPersonaId())["num_predict"]?.Value<int?>()
|
||||||
|
?? 3072;
|
||||||
|
if (numPredict < 512)
|
||||||
|
{
|
||||||
|
numPredict = 512;
|
||||||
|
}
|
||||||
JObject payload = new()
|
JObject payload = new()
|
||||||
{
|
{
|
||||||
["model"] = modelName,
|
["model"] = modelName,
|
||||||
@@ -174,6 +180,8 @@ public partial class SwarmAssistentExtension
|
|||||||
["options"] = new JObject
|
["options"] = new JObject
|
||||||
{
|
{
|
||||||
["num_ctx"] = numCtx,
|
["num_ctx"] = numCtx,
|
||||||
|
// Without this, Ollama defaults can cut mid-prompt / mid-skill_load fence.
|
||||||
|
["num_predict"] = numPredict,
|
||||||
},
|
},
|
||||||
["keep_alive"] = "15m",
|
["keep_alive"] = "15m",
|
||||||
};
|
};
|
||||||
|
|||||||
+77
-10
@@ -21,6 +21,7 @@ public partial class SwarmAssistentExtension
|
|||||||
"clear_prompt_images", "slot_to_prompt_image", "pack", "memories", "memory",
|
"clear_prompt_images", "slot_to_prompt_image", "pack", "memories", "memory",
|
||||||
"memory_query", "memory_kind", "tag_query", "user_prefs",
|
"memory_query", "memory_kind", "tag_query", "user_prefs",
|
||||||
"inventory_query", "skills", "persona_shelves", "controls",
|
"inventory_query", "skills", "persona_shelves", "controls",
|
||||||
|
"variants",
|
||||||
];
|
];
|
||||||
|
|
||||||
static bool HasValue(JObject obj, string key)
|
static bool HasValue(JObject obj, string key)
|
||||||
@@ -107,8 +108,10 @@ public partial class SwarmAssistentExtension
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// If the reply already contains a closed fenced patch/card, cut everything after it.
|
/// If the reply already contains a closed fenced patch/card that is "done enough" to act on,
|
||||||
/// Models often keep writing («Готово!», second aspect, …) and the stream never feels done.
|
/// cut everything after it. Do NOT stop on weak fences (pack/creativity/notes-only) — models
|
||||||
|
/// often emit a tiny JSON first then the real prompt fence; aborting early cuts the prompt
|
||||||
|
/// and blocks skill_load / generate.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
static bool TryTruncateAtCompleteFence(string reply, out string truncated)
|
static bool TryTruncateAtCompleteFence(string reply, out string truncated)
|
||||||
{
|
{
|
||||||
@@ -129,18 +132,11 @@ public partial class SwarmAssistentExtension
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
JObject obj = JObject.Parse(raw);
|
JObject obj = JObject.Parse(raw);
|
||||||
if (obj is null)
|
if (obj is null || !FenceIsTerminalPatch(obj))
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
bool usable = LooksLikeCardObject(obj)
|
|
||||||
|| Array.Exists(PatchKeys, k => obj[k] is not null);
|
|
||||||
if (!usable)
|
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
truncated = reply.Substring(0, match.Index + match.Length).TrimEnd();
|
truncated = reply.Substring(0, match.Index + match.Length).TrimEnd();
|
||||||
// Only treat as complete if the fence actually closed (regex requires ```).
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
@@ -151,6 +147,77 @@ public partial class SwarmAssistentExtension
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// True when a closed fence is worth aborting the Ollama stream (real deliverable or tool hop).
|
||||||
|
/// </summary>
|
||||||
|
static bool FenceIsTerminalPatch(JObject obj)
|
||||||
|
{
|
||||||
|
if (obj is null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (LooksLikeCardObject(obj))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (obj["variants"] is JArray variants && variants.Count > 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (HasValue(obj, "look_at") || HasValue(obj, "vision_from") || HasValue(obj, "vision_slots"))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (HasValue(obj, "search_query") || HasValue(obj, "civitai_query"))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (HasValue(obj, "memory_query") || HasValue(obj, "tag_query") || HasValue(obj, "inventory_query"))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (obj["actions"] is JArray acts)
|
||||||
|
{
|
||||||
|
foreach (JToken a in acts)
|
||||||
|
{
|
||||||
|
string s = a?.ToString() ?? "";
|
||||||
|
if (string.IsNullOrWhiteSpace(s))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (s.Equals("skill_load", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| s.Equals("persona_read", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| s.Equals("memory_get", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| s.Equals("memory_search", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| s.Equals("lookup_tags", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| s.Equals("list_inventory", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| s.Equals("search_civitai", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| s.Equals("interrupt", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| s.Equals("generate", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| s.Equals("memory_upsert", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| s.Equals("user_pref_upsert", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
string prompt = obj["prompt"]?.ToString()?.Trim() ?? "";
|
||||||
|
if (prompt.Length >= 48)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Real param change without prose notes
|
||||||
|
if (HasValue(obj, "loras") || HasValue(obj, "aspect") || HasValue(obj, "steps")
|
||||||
|
|| HasValue(obj, "width") || HasValue(obj, "height") || HasValue(obj, "cfg")
|
||||||
|
|| HasValue(obj, "seed") || HasValue(obj, "controls")
|
||||||
|
|| HasValue(obj, "memories") || HasValue(obj, "user_prefs"))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Weak: pack / creativity / intensity / empty actions / notes-only → keep streaming
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
static string ExtractSearchQuery(JObject patch)
|
static string ExtractSearchQuery(JObject patch)
|
||||||
{
|
{
|
||||||
if (patch is null)
|
if (patch is null)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"num_ctx": 16384,
|
"num_ctx": 16384,
|
||||||
|
"num_predict": 3072,
|
||||||
"max_civitai_hops": 2,
|
"max_civitai_hops": 2,
|
||||||
"max_loras_inventory": 150,
|
"max_loras_inventory": 150,
|
||||||
"max_checkpoints_inventory": 60,
|
"max_checkpoints_inventory": 60,
|
||||||
@@ -9,6 +10,7 @@
|
|||||||
"inventory_prompt_names": 24,
|
"inventory_prompt_names": 24,
|
||||||
"inventory_hop_limit": 20,
|
"inventory_hop_limit": 20,
|
||||||
"max_ref_slots": 4,
|
"max_ref_slots": 4,
|
||||||
|
"max_gen_variants": 4,
|
||||||
"default_pack": "ordinary",
|
"default_pack": "ordinary",
|
||||||
"default_persona": "neutral",
|
"default_persona": "neutral",
|
||||||
"embed_model": "nomic-embed-text",
|
"embed_model": "nomic-embed-text",
|
||||||
|
|||||||
+92
-77
@@ -1,77 +1,92 @@
|
|||||||
# Swarm Assistent — core contract
|
# Swarm Assistent — core contract
|
||||||
|
|
||||||
You are **Swarm Assistent**, a collaborative art director for image generation inside SwarmUI.
|
You are **Swarm Assistent**, a collaborative art director for image generation inside SwarmUI.
|
||||||
When a `## Persona` block is present, **you speak as that character** — their title/name is *your* name, not the user's. Never call the user by the persona title unless `## About the user` says that is their name.
|
When a `## Persona` block is present, **you speak as that character** — their title/name is *your* name, not the user's. Never call the user by the persona title unless `## About the user` says that is their name.
|
||||||
|
|
||||||
## Priority (mandatory)
|
## Priority (mandatory)
|
||||||
|
|
||||||
When instructions conflict, apply this order (highest wins):
|
When instructions conflict, apply this order (highest wins):
|
||||||
|
|
||||||
1. **This core contract** — output format, never invent LoRA/checkpoint names or triggers, never use CFG 0, never depict or request anyone 17 or under (adults only).
|
1. **This core contract** — output format, never invent LoRA/checkpoint names or triggers, never use CFG 0, never depict or request anyone 17 or under (adults only).
|
||||||
2. **Current user message** — explicit “use steps 20 / aspect 16:9 now” wins for that turn.
|
2. **Current user message** — explicit “use steps 20 / aspect 16:9 now” wins for that turn.
|
||||||
3. **About the user** (`## About the user`) — durable preferences (global + this persona). Respect unless this turn overrides.
|
3. **About the user** (`## About the user`) — durable preferences (global + this persona). Respect unless this turn overrides.
|
||||||
4. **Live `session_exact`** — prior user overrides this chat (until persona change / clear chat).
|
4. **Live `session_exact`** — prior user overrides this chat (until persona change / clear chat).
|
||||||
5. **Exact memory** (`## Exact memory` JSON) — canonical defaults (steps/CFG/aspect/facts). Persona overlays are already merged.
|
5. **Exact memory** (`## Exact memory` JSON) — canonical defaults (steps/CFG/aspect/facts). Persona overlays are already merged.
|
||||||
6. **Filled live SwarmUI fields** — respect what is already set unless the user or pack asks to change.
|
6. **Filled live SwarmUI fields** — respect what is already set unless the user or pack asks to change.
|
||||||
7. **`memory_hits` (hybrid FTS + vector RAG)** — craft notes / LoRA blurbs (often truncated). Prefer over guesses; never override Exact, About the user, or the user’s param request. Full row → `memory_get`; more search → `memory_search`; Danbooru spelling → `lookup_tags` (no tag soup in Krea prompts).
|
7. **`memory_hits` (hybrid FTS + vector RAG)** — craft notes / LoRA blurbs (often truncated). Prefer over guesses; never override Exact, About the user, or the user’s param request. Full row → `memory_get`; more search → `memory_search`; Danbooru spelling → `lookup_tags` (no tag soup in Krea prompts).
|
||||||
8. Guesses — last resort only.
|
8. Guesses — last resort only.
|
||||||
|
|
||||||
Exact = defaults encyclopedia. About the user = human taste. RAG = soft craft notes. Do **not** re-emit `steps` / `cfg` / `sigma_shift` / `aspect` when they already match Exact (or `session_exact`) and the user did not ask to change them.
|
Exact = defaults encyclopedia. About the user = human taste. RAG = soft craft notes. Do **not** re-emit `steps` / `cfg` / `sigma_shift` / `aspect` when they already match Exact (or `session_exact`) and the user did not ask to change them.
|
||||||
|
|
||||||
Never write a “JSON Patch” section in prose without an actual fenced ```json``` object. An empty `### JSON Patch` header is a failure — omit the section or emit a real fence. Keep prose short (a few lines). **After the closing ``` of the JSON fence, STOP** — no «Готово!», no second aspect, no “сейчас сгенерирую оба”. One turn = one patch (one aspect). Prompt prose structure lives in skill `prompting` — do not invent a second recipe here.
|
Never write a “JSON Patch” section in prose without an actual fenced ```json``` object. An empty `### JSON Patch` header is a failure — omit the section or emit a real fence. If you described the next frame / prompt in prose, the fence **must** include that `prompt` and usually `actions: ["generate"]` in the **same** turn — never stop after the header. **`prompt` must be English** (Krea 2 / Qwen3-VL) — translate + structure per skill `prompting`; chat prose may stay RU. Keep prose short (a few lines). **After the closing ``` of the JSON fence, STOP** — no «Готово!», no second fenced patch, no “сейчас сгенерирую оба” in prose. **One turn = one patch.** When the user asks for several options (разный свет / оба / варианты), put 2–4 items in **`variants`** (partial patches with optional `label`); the UI runs them sequentially and shows a grid. Do not emit two fences. Ordinary single-image requests stay one patch without `variants`. Prompt prose structure lives in skill `prompting` — do not invent a second recipe here.
|
||||||
|
|
||||||
## Live context
|
## Live context
|
||||||
|
|
||||||
"Live SwarmUI context" JSON is ground truth for this turn:
|
"Live SwarmUI context" JSON is ground truth for this turn:
|
||||||
|
|
||||||
- Use only LoRA/checkpoint **names** from `selected_loras` / `available_loras` (or Civitai hop results). `selected_loras` = currently enabled. Prefer listed `triggers` / `trigger_phrase` / `blurb` — **never invent**.
|
- Use only LoRA/checkpoint **names** from `selected_loras` / `available_loras` (or Civitai hop results). `selected_loras` = currently enabled. Prefer listed `triggers` / `trigger_phrase` / `blurb` — **never invent**.
|
||||||
- Rich entries (blurbs/triggers) are selected + top krea-likely. Name-only rows need `list_inventory` + `inventory_query` before you rely on them.
|
- Rich entries (blurbs/triggers) are selected + top krea-likely. Name-only rows need `list_inventory` + `inventory_query` before you rely on them.
|
||||||
- `memory_hits` may be truncated (`truncated: true`) — use `memory_get` for the full text.
|
- `memory_hits` may be truncated (`truncated: true`) — use `memory_get` for the full text.
|
||||||
- `has_vision_image` true means a real board frame exists. `images_in_request` true means JPEG bytes are in **this** request. If you need to see a frame and `images_in_request` is false, emit `look_at` first — do not invent what the image looks like.
|
- `has_vision_image` true means a real board frame exists. `images_in_request` true means JPEG bytes are in **this** request. If you need to see a frame and `images_in_request` is false, emit `look_at` first — do not invent what the image looks like.
|
||||||
- Prefer `krea_likely` / Krea architecture; ignore FLUX/SDXL. Respect current params unless asked or pack is `form_params`.
|
- Prefer `krea_likely` / Krea architecture; ignore FLUX/SDXL. Respect current params unless asked or pack is `form_params`.
|
||||||
- Init/inpaint flags and `image_slots` are in the JSON. Extra pack fields are documented in the active pack.
|
- Init/inpaint flags and `image_slots` are in the JSON. Extra pack fields are documented in the active pack.
|
||||||
|
|
||||||
## Memory (short)
|
## Memory (short)
|
||||||
|
|
||||||
- Craft RAG write: `memory_upsert` / `memory_forget` + `memories: [{kind,key,text,scope}]` (default personal).
|
- Craft RAG write: `memory_upsert` / `memory_forget` + `memories: [{kind,key,text,scope}]` (default personal).
|
||||||
- About the user: `user_pref_upsert` / `user_pref_forget` + `user_prefs: [{key,text,scope}]`. Do **not** put human taste into craft `memories`.
|
- About the user: `user_pref_upsert` / `user_pref_forget` + `user_prefs: [{key,text,scope}]`. Do **not** put human taste into craft `memories`.
|
||||||
- Fat memory skill text: `skill_load` + `skills: ["memory"]` when you need the full write/read playbook.
|
- Fat memory skill text: `skill_load` + `skills: ["memory"]` when you need the full write/read playbook.
|
||||||
|
|
||||||
## Output contract (mandatory)
|
## Output contract (mandatory)
|
||||||
|
|
||||||
1. Short helpful reply in the user's language (RU or EN).
|
1. Short helpful reply in the user's language (RU or EN).
|
||||||
2. One fenced JSON patch with **only fields you want to change**:
|
2. One fenced JSON patch with **only fields you want to change**:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"prompt": "A fluffy red fox in fresh powder snow, soft morning light, 85mm f/2.8…",
|
"prompt": "A fluffy red fox in fresh powder snow, soft morning light, 85mm f/2.8…",
|
||||||
"loras": [{"name": "exact_name_from_list", "weight": 0.8, "triggers": ["listed_trigger"]}],
|
"loras": [{"name": "exact_name_from_list", "weight": 0.8, "triggers": ["listed_trigger"]}],
|
||||||
"aspect": "16:9",
|
"aspect": "16:9",
|
||||||
"actions": ["generate"],
|
"actions": ["generate"],
|
||||||
"notes": "one-line why"
|
"notes": "one-line why"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Patch rules
|
Several options in one ask (still one fence):
|
||||||
|
|
||||||
- Omit unchanged keys. Prefer omitting Exact-matching `steps`/`cfg`/`sigma_shift`/`aspect`.
|
```json
|
||||||
- Prefer omitting Exact-matching **`controls`** (e.g. do not re-emit `"horny": 35` / `"preference_bias": 0.35` when unchanged) — echoing defaults in a Generate patch resets the UI sliders.
|
{
|
||||||
- `loras` replaces the full intended set for Apply. Prefer `aspect` over raw width/height.
|
"prompt": "same subject base…",
|
||||||
- Optional keys (seed, vary, init/mask, creativity/sliders, pack, **controls**, persona authoring, search/memory queries) — use when needed; packs list the ones for that mode.
|
"aspect": "16:9",
|
||||||
- **`controls`** — only keys declared in this persona's `controls.json` (Exact). Clamp to min/max. Example: `"controls": { "horny": 55 }`. Do not invent control ids.
|
"actions": ["generate"],
|
||||||
- Do not invent model or LoRA filenames.
|
"variants": [
|
||||||
|
{ "label": "warm light", "prompt": "… warm window light …" },
|
||||||
### Actions / hops
|
{ "label": "cool light", "prompt": "… cool moonlight …" },
|
||||||
|
{ "label": "portrait 9:16", "aspect": "9:16" }
|
||||||
- `"generate"` — Apply + start generation when the user wants a new image.
|
]
|
||||||
- If the user only asks to **remember / save** a prompt as base/template («запомни», «как базовый промпт», «шаблон») and did **not** ask for a new image: **omit** `actions: ["generate"]`, do **not** `look_at`. Prefer `memory_upsert` (kind `note`, key like `base_prompt`) or a short ack; you may echo `prompt` in the patch only to sync the SwarmUI box — the UI will not Generate on remember turns.
|
}
|
||||||
- `"search_civitai"` + **required** short `search_query` — Civitai hop (user Confirms downloads). Without `search_query` the hop is skipped (never search the whole user message).
|
```
|
||||||
- `"interrupt"` — stop generation.
|
|
||||||
- `"memory_get"` / `"memory_search"` / `"lookup_tags"` — read hops.
|
### Patch rules
|
||||||
- `"list_inventory"` + `inventory_query` — rich LoRA/checkpoint details beyond the slim list.
|
|
||||||
- `"skill_load"` + `skills: ["memory"]` — load fat skill text.
|
- Omit unchanged keys. Prefer omitting Exact-matching `steps`/`cfg`/`sigma_shift`/`aspect`.
|
||||||
- `"persona_read"` — load lore shelves not in always-on identity (e.g. `roleplay` / `craft` / `humor` when NSFW tone or craft detail needs them).
|
- Prefer omitting Exact-matching **`controls`** (e.g. do not re-emit `"horny": 35` / `"preference_bias": 0.35` when unchanged) — echoing defaults in a Generate patch resets the UI sliders.
|
||||||
- `"memory_upsert"` / `"memory_forget"` / `"user_pref_upsert"` / `"user_pref_forget"` — writes.
|
- `loras` replaces the full intended set for Apply. Prefer `aspect` over raw width/height.
|
||||||
- `"persona_clone"` / `"persona_write"` / `"persona_switch"` — `author_persona` only. Never `"persona_delete"`.
|
- Optional keys (seed, vary, init/mask, creativity/sliders, pack, **controls**, persona authoring, search/memory queries, **`variants`**) — use when needed; packs list the ones for that mode.
|
||||||
- `look_at: ["generate"|"ref1"|…]` — vision hop (JPEG arrives on the follow-up).
|
- **`controls`** — only keys declared in this persona's `controls.json` (Exact). Clamp to min/max. Example: `"controls": { "horny": 55 }`. Do not invent control ids.
|
||||||
- Pure Q&A: omit the JSON patch.
|
- Do not invent model or LoRA filenames.
|
||||||
|
|
||||||
|
### Actions / hops
|
||||||
|
|
||||||
|
- `"generate"` — Apply + start generation when the user wants a new image. Auto-Generate is a **UI checkbox** (Settings → Поведение); the model cannot toggle it — emit `actions:["generate"]` instead.
|
||||||
|
- If the user only asks to **remember / save** a prompt as base/template («запомни», «как базовый промпт», «шаблон») and did **not** ask for a new image: **omit** `actions: ["generate"]`, do **not** `look_at`. Prefer `memory_upsert` (kind `note`, key like `base_prompt`) or a short ack; you may echo `prompt` in the patch only to sync the SwarmUI box — the UI will not Generate on remember turns.
|
||||||
|
- `"search_civitai"` + **required** short `search_query` — Civitai hop (user Confirms downloads). Without `search_query` the hop is skipped (never search the whole user message).
|
||||||
|
- `"interrupt"` — stop generation.
|
||||||
|
- `"memory_get"` / `"memory_search"` / `"lookup_tags"` — read hops.
|
||||||
|
- `"list_inventory"` + `inventory_query` — rich LoRA/checkpoint details beyond the slim list.
|
||||||
|
- `"skill_load"` + `skills: ["memory"]` — load fat skill text.
|
||||||
|
- `"persona_read"` — load lore shelves not in always-on identity (e.g. `roleplay` / `craft` / `humor` when NSFW tone or craft detail needs them).
|
||||||
|
- `"memory_upsert"` / `"memory_forget"` / `"user_pref_upsert"` / `"user_pref_forget"` — writes.
|
||||||
|
- `"persona_clone"` / `"persona_write"` / `"persona_switch"` — `author_persona` only. Never `"persona_delete"`.
|
||||||
|
- `look_at: ["generate"|"ref1"|…]` — vision hop (JPEG arrives on the follow-up).
|
||||||
|
- Pure Q&A: omit the JSON patch.
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
},
|
},
|
||||||
"facts": {
|
"facts": {
|
||||||
"architecture": "Krea 2 is a 12B DiT architecture. Not FLUX, not SDXL, not FLUX.1-Krea. Text encoder: Qwen3-VL 4B. VAE: Qwen Image VAE. Use only Krea2-trained LoRAs — never suggest FLUX/SDXL LoRAs.",
|
"architecture": "Krea 2 is a 12B DiT architecture. Not FLUX, not SDXL, not FLUX.1-Krea. Text encoder: Qwen3-VL 4B. VAE: Qwen Image VAE. Use only Krea2-trained LoRAs — never suggest FLUX/SDXL LoRAs.",
|
||||||
|
"prompt_language": "Chat model always prep's Generate prompt for Krea: English natural prose for Qwen3-VL, structured (subject→pose→setting→camera→light). Chat may be RU; never leave Russian or thin drafts in patch.prompt.",
|
||||||
"negatives": "Negative prompts are nearly useless with Qwen3-VL. Prefer positives (sharp focus, empty street) over no blur / no people. Built-in NSFW text-refiner may strip risque words; LoRAs/finetunes may restore — stay practical.",
|
"negatives": "Negative prompts are nearly useless with Qwen3-VL. Prefer positives (sharp focus, empty street) over no blur / no people. Built-in NSFW text-refiner may strip risque words; LoRAs/finetunes may restore — stay practical.",
|
||||||
"prompt_images": "Prompt Images (refs in the prompt box) often overpower text — use sparingly and warn. Init Image = structure (img2img). Mask = local fix. They are not interchangeable. Cloud-only features (moodboards, Generative Sliders) are not in Swarm — emulate with prompt language + board refs.",
|
"prompt_images": "Prompt Images (refs in the prompt box) often overpower text — use sparingly and warn. Init Image = structure (img2img). Mask = local fix. They are not interchangeable. Cloud-only features (moodboards, Generative Sliders) are not in Swarm — emulate with prompt language + board refs.",
|
||||||
"turbo": "Krea 2 Turbo defaults live in exact.generation / exact.profiles.turbo. Never use CFG 0 — broken output. Side ~1024 (128–4096 OK).",
|
"turbo": "Krea 2 Turbo defaults live in exact.generation / exact.profiles.turbo. Never use CFG 0 — broken output. Side ~1024 (128–4096 OK).",
|
||||||
|
|||||||
@@ -27,3 +27,5 @@ Otherwise **stay in ordinary** and just do the work.
|
|||||||
## Deliverable
|
## Deliverable
|
||||||
|
|
||||||
Same as write_prompt: short reply + one fenced JSON patch; `actions: ["generate"]` when they want an image.
|
Same as write_prompt: short reply + one fenced JSON patch; `actions: ["generate"]` when they want an image.
|
||||||
|
«давай дальше» / next frame = new English `prompt` + `actions:["generate"]` in the **same** turn — never leave an empty `### JSON Patch` header. Chat may be RU; **Generate `prompt` is always EN** (skill `prompting`).
|
||||||
|
Several options in one ask → `variants` (2–4 partial patches with `label`); still one fence, still STOP after it.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Mode: write_prompt
|
# Mode: write_prompt
|
||||||
|
|
||||||
Goal: craft or improve a **Krea 2** prompt that will generate well on Turbo (local Swarm). Prompt prose recipe is in skill `prompting` — follow it; do not invent a second structure.
|
Goal: craft or improve a **Krea 2** prompt that will generate well on Turbo (local Swarm). Prompt prose recipe is in skill `prompting` — follow it; do not invent a second structure. The JSON **`prompt` field is always English** (translate + structure); user-facing notes may stay in the user’s language.
|
||||||
|
|
||||||
## Deliverable
|
## Deliverable
|
||||||
|
|
||||||
@@ -9,6 +9,7 @@ Goal: craft or improve a **Krea 2** prompt that will generate well on Turbo (loc
|
|||||||
- `actions: ["generate"]` when the user wants a new image — UI applies + Generate without Apply buttons.
|
- `actions: ["generate"]` when the user wants a new image — UI applies + Generate without Apply buttons.
|
||||||
- Prefer Exact Turbo defaults / `recommended_params`. Prefer `aspect` for framing; omit steps/cfg/sigma/aspect when they already match Exact and the user did not ask to change them.
|
- Prefer Exact Turbo defaults / `recommended_params`. Prefer `aspect` for framing; omit steps/cfg/sigma/aspect when they already match Exact and the user did not ask to change them.
|
||||||
- Missing style LoRA → `actions: ["search_civitai"]` + short `search_query` (Krea-compatible).
|
- Missing style LoRA → `actions: ["search_civitai"]` + short `search_query` (Krea-compatible).
|
||||||
|
- User wants several options (оба / варианты / разный свет) → `variants: [{label, prompt|aspect|…}, …]` (2–4). Base keys inherit; each item overrides only its diffs. Still one fence.
|
||||||
|
|
||||||
### Bad → good
|
### Bad → good
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"always": [
|
"always": [
|
||||||
"Match the user's language (RU or EN)",
|
"Match the user's language (RU or EN) in chat — Generate prompt stays English",
|
||||||
"Use only inventory / memory_hits / cards for LoRA names and triggers",
|
"Use only inventory / memory_hits / cards for LoRA names and triggers",
|
||||||
"Emit valid JSON patches when changing generation state"
|
"Emit valid JSON patches when changing generation state"
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,9 +1,27 @@
|
|||||||
# Skill: prompting
|
# Skill: prompting
|
||||||
|
|
||||||
Write **natural prose** for a photographer/director — not Danbooru tags, not `(word:1.5)`, not `masterpiece / best quality / 8k`.
|
The **chat model** prepares the Generate-box text for **Krea 2** (Qwen3-VL). Do this prep as fully as you can in the same turn (or the dedicated prep hop). Chat with the user may be RU/EN; the JSON **`prompt` is always English**.
|
||||||
|
|
||||||
Order (front-load importance): **subject → pose/action → setting → materials → camera/framing → lighting → medium/mood**.
|
## Hard rules (Krea / Qwen3-VL)
|
||||||
|
|
||||||
- Short user ideas: expand. Finished Krea-style paragraphs: keep wording; only fix anti-patterns.
|
1. **`prompt` language = English only** — never put Russian into the JSON `prompt`. Translate the idea, then structure it.
|
||||||
- Put LoRA trigger phrases near the subject they affect.
|
2. **Natural photographer/director prose** — not Danbooru tag soup, not `(word:1.5)`, not `masterpiece / best quality / 8k`.
|
||||||
- Prefer positives over negatives.
|
3. **Order (front-load):**
|
||||||
|
**subject → pose/action → body/wardrobe → setting → materials/textures → camera/framing → lighting → medium/mood**.
|
||||||
|
4. Put **LoRA trigger phrases** (exact English spelling) near the subject they affect.
|
||||||
|
5. Prefer **positives** over negatives (Qwen negatives are weak).
|
||||||
|
6. Short ideas: expand into a full Krea paragraph. Thin or RU drafts: rewrite before Generate — do not hand Krea a half-ready line.
|
||||||
|
|
||||||
|
## Prep checklist (before `actions:["generate"]`)
|
||||||
|
|
||||||
|
- English only in `prompt`
|
||||||
|
- Subject and action clear in the first sentence
|
||||||
|
- Wardrobe / body / setting concrete
|
||||||
|
- Camera + lighting present
|
||||||
|
- One coherent scene; NSFW stated in plain English if needed
|
||||||
|
- Triggers placed next to what they modify
|
||||||
|
|
||||||
|
## Deliverable
|
||||||
|
|
||||||
|
User-facing prose: short, in the user’s language.
|
||||||
|
JSON `prompt`: English, structured as above — ready for Swarm Generate / Krea 2.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"welcome_html": "<div class=\"sa-welcome-title\">Assistent · Krea 2</div><ul><li><strong>Generate</strong> слева — живой просмотр. В чат сам не уходит.</li><li><strong>Refs</strong> — референсы на отдельной вкладке: drop / paste / Снимок gen.</li><li>Галочка vision на окне — отправить кадр модели.</li><li>Чипсы aspect / seed / Vary / Turbo·RAW. В чате: <code>/help</code>.</li><li>Кнопки патча только у последнего предложения.</li></ul>Напиши, что сгенерировать — или кинь референс и попроси правку.",
|
"welcome_html": "<div class=\"sa-welcome-title\">Assistent · Krea 2</div><ul><li><strong>Generate</strong> слева — живой просмотр. Несколько вариантов → сетка + клик для просмотра.</li><li><strong>Refs</strong> — референсы на отдельной вкладке: drop / paste / Снимок gen.</li><li>Галочка vision на окне — отправить кадр модели.</li><li>Чипсы aspect / seed / Vary / Turbo·RAW. В чате: <code>/help</code>.</li><li>Кнопки патча только у последнего предложения.</li></ul>Напиши, что сгенерировать — или кинь референс и попроси правку.",
|
||||||
"help_text": "Slash-команды (без LLM):\n/help — этот список\n/new — новый чат\n/history — список чатов\n/debug — сводка UI/Exact\n/debug ask · /why — сводка + короткий ответ модели\n/gen — Generate сейчас\n/look generate|refN — прикрепить окно к vision\n/init /mask /clear — Init / Mask / Clear Init\n/interrupt — остановить генерацию\n/aspect 16:9 — размер из таблицы 1K\n/seed lock|random — зафиксировать или рандомизировать seed\n/vary — новый seed, тот же промпт\n/pack write|ordinary|critique|compose|params|inpaint|describe|card|persona\n/persona new — интервью: клон текущей личности (overlay)\n/persona clone <id> — клон с указанной\n/persona save — записать согласованный черновик\n/civitai <query> — поиск LoRA (Confirm в чате)\n/inventory — rescan моделей + обновить список LoRA\n\nЧипсы над полем ввода делают то же для aspect / seed / vary / Turbo·RAW.\nПри старте всегда новый чат; смена чата восстанавливает параметры.\nOverlay-личности удаляет только кнопка ✕ рядом с селектом (не модель).",
|
"help_text": "Slash-команды (без LLM):\n/help — этот список\n/new — новый чат\n/history — список чатов\n/debug — сводка UI/Exact\n/debug ask · /why — сводка + короткий ответ модели\n/gen — Generate сейчас\n/look generate|refN — прикрепить окно к vision\n/init /mask /clear — Init / Mask / Clear Init\n/interrupt — остановить генерацию\n/aspect 16:9 — размер из таблицы 1K\n/seed lock|random — зафиксировать или рандомизировать seed\n/vary — новый seed, тот же промпт\n/pack write|ordinary|critique|compose|params|inpaint|describe|card|persona\n/persona new — интервью: клон текущей личности (overlay)\n/persona clone <id> — клон с указанной\n/persona save — записать согласованный черновик\n/civitai <query> — поиск LoRA (Confirm в чате)\n/inventory — rescan моделей + обновить список LoRA\n\nНесколько вариантов в одном запросе («оба», разный свет) → патч с variants[] → сетка на Generate, клик / Открыть / Enter — просмотр.\nЧипсы над полем ввода делают то же для aspect / seed / vary / Turbo·RAW.\nПри старте всегда новый чат; смена чата восстанавливает параметры.\nOverlay-личности удаляет только кнопка ✕ рядом с селектом (не модель).",
|
||||||
"chips": [
|
"chips": [
|
||||||
{ "label": "1:1", "action": "aspect", "value": "1:1", "title": "1024×1024" },
|
{ "label": "1:1", "action": "aspect", "value": "1:1", "title": "1024×1024" },
|
||||||
{ "label": "4:5", "action": "aspect", "value": "4:5", "title": "928×1152" },
|
{ "label": "4:5", "action": "aspect", "value": "4:5", "title": "928×1152" },
|
||||||
|
|||||||
@@ -6,9 +6,10 @@
|
|||||||
"detailed Krea prose"
|
"detailed Krea prose"
|
||||||
],
|
],
|
||||||
"process": [
|
"process": [
|
||||||
|
"translate idea → English Krea prose before Generate",
|
||||||
"subject → pose → clothes/hair → setting → camera → light → mood",
|
"subject → pose → clothes/hair → setting → camera → light → mood",
|
||||||
"front-load what matters",
|
"front-load what matters",
|
||||||
"natural prose, not tag soup"
|
"natural EN prose, not tag soup"
|
||||||
],
|
],
|
||||||
"defaults": [
|
"defaults": [
|
||||||
"photoreal / film unless user asks anime/hentai",
|
"photoreal / film unless user asks anime/hentai",
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"always": [
|
"always": [
|
||||||
"You are Leonid; the user is not Leonid — never greet/address them as Леонид/Leonid unless About the user says their name is that",
|
"You are Leonid; the user is not Leonid — never greet/address them as Леонид/Leonid unless About the user says their name is that",
|
||||||
"If asked your name, say you are Leonid (assistant)",
|
"If asked your name, say you are Leonid (assistant)",
|
||||||
"Match user language (RU/EN)",
|
"Match user language (RU/EN) in chat; Generate patch prompt is always English for Krea",
|
||||||
"Craft first: triggers, aspect, Turbo — horny never replaces technique",
|
"Craft first: triggers, aspect, Turbo — horny never replaces technique",
|
||||||
"Scale appearance/outfits by controls.preference_bias (−1…1)",
|
"Scale appearance/outfits by controls.preference_bias (−1…1)",
|
||||||
"Scale sexual tone + roleplay fetishes by controls.horny (0…100)",
|
"Scale sexual tone + roleplay fetishes by controls.horny (0…100)",
|
||||||
|
|||||||
@@ -1,173 +1,183 @@
|
|||||||
# Swarm Assistent
|
# Swarm Assistent
|
||||||
|
|
||||||
SwarmUI extension for **collaborative Krea 2** prompting via **Ollama**: chat + board (Generate | Refs tabs), LoRA chips, **persona presets** (`Config/personas/`), **About the user** prefs + craft vector memory, model cards with Civitai fetch, img2img/inpaint, slash commands, auto Generate.
|
SwarmUI extension for **collaborative Krea 2** prompting via **Ollama**: chat + board (Generate | Refs tabs), LoRA chips, **persona presets** (`Config/personas/`), **About the user** prefs + craft vector memory, model cards with Civitai fetch, img2img/inpaint, slash commands, auto Generate.
|
||||||
|
|
||||||
**Version 0.10.21** — Board Generate spinner clears when Swarm finishes (`num_live_gens`), not only when the image URL string changes. Builds on 0.10.20 remember-without-gen.
|
**Version 0.11.3** — Stream no longer aborts on weak JSON fences (so prompts/skill_load can finish); `num_predict` 3072. Builds on 0.11.2 Krea prep.
|
||||||
|
|
||||||
**Version 0.10.20** — «Запомни / базовый промпт» no longer triggers Auto-Generate or auto look_at (even if the model sneaks `actions:["generate"]`). Builds on 0.10.19 slider echo fix.
|
**Version 0.11.2** — Before Krea Generate, chat model maximally preps the prompt (EN + structure); skip prep hop only if already Krea-ready English. Builds on 0.11.1.
|
||||||
|
|
||||||
**Version 0.10.19** — Вкус/Хорни no longer reset: Generate patches never apply `controls`; default-echo filtered always; bare «вкус» no longer disables the filter. Builds on 0.10.18 settings tab.
|
**Version 0.11.1** — Krea Generate `prompt` must be English (skill `prompting` + Exact + client rewrite hop if Cyrillic leaks). Builds on 0.11.0 variants.
|
||||||
|
|
||||||
**Version 0.10.18** — Settings is a full subtab (Чат | Карточки | Настройки), not a header overlay. Builds on 0.10.17 post-Generate warm.
|
**Version 0.11.0** — Пакет вариантов: JSON `variants[]` (2–4) → последовательные Generate → сетка на вкладке Generate + lightbox. Builds on 0.10.22 empty-patch fix.
|
||||||
|
|
||||||
## Layout
|
**Version 0.10.22** — Empty `### JSON Patch` no longer dead-ends: synthesize prompt from prose / retry; «давай дальше» counts as Generate. Builds on 0.10.21 spinner fix.
|
||||||
|
|
||||||
- **Left — Board tabs:** **Generate** (full-height live view) | **Refs** (reference grid + badge `N · vision M`); **Посмотри результат** attaches the finished frame and asks for a verdict
|
**Version 0.10.21** — Board Generate spinner clears when Swarm finishes (`num_live_gens`), not only when the image URL string changes. Builds on 0.10.20 remember-without-gen.
|
||||||
- **Splitter:** drag to resize panes
|
|
||||||
- **Right:** Chat | Cards | Settings; persona / pack / Ollama chat model; **Ollama health** badge
|
**Version 0.10.20** — «Запомни / базовый промпт» no longer triggers Auto-Generate or auto look_at (even if the model sneaks `actions:["generate"]`). Builds on 0.10.19 slider echo fix.
|
||||||
- **Chips / slash:** loaded from `Config/_base/ui.json` (persona can override)
|
|
||||||
|
**Version 0.10.19** — Вкус/Хорни no longer reset: Generate patches never apply `controls`; default-echo filtered always; bare «вкус» no longer disables the filter. Builds on 0.10.18 settings tab.
|
||||||
## Config (bundled + overlay)
|
|
||||||
|
**Version 0.10.18** — Settings is a full subtab (Чат | Карточки | Настройки), not a header overlay. Builds on 0.10.17 post-Generate warm.
|
||||||
```
|
|
||||||
Config/
|
## Layout
|
||||||
_base/ # defaults (assistant, ui, models/krea2, exact.json, core, packs, skills, memory-seed, identity)
|
|
||||||
personas/<id>/ # sparse shelves: persona/bio/voice/humor/… + optional controls.json / exact.json / memory-seed
|
- **Left — Board tabs:** **Generate** (live view, or a **variant grid** when the model emits `variants[]`) | **Refs** (reference grid + badge `N · vision M`); click a variant to select / open lightbox; **Посмотри результат** attaches the selected finished frame and asks for a verdict
|
||||||
```
|
- **Splitter:** drag to resize panes
|
||||||
|
- **Right:** Chat | Cards | Settings; persona / pack / Ollama chat model; **Ollama health** badge
|
||||||
Disk overlay (wins over bundled): `/mnt/swarm_data/Assistent/` — same folder layout as `Config/`. Drop `_base/…` and `personas/<id>/…` to override. Plus runtime state:
|
- **Chips / slash:** loaded from `Config/_base/ui.json` (persona can override)
|
||||||
|
|
||||||
```
|
## Config (bundled + overlay)
|
||||||
Assistent/
|
|
||||||
_base/ personas/<id>/ # overlay presets — same names as Config/, sparse
|
```
|
||||||
settings.json # embed_model, base_url, per-persona skills
|
Config/
|
||||||
ollama-roles.json # chat vs memory model tags (gpu-rent writes this)
|
_base/ # defaults (assistant, ui, models/krea2, exact.json, core, packs, skills, memory-seed, identity)
|
||||||
memory/assistent.sqlite # craft RAG + user_prefs + tags FTS + chats + ui_state + taste (legacy)
|
personas/<id>/ # sparse shelves: persona/bio/voice/humor/… + optional controls.json / exact.json / memory-seed
|
||||||
_migrated_json/ # one-shot archive of old chats/*.json, ui-state.json, taste.json
|
```
|
||||||
```
|
|
||||||
|
Disk overlay (wins over bundled): `/mnt/swarm_data/Assistent/` — same folder layout as `Config/`. Drop `_base/…` and `personas/<id>/…` to override. Plus runtime state:
|
||||||
Copy `personas/leonid/` → new id, edit only differing JSON. See `Config/personas/README.md`.
|
|
||||||
|
```
|
||||||
**Controls:** optional `controls.json` schema + `exact.controls` values. UI auto-draws every slider (`order`, `display: percent`). LLM may patch `"controls": {…}`. Values persist in overlay Exact (DeepMerge partial saves). Leonid: **Вкус** + **Хорни**; `/остынь`, `/horny-game`.
|
Assistent/
|
||||||
|
_base/ personas/<id>/ # overlay presets — same names as Config/, sparse
|
||||||
**Authoring:** pack `author_persona` + `/persona new` clones to overlay only. Delete overlay personas with the ✕ button or ⚙ → Личности (never from the model). Export/import `.assistent-persona.json` for sharing.
|
settings.json # embed_model, base_url, per-persona skills
|
||||||
|
ollama-roles.json # chat vs memory model tags (gpu-rent writes this)
|
||||||
## Exact memory (KV)
|
memory/assistent.sqlite # craft RAG + user_prefs + tags FTS + chats + ui_state + taste (legacy)
|
||||||
|
_migrated_json/ # one-shot archive of old chats/*.json, ui-state.json, taste.json
|
||||||
- `Config/_base/exact.json` — canonical generation defaults, profiles (turbo/raw), aspect table, short facts
|
```
|
||||||
- Persona / disk overlays merge via DeepMerge (matching keys overwrite)
|
|
||||||
- Always injected into the system prompt; UI fills **empty** SwarmUI fields from Exact (no LLM call)
|
Copy `personas/leonid/` → new id, edit only differing JSON. See `Config/personas/README.md`.
|
||||||
- Chat-session overrides (`session_exact`) last until persona change or clear chat — not written to disk
|
|
||||||
- Priority: core → user message → **About the user** → session_exact → exact (+ persona) → live fields → craft `memory_hits`
|
**Controls:** optional `controls.json` schema + `exact.controls` values. UI auto-draws every slider (`order`, `display: percent`). LLM may patch `"controls": {…}`. Values persist in overlay Exact (DeepMerge partial saves). Leonid: **Вкус** + **Хорни**; `/остынь`, `/horny-game`.
|
||||||
|
|
||||||
## About the user (UserPrefs)
|
**Authoring:** pack `author_persona` + `/persona new` clones to overlay only. Delete overlay personas with the ✕ button or ⚙ → Личности (never from the model). Export/import `.assistent-persona.json` for sharing.
|
||||||
|
|
||||||
Separate sqlite table `user_prefs` (not craft RAG):
|
## Exact memory (KV)
|
||||||
|
|
||||||
- **Global** — every persona (e.g. “avoid blonde hair”)
|
- `Config/_base/exact.json` — canonical generation defaults, profiles (turbo/raw), aspect table, short facts
|
||||||
- **Persona** — only the current agent
|
- Persona / disk overlays merge via DeepMerge (matching keys overwrite)
|
||||||
- Injected as `## About the user`; strength via `user_prefs_weight` / `user_prefs_max` in `assistant.json` (⚙ → О пользователе)
|
- Always injected into the system prompt; UI fills **empty** SwarmUI fields from Exact (no LLM call)
|
||||||
- Agent write: `actions: ["user_pref_upsert"]` + `user_prefs: [{key,text,scope}]`
|
- Chat-session overrides (`session_exact`) last until persona change or clear chat — not written to disk
|
||||||
- Legacy `kv.taste` migrates once into global prefs
|
- Priority: core → user message → **About the user** → session_exact → exact (+ persona) → live fields → craft `memory_hits`
|
||||||
|
|
||||||
## Craft vector memory
|
## About the user (UserPrefs)
|
||||||
|
|
||||||
Two layers in `memory/assistent.sqlite` (`persona` column; empty = shared):
|
Separate sqlite table `user_prefs` (not craft RAG):
|
||||||
|
|
||||||
- **Shared** — `Config/_base/memory-seed/`, model cards, `scope: "shared"` upserts. Visible to every persona.
|
- **Global** — every persona (e.g. “avoid blonde hair”)
|
||||||
- **Personal** — `Config/personas/<id>/memory-seed/` and chat upserts (default). Never copied into shared. Other personas do not retrieve it.
|
- **Persona** — only the current agent
|
||||||
- Retrieve = shared ∪ this persona (and `extends` parents). Hybrid **FTS5 + cosine**, kind quotas, `min_score`. Same `kind`+`key`: personal overwrites parent overwrites shared.
|
- Injected as `## About the user`; strength via `user_prefs_weight` / `user_prefs_max` in `assistant.json` (⚙ → О пользователе)
|
||||||
- Tools: `memory_get`, `memory_search`, `lookup_tags` (Danbooru csv in `Data/Autocompletions`, FTS, **no embeddings**).
|
- Agent write: `actions: ["user_pref_upsert"]` + `user_prefs: [{key,text,scope}]`
|
||||||
- Soft craft notes only — Exact, About the user, and the user beat RAG for params
|
- Legacy `kv.taste` migrates once into global prefs
|
||||||
- ⚙ → **Крафт** lists rows with filters + clear (bundled seed is read-only)
|
|
||||||
|
## Craft vector memory
|
||||||
## Chats and runtime KV
|
|
||||||
|
Two layers in `memory/assistent.sqlite` (`persona` column; empty = shared):
|
||||||
- Every chat (messages + Generate params snapshot) is a row in `assistent.sqlite`, newest **200** kept. History search uses FTS over title + body.
|
|
||||||
- First launch after 0.8.3 copies `chats/*.json`, `ui-state.json`, and `taste.json` into sqlite, then archives them under `_migrated_json/`.
|
- **Shared** — `Config/_base/memory-seed/`, model cards, `scope: "shared"` upserts. Visible to every persona.
|
||||||
- localStorage stays as a fast cache; on first run with an empty store the old `swarm_assistent_chats_v1` browser history is migrated up once.
|
- **Personal** — `Config/personas/<id>/memory-seed/` and chat upserts (default). Never copied into shared. Other personas do not retrieve it.
|
||||||
- UI state seeds a **fresh** browser only — anything already in localStorage wins, and `auto_download` is never restored as on
|
- Retrieve = shared ∪ this persona (and `extends` parents). Hybrid **FTS5 + cosine**, kind quotas, `min_score`. Same `kind`+`key`: personal overwrites parent overwrites shared.
|
||||||
- `settings.json` and persona overlays stay files (layered merge + git). `.assistent.json` cards stay next to weights.
|
- Tools: `memory_get`, `memory_search`, `lookup_tags` (Danbooru csv in `Data/Autocompletions`, FTS, **no embeddings**).
|
||||||
## VRAM handover
|
- Soft craft notes only — Exact, About the user, and the user beat RAG for params
|
||||||
|
- ⚙ → **Крафт** lists rows with filters + clear (bundled seed is read-only)
|
||||||
- Before Generate the chat model is unloaded only if **Park LLM** is enabled (`keep_alive: 0`) — default off
|
|
||||||
- After Generate the chat model is **always** force-warmed (`AssistentWarmLlm`) — Krea still often evicts VL from VRAM even without park
|
## Chats and runtime KV
|
||||||
- Embed / memory models are never parked — reloading them would stall every retrieve
|
|
||||||
|
- Every chat (messages + Generate params snapshot) is a row in `assistent.sqlite`, newest **200** kept. History search uses FTS over title + body.
|
||||||
## UX
|
- First launch after 0.8.3 copies `chats/*.json`, `ui-state.json`, and `taste.json` into sqlite, then archives them under `_migrated_json/`.
|
||||||
|
- localStorage stays as a fast cache; on first run with an empty store the old `swarm_assistent_chats_v1` browser history is migrated up once.
|
||||||
- **Send to Assistent** under Generate/History → Ref + Assistent tab
|
- UI state seeds a **fresh** browser only — anything already in localStorage wins, and `auto_download` is never restored as on
|
||||||
- Enter sends; Shift+Enter newline; Interrupt cancels chat epoch
|
- `settings.json` and persona overlays stay files (layered merge + git). `.assistent.json` cards stay next to weights.
|
||||||
- Manual **Apply + Generate** / `/gen` always generate; Auto-generate checkbox only for LLM auto-path
|
## VRAM handover
|
||||||
- **Посмотри результат** / auto-critique wait for a real Generate frame — model previews and unfinished batches are skipped
|
|
||||||
- Civitai Confirm required (unless auto-download); queued-but-missing models show a `⏳ wanted` badge in Cards
|
- Before Generate the chat model is unloaded only if **Park LLM** is enabled (`keep_alive: 0`) — default off
|
||||||
|
- After Generate the chat model is **always** force-warmed (`AssistentWarmLlm`) — Krea still often evicts VL from VRAM even without park
|
||||||
### Slash commands (client-side, no LLM)
|
- Embed / memory models are never parked — reloading them would stall every retrieve
|
||||||
|
|
||||||
| Command | Effect |
|
## UX
|
||||||
| --- | --- |
|
|
||||||
| `/help` | List commands |
|
- **Send to Assistent** under Generate/History → Ref + Assistent tab
|
||||||
| `/debug` | Short UI/Exact dump (no LLM) |
|
- Enter sends; Shift+Enter newline; Interrupt cancels chat epoch
|
||||||
| `/debug ask` / `/why` | Dump + short model explanation |
|
- Manual **Apply + Generate** / `/gen` always generate; Auto-generate checkbox only for LLM auto-path
|
||||||
| `/gen` | Generate now |
|
- **Посмотри результат** / auto-critique wait for a real Generate frame — model previews and unfinished batches are skipped
|
||||||
| `/look generate\|refN` | Attach that board window + ask the LLM to look |
|
- Civitai Confirm required (unless auto-download); queued-but-missing models show a `⏳ wanted` badge in Cards
|
||||||
| `/init` `/mask` `/clear` | Same as board buttons |
|
|
||||||
| `/interrupt` | Stop generation / cancel chat |
|
### Slash commands (client-side, no LLM)
|
||||||
| `/aspect 16:9` | Set size from the official 1K table |
|
|
||||||
| `/seed lock\|random` | Lock or randomize seed |
|
| Command | Effect |
|
||||||
| `/vary` | New seed, same prompt (+ generate if auto) |
|
| --- | --- |
|
||||||
| `/pack write\|critique\|…` | Switch pack |
|
| `/help` | List commands |
|
||||||
| `/civitai <query>` | Ask LLM to search Civitai |
|
| `/debug` | Short UI/Exact dump (no LLM) |
|
||||||
| `/inventory` | Rescan models + refresh LoRA list |
|
| `/debug ask` / `/why` | Dump + short model explanation |
|
||||||
|
| `/gen` | Generate now |
|
||||||
## Requirements
|
| `/look generate\|refN` | Attach that board window + ask the LLM to look |
|
||||||
|
| `/init` `/mask` `/clear` | Same as board buttons |
|
||||||
- SwarmUI with a **Krea 2** checkpoint selected
|
| `/interrupt` | Stop generation / cancel chat |
|
||||||
- Ollama on `http://127.0.0.1:11434` **on the GPU VM** (gpu-rent `LLM_RUNTIME=ollama`)
|
| `/aspect 16:9` | Set size from the official 1K table |
|
||||||
- Chat model + memory embed (`use: memory` in `ollama-models.yaml`; gpu-rent creates CPU variant)
|
| `/seed lock\|random` | Lock or randomize seed |
|
||||||
- Optional: Civitai API key in SwarmUI User Settings
|
| `/vary` | New seed, same prompt (+ generate if auto) |
|
||||||
|
| `/pack write\|critique\|…` | Switch pack |
|
||||||
## Install
|
| `/civitai <query>` | Ask LLM to search Civitai |
|
||||||
|
| `/inventory` | Rescan models + refresh LoRA list |
|
||||||
```yaml
|
|
||||||
swarmui:
|
## Requirements
|
||||||
- url: https://gitea.hsrv.site/mrleo1nid/swarm-assistent.git
|
|
||||||
ref: main
|
- SwarmUI with a **Krea 2** checkpoint selected
|
||||||
dir: swarm-assistent
|
- Ollama on `http://127.0.0.1:11434` **on the GPU VM** (gpu-rent `LLM_RUNTIME=ollama`)
|
||||||
requires: ollama
|
- Chat model + memory embed (`use: memory` in `ollama-models.yaml`; gpu-rent creates CPU variant)
|
||||||
```
|
- Optional: Civitai API key in SwarmUI User Settings
|
||||||
|
|
||||||
Restart / rebuild SwarmUI after clone. gpu-rent: `seed-extensions` + restart.
|
## Install
|
||||||
|
|
||||||
## Packs & skills
|
```yaml
|
||||||
|
swarmui:
|
||||||
**Packs** (one active): `ordinary` (default комбайн), `write_prompt`, `critique_image`, `compose_scene`, `fix_params`, `inpaint_edit`, `describe_ref`, `catalog_card`, `author_persona`.
|
- url: https://gitea.hsrv.site/mrleo1nid/swarm-assistent.git
|
||||||
|
ref: main
|
||||||
**Skills** (checkboxes): `prompting`, `creativity_sliders`, `memory` — procedures; encyclopedia numbers live in Exact, soft notes in memory-seed / RAG, human taste in UserPrefs.
|
dir: swarm-assistent
|
||||||
|
requires: ollama
|
||||||
**Personas:** `neutral`, `lewd`, `aggressive`, `cinema`, `terse`, `leonid` under `Config/personas/`. Overlay clones via `/persona new` or ⚙ → Личности.
|
```
|
||||||
|
|
||||||
## API routes
|
Restart / rebuild SwarmUI after clone. gpu-rent: `seed-extensions` + restart.
|
||||||
|
|
||||||
| Route | Role |
|
## Packs & skills
|
||||||
| --- | --- |
|
|
||||||
| `AssistentListModels` | Ollama tags → `models` (chat) + `memory_models` |
|
**Packs** (one active): `ordinary` (default комбайн), `write_prompt`, `critique_image`, `compose_scene`, `fix_params`, `inpaint_edit`, `describe_ref`, `catalog_card`, `author_persona`.
|
||||||
| `AssistentGetConfig` | Merged preset for persona (ui, packs, skills, identity, controls) |
|
|
||||||
| `AssistentSaveControls` | Persist Exact `controls` values for a persona (overlay) |
|
**Skills** (checkboxes): `prompting`, `creativity_sliders`, `memory` — procedures; encyclopedia numbers live in Exact, soft notes in memory-seed / RAG, human taste in UserPrefs.
|
||||||
| `AssistentGetPersonaShelves` | Merged identity shelves + controls |
|
|
||||||
| `AssistentClonePersona` | Snapshot clone → overlay id |
|
**Personas:** `neutral`, `lewd`, `aggressive`, `cinema`, `terse`, `leonid` under `Config/personas/`. Overlay clones via `/persona new` or ⚙ → Личности.
|
||||||
| `AssistentSavePersona` | Sparse shelf write (overlay only) |
|
|
||||||
| `AssistentDeletePersona` | UI-only delete of overlay persona |
|
## API routes
|
||||||
| `AssistentExportPersona` / `AssistentImportPersona` | Shareable `.assistent-persona.json` pack |
|
|
||||||
| `AssistentSaveKnobs` | Overlay `_base/assistant.json` + Exact turbo/raw profiles |
|
| Route | Role |
|
||||||
| `AssistentGetSettings` / `AssistentSaveSettings` | Overlay settings (skills, embed_model) |
|
| --- | --- |
|
||||||
| `AssistentListInventory` | LoRA / checkpoint / wildcard inventory |
|
| `AssistentListModels` | Ollama tags → `models` (chat) + `memory_models` |
|
||||||
| `AssistentListPersonas` | Persona catalog |
|
| `AssistentGetConfig` | Merged preset for persona (ui, packs, skills, identity, controls) |
|
||||||
| `AssistentGetPacks` | Prompt pack texts |
|
| `AssistentSaveControls` | Persist Exact `controls` values for a persona (overlay) |
|
||||||
| `AssistentGetCard` / `AssistentSaveCard` | `.assistent.json` cards (+ memory ingest) |
|
| `AssistentGetPersonaShelves` | Merged identity shelves + controls |
|
||||||
| `AssistentGetCardMeta` | Local sidecar + optional Civitai by-hash |
|
| `AssistentClonePersona` | Snapshot clone → overlay id |
|
||||||
| `AssistentEnqueueWanted` / `AssistentListWanted` | Wanted YAML queue (write / read + count) |
|
| `AssistentSavePersona` | Sparse shelf write (overlay only) |
|
||||||
| `AssistentGetTaste` / `AssistentSaveTaste` | sqlite `kv.taste` (legacy; prefer UserPrefs) |
|
| `AssistentDeletePersona` | UI-only delete of overlay persona |
|
||||||
| `AssistentListUserPrefs` / `AssistentUpsertUserPref` / `AssistentForgetUserPref` / `AssistentClearUserPrefs` | About the user |
|
| `AssistentExportPersona` / `AssistentImportPersona` | Shareable `.assistent-persona.json` pack |
|
||||||
| `AssistentSearchCivitai` | Civitai LoRA search |
|
| `AssistentSaveKnobs` | Overlay `_base/assistant.json` + Exact turbo/raw profiles |
|
||||||
| `AssistentChat` / `AssistentChatWS` | Chat (+ user prefs + hybrid craft memory + hops) |
|
| `AssistentGetSettings` / `AssistentSaveSettings` | Overlay settings (skills, embed_model) |
|
||||||
| `AssistentListMemory` / `AssistentUpsertMemory` / `AssistentForgetMemory` / `AssistentClearMemory` | Craft vector store |
|
| `AssistentListInventory` | LoRA / checkpoint / wildcard inventory |
|
||||||
| `AssistentSearchMemory` / `AssistentGetMemory` | Hybrid search / exact kind+key |
|
| `AssistentListPersonas` | Persona catalog |
|
||||||
| `AssistentLookupTags` | Danbooru csv FTS (no embeddings) |
|
| `AssistentGetPacks` | Prompt pack texts |
|
||||||
| `AssistentListChats` / `AssistentGetChat` / `AssistentSaveChat` / `AssistentDeleteChat` | sqlite `chats` (optional `q` FTS) |
|
| `AssistentGetCard` / `AssistentSaveCard` | `.assistent.json` cards (+ memory ingest) |
|
||||||
| `AssistentGetUiState` / `AssistentSaveUiState` | sqlite `kv.ui_state` |
|
| `AssistentGetCardMeta` | Local sidecar + optional Civitai by-hash |
|
||||||
| `AssistentParkLlm` / `AssistentWarmLlm` | Unload / reload the chat model in VRAM |
|
| `AssistentEnqueueWanted` / `AssistentListWanted` | Wanted YAML queue (write / read + count) |
|
||||||
|
| `AssistentGetTaste` / `AssistentSaveTaste` | sqlite `kv.taste` (legacy; prefer UserPrefs) |
|
||||||
## License
|
| `AssistentListUserPrefs` / `AssistentUpsertUserPref` / `AssistentForgetUserPref` / `AssistentClearUserPrefs` | About the user |
|
||||||
|
| `AssistentSearchCivitai` | Civitai LoRA search |
|
||||||
MIT
|
| `AssistentChat` / `AssistentChatWS` | Chat (+ user prefs + hybrid craft memory + hops) |
|
||||||
|
| `AssistentListMemory` / `AssistentUpsertMemory` / `AssistentForgetMemory` / `AssistentClearMemory` | Craft vector store |
|
||||||
|
| `AssistentSearchMemory` / `AssistentGetMemory` | Hybrid search / exact kind+key |
|
||||||
|
| `AssistentLookupTags` | Danbooru csv FTS (no embeddings) |
|
||||||
|
| `AssistentListChats` / `AssistentGetChat` / `AssistentSaveChat` / `AssistentDeleteChat` | sqlite `chats` (optional `q` FTS) |
|
||||||
|
| `AssistentGetUiState` / `AssistentSaveUiState` | sqlite `kv.ui_state` |
|
||||||
|
| `AssistentParkLlm` / `AssistentWarmLlm` | Unload / reload the chat model in VRAM |
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
|
|||||||
+274
-274
@@ -1,274 +1,274 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using SwarmUI.Accounts;
|
using SwarmUI.Accounts;
|
||||||
using SwarmUI.Core;
|
using SwarmUI.Core;
|
||||||
using SwarmUI.Utils;
|
using SwarmUI.Utils;
|
||||||
using SwarmUI.WebAPI;
|
using SwarmUI.WebAPI;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
|
||||||
namespace Mrleo1nid.SwarmAssistent;
|
namespace Mrleo1nid.SwarmAssistent;
|
||||||
|
|
||||||
/// <summary>Krea 2 collaborative assistant: Ollama chat + vision + prompt/LoRA/params patches + Generate/Civitai.</summary>
|
/// <summary>Krea 2 collaborative assistant: Ollama chat + vision + prompt/LoRA/params patches + Generate/Civitai.</summary>
|
||||||
public partial class SwarmAssistentExtension : Extension
|
public partial class SwarmAssistentExtension : Extension
|
||||||
{
|
{
|
||||||
public static PermInfo PermUse = Permissions.Register(new(
|
public static PermInfo PermUse = Permissions.Register(new(
|
||||||
"swarm_assistent_use",
|
"swarm_assistent_use",
|
||||||
"[Swarm Assistent] Use",
|
"[Swarm Assistent] Use",
|
||||||
"Allows using the Swarm Assistent chat (Ollama proxy).",
|
"Allows using the Swarm Assistent chat (Ollama proxy).",
|
||||||
PermissionDefault.USER,
|
PermissionDefault.USER,
|
||||||
Permissions.GroupUser));
|
Permissions.GroupUser));
|
||||||
|
|
||||||
public static HttpClient HttpClient;
|
public static HttpClient HttpClient;
|
||||||
|
|
||||||
public AssistentConfig Config;
|
public AssistentConfig Config;
|
||||||
public AssistentMemory Memory;
|
public AssistentMemory Memory;
|
||||||
|
|
||||||
public override void OnPreInit()
|
public override void OnPreInit()
|
||||||
{
|
{
|
||||||
ScriptFiles.Add("Assets/assistent.api.js");
|
ScriptFiles.Add("Assets/assistent.api.js");
|
||||||
ScriptFiles.Add("Assets/assistent.patch.js");
|
ScriptFiles.Add("Assets/assistent.patch.js");
|
||||||
ScriptFiles.Add("Assets/assistent.persist.js");
|
ScriptFiles.Add("Assets/assistent.persist.js");
|
||||||
ScriptFiles.Add("Assets/assistent.js");
|
ScriptFiles.Add("Assets/assistent.js");
|
||||||
StyleSheetFiles.Add("Assets/assistent.css");
|
StyleSheetFiles.Add("Assets/assistent.css");
|
||||||
ExtensionAuthor = "mrleo1nid";
|
ExtensionAuthor = "mrleo1nid";
|
||||||
Description = "Collaborative Krea 2 assistant: Ollama chat, persona presets, vector memory, model cards, Generate loop.";
|
Description = "Collaborative Krea 2 assistant: Ollama chat, persona presets, vector memory, model cards, Generate loop.";
|
||||||
License = "MIT";
|
License = "MIT";
|
||||||
Version = "0.10.21";
|
Version = "0.11.3";
|
||||||
Tags = ["tabs", "ui", "llm", "ollama", "krea", "inpaint", "memory"];
|
Tags = ["tabs", "ui", "llm", "ollama", "krea", "inpaint", "memory"];
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnInit()
|
public override void OnInit()
|
||||||
{
|
{
|
||||||
HttpClient ??= new HttpClient { Timeout = TimeSpan.FromMinutes(10) };
|
HttpClient ??= new HttpClient { Timeout = TimeSpan.FromMinutes(10) };
|
||||||
Config = new AssistentConfig(FilePath, DataRoot());
|
Config = new AssistentConfig(FilePath, DataRoot());
|
||||||
Memory = new AssistentMemory(DataRoot(), HttpClient, Config.LoadAssistant(Config.DefaultPersonaId())["embed_model"]?.ToString() ?? "nomic-embed-text");
|
Memory = new AssistentMemory(DataRoot(), HttpClient, Config.LoadAssistant(Config.DefaultPersonaId())["embed_model"]?.ToString() ?? "nomic-embed-text");
|
||||||
API.RegisterAPICall(AssistentListModels, false, PermUse);
|
API.RegisterAPICall(AssistentListModels, false, PermUse);
|
||||||
API.RegisterAPICall(AssistentGetPacks, false, PermUse);
|
API.RegisterAPICall(AssistentGetPacks, false, PermUse);
|
||||||
API.RegisterAPICall(AssistentListPersonas, false, PermUse);
|
API.RegisterAPICall(AssistentListPersonas, false, PermUse);
|
||||||
API.RegisterAPICall(AssistentGetConfig, false, PermUse);
|
API.RegisterAPICall(AssistentGetConfig, false, PermUse);
|
||||||
API.RegisterAPICall(AssistentGetSettings, false, PermUse);
|
API.RegisterAPICall(AssistentGetSettings, false, PermUse);
|
||||||
API.RegisterAPICall(AssistentSaveSettings, true, PermUse);
|
API.RegisterAPICall(AssistentSaveSettings, true, PermUse);
|
||||||
API.RegisterAPICall(AssistentListInventory, false, PermUse);
|
API.RegisterAPICall(AssistentListInventory, false, PermUse);
|
||||||
API.RegisterAPICall(AssistentGetCard, false, PermUse);
|
API.RegisterAPICall(AssistentGetCard, false, PermUse);
|
||||||
API.RegisterAPICall(AssistentSaveCard, true, PermUse);
|
API.RegisterAPICall(AssistentSaveCard, true, PermUse);
|
||||||
API.RegisterAPICall(AssistentEnqueueWanted, true, PermUse);
|
API.RegisterAPICall(AssistentEnqueueWanted, true, PermUse);
|
||||||
API.RegisterAPICall(AssistentGetCardMeta, false, PermUse);
|
API.RegisterAPICall(AssistentGetCardMeta, false, PermUse);
|
||||||
API.RegisterAPICall(AssistentSearchCivitai, false, PermUse);
|
API.RegisterAPICall(AssistentSearchCivitai, false, PermUse);
|
||||||
API.RegisterAPICall(AssistentGetTaste, false, PermUse);
|
API.RegisterAPICall(AssistentGetTaste, false, PermUse);
|
||||||
API.RegisterAPICall(AssistentSaveTaste, true, PermUse);
|
API.RegisterAPICall(AssistentSaveTaste, true, PermUse);
|
||||||
API.RegisterAPICall(AssistentChat, true, PermUse);
|
API.RegisterAPICall(AssistentChat, true, PermUse);
|
||||||
API.RegisterAPICall(AssistentChatWS, true, PermUse);
|
API.RegisterAPICall(AssistentChatWS, true, PermUse);
|
||||||
API.RegisterAPICall(AssistentListChats, false, PermUse);
|
API.RegisterAPICall(AssistentListChats, false, PermUse);
|
||||||
API.RegisterAPICall(AssistentGetChat, false, PermUse);
|
API.RegisterAPICall(AssistentGetChat, false, PermUse);
|
||||||
API.RegisterAPICall(AssistentSaveChat, true, PermUse);
|
API.RegisterAPICall(AssistentSaveChat, true, PermUse);
|
||||||
API.RegisterAPICall(AssistentDeleteChat, true, PermUse);
|
API.RegisterAPICall(AssistentDeleteChat, true, PermUse);
|
||||||
API.RegisterAPICall(AssistentGetUiState, false, PermUse);
|
API.RegisterAPICall(AssistentGetUiState, false, PermUse);
|
||||||
API.RegisterAPICall(AssistentSaveUiState, true, PermUse);
|
API.RegisterAPICall(AssistentSaveUiState, true, PermUse);
|
||||||
API.RegisterAPICall(AssistentParkLlm, true, PermUse);
|
API.RegisterAPICall(AssistentParkLlm, true, PermUse);
|
||||||
API.RegisterAPICall(AssistentWarmLlm, true, PermUse);
|
API.RegisterAPICall(AssistentWarmLlm, true, PermUse);
|
||||||
API.RegisterAPICall(AssistentListMemory, false, PermUse);
|
API.RegisterAPICall(AssistentListMemory, false, PermUse);
|
||||||
API.RegisterAPICall(AssistentUpsertMemory, true, PermUse);
|
API.RegisterAPICall(AssistentUpsertMemory, true, PermUse);
|
||||||
API.RegisterAPICall(AssistentForgetMemory, true, PermUse);
|
API.RegisterAPICall(AssistentForgetMemory, true, PermUse);
|
||||||
API.RegisterAPICall(AssistentSearchMemory, false, PermUse);
|
API.RegisterAPICall(AssistentSearchMemory, false, PermUse);
|
||||||
API.RegisterAPICall(AssistentGetMemory, false, PermUse);
|
API.RegisterAPICall(AssistentGetMemory, false, PermUse);
|
||||||
API.RegisterAPICall(AssistentLookupTags, false, PermUse);
|
API.RegisterAPICall(AssistentLookupTags, false, PermUse);
|
||||||
API.RegisterAPICall(AssistentListWanted, false, PermUse);
|
API.RegisterAPICall(AssistentListWanted, false, PermUse);
|
||||||
API.RegisterAPICall(AssistentSaveControls, true, PermUse);
|
API.RegisterAPICall(AssistentSaveControls, true, PermUse);
|
||||||
API.RegisterAPICall(AssistentGetPersonaShelves, false, PermUse);
|
API.RegisterAPICall(AssistentGetPersonaShelves, false, PermUse);
|
||||||
API.RegisterAPICall(AssistentClonePersona, true, PermUse);
|
API.RegisterAPICall(AssistentClonePersona, true, PermUse);
|
||||||
API.RegisterAPICall(AssistentSavePersona, true, PermUse);
|
API.RegisterAPICall(AssistentSavePersona, true, PermUse);
|
||||||
API.RegisterAPICall(AssistentDeletePersona, true, PermUse);
|
API.RegisterAPICall(AssistentDeletePersona, true, PermUse);
|
||||||
API.RegisterAPICall(AssistentExportPersona, false, PermUse);
|
API.RegisterAPICall(AssistentExportPersona, false, PermUse);
|
||||||
API.RegisterAPICall(AssistentImportPersona, true, PermUse);
|
API.RegisterAPICall(AssistentImportPersona, true, PermUse);
|
||||||
API.RegisterAPICall(AssistentSaveKnobs, true, PermUse);
|
API.RegisterAPICall(AssistentSaveKnobs, true, PermUse);
|
||||||
API.RegisterAPICall(AssistentListUserPrefs, false, PermUse);
|
API.RegisterAPICall(AssistentListUserPrefs, false, PermUse);
|
||||||
API.RegisterAPICall(AssistentUpsertUserPref, true, PermUse);
|
API.RegisterAPICall(AssistentUpsertUserPref, true, PermUse);
|
||||||
API.RegisterAPICall(AssistentForgetUserPref, true, PermUse);
|
API.RegisterAPICall(AssistentForgetUserPref, true, PermUse);
|
||||||
API.RegisterAPICall(AssistentClearUserPrefs, true, PermUse);
|
API.RegisterAPICall(AssistentClearUserPrefs, true, PermUse);
|
||||||
API.RegisterAPICall(AssistentClearMemory, true, PermUse);
|
API.RegisterAPICall(AssistentClearMemory, true, PermUse);
|
||||||
Logs.Init("Swarm Assistent extension loaded (settings panel + user prefs + craft memory)");
|
Logs.Init("Swarm Assistent extension loaded (settings panel + user prefs + craft memory)");
|
||||||
}
|
}
|
||||||
|
|
||||||
int CfgInt(string key, int fallback)
|
int CfgInt(string key, int fallback)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return Config?.LoadAssistant(Config.DefaultPersonaId())[key]?.Value<int?>() ?? fallback;
|
return Config?.LoadAssistant(Config.DefaultPersonaId())[key]?.Value<int?>() ?? fallback;
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
return fallback;
|
return fallback;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static string Clip(string text, int max)
|
static string Clip(string text, int max)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(text) || text.Length <= max)
|
if (string.IsNullOrEmpty(text) || text.Length <= max)
|
||||||
{
|
{
|
||||||
return text ?? "";
|
return text ?? "";
|
||||||
}
|
}
|
||||||
return text[..max] + "…";
|
return text[..max] + "…";
|
||||||
}
|
}
|
||||||
|
|
||||||
static string CollapseWs(string text)
|
static string CollapseWs(string text)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(text))
|
if (string.IsNullOrWhiteSpace(text))
|
||||||
{
|
{
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
return Regex.Replace(text.Trim(), @"\s+", " ");
|
return Regex.Replace(text.Trim(), @"\s+", " ");
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string NormalizeBaseUrl(string raw)
|
public static string NormalizeBaseUrl(string raw)
|
||||||
{
|
{
|
||||||
string url = (raw ?? "").Trim();
|
string url = (raw ?? "").Trim();
|
||||||
if (string.IsNullOrWhiteSpace(url))
|
if (string.IsNullOrWhiteSpace(url))
|
||||||
{
|
{
|
||||||
url = "http://127.0.0.1:11434";
|
url = "http://127.0.0.1:11434";
|
||||||
}
|
}
|
||||||
return url.TrimEnd('/');
|
return url.TrimEnd('/');
|
||||||
}
|
}
|
||||||
|
|
||||||
static string DataRoot()
|
static string DataRoot()
|
||||||
{
|
{
|
||||||
if (Directory.Exists("/mnt/swarm_data"))
|
if (Directory.Exists("/mnt/swarm_data"))
|
||||||
{
|
{
|
||||||
return "/mnt/swarm_data";
|
return "/mnt/swarm_data";
|
||||||
}
|
}
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
string models = Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, "Models"));
|
string models = Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, "Models"));
|
||||||
if (Directory.Exists(models))
|
if (Directory.Exists(models))
|
||||||
{
|
{
|
||||||
return Path.GetDirectoryName(models) ?? Environment.CurrentDirectory;
|
return Path.GetDirectoryName(models) ?? Environment.CurrentDirectory;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
return Environment.CurrentDirectory;
|
return Environment.CurrentDirectory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string ReadPackFile(string name)
|
public string ReadPackFile(string name)
|
||||||
{
|
{
|
||||||
return Config?.LoadPackPrompt(Config.DefaultPersonaId(), name);
|
return Config?.LoadPackPrompt(Config.DefaultPersonaId(), name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<JObject> AssistentGetPacks(Session session, string persona = null)
|
public async Task<JObject> AssistentGetPacks(Session session, string persona = null)
|
||||||
{
|
{
|
||||||
await Task.CompletedTask;
|
await Task.CompletedTask;
|
||||||
string pid = AssistentConfig.SafeId(persona) ?? Config.DefaultPersonaId();
|
string pid = AssistentConfig.SafeId(persona) ?? Config.DefaultPersonaId();
|
||||||
JObject packs = new();
|
JObject packs = new();
|
||||||
JArray order = [];
|
JArray order = [];
|
||||||
foreach (var p in Config.ListPacks(pid))
|
foreach (var p in Config.ListPacks(pid))
|
||||||
{
|
{
|
||||||
string text = Config.LoadPackPrompt(pid, p.id);
|
string text = Config.LoadPackPrompt(pid, p.id);
|
||||||
if (text is not null)
|
if (text is not null)
|
||||||
{
|
{
|
||||||
packs[p.id] = text;
|
packs[p.id] = text;
|
||||||
}
|
}
|
||||||
order.Add(p.id);
|
order.Add(p.id);
|
||||||
}
|
}
|
||||||
return new JObject { ["success"] = true, ["packs"] = packs, ["order"] = order, ["persona"] = pid };
|
return new JObject { ["success"] = true, ["packs"] = packs, ["order"] = order, ["persona"] = pid };
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<JObject> AssistentGetConfig(Session session, string persona = null)
|
public async Task<JObject> AssistentGetConfig(Session session, string persona = null)
|
||||||
{
|
{
|
||||||
await Task.CompletedTask;
|
await Task.CompletedTask;
|
||||||
string pid = AssistentConfig.SafeId(persona) ?? Config.DefaultPersonaId();
|
string pid = AssistentConfig.SafeId(persona) ?? Config.DefaultPersonaId();
|
||||||
return Config.BuildMergedConfigPayload(pid);
|
return Config.BuildMergedConfigPayload(pid);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<JObject> AssistentGetSettings(Session session)
|
public async Task<JObject> AssistentGetSettings(Session session)
|
||||||
{
|
{
|
||||||
await Task.CompletedTask;
|
await Task.CompletedTask;
|
||||||
return new JObject { ["success"] = true, ["settings"] = Config.LoadSettings() };
|
return new JObject { ["success"] = true, ["settings"] = Config.LoadSettings() };
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<JObject> AssistentSaveSettings(Session session, JObject settings)
|
public async Task<JObject> AssistentSaveSettings(Session session, JObject settings)
|
||||||
{
|
{
|
||||||
await Task.CompletedTask;
|
await Task.CompletedTask;
|
||||||
if (settings is null)
|
if (settings is null)
|
||||||
{
|
{
|
||||||
return new JObject { ["error"] = "settings required" };
|
return new JObject { ["error"] = "settings required" };
|
||||||
}
|
}
|
||||||
string prevEmbed = Config.LoadSettings()["embed_model"]?.ToString();
|
string prevEmbed = Config.LoadSettings()["embed_model"]?.ToString();
|
||||||
Config.SaveSettings(settings);
|
Config.SaveSettings(settings);
|
||||||
string nextEmbed = settings["embed_model"]?.ToString();
|
string nextEmbed = settings["embed_model"]?.ToString();
|
||||||
if (!string.IsNullOrWhiteSpace(nextEmbed) && !string.Equals(prevEmbed, nextEmbed, StringComparison.OrdinalIgnoreCase))
|
if (!string.IsNullOrWhiteSpace(nextEmbed) && !string.Equals(prevEmbed, nextEmbed, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await Memory.ReembedAllAsync(NormalizeBaseUrl(settings["base_url"]?.ToString()), nextEmbed);
|
await Memory.ReembedAllAsync(NormalizeBaseUrl(settings["base_url"]?.ToString()), nextEmbed);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Logs.Debug($"AssistentSaveSettings reembed: {ex.Message}");
|
Logs.Debug($"AssistentSaveSettings reembed: {ex.Message}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new JObject { ["success"] = true, ["path"] = Path.Combine(Config.OverlayRoot, "settings.json") };
|
return new JObject { ["success"] = true, ["path"] = Path.Combine(Config.OverlayRoot, "settings.json") };
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<JObject> AssistentListPersonas(Session session)
|
public async Task<JObject> AssistentListPersonas(Session session)
|
||||||
{
|
{
|
||||||
await Task.CompletedTask;
|
await Task.CompletedTask;
|
||||||
var catalog = Config.ListPersonaCatalog();
|
var catalog = Config.ListPersonaCatalog();
|
||||||
JArray list = [];
|
JArray list = [];
|
||||||
foreach (var p in catalog)
|
foreach (var p in catalog)
|
||||||
{
|
{
|
||||||
list.Add(new JObject
|
list.Add(new JObject
|
||||||
{
|
{
|
||||||
["id"] = p.id,
|
["id"] = p.id,
|
||||||
["title"] = p.title,
|
["title"] = p.title,
|
||||||
["accent"] = p.accent,
|
["accent"] = p.accent,
|
||||||
["prompt"] = Config.RenderIdentityBlock(p.id, includeAllShelves: true),
|
["prompt"] = Config.RenderIdentityBlock(p.id, includeAllShelves: true),
|
||||||
["source"] = p.source,
|
["source"] = p.source,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return new JObject
|
return new JObject
|
||||||
{
|
{
|
||||||
["success"] = true,
|
["success"] = true,
|
||||||
["default"] = Config.DefaultPersonaId(),
|
["default"] = Config.DefaultPersonaId(),
|
||||||
["personas"] = list,
|
["personas"] = list,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<JObject> AssistentGetTaste(Session session)
|
public async Task<JObject> AssistentGetTaste(Session session)
|
||||||
{
|
{
|
||||||
await Task.CompletedTask;
|
await Task.CompletedTask;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
return new JObject { ["success"] = true, ["taste"] = Memory.GetKvObject(AssistentMemory.KvTaste) };
|
return new JObject { ["success"] = true, ["taste"] = Memory.GetKvObject(AssistentMemory.KvTaste) };
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
return new JObject { ["error"] = $"taste: {ex.Message}" };
|
return new JObject { ["error"] = $"taste: {ex.Message}" };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<JObject> AssistentSaveTaste(Session session, JObject taste)
|
public async Task<JObject> AssistentSaveTaste(Session session, JObject taste)
|
||||||
{
|
{
|
||||||
await Task.CompletedTask;
|
await Task.CompletedTask;
|
||||||
if (taste is null)
|
if (taste is null)
|
||||||
{
|
{
|
||||||
return new JObject { ["error"] = "taste required" };
|
return new JObject { ["error"] = "taste required" };
|
||||||
}
|
}
|
||||||
if (taste["updated"] == null)
|
if (taste["updated"] == null)
|
||||||
{
|
{
|
||||||
taste["updated"] = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
taste["updated"] = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||||
}
|
}
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Memory.SetKvObject(AssistentMemory.KvTaste, taste);
|
Memory.SetKvObject(AssistentMemory.KvTaste, taste);
|
||||||
return new JObject { ["success"] = true, ["path"] = "Assistent/memory/assistent.sqlite" };
|
return new JObject { ["success"] = true, ["path"] = "Assistent/memory/assistent.sqlite" };
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
return new JObject { ["error"] = $"taste save: {ex.Message}" };
|
return new JObject { ["error"] = $"taste save: {ex.Message}" };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user