Ship Assistent 0.15.3: user-owned Generate, SwarmUI aspects, HF import UX.

Chips and quick patches apply params without auto-Generate; aspect sizes match Swarm Side Length 1024. HF import shows drafts after switching filter from approved-only; training status and job polling improved.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Leonid Pershin
2026-08-23 19:13:36 +03:00
co-authored by Cursor
parent e53f483a00
commit 65a0982b9d
24 changed files with 1088 additions and 275 deletions
+31
View File
@@ -0,0 +1,31 @@
import assert from 'node:assert/strict';
import { describe, it } from 'node:test';
import {
DEFAULT_ASPECT_TABLE,
buildDefaultAspectTable,
swarmSizeFromRef,
} from '../src/aspect.js';
describe('aspect.js (SwarmUI side 1024)', () => {
it('16:9 and 9:16 match Swarm Resolution dropdown', () => {
assert.deepEqual(swarmSizeFromRef('16:9'), [1344, 768]);
assert.deepEqual(swarmSizeFromRef('9:16'), [768, 1344]);
});
it('default table uses Swarm formula for landscape/portrait chips', () => {
assert.deepEqual(DEFAULT_ASPECT_TABLE['16:9'], [1344, 768]);
assert.deepEqual(DEFAULT_ASPECT_TABLE['9:16'], [768, 1344]);
assert.deepEqual(DEFAULT_ASPECT_TABLE['2:3'], [832, 1216]);
assert.deepEqual(DEFAULT_ASPECT_TABLE['1:1'], [1024, 1024]);
});
it('2.35:1 aliases 21:9', () => {
assert.deepEqual(DEFAULT_ASPECT_TABLE['2.35:1'], DEFAULT_ASPECT_TABLE['21:9']);
assert.deepEqual(DEFAULT_ASPECT_TABLE['21:9'], [1536, 640]);
});
it('scales with side length', () => {
const t = buildDefaultAspectTable(512);
assert.deepEqual(t['16:9'], [672, 384]);
});
});
+18 -1
View File
@@ -23,11 +23,13 @@ describe('intent.js', () => {
it('userAsksGenerate detects сгенерируй', () => {
assert.equal(userAsksGenerate('сгенерируй'), true);
assert.equal(userAsksGenerate('нарисуй лису'), true);
assert.equal(userAsksGenerate('Давай сделаем изображение красивой рыжеволосой девушки'), true);
assert.equal(userAsksGenerate('а ты знаешь какие то промпты с civitai'), false);
assert.equal(userAsksGenerate('что ты умеешь'), false);
assert.equal(userAsksGenerate('не генерируй'), false);
});
it('resolveTurnIntent uses model generate + user generate ask + veto', () => {
it('resolveTurnIntent uses user generate ask + veto, not model flag alone', () => {
const lookOnly = resolveTurnIntent({ look_at: ['generate'] }, 'что не так с кадром?', {});
assert.equal(lookOnly.generate, false);
assert.equal(lookOnly.look, true);
@@ -44,6 +46,21 @@ describe('intent.js', () => {
const chatOnly = resolveTurnIntent({ prompt: 'fox' }, 'что ты умеешь про генерацию?', {});
assert.equal(chatOnly.generate, false);
const falseDump = resolveTurnIntent(
{ prompt: 'civitai example', generate: true },
'а ты знаешь какие то промпты с civitai',
{},
);
assert.equal(falseDump.generate, false);
assert.equal(falseDump.modelAsked, true);
const sdelаем = resolveTurnIntent(
{ prompt: 'A 19-year-old redhead', generate: true },
'Давай сделаем изображение красивой рыжеволосой девушки',
{},
);
assert.equal(sdelаем.generate, true);
});
it('packWantsVision for critique pack', () => {
+17
View File
@@ -5,6 +5,7 @@ import {
isPatchObject,
normalizePatch,
setPatchKeys,
visibleProse,
} from '../src/patch.js';
import {
mergeDelta,
@@ -56,6 +57,12 @@ describe('patch.js', () => {
assert.equal(raw.prose, 'done');
});
it('visibleProse hides the JSON fence when chat text is empty', () => {
assert.equal(visibleProse('```json\n{"prompt":"fox","generate":true}\n```'), '');
assert.equal(visibleProse('Ок, держи\n```json\n{"prompt":"fox"}\n```'), 'Ок, держи');
assert.equal(visibleProse('просто чат без патча'), 'просто чат без патча');
});
it('scheduler-only patch is detected with full key list', () => {
setPatchKeys(['prompt', 'scheduler', 'generate', 'ask']);
assert.equal(isPatchObject({ scheduler: 'euler' }), true);
@@ -112,6 +119,16 @@ describe('session.js', () => {
assert.equal(patchWantsGenerate({ generate: true }), true);
});
it('resolveTurnIntent ignores model generate:true on a knowledge question', () => {
const intent = resolveTurnIntent(
{ prompt: 'civitai example prompt', generate: true },
'а ты знаешь какие то промпты с civitai',
{ askGenerateFn: (t) => /сгенер|нарисуй/i.test(t) },
);
assert.equal(intent.generate, false);
assert.equal(intent.modelAsked, true);
});
it('mergeExactParamsForGenerate fills turbo profile when LLM omits steps/cfg', () => {
const exact = {
generation: { profile: 'turbo', steps: 8, cfg: 1, sigma_shift: 1.15 },