litevision 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +160 -111
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -24047,110 +24047,143 @@ function renderPage(nonce) {
|
|
|
24047
24047
|
<style>
|
|
24048
24048
|
:root {
|
|
24049
24049
|
color-scheme: light dark;
|
|
24050
|
-
--bg: #
|
|
24051
|
-
--accent: #
|
|
24050
|
+
--bg: #fafafa; --surface: #fff; --field: #fff; --hover: #f4f4f5; --text: #18181b; --muted: #71717a; --border: #e4e4e7;
|
|
24051
|
+
--accent: #2563eb; --focus: rgb(37 99 235 / 0.22); --danger: #dc2626; --ok: #15803d; --warn: #b45309;
|
|
24052
24052
|
}
|
|
24053
24053
|
@media (prefers-color-scheme: dark) {
|
|
24054
|
-
:root { --bg: #
|
|
24055
|
-
--accent: #
|
|
24054
|
+
:root { --bg: #0f0f11; --surface: #18181b; --field: #121214; --hover: #26262a; --text: #f4f4f5; --muted: #a1a1aa; --border: #2e2e33;
|
|
24055
|
+
--accent: #3b82f6; --focus: rgb(59 130 246 / 0.35); --danger: #f87171; --ok: #4ade80; --warn: #fbbf24; }
|
|
24056
24056
|
}
|
|
24057
24057
|
* { box-sizing: border-box; }
|
|
24058
24058
|
body { margin: 0; background: var(--bg); color: var(--text);
|
|
24059
|
-
font:
|
|
24060
|
-
main { max-width:
|
|
24061
|
-
h1 { font-size:
|
|
24062
|
-
h2 { font-size:
|
|
24063
|
-
h3 { font-size: 15px; margin: 0; }
|
|
24064
|
-
p
|
|
24065
|
-
|
|
24066
|
-
|
|
24067
|
-
|
|
24068
|
-
.
|
|
24069
|
-
.
|
|
24070
|
-
|
|
24071
|
-
|
|
24072
|
-
|
|
24073
|
-
|
|
24074
|
-
.
|
|
24075
|
-
.
|
|
24076
|
-
.
|
|
24077
|
-
|
|
24059
|
+
font: 14px/1.5 system-ui, -apple-system, "Segoe UI", sans-serif; -webkit-font-smoothing: antialiased; }
|
|
24060
|
+
main { max-width: 720px; margin: 0 auto; padding: 48px 20px 96px; }
|
|
24061
|
+
h1 { font-size: 20px; font-weight: 600; margin: 0 0 6px; }
|
|
24062
|
+
h2 { font-size: 15px; font-weight: 600; margin: 0; }
|
|
24063
|
+
h3 { font-size: 15px; font-weight: 600; margin: 0; flex: 1; min-width: 0; overflow-wrap: anywhere; }
|
|
24064
|
+
p { margin: 0; }
|
|
24065
|
+
.muted { color: var(--muted); }
|
|
24066
|
+
header p { color: var(--muted); }
|
|
24067
|
+
section { margin-top: 44px; }
|
|
24068
|
+
.section-head { margin-bottom: 14px; }
|
|
24069
|
+
.section-head p { color: var(--muted); font-size: 13px; margin-top: 2px; }
|
|
24070
|
+
|
|
24071
|
+
.card { display: grid; gap: 16px; background: var(--surface); border: 1px solid var(--border); border-radius: 12px;
|
|
24072
|
+
padding: 20px; margin-bottom: 12px; }
|
|
24073
|
+
.card-head { display: flex; align-items: center; gap: 10px; }
|
|
24074
|
+
.fields { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 14px; }
|
|
24075
|
+
.field { min-width: 0; }
|
|
24076
|
+
.wide { grid-column: 1 / -1; }
|
|
24077
|
+
.inline { display: flex; flex-wrap: wrap; gap: 8px; }
|
|
24078
|
+
.inline > input, .inline > select { flex: 1 1 200px; width: auto; }
|
|
24079
|
+
.actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding-top: 16px; border-top: 1px solid var(--border); }
|
|
24080
|
+
.actions > select { width: auto; flex: 0 1 180px; }
|
|
24081
|
+
.spacer { flex: 1; }
|
|
24082
|
+
.empty { color: var(--muted); padding: 20px; border: 1px dashed var(--border); border-radius: 12px; text-align: center; margin-bottom: 12px; }
|
|
24083
|
+
|
|
24084
|
+
label { display: block; font-size: 12px; font-weight: 500; color: var(--muted); margin-bottom: 6px; }
|
|
24085
|
+
input, select, textarea, button { font: inherit; color: inherit; }
|
|
24086
|
+
input, select, textarea { width: 100%; height: 34px; padding: 0 10px; border: 1px solid var(--border); border-radius: 8px;
|
|
24087
|
+
background: var(--field); color: var(--text); }
|
|
24088
|
+
textarea { height: auto; min-height: 72px; padding: 8px 10px; resize: vertical; }
|
|
24089
|
+
input:focus, select:focus, textarea:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--focus); }
|
|
24090
|
+
input::placeholder, textarea::placeholder { color: var(--muted); opacity: 0.8; }
|
|
24091
|
+
/* API keys are masked with CSS: a real key field would make the browser offer to save the key as a site login. */
|
|
24092
|
+
.secret { -webkit-text-security: disc; }
|
|
24093
|
+
.counter { font-size: 12px; color: var(--muted); text-align: right; margin-top: 4px; }
|
|
24094
|
+
|
|
24095
|
+
button { height: 34px; padding: 0 14px; border: 1px solid var(--border); border-radius: 8px; background: var(--surface);
|
|
24096
|
+
font-weight: 500; cursor: pointer; white-space: nowrap; }
|
|
24097
|
+
button:hover:not(:disabled) { background: var(--hover); }
|
|
24098
|
+
button:focus-visible { outline: none; box-shadow: 0 0 0 3px var(--focus); }
|
|
24078
24099
|
button.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
|
|
24079
|
-
button.
|
|
24080
|
-
button
|
|
24081
|
-
|
|
24100
|
+
button.primary:hover:not(:disabled) { background: var(--accent); filter: brightness(1.08); }
|
|
24101
|
+
button.danger { border-color: transparent; background: transparent; color: var(--danger); }
|
|
24102
|
+
button.small { height: 28px; padding: 0 10px; font-size: 13px; }
|
|
24103
|
+
button:disabled { opacity: 0.5; cursor: default; }
|
|
24104
|
+
|
|
24105
|
+
.chip { flex: none; font-size: 12px; font-weight: 500; padding: 2px 10px; border-radius: 999px; border: 1px solid var(--border); color: var(--muted); }
|
|
24106
|
+
.chip.ok { color: var(--ok); border-color: currentColor; }
|
|
24107
|
+
.chip.accent { color: #fff; background: var(--accent); border-color: var(--accent); }
|
|
24108
|
+
.chip.failed { color: var(--danger); border-color: currentColor; }
|
|
24109
|
+
.chip.running { color: var(--warn); border-color: currentColor; }
|
|
24082
24110
|
.key { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
|
|
24083
|
-
|
|
24111
|
+
|
|
24112
|
+
.result { font-size: 13px; }
|
|
24113
|
+
.result:empty { display: none; }
|
|
24114
|
+
.field > .result { margin-top: 8px; }
|
|
24084
24115
|
.result.success { color: var(--ok); } .result.failure { color: var(--danger); } .result.unsupported { color: var(--warn); }
|
|
24085
|
-
|
|
24086
|
-
|
|
24087
|
-
|
|
24088
|
-
.
|
|
24089
|
-
.
|
|
24090
|
-
.
|
|
24091
|
-
.thumb { width: 112px; height: 112px; object-fit: cover; border-radius: 7px; border: 1px solid var(--border); background: var(--bg); }
|
|
24116
|
+
.note { font-size: 12px; color: var(--muted); display: flex; align-items: center; gap: 8px; margin-top: 8px; }
|
|
24117
|
+
|
|
24118
|
+
.job-meta { font-size: 12px; color: var(--muted); margin-top: 2px; }
|
|
24119
|
+
.prompt { white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
24120
|
+
.thumbs { display: flex; flex-wrap: wrap; gap: 8px; }
|
|
24121
|
+
.thumb { width: 96px; height: 96px; object-fit: cover; border-radius: 8px; border: 1px solid var(--border); background: var(--bg); }
|
|
24092
24122
|
.thumb.placeholder { display: flex; align-items: center; justify-content: center; text-align: center; padding: 6px;
|
|
24093
|
-
font-size:
|
|
24094
|
-
.
|
|
24123
|
+
font-size: 11px; color: var(--muted); border-style: dashed; }
|
|
24124
|
+
.actions.rate { display: grid; gap: 10px; }
|
|
24125
|
+
.rating { font-size: 13px; }
|
|
24095
24126
|
.rating.good { color: var(--ok); } .rating.bad { color: var(--danger); }
|
|
24096
|
-
|
|
24127
|
+
|
|
24128
|
+
#status { position: fixed; left: 50%; bottom: 20px; transform: translateX(-50%); max-width: calc(100% - 32px);
|
|
24129
|
+
background: var(--text); color: var(--bg); padding: 8px 14px; border-radius: 8px; font-size: 13px; display: none;
|
|
24130
|
+
box-shadow: 0 6px 24px rgb(0 0 0 / 0.18); }
|
|
24131
|
+
#status.error { background: var(--danger); color: #fff; }
|
|
24097
24132
|
</style>
|
|
24098
24133
|
</head>
|
|
24099
24134
|
<body>
|
|
24100
24135
|
<main>
|
|
24101
|
-
<
|
|
24102
|
-
|
|
24136
|
+
<header>
|
|
24137
|
+
<h1>litevision settings</h1>
|
|
24138
|
+
<p>Credentials you enter here are saved on this computer only, in a file only you can read. They never pass through your Agent.</p>
|
|
24139
|
+
</header>
|
|
24103
24140
|
|
|
24104
24141
|
<section id="providers">
|
|
24105
|
-
<h2>Providers</h2>
|
|
24142
|
+
<div class="section-head"><h2>Providers</h2></div>
|
|
24106
24143
|
<div id="provider-list"><p class="muted">Loading…</p></div>
|
|
24107
24144
|
|
|
24108
24145
|
<form id="add-provider" class="card" autocomplete="off">
|
|
24109
24146
|
<h3>Add a Provider</h3>
|
|
24110
|
-
<div class="
|
|
24111
|
-
<div class="
|
|
24112
|
-
<div class="
|
|
24147
|
+
<div class="fields">
|
|
24148
|
+
<div class="field"><label for="add-preset">Preset</label><select id="add-preset"></select></div>
|
|
24149
|
+
<div class="field"><label for="add-id">Name</label><input id="add-id" required spellcheck="false" placeholder="openai"></div>
|
|
24150
|
+
<div class="field wide"><label for="add-base-url">Base URL</label><input id="add-base-url" required spellcheck="false" placeholder="https://relay.example/v1"></div>
|
|
24151
|
+
<div class="field wide"><label for="add-credential">API key</label><input id="add-credential" class="secret" autocomplete="off" data-1p-ignore data-lpignore="true" data-bwignore spellcheck="false" placeholder="Paste your API key"></div>
|
|
24113
24152
|
</div>
|
|
24114
|
-
<div class="
|
|
24115
|
-
<div class="grow"><label for="add-base-url">Base URL</label><input id="add-base-url" required spellcheck="false" placeholder="https://relay.example/v1"></div>
|
|
24116
|
-
</div>
|
|
24117
|
-
<div class="row">
|
|
24118
|
-
<div class="grow"><label for="add-credential">API key</label><input id="add-credential" type="password" autocomplete="new-password" spellcheck="false" placeholder="Paste your API key"></div>
|
|
24119
|
-
</div>
|
|
24120
|
-
<div class="row"><button class="primary" type="submit">Add Provider</button></div>
|
|
24153
|
+
<div class="actions"><button class="primary" type="submit">Add Provider</button></div>
|
|
24121
24154
|
</form>
|
|
24122
24155
|
</section>
|
|
24123
24156
|
|
|
24124
24157
|
<section id="models">
|
|
24125
|
-
<
|
|
24126
|
-
|
|
24158
|
+
<div class="section-head">
|
|
24159
|
+
<h2>Models</h2>
|
|
24160
|
+
<p>Your Agent picks a Model by its Alias. Model Notes tell it what each Model is good or bad at.</p>
|
|
24161
|
+
</div>
|
|
24127
24162
|
<div id="model-list"><p class="muted">Loading…</p></div>
|
|
24128
24163
|
|
|
24129
24164
|
<form id="add-model" class="card" autocomplete="off">
|
|
24130
24165
|
<h3>Add a Model</h3>
|
|
24131
|
-
<div class="
|
|
24132
|
-
<div class="
|
|
24133
|
-
<div class="
|
|
24134
|
-
|
|
24135
|
-
|
|
24136
|
-
|
|
24137
|
-
|
|
24138
|
-
|
|
24139
|
-
|
|
24140
|
-
|
|
24141
|
-
|
|
24142
|
-
<div class="grow"><label for="model-alias">Alias</label><input id="model-alias" required spellcheck="false" placeholder="gpt-image-cheap"></div>
|
|
24143
|
-
</div>
|
|
24144
|
-
<div class="row">
|
|
24145
|
-
<div class="grow"><label for="model-notes">Model Notes</label><textarea id="model-notes" maxlength="${modelNotesMaxLength}" placeholder="photoreal, cheap, bad at Chinese text"></textarea></div>
|
|
24166
|
+
<div class="fields">
|
|
24167
|
+
<div class="field"><label for="model-provider">Provider</label><select id="model-provider" required></select></div>
|
|
24168
|
+
<div class="field"><label for="model-protocol">Protocol</label><select id="model-protocol" required></select></div>
|
|
24169
|
+
<div class="field wide">
|
|
24170
|
+
<label for="model-id">Model id</label>
|
|
24171
|
+
<div class="inline"><input id="model-id" required spellcheck="false" list="model-ids" placeholder="gpt-image-1"><button id="fetch-models" type="button">Fetch model list</button></div>
|
|
24172
|
+
<datalist id="model-ids"></datalist>
|
|
24173
|
+
<div id="fetch-result" class="result"></div>
|
|
24174
|
+
</div>
|
|
24175
|
+
<div class="field wide"><label for="model-alias">Alias</label><input id="model-alias" required spellcheck="false" placeholder="gpt-image-cheap"></div>
|
|
24176
|
+
<div class="field wide"><label for="model-notes">Model Notes</label><textarea id="model-notes" maxlength="${modelNotesMaxLength}" placeholder="photoreal, cheap, bad at Chinese text"></textarea></div>
|
|
24146
24177
|
</div>
|
|
24147
|
-
<div class="
|
|
24178
|
+
<div class="actions"><button class="primary" type="submit">Add Model</button></div>
|
|
24148
24179
|
</form>
|
|
24149
24180
|
</section>
|
|
24150
24181
|
|
|
24151
24182
|
<section id="history">
|
|
24152
|
-
<
|
|
24153
|
-
|
|
24183
|
+
<div class="section-head">
|
|
24184
|
+
<h2>Job history</h2>
|
|
24185
|
+
<p>Newest first. Rate a Job to keep your verdict on it; rating it again replaces the earlier Rating.</p>
|
|
24186
|
+
</div>
|
|
24154
24187
|
<div id="job-list"><p class="muted">Loading…</p></div>
|
|
24155
24188
|
</section>
|
|
24156
24189
|
</main>
|
|
@@ -24193,6 +24226,25 @@ function el(tag, props, ...children) {
|
|
|
24193
24226
|
return node;
|
|
24194
24227
|
}
|
|
24195
24228
|
|
|
24229
|
+
let fieldCount = 0;
|
|
24230
|
+
/** A labelled control, with any buttons on the same line. */
|
|
24231
|
+
function field(text, control, ...buttons) {
|
|
24232
|
+
control.id ||= "field-" + ++fieldCount;
|
|
24233
|
+
return el("div", { className: "field" }, el("label", { htmlFor: control.id, textContent: text }),
|
|
24234
|
+
buttons.length === 0 ? control : el("div", { className: "inline" }, control, ...buttons));
|
|
24235
|
+
}
|
|
24236
|
+
|
|
24237
|
+
/** An API key input, masked with CSS (see .secret) and marked for 1Password, LastPass and Bitwarden to leave alone. */
|
|
24238
|
+
function secretInput(placeholder) {
|
|
24239
|
+
const input = el("input", { className: "secret", autocomplete: "off", spellcheck: false, placeholder });
|
|
24240
|
+
for (const name of ["data-1p-ignore", "data-lpignore", "data-bwignore"]) input.setAttribute(name, "true");
|
|
24241
|
+
return input;
|
|
24242
|
+
}
|
|
24243
|
+
|
|
24244
|
+
function emptyState(text) {
|
|
24245
|
+
return el("p", { className: "empty", textContent: text });
|
|
24246
|
+
}
|
|
24247
|
+
|
|
24196
24248
|
let statusTimer;
|
|
24197
24249
|
function showStatus(message, isError) {
|
|
24198
24250
|
const status = document.getElementById("status");
|
|
@@ -24230,8 +24282,7 @@ function protocolSelect(selected) {
|
|
|
24230
24282
|
|
|
24231
24283
|
function providerCard(provider) {
|
|
24232
24284
|
const baseUrl = el("input", { value: provider.baseUrl, spellcheck: false });
|
|
24233
|
-
const credential =
|
|
24234
|
-
placeholder: provider.credential ? "New API key to replace the saved one" : "Paste your API key" });
|
|
24285
|
+
const credential = secretInput(provider.credential ? "New API key to replace the saved one" : "Paste your API key");
|
|
24235
24286
|
const protocol = protocolSelect(protocolFor(provider));
|
|
24236
24287
|
const result = el("div", { className: "result" });
|
|
24237
24288
|
|
|
@@ -24248,7 +24299,7 @@ function providerCard(provider) {
|
|
|
24248
24299
|
await loadProviders();
|
|
24249
24300
|
}, "API key saved.");
|
|
24250
24301
|
|
|
24251
|
-
const removeKey = el("button", { type: "button", className: "danger", textContent: "Remove key"
|
|
24302
|
+
const removeKey = el("button", { type: "button", className: "danger", textContent: "Remove key" });
|
|
24252
24303
|
removeKey.onclick = () => {
|
|
24253
24304
|
if (!confirm("Remove the saved API key for " + provider.id + "?")) return;
|
|
24254
24305
|
act(removeKey, async () => {
|
|
@@ -24275,15 +24326,16 @@ function providerCard(provider) {
|
|
|
24275
24326
|
}, "Provider removed.");
|
|
24276
24327
|
};
|
|
24277
24328
|
|
|
24278
|
-
const
|
|
24279
|
-
? el("span", { className: "key", textContent: "
|
|
24280
|
-
: el("span", { className: "
|
|
24329
|
+
const keyChip = provider.credential
|
|
24330
|
+
? el("span", { className: "chip ok key", textContent: "Key ••••" + provider.credential.last4 })
|
|
24331
|
+
: el("span", { className: "chip", textContent: "No API key" });
|
|
24281
24332
|
|
|
24282
24333
|
return el("div", { className: "card" },
|
|
24283
|
-
el("div", { className: "
|
|
24284
|
-
el("div", { className: "
|
|
24285
|
-
|
|
24286
|
-
|
|
24334
|
+
el("div", { className: "card-head" }, el("h3", { textContent: provider.id }), keyChip),
|
|
24335
|
+
el("div", { className: "fields" },
|
|
24336
|
+
el("div", { className: "wide" }, field("Base URL", baseUrl, saveUrl)),
|
|
24337
|
+
el("div", { className: "wide" }, provider.credential ? field("API key", credential, saveKey, removeKey) : field("API key", credential, saveKey))),
|
|
24338
|
+
el("div", { className: "actions" }, protocol, test, el("span", { className: "spacer" }), remove),
|
|
24287
24339
|
result,
|
|
24288
24340
|
);
|
|
24289
24341
|
}
|
|
@@ -24292,7 +24344,7 @@ async function loadProviders() {
|
|
|
24292
24344
|
const list = document.getElementById("provider-list");
|
|
24293
24345
|
const { providers } = await api("GET", "/api/providers");
|
|
24294
24346
|
list.replaceChildren(...(providers.length === 0
|
|
24295
|
-
? [
|
|
24347
|
+
? [emptyState("No Providers yet. Add one below.")]
|
|
24296
24348
|
: providers.map(providerCard)));
|
|
24297
24349
|
providerIds = providers.map((p) => p.id);
|
|
24298
24350
|
const select = document.getElementById("model-provider");
|
|
@@ -24314,7 +24366,10 @@ function notesField(value) {
|
|
|
24314
24366
|
const update = () => (counter.textContent = textarea.value.length + " / " + notesMax);
|
|
24315
24367
|
textarea.oninput = update;
|
|
24316
24368
|
update();
|
|
24317
|
-
|
|
24369
|
+
const wrapper = field("Model Notes", textarea);
|
|
24370
|
+
wrapper.classList.add("wide");
|
|
24371
|
+
wrapper.append(counter);
|
|
24372
|
+
return { textarea, wrapper };
|
|
24318
24373
|
}
|
|
24319
24374
|
|
|
24320
24375
|
/** "Last changed by the Agent, 30 Oct 2026, 10:00", plus an undo button when there is a previous version. */
|
|
@@ -24323,20 +24378,16 @@ function notesHistoryRow(model, path) {
|
|
|
24323
24378
|
if (!history) return [];
|
|
24324
24379
|
const who = history.changedBy === "Agent" ? "your Agent" : "you";
|
|
24325
24380
|
const when = new Date(history.changedAt).toLocaleString(undefined, { dateStyle: "medium", timeStyle: "short" });
|
|
24326
|
-
const parts = [el("span", {
|
|
24381
|
+
const parts = [el("span", { textContent: "Last changed by " + who + ", " + when + "." })];
|
|
24327
24382
|
if (history.previous !== undefined) {
|
|
24328
|
-
const undo = el("button", { type: "button", textContent: "Undo", title: "Back to: " + (history.previous || "(no Model Notes)") });
|
|
24383
|
+
const undo = el("button", { type: "button", className: "small", textContent: "Undo", title: "Back to: " + (history.previous || "(no Model Notes)") });
|
|
24329
24384
|
undo.onclick = () => act(undo, async () => {
|
|
24330
24385
|
await api("POST", path + "/notes/undo");
|
|
24331
24386
|
await loadModels();
|
|
24332
24387
|
}, "Model Notes restored. Undo again to redo.");
|
|
24333
24388
|
parts.push(undo);
|
|
24334
24389
|
}
|
|
24335
|
-
return [el("div", { className: "
|
|
24336
|
-
}
|
|
24337
|
-
|
|
24338
|
-
function labelled(text, control) {
|
|
24339
|
-
return el("div", { className: "grow" }, el("label", { textContent: text }), control);
|
|
24390
|
+
return [el("div", { className: "note" }, ...parts)];
|
|
24340
24391
|
}
|
|
24341
24392
|
|
|
24342
24393
|
function modelCard(model) {
|
|
@@ -24353,7 +24404,7 @@ function modelCard(model) {
|
|
|
24353
24404
|
await loadModels();
|
|
24354
24405
|
}, "Model saved.");
|
|
24355
24406
|
|
|
24356
|
-
const makeDefault = el("button", { type: "button", textContent: "Make default" });
|
|
24407
|
+
const makeDefault = el("button", { type: "button", className: "small", textContent: "Make default" });
|
|
24357
24408
|
makeDefault.onclick = () => act(makeDefault, async () => {
|
|
24358
24409
|
await api("PUT", "/api/default-image-model", { alias: model.alias });
|
|
24359
24410
|
await loadModels();
|
|
@@ -24368,14 +24419,14 @@ function modelCard(model) {
|
|
|
24368
24419
|
}, "Model removed.");
|
|
24369
24420
|
};
|
|
24370
24421
|
|
|
24422
|
+
notes.wrapper.append(...notesHistoryRow(model, path));
|
|
24423
|
+
|
|
24371
24424
|
return el("div", { className: "card" },
|
|
24372
|
-
el("div", { className: "
|
|
24373
|
-
model.default ? el("span", { className: "
|
|
24374
|
-
el("div", { className: "
|
|
24375
|
-
|
|
24376
|
-
el("div", { className: "
|
|
24377
|
-
...notesHistoryRow(model, path),
|
|
24378
|
-
el("div", { className: "row" }, save, remove),
|
|
24425
|
+
el("div", { className: "card-head" }, el("h3", { textContent: model.alias }),
|
|
24426
|
+
model.default ? el("span", { className: "chip accent", textContent: "Default" }) : makeDefault),
|
|
24427
|
+
el("div", { className: "fields" },
|
|
24428
|
+
field("Alias", alias), field("Provider", provider), field("Model id", modelId), field("Protocol", protocol), notes.wrapper),
|
|
24429
|
+
el("div", { className: "actions" }, save, el("span", { className: "spacer" }), remove),
|
|
24379
24430
|
);
|
|
24380
24431
|
}
|
|
24381
24432
|
|
|
@@ -24383,7 +24434,7 @@ async function loadModels() {
|
|
|
24383
24434
|
const list = document.getElementById("model-list");
|
|
24384
24435
|
const { models } = await api("GET", "/api/models");
|
|
24385
24436
|
list.replaceChildren(...(models.length === 0
|
|
24386
|
-
? [
|
|
24437
|
+
? [emptyState(providerIds.length === 0 ? "Add a Provider first, then add its Models below." : "No Models yet. Add one below.")]
|
|
24387
24438
|
: models.map(modelCard)));
|
|
24388
24439
|
}
|
|
24389
24440
|
|
|
@@ -24485,11 +24536,11 @@ function formatDuration(ms) {
|
|
|
24485
24536
|
}
|
|
24486
24537
|
|
|
24487
24538
|
/** The current Rating, and a one-line reason with Good and Bad buttons to rate the Job. Only completed Jobs can be rated. */
|
|
24488
|
-
function
|
|
24539
|
+
function ratingRow(job) {
|
|
24489
24540
|
if (job.outcome !== "completed") return [];
|
|
24490
24541
|
const current = job.rating
|
|
24491
|
-
? el("span", { className: "
|
|
24492
|
-
: el("span", { className: "
|
|
24542
|
+
? el("span", { className: "rating " + job.rating.verdict, textContent: (job.rating.verdict === "good" ? "Good" : "Bad") + ": " + job.rating.reason })
|
|
24543
|
+
: el("span", { className: "rating muted", textContent: "Not rated yet." });
|
|
24493
24544
|
const reason = el("input", { maxLength: reasonMax, placeholder: "One-line reason, e.g. the text is misspelled" });
|
|
24494
24545
|
const rateAs = (verdict, label) => {
|
|
24495
24546
|
const button = el("button", { type: "button", textContent: label });
|
|
@@ -24499,10 +24550,7 @@ function ratingRows(job) {
|
|
|
24499
24550
|
}, "Rating saved.");
|
|
24500
24551
|
return button;
|
|
24501
24552
|
};
|
|
24502
|
-
return [
|
|
24503
|
-
el("div", { className: "row" }, current),
|
|
24504
|
-
el("div", { className: "row" }, el("div", { className: "grow" }, reason), rateAs("good", "Good"), rateAs("bad", "Bad")),
|
|
24505
|
-
];
|
|
24553
|
+
return [el("div", { className: "actions rate" }, current, el("div", { className: "inline" }, reason, rateAs("good", "Good"), rateAs("bad", "Bad")))];
|
|
24506
24554
|
}
|
|
24507
24555
|
|
|
24508
24556
|
function jobCard(job) {
|
|
@@ -24514,13 +24562,14 @@ function jobCard(job) {
|
|
|
24514
24562
|
if (job.error) details.push(el("p", { className: "result failure", textContent: job.error }));
|
|
24515
24563
|
if (job.shortfall) details.push(el("p", { className: "result unsupported", textContent: job.shortfall }));
|
|
24516
24564
|
return el("div", { className: "card" },
|
|
24517
|
-
el("div", {
|
|
24518
|
-
el("
|
|
24519
|
-
|
|
24565
|
+
el("div", {},
|
|
24566
|
+
el("div", { className: "card-head" }, el("h3", { textContent: job.model }),
|
|
24567
|
+
el("span", { className: "chip " + (job.outcome === "completed" ? "ok" : job.outcome), textContent: outcome })),
|
|
24568
|
+
el("p", { className: "job-meta", textContent: job.modelId + " on " + job.provider + " · " + when + " · " + formatDuration(job.durationMs) })),
|
|
24520
24569
|
el("p", { className: "prompt", textContent: job.prompt }),
|
|
24521
24570
|
...(job.files.length === 0 ? [] : [el("div", { className: "thumbs" }, ...job.files.map(thumbnail))]),
|
|
24522
24571
|
...details,
|
|
24523
|
-
...
|
|
24572
|
+
...ratingRow(job),
|
|
24524
24573
|
);
|
|
24525
24574
|
}
|
|
24526
24575
|
|
|
@@ -24528,7 +24577,7 @@ async function loadJobs() {
|
|
|
24528
24577
|
const list = document.getElementById("job-list");
|
|
24529
24578
|
const { jobs } = await api("GET", "/api/jobs");
|
|
24530
24579
|
list.replaceChildren(...(jobs.length === 0
|
|
24531
|
-
? [
|
|
24580
|
+
? [emptyState("No Jobs yet. Images your Agent generates will appear here.")]
|
|
24532
24581
|
: jobs.map(jobCard)));
|
|
24533
24582
|
}
|
|
24534
24583
|
|
|
@@ -24698,7 +24747,7 @@ function settingsOpener(options) {
|
|
|
24698
24747
|
};
|
|
24699
24748
|
}
|
|
24700
24749
|
// package.json
|
|
24701
|
-
var version2 = "0.1.
|
|
24750
|
+
var version2 = "0.1.1";
|
|
24702
24751
|
|
|
24703
24752
|
// src/server.ts
|
|
24704
24753
|
var instructions = `litevision generates images through Models the User has configured with their own Providers.
|