nai-aclab 1.0.10 → 1.0.12

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nai-aclab",
3
- "version": "1.0.10",
3
+ "version": "1.0.12",
4
4
  "description": "Local web UI for experimenting with NovelAI artist tag weight combinations.",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",
package/web/main.js CHANGED
@@ -149,20 +149,26 @@ function scheduleSave() {
149
149
  saveTimer = setTimeout(saveNow, 450);
150
150
  }
151
151
 
152
- async function saveNow() {
153
- if (!state) return;
154
- syncEditorsToState();
155
- try {
156
- const data = await request("/api/state", {
157
- method: "POST",
158
- body: JSON.stringify({ state }),
159
- });
160
- state = data.state;
161
- setSaveState("자동 저장됨");
162
- } catch (error) {
163
- setSaveState("저장 실패");
164
- console.error(error);
165
- }
152
+ async function saveNow() {
153
+ if (!state) return;
154
+ const tokenInput = $("#api_token");
155
+ const hadPendingToken = Boolean(tokenInput?.value.trim());
156
+ syncEditorsToState();
157
+ try {
158
+ const data = await request("/api/state", {
159
+ method: "POST",
160
+ body: JSON.stringify({ state }),
161
+ });
162
+ state = data.state;
163
+ if (hadPendingToken && tokenInput) {
164
+ tokenInput.value = "";
165
+ tokenInput.placeholder = "저장된 토큰을 사용합니다. 새 토큰을 입력하면 교체됩니다.";
166
+ }
167
+ setSaveState(hadPendingToken ? "API 토큰 변경 저장됨" : "자동 저장됨");
168
+ } catch (error) {
169
+ setSaveState("저장 실패");
170
+ console.error(error);
171
+ }
166
172
  }
167
173
 
168
174
  function syncEditorsToState() {
@@ -601,9 +607,17 @@ function renderSettings() {
601
607
  input.value = key === "token" ? "" : state.api[key] ?? "";
602
608
  }
603
609
  if (key === "token") {
604
- input.placeholder = state.api.token_saved ? "저장된 토큰을 사용합니다" : "API 토큰을 입력하세요";
610
+ input.placeholder = state.api.token_saved
611
+ ? "저장된 토큰을 사용합니다. 새 토큰을 입력하면 교체됩니다."
612
+ : "API 토큰을 입력하세요";
605
613
  input.autocomplete = "off";
606
- }
614
+ const help = document.createElement("small");
615
+ help.className = "field-help";
616
+ help.textContent = state.api.token_saved
617
+ ? "보안을 위해 저장된 토큰은 다시 표시하지 않습니다. 새 토큰을 입력하고 설정 저장을 누르면 기존 토큰을 교체합니다."
618
+ : "토큰은 브라우저에 다시 노출하지 않고 로컬에만 저장합니다.";
619
+ wrap.appendChild(help);
620
+ }
607
621
  if (type === "number") input.step = "any";
608
622
  }
609
623
  $("#mockMode").checked = !!state.api.mock_mode;
@@ -1619,11 +1633,13 @@ function bindAutosaveInputs(root = document) {
1619
1633
  root.querySelectorAll("input, textarea, select").forEach((node) => {
1620
1634
  if (node.id === "compareHistorySelect") return;
1621
1635
  if (node.id === "deleteFilesToggle" || node.closest("#historyList")) return;
1622
- if (node.id === "api_token") {
1623
- node.oninput = null;
1624
- node.onchange = () => {
1625
- syncEditorsToState();
1626
- scheduleSave();
1636
+ if (node.id === "api_token") {
1637
+ node.oninput = () => {
1638
+ setSaveState(node.value.trim() ? "새 API 토큰 입력됨" : "저장된 API 토큰 유지");
1639
+ };
1640
+ node.onchange = () => {
1641
+ syncEditorsToState();
1642
+ scheduleSave();
1627
1643
  };
1628
1644
  return;
1629
1645
  }
package/web/styles.css CHANGED
@@ -319,6 +319,12 @@ label span,
319
319
  font-size: 12px;
320
320
  }
321
321
 
322
+ .field-help {
323
+ color: var(--muted);
324
+ font-size: 11px;
325
+ line-height: 1.45;
326
+ }
327
+
322
328
  input,
323
329
  select,
324
330
  textarea {
package/web_app.py CHANGED
@@ -381,7 +381,6 @@ def run_batting_test(job_id: str, state: AppState) -> None:
381
381
  run_id = now_id()
382
382
  out_dir = OUTPUT_DIR / f"{run_id}_타율테스트_{len(scenes)}씬"
383
383
  out_dir.mkdir(parents=True, exist_ok=True)
384
- client = NovelAIClient(state.api)
385
384
  items = []
386
385
  progress = 0
387
386
  JOBS[job_id].update(
@@ -402,6 +401,7 @@ def run_batting_test(job_id: str, state: AppState) -> None:
402
401
  JOBS[job_id]["log"].append("중지 요청으로 남은 씬을 건너뜁니다.")
403
402
  break
404
403
  current = scene_state(state, scene)
404
+ client = NovelAIClient(current.api)
405
405
  base = selected_base(current)
406
406
  character = selected_character(current)
407
407
  count = scene_count(scene)
@@ -426,6 +426,9 @@ def run_batting_test(job_id: str, state: AppState) -> None:
426
426
  "scene_index": scene_index,
427
427
  "source_base_preset": base.name if base else "",
428
428
  "source_character_preset": character.name if character else "",
429
+ "image_size": current.generation.image_size,
430
+ "width": current.api.width,
431
+ "height": current.api.height,
429
432
  "created_at": time.strftime("%Y-%m-%d %H:%M:%S"),
430
433
  }
431
434
  try: