Improve Assistent UX: send from Generate, drag-drop, resize.
Add Send to Assistent media button, paste/drop vision image, pane splitter, and polish layout for Krea collaborative chat. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+172
-40
@@ -3,71 +3,148 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: calc(100vh - 8rem);
|
height: calc(100vh - 8rem);
|
||||||
min-height: 28rem;
|
min-height: 28rem;
|
||||||
padding: 0.5rem;
|
padding: 0.5rem 0.65rem 0.65rem;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sa-gate {
|
.sa-gate {
|
||||||
padding: 1.25rem;
|
padding: 1.25rem 1.5rem;
|
||||||
opacity: 0.9;
|
margin-bottom: 0.5rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
border: 1px solid color-mix(in srgb, currentColor 22%, transparent);
|
||||||
|
background: color-mix(in srgb, currentColor 6%, transparent);
|
||||||
|
opacity: 0.95;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sa-layout {
|
.sa-layout {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
gap: 0.75rem;
|
gap: 0;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sa-image-pane {
|
.sa-image-pane {
|
||||||
flex: 0 0 32%;
|
flex: 0 0 var(--sa-image-width, 34%);
|
||||||
max-width: 22rem;
|
max-width: 40%;
|
||||||
|
min-width: 11rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
min-width: 12rem;
|
}
|
||||||
|
|
||||||
|
.sa-splitter {
|
||||||
|
flex: 0 0 0.45rem;
|
||||||
|
margin: 0 0.15rem;
|
||||||
|
cursor: col-resize;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: color-mix(in srgb, currentColor 12%, transparent);
|
||||||
|
transition: background 0.15s ease;
|
||||||
|
align-self: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sa-splitter:hover,
|
||||||
|
.sa-splitter.sa-dragging {
|
||||||
|
background: color-mix(in srgb, currentColor 35%, transparent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.sa-image-frame {
|
.sa-image-frame {
|
||||||
|
position: relative;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
border: 1px solid color-mix(in srgb, currentColor 25%, transparent);
|
border: 1px dashed color-mix(in srgb, currentColor 28%, transparent);
|
||||||
border-radius: 0.35rem;
|
border-radius: 0.55rem;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: color-mix(in srgb, currentColor 6%, transparent);
|
background:
|
||||||
|
radial-gradient(ellipse at 30% 20%, color-mix(in srgb, currentColor 8%, transparent), transparent 55%),
|
||||||
|
color-mix(in srgb, currentColor 4%, transparent);
|
||||||
min-height: 14rem;
|
min-height: 14rem;
|
||||||
|
outline: none;
|
||||||
|
transition: border-color 0.15s ease, box-shadow 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sa-image-frame:focus-visible {
|
||||||
|
box-shadow: 0 0 0 2px color-mix(in srgb, currentColor 35%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sa-image-frame.sa-has-image {
|
||||||
|
border-style: solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sa-image-frame.sa-dragover {
|
||||||
|
border-color: color-mix(in srgb, currentColor 70%, transparent);
|
||||||
|
box-shadow: inset 0 0 0 2px color-mix(in srgb, currentColor 25%, transparent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.sa-image-frame img {
|
.sa-image-frame img {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sa-image-empty {
|
.sa-image-empty {
|
||||||
padding: 1rem;
|
padding: 1.25rem 1rem;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
opacity: 0.65;
|
opacity: 0.7;
|
||||||
font-size: 0.95rem;
|
}
|
||||||
|
|
||||||
|
.sa-empty-title {
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
margin-bottom: 0.35rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sa-empty-hint {
|
||||||
|
font-size: 0.88rem;
|
||||||
|
line-height: 1.4;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sa-drop-overlay {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: color-mix(in srgb, currentColor 18%, transparent);
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sa-vision-chip {
|
||||||
|
position: absolute;
|
||||||
|
top: 0.45rem;
|
||||||
|
left: 0.45rem;
|
||||||
|
padding: 0.15rem 0.45rem;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
background: color-mix(in srgb, currentColor 16%, transparent);
|
||||||
|
border: 1px solid color-mix(in srgb, currentColor 28%, transparent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.sa-image-actions {
|
.sa-image-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 0.5rem;
|
gap: 0.45rem;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sa-chat-pane {
|
.sa-chat-pane {
|
||||||
flex: 1 1 68%;
|
flex: 1 1 auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
border: 1px solid color-mix(in srgb, currentColor 25%, transparent);
|
border: 1px solid color-mix(in srgb, currentColor 22%, transparent);
|
||||||
border-radius: 0.35rem;
|
border-radius: 0.55rem;
|
||||||
|
background: color-mix(in srgb, currentColor 3%, transparent);
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sa-chat-header {
|
.sa-chat-header {
|
||||||
@@ -75,13 +152,14 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
padding: 0.45rem 0.65rem;
|
padding: 0.5rem 0.75rem;
|
||||||
border-bottom: 1px solid color-mix(in srgb, currentColor 20%, transparent);
|
border-bottom: 1px solid color-mix(in srgb, currentColor 18%, transparent);
|
||||||
|
background: color-mix(in srgb, currentColor 5%, transparent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.sa-chat-title {
|
.sa-chat-title {
|
||||||
font-weight: 600;
|
font-weight: 650;
|
||||||
letter-spacing: 0.02em;
|
letter-spacing: 0.03em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sa-header-right {
|
.sa-header-right {
|
||||||
@@ -99,8 +177,8 @@
|
|||||||
.sa-settings {
|
.sa-settings {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 0.45rem;
|
gap: 0.45rem;
|
||||||
padding: 0.65rem;
|
padding: 0.7rem 0.75rem;
|
||||||
border-bottom: 1px solid color-mix(in srgb, currentColor 20%, transparent);
|
border-bottom: 1px solid color-mix(in srgb, currentColor 18%, transparent);
|
||||||
background: color-mix(in srgb, currentColor 5%, transparent);
|
background: color-mix(in srgb, currentColor 5%, transparent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -117,72 +195,107 @@
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sa-header-right .sa-select {
|
||||||
|
width: auto;
|
||||||
|
min-width: 9rem;
|
||||||
|
max-width: 12rem;
|
||||||
|
}
|
||||||
|
|
||||||
.sa-check {
|
.sa-check {
|
||||||
flex-direction: row !important;
|
flex-direction: row !important;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.4rem !important;
|
gap: 0.4rem !important;
|
||||||
|
font-size: 0.9rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sa-messages {
|
.sa-messages {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding: 0.75rem;
|
padding: 0.85rem 0.9rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 0.65rem;
|
gap: 0.7rem;
|
||||||
|
scroll-behavior: smooth;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sa-msg {
|
.sa-msg {
|
||||||
max-width: 95%;
|
max-width: 92%;
|
||||||
padding: 0.55rem 0.7rem;
|
padding: 0.6rem 0.8rem;
|
||||||
border-radius: 0.35rem;
|
border-radius: 0.75rem;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
line-height: 1.35;
|
line-height: 1.4;
|
||||||
|
box-shadow: 0 1px 0 color-mix(in srgb, currentColor 8%, transparent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.sa-msg.user {
|
.sa-msg.user {
|
||||||
align-self: flex-end;
|
align-self: flex-end;
|
||||||
background: color-mix(in srgb, currentColor 12%, transparent);
|
border-bottom-right-radius: 0.25rem;
|
||||||
|
background: color-mix(in srgb, currentColor 14%, transparent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.sa-msg.assistant {
|
.sa-msg.assistant {
|
||||||
align-self: flex-start;
|
align-self: flex-start;
|
||||||
|
border-bottom-left-radius: 0.25rem;
|
||||||
background: color-mix(in srgb, currentColor 7%, transparent);
|
background: color-mix(in srgb, currentColor 7%, transparent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.sa-msg.error {
|
.sa-msg.error {
|
||||||
align-self: stretch;
|
align-self: stretch;
|
||||||
border: 1px solid color-mix(in srgb, #c44 50%, transparent);
|
border: 1px solid color-mix(in srgb, #c44 55%, transparent);
|
||||||
|
background: color-mix(in srgb, #c44 12%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sa-msg.sa-system {
|
||||||
|
align-self: center;
|
||||||
|
max-width: 100%;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
opacity: 0.75;
|
||||||
|
background: transparent;
|
||||||
|
box-shadow: none;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sa-patch {
|
.sa-patch {
|
||||||
margin-top: 0.5rem;
|
margin-top: 0.55rem;
|
||||||
padding-top: 0.45rem;
|
padding-top: 0.5rem;
|
||||||
border-top: 1px dashed color-mix(in srgb, currentColor 25%, transparent);
|
border-top: 1px dashed color-mix(in srgb, currentColor 25%, transparent);
|
||||||
font-size: 0.9rem;
|
font-size: 0.88rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sa-patch pre {
|
||||||
|
margin: 0;
|
||||||
|
max-height: 10rem;
|
||||||
|
overflow: auto;
|
||||||
|
padding: 0.45rem 0.55rem;
|
||||||
|
border-radius: 0.35rem;
|
||||||
|
background: color-mix(in srgb, currentColor 6%, transparent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.sa-patch-actions {
|
.sa-patch-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 0.35rem;
|
gap: 0.35rem;
|
||||||
margin-top: 0.4rem;
|
margin-top: 0.45rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sa-composer {
|
.sa-composer {
|
||||||
border-top: 1px solid color-mix(in srgb, currentColor 20%, transparent);
|
border-top: 1px solid color-mix(in srgb, currentColor 18%, transparent);
|
||||||
padding: 0.55rem;
|
padding: 0.6rem 0.7rem 0.7rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 0.4rem;
|
gap: 0.45rem;
|
||||||
|
background: color-mix(in srgb, currentColor 4%, transparent);
|
||||||
}
|
}
|
||||||
|
|
||||||
.sa-composer textarea {
|
.sa-composer textarea {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
resize: vertical;
|
resize: vertical;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
min-height: 4rem;
|
min-height: 4.25rem;
|
||||||
|
border-radius: 0.45rem;
|
||||||
|
padding: 0.55rem 0.65rem;
|
||||||
|
line-height: 1.35;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sa-composer-actions {
|
.sa-composer-actions {
|
||||||
@@ -192,9 +305,14 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sa-primary {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
.sa-status {
|
.sa-status {
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
opacity: 0.75;
|
opacity: 0.75;
|
||||||
|
margin-left: 0.15rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sa-disabled {
|
.sa-disabled {
|
||||||
@@ -202,6 +320,16 @@
|
|||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sa-flash {
|
||||||
|
animation: sa-flash 0.85s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes sa-flash {
|
||||||
|
0% { box-shadow: 0 0 0 0 color-mix(in srgb, currentColor 45%, transparent); }
|
||||||
|
40% { box-shadow: 0 0 0 6px color-mix(in srgb, currentColor 20%, transparent); }
|
||||||
|
100% { box-shadow: 0 0 0 0 transparent; }
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 900px) {
|
@media (max-width: 900px) {
|
||||||
.sa-layout {
|
.sa-layout {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -209,6 +337,10 @@
|
|||||||
.sa-image-pane {
|
.sa-image-pane {
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
max-width: none;
|
max-width: none;
|
||||||
max-height: 40%;
|
max-height: 38%;
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
.sa-splitter {
|
||||||
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+364
-42
@@ -6,12 +6,16 @@
|
|||||||
const LS_MODEL = 'swarm_assistent_model';
|
const LS_MODEL = 'swarm_assistent_model';
|
||||||
const LS_PACK = 'swarm_assistent_pack';
|
const LS_PACK = 'swarm_assistent_pack';
|
||||||
const LS_AUTO_VISION = 'swarm_assistent_auto_vision';
|
const LS_AUTO_VISION = 'swarm_assistent_auto_vision';
|
||||||
|
const LS_PANE_WIDTH = 'swarm_assistent_pane_width';
|
||||||
|
const TAB_BUTTON_ID = 'maintab_assistent';
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
history: [],
|
history: [],
|
||||||
packsLoaded: false,
|
packsLoaded: false,
|
||||||
busy: false,
|
busy: false,
|
||||||
lastImageDataUrl: null,
|
lastImageDataUrl: null,
|
||||||
|
preferredModel: null,
|
||||||
|
dragDepth: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
function $(id) {
|
function $(id) {
|
||||||
@@ -66,6 +70,93 @@
|
|||||||
el.dispatchEvent(new Event('change', { bubbles: true }));
|
el.dispatchEvent(new Event('change', { bubbles: true }));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openAssistentTab() {
|
||||||
|
const tab = document.getElementById(TAB_BUTTON_ID);
|
||||||
|
if (tab) {
|
||||||
|
tab.click();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Fallback: try hash / pane
|
||||||
|
const pane = document.getElementById('assistent');
|
||||||
|
if (pane && typeof bootstrap !== 'undefined' && bootstrap.Tab) {
|
||||||
|
try {
|
||||||
|
bootstrap.Tab.getOrCreateInstance(tab || pane).show();
|
||||||
|
} catch (e) { /* ignore */ }
|
||||||
|
}
|
||||||
|
return !!tab;
|
||||||
|
}
|
||||||
|
|
||||||
|
function flashImagePane() {
|
||||||
|
const frame = $('sa_image_frame');
|
||||||
|
if (!frame) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
frame.classList.remove('sa-flash');
|
||||||
|
// reflow
|
||||||
|
void frame.offsetWidth;
|
||||||
|
frame.classList.add('sa-flash');
|
||||||
|
}
|
||||||
|
|
||||||
|
function setImageFromSrc(src, { switchTab = false, note = null } = {}) {
|
||||||
|
if (!src) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const cleaned = String(src).trim().split(/\s+/)[0];
|
||||||
|
if (!cleaned || cleaned.startsWith('#')) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
state.lastImageDataUrl = cleaned;
|
||||||
|
const img = $('sa_image_preview');
|
||||||
|
const empty = $('sa_image_empty');
|
||||||
|
const chip = $('sa_vision_chip');
|
||||||
|
const frame = $('sa_image_frame');
|
||||||
|
if (img) {
|
||||||
|
img.src = cleaned;
|
||||||
|
img.hidden = false;
|
||||||
|
}
|
||||||
|
if (empty) {
|
||||||
|
empty.hidden = true;
|
||||||
|
}
|
||||||
|
if (chip) {
|
||||||
|
chip.hidden = false;
|
||||||
|
}
|
||||||
|
if (frame) {
|
||||||
|
frame.classList.add('sa-has-image');
|
||||||
|
}
|
||||||
|
if (switchTab) {
|
||||||
|
openAssistentTab();
|
||||||
|
}
|
||||||
|
flashImagePane();
|
||||||
|
if (note) {
|
||||||
|
setStatus(note);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearVisionImage({ silent = false } = {}) {
|
||||||
|
state.lastImageDataUrl = null;
|
||||||
|
const img = $('sa_image_preview');
|
||||||
|
const empty = $('sa_image_empty');
|
||||||
|
const chip = $('sa_vision_chip');
|
||||||
|
const frame = $('sa_image_frame');
|
||||||
|
if (img) {
|
||||||
|
img.removeAttribute('src');
|
||||||
|
img.hidden = true;
|
||||||
|
}
|
||||||
|
if (empty) {
|
||||||
|
empty.hidden = false;
|
||||||
|
}
|
||||||
|
if (chip) {
|
||||||
|
chip.hidden = true;
|
||||||
|
}
|
||||||
|
if (frame) {
|
||||||
|
frame.classList.remove('sa-has-image');
|
||||||
|
}
|
||||||
|
if (!silent) {
|
||||||
|
setStatus('Vision image cleared');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function collectLiveContext() {
|
function collectLiveContext() {
|
||||||
const ctx = {
|
const ctx = {
|
||||||
architecture_ok: isKreaSelected(),
|
architecture_ok: isKreaSelected(),
|
||||||
@@ -110,17 +201,14 @@
|
|||||||
if (!m) {
|
if (!m) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const arch = `${m.architecture || ''} ${m.class || ''} ${m.name || ''} ${m.title || ''}`;
|
|
||||||
const isLora = /lora/i.test(m.category || m.type || '') || (m.name && String(m.name).toLowerCase().includes('lora'));
|
|
||||||
const folder = `${m.folder || m.path || ''}`;
|
const folder = `${m.folder || m.path || ''}`;
|
||||||
|
const isLora = /lora/i.test(m.category || m.type || '') || (m.name && String(m.name).toLowerCase().includes('lora'));
|
||||||
const inLoraFolder = /lora/i.test(folder);
|
const inLoraFolder = /lora/i.test(folder);
|
||||||
if (!(isLora || inLoraFolder)) {
|
if (!(isLora || inLoraFolder)) {
|
||||||
// Still include if metadata says lora
|
|
||||||
if (!/lora/i.test(JSON.stringify(m).slice(0, 200))) {
|
if (!/lora/i.test(JSON.stringify(m).slice(0, 200))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Prefer Krea-tagged or unknown; skip obvious FLUX/SDXL-only names when tagged
|
|
||||||
ctx.available_loras.push({
|
ctx.available_loras.push({
|
||||||
name: m.name || m.title,
|
name: m.name || m.title,
|
||||||
title: m.title || m.name,
|
title: m.title || m.name,
|
||||||
@@ -128,13 +216,11 @@
|
|||||||
architecture: m.architecture || null,
|
architecture: m.architecture || null,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Cap list size for context window
|
|
||||||
if (ctx.available_loras.length > 80) {
|
if (ctx.available_loras.length > 80) {
|
||||||
ctx.available_loras = ctx.available_loras.slice(0, 80);
|
ctx.available_loras = ctx.available_loras.slice(0, 80);
|
||||||
}
|
}
|
||||||
} catch (e) { /* ignore */ }
|
} catch (e) { /* ignore */ }
|
||||||
|
|
||||||
// Fallback: parse multi-select input_loras options as available names
|
|
||||||
try {
|
try {
|
||||||
const sel = document.getElementById('input_loras');
|
const sel = document.getElementById('input_loras');
|
||||||
if (sel && sel.options && ctx.available_loras.length === 0) {
|
if (sel && sel.options && ctx.available_loras.length === 0) {
|
||||||
@@ -188,7 +274,6 @@
|
|||||||
if (patch.negative != null) {
|
if (patch.negative != null) {
|
||||||
setVal('input_negativeprompt', patch.negative);
|
setVal('input_negativeprompt', patch.negative);
|
||||||
}
|
}
|
||||||
// Ensure triggers present
|
|
||||||
if (Array.isArray(patch.loras)) {
|
if (Array.isArray(patch.loras)) {
|
||||||
for (const l of patch.loras) {
|
for (const l of patch.loras) {
|
||||||
const triggers = l.triggers || (l.trigger_phrase ? [l.trigger_phrase] : []);
|
const triggers = l.triggers || (l.trigger_phrase ? [l.trigger_phrase] : []);
|
||||||
@@ -290,47 +375,85 @@
|
|||||||
box.scrollTop = box.scrollHeight;
|
box.scrollTop = box.scrollHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
function refreshImagePreview() {
|
function findCurrentGenerateSrc() {
|
||||||
let src = null;
|
|
||||||
try {
|
try {
|
||||||
const cur = document.getElementById('current_image_img') || document.querySelector('#current_image img') || document.querySelector('.current-image img');
|
const cur = document.getElementById('current_image_img')
|
||||||
if (cur && cur.src) {
|
|| document.querySelector('#current_image img')
|
||||||
src = cur.src;
|
|| document.querySelector('.current-image img')
|
||||||
|
|| document.querySelector('#current_image_batch img');
|
||||||
|
if (cur) {
|
||||||
|
return cur.dataset?.src || cur.src || null;
|
||||||
}
|
}
|
||||||
} catch (e) { /* ignore */ }
|
} catch (e) { /* ignore */ }
|
||||||
try {
|
try {
|
||||||
if (!src && typeof currentMetadataMap !== 'undefined' && currentMetadataMap && currentMetadataMap.image) {
|
if (typeof currentMetadataMap !== 'undefined' && currentMetadataMap && currentMetadataMap.image) {
|
||||||
src = currentMetadataMap.image;
|
return currentMetadataMap.image;
|
||||||
}
|
}
|
||||||
} catch (e) { /* ignore */ }
|
} catch (e) { /* ignore */ }
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
const img = $('sa_image_preview');
|
function refreshImagePreview({ onlyIfEmpty = false } = {}) {
|
||||||
const empty = $('sa_image_empty');
|
if (onlyIfEmpty && state.lastImageDataUrl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const src = findCurrentGenerateSrc();
|
||||||
if (src) {
|
if (src) {
|
||||||
state.lastImageDataUrl = src;
|
setImageFromSrc(src);
|
||||||
if (img) {
|
} else if (!state.lastImageDataUrl) {
|
||||||
img.src = src;
|
clearVisionImage({ silent: true });
|
||||||
img.hidden = false;
|
|
||||||
}
|
|
||||||
if (empty) {
|
|
||||||
empty.hidden = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
state.lastImageDataUrl = null;
|
|
||||||
if (img) {
|
|
||||||
img.hidden = true;
|
|
||||||
}
|
|
||||||
if (empty) {
|
|
||||||
empty.hidden = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function fileToDataUrl(file) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = () => resolve(String(reader.result || ''));
|
||||||
|
reader.onerror = reject;
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function acceptImageFile(file) {
|
||||||
|
if (!file || !String(file.type || '').startsWith('image/')) {
|
||||||
|
setStatus('Not an image file');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const dataUrl = await fileToDataUrl(file);
|
||||||
|
return setImageFromSrc(dataUrl, { note: `Loaded ${file.name || 'image'}` });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleDropDataTransfer(dt) {
|
||||||
|
if (!dt) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (dt.files && dt.files.length) {
|
||||||
|
for (const file of dt.files) {
|
||||||
|
if (String(file.type || '').startsWith('image/')) {
|
||||||
|
return acceptImageFile(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const uri = (dt.getData('text/uri-list') || dt.getData('text/plain') || '').trim();
|
||||||
|
if (uri) {
|
||||||
|
const first = uri.split('\n').map((l) => l.trim()).find((l) => l && !l.startsWith('#'));
|
||||||
|
if (first) {
|
||||||
|
return setImageFromSrc(first, { note: 'Image from drag' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// HTML img drag
|
||||||
|
const html = dt.getData('text/html') || '';
|
||||||
|
const m = html.match(/src=["']([^"']+)["']/i);
|
||||||
|
if (m && m[1]) {
|
||||||
|
return setImageFromSrc(m[1], { note: 'Image from drag' });
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function imageToBase64ForOllama(src) {
|
async function imageToBase64ForOllama(src) {
|
||||||
if (!src) {
|
if (!src) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
// Already data URL
|
|
||||||
if (src.startsWith('data:')) {
|
if (src.startsWith('data:')) {
|
||||||
const i = src.indexOf(',');
|
const i = src.indexOf(',');
|
||||||
return i >= 0 ? src.slice(i + 1) : null;
|
return i >= 0 ? src.slice(i + 1) : null;
|
||||||
@@ -359,6 +482,7 @@
|
|||||||
const model = localStorage.getItem(LS_MODEL);
|
const model = localStorage.getItem(LS_MODEL);
|
||||||
const pack = localStorage.getItem(LS_PACK);
|
const pack = localStorage.getItem(LS_PACK);
|
||||||
const auto = localStorage.getItem(LS_AUTO_VISION);
|
const auto = localStorage.getItem(LS_AUTO_VISION);
|
||||||
|
const paneW = localStorage.getItem(LS_PANE_WIDTH);
|
||||||
if (base && $('sa_base_url')) {
|
if (base && $('sa_base_url')) {
|
||||||
$('sa_base_url').value = base;
|
$('sa_base_url').value = base;
|
||||||
}
|
}
|
||||||
@@ -371,6 +495,9 @@
|
|||||||
if (model) {
|
if (model) {
|
||||||
state.preferredModel = model;
|
state.preferredModel = model;
|
||||||
}
|
}
|
||||||
|
if (paneW) {
|
||||||
|
document.documentElement.style.setProperty('--sa-image-width', paneW);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveSettings() {
|
function saveSettings() {
|
||||||
@@ -433,7 +560,9 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!state.lastImageDataUrl) {
|
||||||
refreshImagePreview();
|
refreshImagePreview();
|
||||||
|
}
|
||||||
const attach = ($('sa_attach_vision')?.checked || $('sa_auto_vision')?.checked) && state.lastImageDataUrl;
|
const attach = ($('sa_attach_vision')?.checked || $('sa_auto_vision')?.checked) && state.lastImageDataUrl;
|
||||||
let images = null;
|
let images = null;
|
||||||
if (attach) {
|
if (attach) {
|
||||||
@@ -444,19 +573,13 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const userMsg = { role: 'user', content: text };
|
|
||||||
if (images) {
|
|
||||||
userMsg.images = images;
|
|
||||||
}
|
|
||||||
state.history.push({ role: 'user', content: text });
|
state.history.push({ role: 'user', content: text });
|
||||||
appendMessage('user', text);
|
appendMessage('user', text);
|
||||||
$('sa_input').value = '';
|
$('sa_input').value = '';
|
||||||
|
|
||||||
const context = collectLiveContext();
|
const context = collectLiveContext();
|
||||||
context.has_vision_image = !!images;
|
context.has_vision_image = !!images;
|
||||||
// Strip huge fields from history replay — only send recent turns without images in history payload
|
|
||||||
const messages = state.history.slice(-12).map((m) => ({ role: m.role, content: m.content }));
|
const messages = state.history.slice(-12).map((m) => ({ role: m.role, content: m.content }));
|
||||||
// Last user message may include images
|
|
||||||
if (images && messages.length) {
|
if (images && messages.length) {
|
||||||
messages[messages.length - 1].images = images;
|
messages[messages.length - 1].images = images;
|
||||||
}
|
}
|
||||||
@@ -493,6 +616,186 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function wireDropZone() {
|
||||||
|
const frame = $('sa_image_frame');
|
||||||
|
const overlay = $('sa_drop_overlay');
|
||||||
|
if (!frame) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const setDrag = (on) => {
|
||||||
|
frame.classList.toggle('sa-dragover', on);
|
||||||
|
if (overlay) {
|
||||||
|
overlay.hidden = !on;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
['dragenter', 'dragover'].forEach((ev) => {
|
||||||
|
frame.addEventListener(ev, (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
state.dragDepth += ev === 'dragenter' ? 1 : 0;
|
||||||
|
setDrag(true);
|
||||||
|
if (e.dataTransfer) {
|
||||||
|
e.dataTransfer.dropEffect = 'copy';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
frame.addEventListener('dragleave', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
state.dragDepth = Math.max(0, state.dragDepth - 1);
|
||||||
|
if (state.dragDepth === 0) {
|
||||||
|
setDrag(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
frame.addEventListener('drop', async (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
state.dragDepth = 0;
|
||||||
|
setDrag(false);
|
||||||
|
await handleDropDataTransfer(e.dataTransfer);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Also accept drops on the whole layout (easier target)
|
||||||
|
const layout = $('sa_layout');
|
||||||
|
layout?.addEventListener('dragover', (e) => {
|
||||||
|
if (e.dataTransfer?.types?.includes('Files') || e.dataTransfer?.types?.includes('text/uri-list')) {
|
||||||
|
e.preventDefault();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
layout?.addEventListener('drop', async (e) => {
|
||||||
|
if (!e.dataTransfer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
e.preventDefault();
|
||||||
|
await handleDropDataTransfer(e.dataTransfer);
|
||||||
|
});
|
||||||
|
|
||||||
|
frame.addEventListener('paste', async (e) => {
|
||||||
|
const items = e.clipboardData?.items;
|
||||||
|
if (!items) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const item of items) {
|
||||||
|
if (item.type.startsWith('image/')) {
|
||||||
|
e.preventDefault();
|
||||||
|
const file = item.getAsFile();
|
||||||
|
if (file) {
|
||||||
|
await acceptImageFile(file);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Global paste when Assistent tab is visible
|
||||||
|
document.addEventListener('paste', async (e) => {
|
||||||
|
const pane = document.getElementById('assistent');
|
||||||
|
if (!pane || !pane.classList.contains('active')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (e.target && (e.target.tagName === 'TEXTAREA' || e.target.tagName === 'INPUT')) {
|
||||||
|
// Still allow image paste over text fields when clipboard has image
|
||||||
|
const items = e.clipboardData?.items;
|
||||||
|
let hasImage = false;
|
||||||
|
if (items) {
|
||||||
|
for (const item of items) {
|
||||||
|
if (item.type.startsWith('image/')) {
|
||||||
|
hasImage = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!hasImage) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const items = e.clipboardData?.items;
|
||||||
|
if (!items) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const item of items) {
|
||||||
|
if (item.type.startsWith('image/')) {
|
||||||
|
e.preventDefault();
|
||||||
|
const file = item.getAsFile();
|
||||||
|
if (file) {
|
||||||
|
await acceptImageFile(file);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function wireSplitter() {
|
||||||
|
const splitter = $('sa_splitter');
|
||||||
|
const layout = $('sa_layout');
|
||||||
|
const pane = $('sa_image_pane');
|
||||||
|
if (!splitter || !layout || !pane) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let dragging = false;
|
||||||
|
splitter.addEventListener('mousedown', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
dragging = true;
|
||||||
|
splitter.classList.add('sa-dragging');
|
||||||
|
document.body.style.cursor = 'col-resize';
|
||||||
|
document.body.style.userSelect = 'none';
|
||||||
|
});
|
||||||
|
window.addEventListener('mousemove', (e) => {
|
||||||
|
if (!dragging) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const rect = layout.getBoundingClientRect();
|
||||||
|
const x = e.clientX - rect.left;
|
||||||
|
const pct = Math.min(45, Math.max(18, (x / rect.width) * 100));
|
||||||
|
const value = `${pct}%`;
|
||||||
|
document.documentElement.style.setProperty('--sa-image-width', value);
|
||||||
|
localStorage.setItem(LS_PANE_WIDTH, value);
|
||||||
|
});
|
||||||
|
window.addEventListener('mouseup', () => {
|
||||||
|
if (!dragging) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dragging = false;
|
||||||
|
splitter.classList.remove('sa-dragging');
|
||||||
|
document.body.style.cursor = '';
|
||||||
|
document.body.style.userSelect = '';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function registerSendButton() {
|
||||||
|
if (typeof registerMediaButton !== 'function') {
|
||||||
|
// SwarmUI core not ready yet — retry briefly
|
||||||
|
setTimeout(registerSendButton, 500);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Avoid duplicate registrations on hot-reload
|
||||||
|
if (window.__swarmAssistentMediaRegistered) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
window.__swarmAssistentMediaRegistered = true;
|
||||||
|
registerMediaButton(
|
||||||
|
'Send to Assistent',
|
||||||
|
(src) => {
|
||||||
|
setImageFromSrc(src, {
|
||||||
|
switchTab: true,
|
||||||
|
note: 'Image sent to Assistent',
|
||||||
|
});
|
||||||
|
// Prefer critique pack when coming from an image
|
||||||
|
const pack = $('sa_pack');
|
||||||
|
if (pack && pack.value === 'write_prompt') {
|
||||||
|
pack.value = 'critique_image';
|
||||||
|
saveSettings();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'Open Assistent with this image (vision / critique / prompt help)',
|
||||||
|
['image'],
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function wire() {
|
function wire() {
|
||||||
if (!$('swarm_assistent_root')) {
|
if (!$('swarm_assistent_root')) {
|
||||||
return;
|
return;
|
||||||
@@ -501,6 +804,9 @@
|
|||||||
updateGate();
|
updateGate();
|
||||||
refreshImagePreview();
|
refreshImagePreview();
|
||||||
refreshModels();
|
refreshModels();
|
||||||
|
wireDropZone();
|
||||||
|
wireSplitter();
|
||||||
|
registerSendButton();
|
||||||
|
|
||||||
$('sa_btn_settings')?.addEventListener('click', () => {
|
$('sa_btn_settings')?.addEventListener('click', () => {
|
||||||
const s = $('sa_settings');
|
const s = $('sa_settings');
|
||||||
@@ -512,7 +818,15 @@
|
|||||||
saveSettings();
|
saveSettings();
|
||||||
refreshModels();
|
refreshModels();
|
||||||
});
|
});
|
||||||
$('sa_btn_refresh_image')?.addEventListener('click', refreshImagePreview);
|
$('sa_btn_use_current')?.addEventListener('click', () => {
|
||||||
|
const src = findCurrentGenerateSrc();
|
||||||
|
if (src) {
|
||||||
|
setImageFromSrc(src, { note: 'Using current Generate image' });
|
||||||
|
} else {
|
||||||
|
setStatus('No current Generate image');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$('sa_btn_clear_image')?.addEventListener('click', clearVisionImage);
|
||||||
$('sa_btn_send')?.addEventListener('click', () => sendChat());
|
$('sa_btn_send')?.addEventListener('click', () => sendChat());
|
||||||
$('sa_btn_clear')?.addEventListener('click', () => {
|
$('sa_btn_clear')?.addEventListener('click', () => {
|
||||||
state.history = [];
|
state.history = [];
|
||||||
@@ -523,7 +837,7 @@
|
|||||||
setStatus('');
|
setStatus('');
|
||||||
});
|
});
|
||||||
$('sa_input')?.addEventListener('keydown', (e) => {
|
$('sa_input')?.addEventListener('keydown', (e) => {
|
||||||
if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
|
if (e.key === 'Enter' && !e.shiftKey && !e.altKey) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
sendChat();
|
sendChat();
|
||||||
}
|
}
|
||||||
@@ -533,9 +847,17 @@
|
|||||||
$('sa_pack')?.addEventListener('change', saveSettings);
|
$('sa_pack')?.addEventListener('change', saveSettings);
|
||||||
$('sa_auto_vision')?.addEventListener('change', saveSettings);
|
$('sa_auto_vision')?.addEventListener('change', saveSettings);
|
||||||
|
|
||||||
// Re-check Krea gate when user may swap models
|
|
||||||
setInterval(updateGate, 2000);
|
setInterval(updateGate, 2000);
|
||||||
setInterval(refreshImagePreview, 4000);
|
// Soft sync from Generate only when we have no pinned vision image
|
||||||
|
setInterval(() => refreshImagePreview({ onlyIfEmpty: true }), 5000);
|
||||||
|
|
||||||
|
// Expose for console / other extensions
|
||||||
|
window.swarmAssistent = {
|
||||||
|
setImageFromSrc,
|
||||||
|
clearVisionImage,
|
||||||
|
openAssistentTab,
|
||||||
|
sendToAssistent: (src) => setImageFromSrc(src, { switchTab: true, note: 'Image sent to Assistent' }),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (document.readyState === 'loading') {
|
if (document.readyState === 'loading') {
|
||||||
|
|||||||
@@ -4,9 +4,18 @@ SwarmUI extension for **collaborative Krea 2** prompting via **Ollama**: chat +
|
|||||||
|
|
||||||
## Layout
|
## Layout
|
||||||
|
|
||||||
- **Left:** current / selected image preview (attach for vision)
|
- **Left:** vision reference (drop / paste / *Send to Assistent* from Generate)
|
||||||
|
- **Splitter:** drag to resize panes
|
||||||
- **Right (wider):** chat
|
- **Right (wider):** chat
|
||||||
- **Top-right:** settings (Ollama URL, model, auto-attach)
|
- **Top-right:** prompt pack + settings (Ollama URL, model, auto-attach)
|
||||||
|
|
||||||
|
## UX
|
||||||
|
|
||||||
|
- **Send to Assistent** under the current Generate image (and History) — loads vision + opens the Assistent tab
|
||||||
|
- Drag images from Generate/History or the OS onto the left pane
|
||||||
|
- Paste (`Ctrl+V`) while the Assistent tab is open
|
||||||
|
- **Use current** / **Clear** for the vision reference
|
||||||
|
- Enter sends; Shift+Enter newline
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ public class SwarmAssistentExtension : Extension
|
|||||||
ExtensionAuthor = "mrleo1nid";
|
ExtensionAuthor = "mrleo1nid";
|
||||||
Description = "Collaborative Krea 2 assistant via Ollama: chat, vision, prompts, LoRA triggers, size patches.";
|
Description = "Collaborative Krea 2 assistant via Ollama: chat, vision, prompts, LoRA triggers, size patches.";
|
||||||
License = "MIT";
|
License = "MIT";
|
||||||
Version = "0.1.0";
|
Version = "0.2.0";
|
||||||
Tags = ["tabs", "ui", "llm", "ollama", "krea"];
|
Tags = ["tabs", "ui", "llm", "ollama", "krea"];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,16 +3,23 @@
|
|||||||
<p>Swarm Assistent is for <strong>Krea 2</strong> models only. Select a Krea 2 checkpoint to enable the chat.</p>
|
<p>Swarm Assistent is for <strong>Krea 2</strong> models only. Select a Krea 2 checkpoint to enable the chat.</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="sa-layout" id="sa_layout">
|
<div class="sa-layout" id="sa_layout">
|
||||||
<aside class="sa-image-pane">
|
<aside class="sa-image-pane" id="sa_image_pane">
|
||||||
<div class="sa-image-frame" id="sa_image_frame">
|
<div class="sa-image-frame" id="sa_image_frame" tabindex="0" title="Drop an image here, or paste (Ctrl+V). Drag from Generate / History.">
|
||||||
<img id="sa_image_preview" alt="" hidden />
|
<img id="sa_image_preview" alt="Vision reference" hidden />
|
||||||
<div class="sa-image-empty" id="sa_image_empty">Generate or select an image</div>
|
<div class="sa-image-empty" id="sa_image_empty">
|
||||||
|
<div class="sa-empty-title">Vision reference</div>
|
||||||
|
<div class="sa-empty-hint">Drop · paste · <em>Send to Assistent</em> from Generate</div>
|
||||||
|
</div>
|
||||||
|
<div class="sa-drop-overlay" id="sa_drop_overlay" hidden>Drop image</div>
|
||||||
|
<span class="sa-vision-chip" id="sa_vision_chip" hidden>vision</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="sa-image-actions">
|
<div class="sa-image-actions">
|
||||||
<button type="button" class="basic-button" id="sa_btn_refresh_image">Refresh preview</button>
|
<button type="button" class="basic-button" id="sa_btn_use_current" title="Grab the current Generate preview">Use current</button>
|
||||||
<label class="sa-check"><input type="checkbox" id="sa_attach_vision" checked /> Attach to next message</label>
|
<button type="button" class="basic-button" id="sa_btn_clear_image" title="Clear vision image">Clear</button>
|
||||||
|
<label class="sa-check"><input type="checkbox" id="sa_attach_vision" checked /> Attach next</label>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
<div class="sa-splitter" id="sa_splitter" role="separator" aria-orientation="vertical" title="Drag to resize"></div>
|
||||||
<section class="sa-chat-pane">
|
<section class="sa-chat-pane">
|
||||||
<header class="sa-chat-header">
|
<header class="sa-chat-header">
|
||||||
<div class="sa-chat-title">Assistent</div>
|
<div class="sa-chat-title">Assistent</div>
|
||||||
@@ -36,9 +43,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="sa-messages" id="sa_messages"></div>
|
<div class="sa-messages" id="sa_messages"></div>
|
||||||
<div class="sa-composer">
|
<div class="sa-composer">
|
||||||
<textarea id="sa_input" rows="3" placeholder="Ask for a prompt, critique the image, change aspect…"></textarea>
|
<textarea id="sa_input" rows="3" placeholder="Ask for a prompt, critique the image, change aspect… (Enter = send, Shift+Enter = newline)"></textarea>
|
||||||
<div class="sa-composer-actions">
|
<div class="sa-composer-actions">
|
||||||
<button type="button" class="basic-button" id="sa_btn_send">Send</button>
|
<button type="button" class="basic-button sa-primary" id="sa_btn_send">Send</button>
|
||||||
<button type="button" class="basic-button" id="sa_btn_clear">Clear chat</button>
|
<button type="button" class="basic-button" id="sa_btn_clear">Clear chat</button>
|
||||||
<span class="sa-status" id="sa_status"></span>
|
<span class="sa-status" id="sa_status"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user