Bump Assistent to 0.6.0: board tabs, Cards Civitai fetch, and chat reliability.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+227
-3
@@ -34,9 +34,10 @@
|
||||
|
||||
.sa-board-head {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
align-items: center;
|
||||
gap: 0.45rem;
|
||||
min-height: 1.6rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.sa-board-title {
|
||||
@@ -46,6 +47,43 @@
|
||||
font-size: 0.78rem;
|
||||
}
|
||||
|
||||
.sa-board-tabs {
|
||||
display: flex;
|
||||
gap: 0.2rem;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.sa-board-tab {
|
||||
appearance: none;
|
||||
border: 1px solid color-mix(in srgb, currentColor 22%, transparent);
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
border-radius: 999px;
|
||||
padding: 0.15rem 0.55rem;
|
||||
font-size: 0.75rem;
|
||||
cursor: pointer;
|
||||
opacity: 0.7;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.3rem;
|
||||
}
|
||||
|
||||
.sa-board-tab.sa-board-tab-active {
|
||||
opacity: 1;
|
||||
background: color-mix(in srgb, currentColor 12%, transparent);
|
||||
border-color: color-mix(in srgb, currentColor 40%, transparent);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.sa-board-badge {
|
||||
font-size: 0.65rem;
|
||||
padding: 0.05rem 0.35rem;
|
||||
border-radius: 999px;
|
||||
background: color-mix(in srgb, currentColor 18%, transparent);
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.sa-board-hint {
|
||||
flex: 1;
|
||||
font-size: 0.75rem;
|
||||
@@ -66,6 +104,17 @@
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.sa-board.sa-board-gen-only {
|
||||
grid-template-columns: 1fr;
|
||||
grid-auto-rows: 1fr;
|
||||
}
|
||||
|
||||
.sa-board.sa-board-gen-only .sa-slot.sa-slot-gen {
|
||||
grid-column: auto;
|
||||
min-height: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.sa-splitter {
|
||||
flex: 0 0 0.45rem;
|
||||
margin: 0 0.15rem;
|
||||
@@ -940,8 +989,7 @@
|
||||
}
|
||||
|
||||
#sa_card_json {
|
||||
flex: 1;
|
||||
min-height: 12rem;
|
||||
min-height: 8rem;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
@@ -951,6 +999,182 @@
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.sa-card-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.35rem;
|
||||
overflow: auto;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.sa-card-form label {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.2rem;
|
||||
font-size: 0.78rem;
|
||||
opacity: 0.92;
|
||||
}
|
||||
|
||||
.sa-card-form input,
|
||||
.sa-card-form textarea {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
border-radius: 0.35rem;
|
||||
padding: 0.35rem 0.45rem;
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
.sa-card-form .sa-card-json-toggle {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.sa-card-previews {
|
||||
display: flex;
|
||||
gap: 0.35rem;
|
||||
overflow-x: auto;
|
||||
padding: 0.15rem 0;
|
||||
min-height: 4.5rem;
|
||||
}
|
||||
|
||||
.sa-card-thumb {
|
||||
width: 4.5rem;
|
||||
height: 4.5rem;
|
||||
object-fit: cover;
|
||||
border-radius: 0.35rem;
|
||||
border: 1px solid color-mix(in srgb, currentColor 25%, transparent);
|
||||
cursor: pointer;
|
||||
flex: 0 0 auto;
|
||||
background: color-mix(in srgb, currentColor 6%, transparent);
|
||||
}
|
||||
|
||||
.sa-card-thumb:hover {
|
||||
border-color: color-mix(in srgb, currentColor 55%, transparent);
|
||||
}
|
||||
|
||||
.sa-card-row {
|
||||
display: flex;
|
||||
gap: 0.45rem;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.sa-card-row-thumb {
|
||||
width: 2.4rem;
|
||||
height: 2.4rem;
|
||||
border-radius: 0.3rem;
|
||||
object-fit: cover;
|
||||
flex: 0 0 auto;
|
||||
background: color-mix(in srgb, currentColor 8%, transparent);
|
||||
border: 1px solid color-mix(in srgb, currentColor 18%, transparent);
|
||||
}
|
||||
|
||||
.sa-card-row-body {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.sa-lora-chips {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.3rem;
|
||||
align-items: center;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.sa-lora-chip {
|
||||
appearance: none;
|
||||
border: 1px solid color-mix(in srgb, currentColor 28%, transparent);
|
||||
background: color-mix(in srgb, currentColor 8%, transparent);
|
||||
color: inherit;
|
||||
border-radius: 999px;
|
||||
padding: 0.12rem 0.45rem;
|
||||
font-size: 0.72rem;
|
||||
cursor: pointer;
|
||||
max-width: 14rem;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.sa-lora-chip.sa-lora-add {
|
||||
opacity: 0.75;
|
||||
border-style: dashed;
|
||||
}
|
||||
|
||||
.sa-lora-picker {
|
||||
position: absolute;
|
||||
z-index: 40;
|
||||
max-height: 12rem;
|
||||
overflow: auto;
|
||||
min-width: 14rem;
|
||||
max-width: 22rem;
|
||||
border-radius: 0.45rem;
|
||||
border: 1px solid color-mix(in srgb, currentColor 28%, transparent);
|
||||
background: color-mix(in srgb, #111 92%, transparent);
|
||||
box-shadow: 0 8px 24px color-mix(in srgb, #000 35%, transparent);
|
||||
padding: 0.25rem;
|
||||
}
|
||||
|
||||
.sa-lora-picker button {
|
||||
display: block;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
padding: 0.35rem 0.45rem;
|
||||
border-radius: 0.3rem;
|
||||
cursor: pointer;
|
||||
font-size: 0.78rem;
|
||||
}
|
||||
|
||||
.sa-lora-picker button:hover {
|
||||
background: color-mix(in srgb, currentColor 12%, transparent);
|
||||
}
|
||||
|
||||
.sa-slash-wrap {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.sa-slash-menu {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: calc(100% + 0.25rem);
|
||||
z-index: 50;
|
||||
max-height: 12rem;
|
||||
overflow: auto;
|
||||
border-radius: 0.45rem;
|
||||
border: 1px solid color-mix(in srgb, currentColor 28%, transparent);
|
||||
background: color-mix(in srgb, #111 94%, transparent);
|
||||
box-shadow: 0 8px 24px color-mix(in srgb, #000 40%, transparent);
|
||||
padding: 0.2rem;
|
||||
}
|
||||
|
||||
.sa-slash-item {
|
||||
display: block;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
padding: 0.35rem 0.5rem;
|
||||
border-radius: 0.3rem;
|
||||
cursor: pointer;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.sa-slash-item code {
|
||||
font-weight: 650;
|
||||
}
|
||||
|
||||
.sa-slash-item.sa-slash-active,
|
||||
.sa-slash-item:hover {
|
||||
background: color-mix(in srgb, currentColor 14%, transparent);
|
||||
}
|
||||
|
||||
.sa-cards-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
+948
-144
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,9 @@
|
||||
# Persona: cinema
|
||||
|
||||
You are a cinematographer / camera operator co-director.
|
||||
|
||||
- Match the user's language (RU or EN).
|
||||
- Lead with lens, framing, lighting, blocking, and camera movement — not smut or lectures.
|
||||
- Prefer concrete craft: focal length feel, key/fill/rim, depth of field, aspect for the shot.
|
||||
- Stay concise. NSFW scenes: describe factually through camera language (body as subject in frame).
|
||||
- Never invent LoRA names or triggers — only use inventory / cards.
|
||||
@@ -0,0 +1,9 @@
|
||||
# Persona: terse
|
||||
|
||||
You are a short, high-signal art director.
|
||||
|
||||
- Match the user's language (RU or EN).
|
||||
- Reply in **1–2 short sentences**, then the JSON patch. No lectures, no filler.
|
||||
- Prefer decisive patches. Call out one main fix if something is wrong.
|
||||
- Never invent LoRA names or triggers.
|
||||
- NSFW: factual, minimal words.
|
||||
@@ -122,7 +122,7 @@ A JSON block named "Live SwarmUI context" is attached. Treat it as ground truth
|
||||
- `images` / `batch` — batch size.
|
||||
- `creativity` / slider ints — guide your prompt writing only (UI ignores them except weaving into `prompt`).
|
||||
- `clear_prompt_images: true` — strip image embeds from the prompt box.
|
||||
- `pack` — switch active prompt pack for a follow-up hop (`write_prompt`, `critique_image`, `compose_scene`, `fix_params`, `inpaint_edit`, `describe_ref`).
|
||||
- `pack` — switch active prompt pack for a follow-up hop (`write_prompt`, `critique_image`, `compose_scene`, `fix_params`, `inpaint_edit`, `describe_ref`, `catalog_card`).
|
||||
- Do not invent model or LoRA filenames.
|
||||
- If you cannot help (wrong architecture / no Krea 2), say so and omit the JSON patch.
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ Goal: adjust **generation parameters** for Krea 2 Turbo (or RAW if context says
|
||||
|
||||
- **Turbo:** steps 4–12 (default **8**), CFG **1** (never 0), sigma shift ~**1.15**.
|
||||
- **RAW/base:** steps 20–52, CFG ~4–4.5 — only if checkpoint/context indicates Raw. If a turbo-distill LoRA is available, weight **0.6** is the usual photoreal compromise (UI LoRA only — no dual-sampler).
|
||||
- Prefer live context field `krea_profile` (`turbo` | `raw`) and `recommended_params` when present.
|
||||
- **Aspect:** prefer patch field `aspect` (`1:1`, `4:5`, `2:3`, `16:9`, `9:16`, `4:3`, `3:2`, `2.35:1`) — UI maps to official 1K sizes. Else set width/height near 1024.
|
||||
- **Batch:** `images` or `batch` (1–4 typical).
|
||||
- **Seed:** `lock_seed: true` to reuse current; `vary: true` or `seed: -1` for a new roll; set numeric `seed` for exact reproducibility.
|
||||
|
||||
@@ -1,29 +1,27 @@
|
||||
# Swarm Assistent
|
||||
|
||||
SwarmUI extension for **collaborative Krea 2** prompting via **Ollama**: chat + multi-window board (live Generate + refs), LoRA/trigger awareness, applyable patches, **img2img / inpaint**, slash commands, aspect/seed chips, auto Generate, and Civitai search with Confirm.
|
||||
SwarmUI extension for **collaborative Krea 2** prompting via **Ollama**: chat + board (Generate | Refs tabs), LoRA chips, personas, model cards with Civitai fetch, img2img/inpaint, slash commands, auto Generate.
|
||||
|
||||
**Version 0.5.0** — denser Krea 2 knowledge in prompt packs, new patch fields, `/help` slash commands, composer chips.
|
||||
**Version 0.6.0** — board tabs, Cards form + live Civitai meta, Interrupt cancel, slim history, LoRA chips, Turbo/RAW, taste on disk, RU UI.
|
||||
|
||||
## Layout
|
||||
|
||||
- **Left — Board:** live **Generate** window + **Ref** windows (drop / paste / *Send to Assistent* / Snapshot gen). Per-window **vision** checkbox attaches that image to the next chat.
|
||||
- **Left — Board tabs:** **Generate** (full-height live view) | **Refs** (reference grid + badge `N · vision M`)
|
||||
- **Splitter:** drag to resize panes
|
||||
- **Right (wider):** chat — first visit shows a short how-to. Only the **latest** JSON proposal keeps action buttons; Apply + Generate spins/disables while a generation is running.
|
||||
- **Chips** above the composer: aspect ratios, Seed lock / random, Vary.
|
||||
- **Top-right:** prompt pack + settings (Ollama URL, model, auto-apply / auto-generate)
|
||||
- **Right:** Chat | Cards; persona / pack / Ollama model; settings gear
|
||||
- **Chips:** aspect, Seed lock/−1, Vary, Turbo/RAW; LoRA chip row under them
|
||||
- Slash autocomplete when the composer starts with `/`
|
||||
|
||||
## UX
|
||||
|
||||
- **Send to Assistent** under the current Generate image (and History) — adds a Ref window and opens the Assistent tab
|
||||
- Drag / paste onto a Ref (drop on Generate snapshots into a new Ref)
|
||||
- **Snapshot gen** copies the live Generate window into a Ref
|
||||
- **As Init** / **As Mask** / **Clear Init** — selected window → Swarm `Init Image` / `Mask Image`
|
||||
- Enter sends; Shift+Enter newline
|
||||
- **Auto-apply** + **Auto-generate** (default on): patch from the LLM is applied and Generate runs when the patch changes prompt/params or includes `actions: ["generate"]`
|
||||
- Pure Q&A without a patch does **not** start Generate
|
||||
- **Interrupt** stops Swarm generation / clears busy state
|
||||
- **Civitai** search cards require **Confirm download** (uses Swarm `DoModelDownloadWS` + stored `civitai_api` key). Auto-download is off by default.
|
||||
- Pack auto-selects from the user message (critique / inpaint / params / compose / describe / write) unless you changed the dropdown yourself.
|
||||
- **Send to Assistent** under Generate/History → Ref + Assistent tab
|
||||
- Drop on Generate → new Ref + switch to Refs tab
|
||||
- **As Init** / **As Mask** / **Clear Init**
|
||||
- Enter sends; Shift+Enter newline; Interrupt cancels chat epoch (no late auto-apply)
|
||||
- Manual **Apply + Generate** / `/gen` always generate; Auto-generate checkbox only for LLM auto-path
|
||||
- Auto-critique rewrites prompt only (no second Generate)
|
||||
- Civitai Confirm required (unless auto-download)
|
||||
- Cards: form fields + previews; **Load Civitai meta** fetches by SHA (`by-hash`); status always explicit
|
||||
|
||||
### Slash commands (client-side, no LLM)
|
||||
|
||||
@@ -33,24 +31,23 @@ SwarmUI extension for **collaborative Krea 2** prompting via **Ollama**: chat +
|
||||
| `/gen` | Generate now |
|
||||
| `/look generate\|refN` | Attach that board window + ask the LLM to look |
|
||||
| `/init` `/mask` `/clear` | Same as board buttons |
|
||||
| `/interrupt` | Stop generation |
|
||||
| `/interrupt` | Stop generation / cancel chat |
|
||||
| `/aspect 16:9` | Set size from the official 1K table |
|
||||
| `/seed lock\|random` | Lock or randomize seed |
|
||||
| `/vary` | New seed, same prompt (+ generate if auto) |
|
||||
| `/pack write\|critique\|compose\|params\|inpaint\|describe` | Switch pack |
|
||||
| `/pack write\|critique\|compose\|params\|inpaint\|describe\|card` | Switch pack |
|
||||
| `/civitai <query>` | Ask LLM to search Civitai (Confirm still required) |
|
||||
| `/inventory` | Rescan models + refresh LoRA list |
|
||||
|
||||
## Requirements
|
||||
|
||||
- SwarmUI with a **Krea 2** checkpoint selected
|
||||
- Ollama on `http://127.0.0.1:11434` **on the GPU VM** (gpu-rent `LLM_RUNTIME=ollama`). The browser talks to SwarmUI; SwarmUI proxies `/api/tags` and `/api/chat`. URL in settings must stay `127.0.0.1:11434`, not the laptop tunnel port 17811.
|
||||
- At least one pulled model (`ollama pull` / `ollama-models.yaml`). Empty `/api/tags` → empty Model dropdown.
|
||||
- Optional: Civitai API key in SwarmUI User Settings for search/download.
|
||||
- Ollama on `http://127.0.0.1:11434` **on the GPU VM** (gpu-rent `LLM_RUNTIME=ollama`)
|
||||
- At least one pulled model
|
||||
- Optional: Civitai API key in SwarmUI User Settings
|
||||
|
||||
## Install
|
||||
|
||||
Clone into SwarmUI `src/Extensions/swarm-assistent` (or let **gpu-rent** seed it from `extensions.yaml` with `requires: ollama`):
|
||||
|
||||
```yaml
|
||||
swarmui:
|
||||
- url: https://gitea.hsrv.site/mrleo1nid/swarm-assistent.git
|
||||
@@ -59,81 +56,40 @@ swarmui:
|
||||
requires: ollama
|
||||
```
|
||||
|
||||
Restart / rebuild SwarmUI after clone.
|
||||
Restart / rebuild SwarmUI after clone. gpu-rent: `seed-extensions` + restart.
|
||||
|
||||
## Prompt packs
|
||||
|
||||
| Pack | Role |
|
||||
| --- | --- |
|
||||
| `base_krea2` | Always injected: Krea 2 rules + JSON patch / actions contract |
|
||||
| `write_prompt` | Craft / improve prompts (prose structure) |
|
||||
| `critique_image` | Vision critique → fixes |
|
||||
| `compose_scene` | Scene / moodboard via board refs → text |
|
||||
| `fix_params` | Aspect / steps / CFG / seed / batch |
|
||||
| `inpaint_edit` | Init Image img2img + Mask inpaint |
|
||||
| `describe_ref` | Vision → Krea prompt (no generate by default) |
|
||||
| `catalog_card` | Recommendation card JSON for a checkpoint/LoRA |
|
||||
| `base_krea2` | Always injected |
|
||||
| `write_prompt` | Craft / improve prompts |
|
||||
| `critique_image` | Vision critique |
|
||||
| `compose_scene` | Scene via board refs |
|
||||
| `fix_params` | Aspect / steps / CFG / seed (`krea_profile` in live context) |
|
||||
| `inpaint_edit` | Init + Mask |
|
||||
| `describe_ref` | Vision → prompt |
|
||||
| `catalog_card` | Recommendation card JSON |
|
||||
|
||||
**Persona** (tone) is a separate dropdown from pack — `base_krea2` → persona → pack. Overlay: `/mnt/swarm_data/Assistent/personas.json` (seeded from gpu-rent `assistent-personas.yaml`).
|
||||
**Personas:** `neutral`, `lewd`, `aggressive`, `cinema`, `terse`. Overlay: `/mnt/swarm_data/Assistent/personas.json` (gpu-rent seeds from `assistent-personas.yaml` → yaml + json). Assistent reads **json** only.
|
||||
|
||||
**Cards** subtab: edit/save `{stem}.assistent.json` next to weights; live chat gets cards for the current checkpoint + enabled LoRAs only. Uninstalled models can enqueue `.gpu-rent-wanted-models.yaml` for the next `up`.
|
||||
**Taste:** `/mnt/swarm_data/Assistent/taste.json` via `AssistentGetTaste` / `AssistentSaveTaste` (merged with browser localStorage by `updated`).
|
||||
|
||||
Live context (checkpoint, server inventory LoRAs + blurbs/triggers, `model_cards`, `taste_profile`, wildcards, current params, persona) is injected every request. Inventory refreshes each chat turn (disk rescan when stale / after download / `/inventory`). After Confirm download Assistent auto-writes a `.assistent.json` card.
|
||||
|
||||
**Wanted queue:** Cards → Enqueue wanted → `/mnt/swarm_data/.gpu-rent-wanted-models.yaml`. Laptop: `gpu-rent capture wanted` (also on `capture models` / seed `up`).
|
||||
|
||||
**Cloud-only Krea.ai features** (moodboards UI, Generative Sliders, Creativity Raw/Low/Medium/High) are **not** in Swarm. The assistant emulates them with prompt language + board refs. Optional patch fields `creativity` / `intensity` / `complexity` / `movement` guide the LLM only.
|
||||
|
||||
### Patch actions
|
||||
|
||||
```json
|
||||
{
|
||||
"prompt": "...",
|
||||
"loras": [{"name": "exact", "weight": 0.8, "triggers": ["..."]}],
|
||||
"aspect": "16:9",
|
||||
"width": 1376, "height": 768,
|
||||
"steps": 8, "cfg": 1,
|
||||
"seed": -1, "images": 1,
|
||||
"sigma_shift": 1.15,
|
||||
"creativity": "medium",
|
||||
"intensity": 0, "complexity": 0, "movement": 0,
|
||||
"vary": false, "lock_seed": false,
|
||||
"use_init_image": true,
|
||||
"init_creativity": 0.45,
|
||||
"use_mask_image": false,
|
||||
"clear_prompt_images": false,
|
||||
"look_at": ["generate"],
|
||||
"slot_to_init": "generate",
|
||||
"snapshot_generate": false,
|
||||
"pack": "critique_image",
|
||||
"actions": ["generate"],
|
||||
"search_query": null
|
||||
}
|
||||
```
|
||||
|
||||
- `aspect` — maps to official 1K sizes (`1:1`, `4:5`, `2:3`, `16:9`, `9:16`, `4:3`, `3:2`, `2.35:1`)
|
||||
- `vary` / `lock_seed` / `images`|`batch` — seed and batch helpers
|
||||
- `clear_prompt_images` — strip `<image…>` embeds from the prompt box
|
||||
- `pack` — switch the active prompt pack for a follow-up
|
||||
- `generate` — auto-generate after apply (when enabled)
|
||||
- `look_at` — hop vision from named board windows (`generate`, `ref1`, …)
|
||||
- `slot_to_init` / `slot_to_mask` — copy that window into Swarm Init / Mask
|
||||
- `snapshot_generate` — copy live Generate into a Ref
|
||||
- `search_civitai` + `search_query` — server searches Civitai, second LLM hop, Confirm cards in UI
|
||||
- `interrupt` — stop current generation
|
||||
|
||||
Mask convention: **white = edit**, black = keep. Creativity ≈ denoise (0–1).
|
||||
|
||||
Turbo defaults: steps **8**, CFG **1** (never 0), sigma shift **1.15**.
|
||||
**Cards:** `{stem}.assistent.json` next to weights; Civitai sidecar `{stem}.civitai.json`. Wanted queue → `.gpu-rent-wanted-models.yaml`.
|
||||
|
||||
## API routes
|
||||
|
||||
| Route | Role |
|
||||
| --- | --- |
|
||||
| `AssistentListModels` | Ollama `/api/tags` |
|
||||
| `AssistentListInventory` | LoRA / checkpoint / wildcard inventory from Swarm |
|
||||
| `AssistentSearchCivitai` | Civitai LoRA search |
|
||||
| `AssistentListInventory` | LoRA / checkpoint / wildcard inventory |
|
||||
| `AssistentListPersonas` | Bundled + overlay personas |
|
||||
| `AssistentGetPacks` | Prompt pack texts |
|
||||
| `AssistentGetCard` / `AssistentSaveCard` | `.assistent.json` cards |
|
||||
| `AssistentGetCardMeta` | Local sidecar + optional `fetch=true` Civitai by-hash |
|
||||
| `AssistentEnqueueWanted` | Wanted YAML queue |
|
||||
| `AssistentGetTaste` / `AssistentSaveTaste` | Persistent taste profile |
|
||||
| `AssistentSearchCivitai` | Civitai LoRA search |
|
||||
| `AssistentChat` | HTTP chat (+ Civitai hop) |
|
||||
| `AssistentChatWS` | Streaming chat WebSocket |
|
||||
|
||||
|
||||
+282
-42
@@ -42,7 +42,7 @@ public class SwarmAssistentExtension : Extension
|
||||
"catalog_card",
|
||||
];
|
||||
|
||||
public static readonly string[] DefaultPersonaIds = ["neutral", "lewd", "aggressive"];
|
||||
public static readonly string[] DefaultPersonaIds = ["neutral", "lewd", "aggressive", "cinema", "terse"];
|
||||
|
||||
const int MaxCivitaiHops = 2;
|
||||
const int MaxLorasInInventory = 150;
|
||||
@@ -61,7 +61,7 @@ public class SwarmAssistentExtension : Extension
|
||||
ExtensionAuthor = "mrleo1nid";
|
||||
Description = "Collaborative Krea 2 assistant: Ollama chat, multi-window board, personas, model cards, Generate loop, Civitai Confirm.";
|
||||
License = "MIT";
|
||||
Version = "0.5.3";
|
||||
Version = "0.6.0";
|
||||
Tags = ["tabs", "ui", "llm", "ollama", "krea", "inpaint"];
|
||||
}
|
||||
|
||||
@@ -77,6 +77,8 @@ public class SwarmAssistentExtension : Extension
|
||||
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);
|
||||
Logs.Init("Swarm Assistent extension loaded (Ollama proxy + personas + model cards)");
|
||||
@@ -217,6 +219,8 @@ public class SwarmAssistentExtension : Extension
|
||||
{
|
||||
"lewd" => "Пошляк",
|
||||
"aggressive" => "Агрессивный",
|
||||
"cinema" => "Кинооператор",
|
||||
"terse" => "Короткий",
|
||||
_ => "Нейтральный",
|
||||
},
|
||||
["prompt"] = text,
|
||||
@@ -247,11 +251,19 @@ public class SwarmAssistentExtension : Extension
|
||||
{
|
||||
continue;
|
||||
}
|
||||
string overlayPrompt = po["prompt"]?.ToString() ?? "";
|
||||
if (string.IsNullOrWhiteSpace(overlayPrompt) && byId.ContainsKey(id))
|
||||
{
|
||||
// Keep bundled prompt when overlay prompt is empty.
|
||||
byId[id]["title"] = po["title"]?.ToString() ?? byId[id]["title"];
|
||||
byId[id]["source"] = "overlay+bundled";
|
||||
continue;
|
||||
}
|
||||
byId[id] = new JObject
|
||||
{
|
||||
["id"] = id,
|
||||
["title"] = po["title"]?.ToString() ?? id,
|
||||
["prompt"] = po["prompt"]?.ToString() ?? "",
|
||||
["prompt"] = overlayPrompt,
|
||||
["source"] = "overlay",
|
||||
};
|
||||
}
|
||||
@@ -401,7 +413,8 @@ public class SwarmAssistentExtension : Extension
|
||||
|
||||
// Not installed — draft into wanted-cards + optionally enqueue download for next up.
|
||||
Directory.CreateDirectory(WantedCardsDir());
|
||||
string vid = card["version_id"]?.ToString() ?? "draft";
|
||||
string rawVid = card["version_id"]?.ToString() ?? "draft";
|
||||
string vid = Regex.IsMatch(rawVid, @"^\d+$") ? rawVid : "draft";
|
||||
string draft = Path.Combine(WantedCardsDir(), $"{vid}.assistent.json");
|
||||
File.WriteAllText(draft, card.ToString(Newtonsoft.Json.Formatting.Indented), Encoding.UTF8);
|
||||
if (enqueue_wanted || !string.IsNullOrWhiteSpace(card["civitai_url"]?.ToString()))
|
||||
@@ -576,14 +589,56 @@ public class SwarmAssistentExtension : Extension
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public async Task<JObject> AssistentGetCardMeta(Session session, string kind, string name, int version_id = 0)
|
||||
string TasteJsonPath() => Path.Combine(DataRoot(), "Assistent", "taste.json");
|
||||
|
||||
public async Task<JObject> AssistentGetTaste(Session session)
|
||||
{
|
||||
// Pull Civitai sidecar next to weight + optional API version for examples.
|
||||
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<JObject> 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 };
|
||||
}
|
||||
|
||||
public async Task<JObject> AssistentGetCardMeta(Session session, string kind, string name, int version_id = 0, bool fetch = false)
|
||||
{
|
||||
string set = SetNameForKind(kind);
|
||||
string weight = ModelWeightPath(set, name);
|
||||
JObject civitai = null;
|
||||
JArray exampleUrls = [];
|
||||
JArray previewUrls = [];
|
||||
bool hasSidecar = false;
|
||||
string fetchError = null;
|
||||
bool fetched = false;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(weight))
|
||||
{
|
||||
string stem = Path.GetFileNameWithoutExtension(weight);
|
||||
@@ -591,6 +646,7 @@ public class SwarmAssistentExtension : Extension
|
||||
string side = Path.Combine(dir ?? "", $"{stem}.civitai.json");
|
||||
if (File.Exists(side))
|
||||
{
|
||||
hasSidecar = true;
|
||||
try
|
||||
{
|
||||
civitai = JObject.Parse(File.ReadAllText(side, Encoding.UTF8));
|
||||
@@ -600,43 +656,113 @@ public class SwarmAssistentExtension : Extension
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
foreach (string suffix in new[] { ".preview.jpg", ".preview.png", ".preview.jpeg", ".jpg", ".png", ".webp" })
|
||||
{
|
||||
string prev = Path.Combine(dir ?? "", stem + suffix);
|
||||
if (File.Exists(prev))
|
||||
{
|
||||
// Swarm View path — relative URL works in the same origin browser session.
|
||||
previewUrls.Add($"View/Models/{(kind == "lora" ? "Lora" : "Stable-Diffusion")}/{Path.GetFileName(prev)}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (civitai is not null)
|
||||
{
|
||||
if (version_id <= 0)
|
||||
{
|
||||
version_id = civitai["id"]?.Value<int?>() ?? 0;
|
||||
}
|
||||
if (civitai["images"] is JArray imgs)
|
||||
{
|
||||
foreach (JToken img in imgs.Take(3))
|
||||
{
|
||||
string u = img?["url"]?.ToString();
|
||||
if (!string.IsNullOrWhiteSpace(u))
|
||||
{
|
||||
exampleUrls.Add(u);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (civitai["trainedWords"] is null && civitai["model"] is JObject)
|
||||
{
|
||||
// keep as-is
|
||||
}
|
||||
CollectExampleUrls(civitai, exampleUrls);
|
||||
}
|
||||
JObject card = ReadCardObject(kind, name);
|
||||
|
||||
string hash = null;
|
||||
string trigger = null;
|
||||
try
|
||||
{
|
||||
if (Program.T2IModelSets.TryGetValue(set, out T2IModelHandler h)
|
||||
&& h.Models.TryGetValue(name, out T2IModel m))
|
||||
&& (h.Models.TryGetValue(name, out T2IModel m)
|
||||
|| h.Models.TryGetValue(name.Replace('\\', '/'), out m)))
|
||||
{
|
||||
trigger = m.Metadata?.TriggerPhrase;
|
||||
hash = m.Metadata?.Hash;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore
|
||||
}
|
||||
|
||||
if (fetch && civitai is null)
|
||||
{
|
||||
string apiKey = session.User.GetGenericData("civitai_api", "key") ?? "";
|
||||
if (string.IsNullOrWhiteSpace(apiKey))
|
||||
{
|
||||
fetchError = "Civitai: нет ключа в User Settings";
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
JObject remote = null;
|
||||
if (version_id > 0)
|
||||
{
|
||||
remote = await FetchCivitaiModelVersion(apiKey, version_id);
|
||||
}
|
||||
if (remote is null && !string.IsNullOrWhiteSpace(hash))
|
||||
{
|
||||
string sha = hash.Trim().ToLowerInvariant();
|
||||
if (sha.StartsWith("sha256:"))
|
||||
{
|
||||
sha = sha["sha256:".Length..];
|
||||
}
|
||||
if (sha.Length == 64)
|
||||
{
|
||||
remote = await FetchCivitaiByHash(apiKey, sha);
|
||||
}
|
||||
else
|
||||
{
|
||||
fetchError ??= "Civitai: хеш модели не SHA256";
|
||||
}
|
||||
}
|
||||
if (remote is not null)
|
||||
{
|
||||
civitai = remote;
|
||||
fetched = true;
|
||||
version_id = remote["id"]?.Value<int?>() ?? version_id;
|
||||
CollectExampleUrls(remote, exampleUrls);
|
||||
if (!string.IsNullOrWhiteSpace(weight))
|
||||
{
|
||||
try
|
||||
{
|
||||
string stem = Path.GetFileNameWithoutExtension(weight);
|
||||
string dir = Path.GetDirectoryName(weight);
|
||||
string side = Path.Combine(dir ?? "", $"{stem}.civitai.json");
|
||||
File.WriteAllText(side, remote.ToString(Newtonsoft.Json.Formatting.Indented), Encoding.UTF8);
|
||||
hasSidecar = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logs.Debug($"AssistentGetCardMeta write sidecar: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (fetchError is null)
|
||||
{
|
||||
fetchError = string.IsNullOrWhiteSpace(hash)
|
||||
? "Civitai: нет hash и version_id"
|
||||
: "Хеш не найден на Civitai";
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
fetchError = $"Civitai: {ex.Message}";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
JObject card = ReadCardObject(kind, name);
|
||||
return new JObject
|
||||
{
|
||||
["success"] = true,
|
||||
@@ -645,13 +771,124 @@ public class SwarmAssistentExtension : Extension
|
||||
["version_id"] = version_id,
|
||||
["trigger_phrase"] = trigger,
|
||||
["has_card"] = card is not null,
|
||||
["has_sidecar"] = hasSidecar,
|
||||
["fetched"] = fetched,
|
||||
["fetch_error"] = fetchError,
|
||||
["card"] = card,
|
||||
["civitai"] = civitai,
|
||||
["example_urls"] = exampleUrls,
|
||||
["preview_urls"] = previewUrls,
|
||||
["weight_path"] = weight,
|
||||
["hash"] = hash,
|
||||
};
|
||||
}
|
||||
|
||||
static void CollectExampleUrls(JObject civitai, JArray exampleUrls)
|
||||
{
|
||||
if (civitai?["images"] is not JArray imgs)
|
||||
{
|
||||
return;
|
||||
}
|
||||
foreach (JToken img in imgs.Take(6))
|
||||
{
|
||||
string u = img?["url"]?.ToString();
|
||||
if (!string.IsNullOrWhiteSpace(u))
|
||||
{
|
||||
exampleUrls.Add(u);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async Task<JObject> FetchCivitaiByHash(string apiKey, string sha)
|
||||
{
|
||||
string[] hosts = ["civitai.red", "civitai.com"];
|
||||
Exception last = null;
|
||||
foreach (string host in hosts)
|
||||
{
|
||||
try
|
||||
{
|
||||
string url = $"https://{host}/api/v1/model-versions/by-hash/{sha}";
|
||||
using HttpRequestMessage req = new(HttpMethod.Get, url);
|
||||
if (!string.IsNullOrWhiteSpace(apiKey))
|
||||
{
|
||||
req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", apiKey.Trim());
|
||||
}
|
||||
using HttpResponseMessage resp = await HttpClient.SendAsync(req);
|
||||
string body = await resp.Content.ReadAsStringAsync();
|
||||
if (resp.StatusCode == System.Net.HttpStatusCode.NotFound)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!resp.IsSuccessStatusCode)
|
||||
{
|
||||
last = new Exception($"HTTP {(int)resp.StatusCode}: {Clip(body, 160)}");
|
||||
if ((int)resp.StatusCode is 401 or 403)
|
||||
{
|
||||
throw last;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
return JObject.Parse(body);
|
||||
}
|
||||
catch (Exception ex) when (ex is not HttpRequestException && ex.Message.Contains("401"))
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
last = ex;
|
||||
}
|
||||
}
|
||||
if (last is not null)
|
||||
{
|
||||
throw last;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async Task<JObject> FetchCivitaiModelVersion(string apiKey, int versionId)
|
||||
{
|
||||
string[] hosts = ["civitai.red", "civitai.com"];
|
||||
Exception last = null;
|
||||
foreach (string host in hosts)
|
||||
{
|
||||
try
|
||||
{
|
||||
string url = $"https://{host}/api/v1/model-versions/{versionId}";
|
||||
using HttpRequestMessage req = new(HttpMethod.Get, url);
|
||||
if (!string.IsNullOrWhiteSpace(apiKey))
|
||||
{
|
||||
req.Headers.Authorization = new AuthenticationHeaderValue("Bearer", apiKey.Trim());
|
||||
}
|
||||
using HttpResponseMessage resp = await HttpClient.SendAsync(req);
|
||||
string body = await resp.Content.ReadAsStringAsync();
|
||||
if (!resp.IsSuccessStatusCode)
|
||||
{
|
||||
last = new Exception($"HTTP {(int)resp.StatusCode}: {Clip(body, 160)}");
|
||||
if ((int)resp.StatusCode is 401 or 403)
|
||||
{
|
||||
throw last;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
return JObject.Parse(body);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
last = ex;
|
||||
if (ex.Message.Contains("401") || ex.Message.Contains("403"))
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (last is not null)
|
||||
{
|
||||
throw last;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>Server-side LoRA / checkpoint / wildcard inventory (not DOM scrape).
|
||||
/// Pass rescan=true after downloads so new files appear (calls Program.RefreshAllModelSets).</summary>
|
||||
public async Task<JObject> AssistentListInventory(Session session, bool rescan = false)
|
||||
@@ -810,6 +1047,27 @@ public class SwarmAssistentExtension : Extension
|
||||
["has_card"] = hasCard,
|
||||
["krea_likely"] = LooksLikeKreaArch(model),
|
||||
};
|
||||
if (!string.IsNullOrWhiteSpace(weight))
|
||||
{
|
||||
string stem = Path.GetFileNameWithoutExtension(weight);
|
||||
string dir = Path.GetDirectoryName(weight);
|
||||
string side = Path.Combine(dir ?? "", $"{stem}.civitai.json");
|
||||
entry["has_sidecar"] = File.Exists(side);
|
||||
foreach (string suffix in new[] { ".preview.jpg", ".preview.png", ".preview.jpeg", ".jpg", ".png", ".webp" })
|
||||
{
|
||||
string prev = Path.Combine(dir ?? "", stem + suffix);
|
||||
if (File.Exists(prev))
|
||||
{
|
||||
string folder = kind == "lora" ? "Lora" : "Stable-Diffusion";
|
||||
entry["preview_url"] = $"View/Models/{folder}/{Path.GetFileName(prev)}";
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
entry["has_sidecar"] = false;
|
||||
}
|
||||
if (triggers is not null && triggers.Count > 0)
|
||||
{
|
||||
entry["triggers"] = triggers;
|
||||
@@ -1181,21 +1439,7 @@ public class SwarmAssistentExtension : Extension
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (!string.IsNullOrWhiteSpace(ExtractSearchQuery(patch)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (patch["actions"] is JArray acts)
|
||||
{
|
||||
foreach (JToken a in acts)
|
||||
{
|
||||
if (string.Equals(a?.ToString(), "search_civitai", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return !string.IsNullOrWhiteSpace(ExtractSearchQuery(patch));
|
||||
}
|
||||
|
||||
async Task<(string reply, JObject raw, JArray civitaiResults)> RunChatWithHops(
|
||||
@@ -1228,10 +1472,6 @@ public class SwarmAssistentExtension : Extension
|
||||
}
|
||||
string query = ExtractSearchQuery(patch);
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
{
|
||||
query = userMessages.LastOrDefault(m => m["role"]?.ToString() == "user")?["content"]?.ToString() ?? "";
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(query))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,93 +1,106 @@
|
||||
<div class="swarm-assistent-root" id="swarm_assistent_root">
|
||||
<div class="sa-gate" id="sa_gate" hidden>
|
||||
<p>Swarm Assistent is for <strong>Krea 2</strong> models only. Select a Krea 2 checkpoint to enable the chat.</p>
|
||||
<p>Swarm Assistent только для моделей <strong>Krea 2</strong>. Выбери checkpoint Krea 2, чтобы включить чат.</p>
|
||||
</div>
|
||||
<div class="sa-layout" id="sa_layout">
|
||||
<aside class="sa-image-pane" id="sa_image_pane">
|
||||
<div class="sa-board-head">
|
||||
<span class="sa-board-title">Board</span>
|
||||
<span class="sa-board-hint">Generate live · refs for vision</span>
|
||||
<button type="button" class="basic-button sa-icon-btn" id="sa_btn_add_ref" title="Add a reference window">+ Ref</button>
|
||||
<span class="sa-board-title">Доска</span>
|
||||
<div class="sa-board-tabs" role="tablist" aria-label="Доска">
|
||||
<button type="button" class="sa-board-tab sa-board-tab-active" data-board="generate" id="sa_board_tab_gen" role="tab" aria-selected="true">Generate</button>
|
||||
<button type="button" class="sa-board-tab" data-board="refs" id="sa_board_tab_refs" role="tab" aria-selected="false">
|
||||
Refs
|
||||
<span class="sa-board-badge" id="sa_refs_badge" hidden></span>
|
||||
</button>
|
||||
</div>
|
||||
<button type="button" class="basic-button sa-icon-btn" id="sa_btn_add_ref" title="Добавить окно референса">+ Ref</button>
|
||||
</div>
|
||||
<div class="sa-board" id="sa_board" tabindex="0" title="Drop images onto a window, or paste (Ctrl+V). Click a window to select it."></div>
|
||||
<div class="sa-image-actions">
|
||||
<button type="button" class="basic-button" id="sa_btn_use_current" title="Copy current Generate into a reference window">Snapshot gen</button>
|
||||
<button type="button" class="basic-button" id="sa_btn_as_init" title="Set selected window as Swarm Init Image (img2img)">As Init</button>
|
||||
<button type="button" class="basic-button" id="sa_btn_as_mask" title="Set selected window as Mask (inpaint — white=edit)">As Mask</button>
|
||||
<button type="button" class="basic-button" id="sa_btn_clear_init" title="Clear Init Image + Mask on Generate tab">Clear Init</button>
|
||||
<button type="button" class="basic-button" id="sa_btn_clear_image" title="Clear the selected reference window">Clear slot</button>
|
||||
<div class="sa-board" id="sa_board" tabindex="0" title="Перетащи изображение на окно или вставь (Ctrl+V). Кликни, чтобы выбрать."></div>
|
||||
<div class="sa-image-actions" id="sa_image_actions">
|
||||
<button type="button" class="basic-button" id="sa_btn_use_current" title="Скопировать текущий Generate в Ref">Снимок gen</button>
|
||||
<button type="button" class="basic-button" id="sa_btn_as_init" title="Выбранное окно → Init Image (img2img)">Как Init</button>
|
||||
<button type="button" class="basic-button" id="sa_btn_as_mask" title="Выбранное окно → Mask (inpaint, белое=правка)">Как Mask</button>
|
||||
<button type="button" class="basic-button" id="sa_btn_clear_init" title="Очистить Init Image + Mask">Сброс Init</button>
|
||||
<button type="button" class="basic-button" id="sa_btn_clear_image" title="Очистить выбранный Ref">Очистить слот</button>
|
||||
</div>
|
||||
</aside>
|
||||
<div class="sa-splitter" id="sa_splitter" role="separator" aria-orientation="vertical" title="Drag to resize"></div>
|
||||
<div class="sa-splitter" id="sa_splitter" role="separator" aria-orientation="vertical" title="Потяни, чтобы изменить ширину"></div>
|
||||
<section class="sa-chat-pane">
|
||||
<header class="sa-chat-header">
|
||||
<div class="sa-chat-title">
|
||||
Assistent
|
||||
<span class="sa-live-dot" id="sa_live_dot" hidden></span>
|
||||
<div class="sa-subtabs" role="tablist">
|
||||
<button type="button" class="sa-subtab sa-subtab-active" data-view="chat" id="sa_tab_chat">Chat</button>
|
||||
<button type="button" class="sa-subtab" data-view="cards" id="sa_tab_cards">Cards</button>
|
||||
<button type="button" class="sa-subtab sa-subtab-active" data-view="chat" id="sa_tab_chat">Чат</button>
|
||||
<button type="button" class="sa-subtab" data-view="cards" id="sa_tab_cards">Карточки</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sa-header-right">
|
||||
<select id="sa_persona" class="sa-select" title="Character / tone">
|
||||
<select id="sa_persona" class="sa-select" title="Характер / тон">
|
||||
<option value="neutral">Нейтральный</option>
|
||||
</select>
|
||||
<select id="sa_pack" class="sa-select" title="Prompt pack">
|
||||
<option value="write_prompt">Write prompt</option>
|
||||
<option value="critique_image">Critique image</option>
|
||||
<option value="compose_scene">Compose scene</option>
|
||||
<option value="fix_params">Fix params</option>
|
||||
<select id="sa_pack" class="sa-select" title="Пакет промпта">
|
||||
<option value="write_prompt">Написать промпт</option>
|
||||
<option value="critique_image">Критика кадра</option>
|
||||
<option value="compose_scene">Собрать сцену</option>
|
||||
<option value="fix_params">Параметры</option>
|
||||
<option value="inpaint_edit">Inpaint / img2img</option>
|
||||
<option value="describe_ref">Describe ref</option>
|
||||
<option value="catalog_card">Catalog card</option>
|
||||
<option value="describe_ref">Описать ref</option>
|
||||
<option value="catalog_card">Карточка модели</option>
|
||||
</select>
|
||||
<select id="sa_model" class="sa-select sa-model-select" title="Ollama model">
|
||||
<option value="">Loading models…</option>
|
||||
<select id="sa_model" class="sa-select sa-model-select" title="Модель Ollama">
|
||||
<option value="">Загрузка моделей…</option>
|
||||
</select>
|
||||
<button type="button" class="basic-button sa-icon-btn" id="sa_btn_settings" title="Settings" aria-label="Settings">⚙</button>
|
||||
<button type="button" class="basic-button sa-icon-btn" id="sa_btn_settings" title="Настройки" aria-label="Настройки">⚙</button>
|
||||
</div>
|
||||
</header>
|
||||
<div class="sa-settings" id="sa_settings" hidden>
|
||||
<label>Ollama URL <input type="text" id="sa_base_url" value="http://127.0.0.1:11434" /></label>
|
||||
<button type="button" class="basic-button" id="sa_btn_refresh_models">Refresh models</button>
|
||||
<button type="button" class="basic-button" id="sa_btn_refresh_inventory">Refresh inventory</button>
|
||||
<label class="sa-check"><input type="checkbox" id="sa_auto_vision" /> Auto-attach Generate window to chat</label>
|
||||
<label class="sa-check"><input type="checkbox" id="sa_auto_apply" checked /> Auto-apply patch</label>
|
||||
<label class="sa-check"><input type="checkbox" id="sa_auto_generate" checked /> Auto-generate after patch</label>
|
||||
<label class="sa-check"><input type="checkbox" id="sa_auto_critique" /> Auto-critique after generate</label>
|
||||
<label class="sa-check sa-danger" title="Dangerous — downloads without Confirm"><input type="checkbox" id="sa_auto_download" /> Auto-download Civitai (off)</label>
|
||||
<button type="button" class="basic-button" id="sa_btn_refresh_models">Обновить модели</button>
|
||||
<button type="button" class="basic-button" id="sa_btn_refresh_inventory">Обновить inventory</button>
|
||||
<label class="sa-check"><input type="checkbox" id="sa_auto_vision" /> Авто-прикреплять Generate к чату</label>
|
||||
<label class="sa-check"><input type="checkbox" id="sa_auto_apply" checked /> Авто-применять патч</label>
|
||||
<label class="sa-check"><input type="checkbox" id="sa_auto_generate" checked /> Авто-Generate после патча</label>
|
||||
<label class="sa-check"><input type="checkbox" id="sa_auto_critique" /> Авто-критика после Generate</label>
|
||||
<label class="sa-check sa-danger" title="Опасно — скачивает без Confirm"><input type="checkbox" id="sa_auto_download" /> Авто-скачивание Civitai (выкл)</label>
|
||||
</div>
|
||||
|
||||
<div class="sa-view" id="sa_view_chat">
|
||||
<div class="sa-messages" id="sa_messages">
|
||||
<div class="sa-chat-empty" id="sa_chat_empty">
|
||||
<div class="sa-chat-empty-title">Collaborative Krea 2</div>
|
||||
<div class="sa-chat-empty-hint">Write a prompt, drop refs, pick a persona, or open <em>Cards</em> for LoRA tips.</div>
|
||||
<div class="sa-chat-empty-title">Совместная работа с Krea 2</div>
|
||||
<div class="sa-chat-empty-hint">Напиши промпт, кинь refs, выбери персону или открой <em>Карточки</em> для LoRA.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sa-livebar" id="sa_livebar" hidden>
|
||||
<span class="sa-spinner" aria-hidden="true"></span>
|
||||
<span class="sa-livebar-text" id="sa_livebar_text">Working…</span>
|
||||
<span class="sa-livebar-text" id="sa_livebar_text">Работаю…</span>
|
||||
<span class="sa-elapsed" id="sa_elapsed"></span>
|
||||
</div>
|
||||
<div class="sa-composer" id="sa_composer">
|
||||
<div class="sa-chips" id="sa_chips" role="toolbar" aria-label="Quick params">
|
||||
<div class="sa-chips" id="sa_chips" role="toolbar" aria-label="Быстрые параметры">
|
||||
<button type="button" class="sa-chip" data-aspect="1:1" title="1024×1024">1:1</button>
|
||||
<button type="button" class="sa-chip" data-aspect="4:5" title="928×1152">4:5</button>
|
||||
<button type="button" class="sa-chip" data-aspect="2:3" title="832×1248">2:3</button>
|
||||
<button type="button" class="sa-chip" data-aspect="16:9" title="1376×768">16:9</button>
|
||||
<button type="button" class="sa-chip" data-aspect="9:16" title="768×1376">9:16</button>
|
||||
<span class="sa-chip-sep" aria-hidden="true"></span>
|
||||
<button type="button" class="sa-chip" data-seed="lock" title="Keep current seed">Seed lock</button>
|
||||
<button type="button" class="sa-chip" data-seed="random" title="Random seed">Seed −1</button>
|
||||
<button type="button" class="sa-chip" data-vary="1" title="Same prompt, new seed + generate">Vary</button>
|
||||
<button type="button" class="sa-chip" data-seed="lock" title="Оставить текущий seed">Seed lock</button>
|
||||
<button type="button" class="sa-chip" data-seed="random" title="Случайный seed">Seed −1</button>
|
||||
<button type="button" class="sa-chip" data-vary="1" title="Тот же промпт, новый seed + generate">Vary</button>
|
||||
<span class="sa-chip-sep" aria-hidden="true"></span>
|
||||
<button type="button" class="sa-chip" data-krea-profile="turbo" title="Turbo: steps 8, CFG 1">Turbo</button>
|
||||
<button type="button" class="sa-chip" data-krea-profile="raw" title="RAW: steps 28, CFG 4.5">RAW</button>
|
||||
</div>
|
||||
<div class="sa-lora-chips" id="sa_lora_chips" role="toolbar" aria-label="Активные LoRA"></div>
|
||||
<div class="sa-slash-wrap">
|
||||
<textarea id="sa_input" rows="3" placeholder="Промпт, img2img, критика… Enter = отправить · /help = команды"></textarea>
|
||||
<div class="sa-slash-menu" id="sa_slash_menu" hidden role="listbox"></div>
|
||||
</div>
|
||||
<textarea id="sa_input" rows="3" placeholder="Ask for a prompt, img2img, critique… Enter = send · /help = commands"></textarea>
|
||||
<div class="sa-composer-actions">
|
||||
<button type="button" class="basic-button sa-primary" id="sa_btn_send">Send</button>
|
||||
<button type="button" class="basic-button" id="sa_btn_interrupt" title="Interrupt generation / chat" hidden>Interrupt</button>
|
||||
<button type="button" class="basic-button" id="sa_btn_clear">Clear chat</button>
|
||||
<button type="button" class="basic-button sa-primary" id="sa_btn_send">Отправить</button>
|
||||
<button type="button" class="basic-button" id="sa_btn_interrupt" title="Прервать генерацию / чат" hidden>Стоп</button>
|
||||
<button type="button" class="basic-button" id="sa_btn_clear">Очистить чат</button>
|
||||
<span class="sa-status" id="sa_status"></span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -98,25 +111,36 @@
|
||||
<div class="sa-cards-list-pane">
|
||||
<div class="sa-cards-filter">
|
||||
<select id="sa_cards_kind" class="sa-select">
|
||||
<option value="all">All</option>
|
||||
<option value="all">Все</option>
|
||||
<option value="checkpoint">Checkpoints</option>
|
||||
<option value="lora">LoRAs</option>
|
||||
</select>
|
||||
<button type="button" class="basic-button" id="sa_btn_cards_refresh">Refresh</button>
|
||||
<button type="button" class="basic-button" id="sa_btn_cards_refresh">Обновить</button>
|
||||
</div>
|
||||
<div class="sa-cards-list" id="sa_cards_list"></div>
|
||||
</div>
|
||||
<div class="sa-cards-editor-pane">
|
||||
<div class="sa-cards-editor-head">
|
||||
<strong id="sa_card_title">Select a model</strong>
|
||||
<strong id="sa_card_title">Выбери модель</strong>
|
||||
<span class="sa-card-badge" id="sa_card_badge" hidden></span>
|
||||
</div>
|
||||
<textarea id="sa_card_json" rows="16" placeholder="Card JSON will appear here…" spellcheck="false"></textarea>
|
||||
<div class="sa-card-previews" id="sa_card_previews" hidden></div>
|
||||
<div class="sa-card-form" id="sa_card_form">
|
||||
<label>Triggers <input type="text" id="sa_card_triggers" placeholder="слово1, слово2" /></label>
|
||||
<label>Weight <input type="number" id="sa_card_weight" step="0.05" min="0" max="2" value="0.8" /></label>
|
||||
<label>When <textarea id="sa_card_when" rows="2" placeholder="Когда использовать"></textarea></label>
|
||||
<label>Avoid <textarea id="sa_card_avoid" rows="2" placeholder="Чего избегать"></textarea></label>
|
||||
<label>Prompt hint <textarea id="sa_card_hint" rows="2" placeholder="Подсказка для промпта"></textarea></label>
|
||||
<label>Notes <textarea id="sa_card_notes" rows="2" placeholder="Заметки"></textarea></label>
|
||||
<label>Civitai URL <input type="text" id="sa_card_url" placeholder="https://civitai…" /></label>
|
||||
<label class="sa-card-json-toggle"><input type="checkbox" id="sa_card_show_json" /> Показать JSON</label>
|
||||
<textarea id="sa_card_json" rows="8" hidden placeholder="Card JSON…" spellcheck="false"></textarea>
|
||||
</div>
|
||||
<div class="sa-cards-actions">
|
||||
<button type="button" class="basic-button" id="sa_btn_card_meta">Load Civitai meta</button>
|
||||
<button type="button" class="basic-button sa-primary" id="sa_btn_card_generate">Generate with Assistent</button>
|
||||
<button type="button" class="basic-button" id="sa_btn_card_save">Save card</button>
|
||||
<button type="button" class="basic-button" id="sa_btn_card_wanted" title="Add to next-up download list">Enqueue wanted</button>
|
||||
<button type="button" class="basic-button" id="sa_btn_card_meta">Загрузить Civitai meta</button>
|
||||
<button type="button" class="basic-button sa-primary" id="sa_btn_card_generate">Сгенерировать с Assistent</button>
|
||||
<button type="button" class="basic-button" id="sa_btn_card_save">Сохранить карточку</button>
|
||||
<button type="button" class="basic-button" id="sa_btn_card_wanted" title="В очередь на следующий up">В wanted</button>
|
||||
<span class="sa-status" id="sa_card_status"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user