Update visible character manager

This commit is contained in:
sn0w
2025-10-31 15:48:33 +01:00
parent 4cc9df46fd
commit db1c7a10fd
3 changed files with 171 additions and 149 deletions
+6 -137
View File
@@ -172,12 +172,9 @@ async def series_endpoint(request):
@PromptServer.instance.routes.get(f"{API_PREFIX}/visible_series")
async def get_visible_series(request):
path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "web/settings/visible_series.json")
if os.path.exists(path):
with open(path, "r", encoding="utf-8") as f:
data = json.load(f)
return web.json_response({"visible_series": data})
return web.json_response({"visible_series": []})
db = CharacterDBLoader()
visible_series = db.load_visible_series()
return web.json_response({"visible_series": list(visible_series) if visible_series else []})
@PromptServer.instance.routes.post(f"{API_PREFIX}/visible_series")
@@ -201,135 +198,7 @@ async def get_characters_by_series_endpoint(request):
@PromptServer.instance.routes.get(f"{API_PREFIX}/series_selector")
async def serve_series_selector(request):
html = """
<html>
<head>
<title>Select Series</title>
<style>
body {
font-family: 'Segoe UI', Arial, sans-serif;
background: #181a20;
margin: 0;
padding: 0;
color: #e6e6e6;
}
.container {
max-width: 520px;
margin: 48px auto;
background: #23242b;
border-radius: 14px;
padding: 36px 28px 28px 28px;
}
h2 {
margin-top: 0;
color: #fff;
font-size: 1.6em;
margin-bottom: 0px;
letter-spacing: 0.5px;
}
#series-list {
border-radius: 8px;
padding: 10px 0 0 0;
background: transparent;
border: none;
max-height: calc(100vh - 300px);
overflow: auto;
}
label {
display: flex;
align-items: center;
font-size: 1.13em;
cursor: pointer;
border-radius: 6px;
transition: background 0.15s;
}
label:hover {
background: #292b33;
}
input[type="checkbox"] {
margin-right: 12px;
accent-color: #4fa3ff;
width: 18px;
height: 18px;
}
button {
background-color: #4fa3ff;
color: #fff;
border: none;
border-radius: 6px;
padding: 12px 28px;
font-size: 1.08em;
cursor: pointer;
font-weight: 500;
transition: background 0.2s;
}
button:hover {
background-color: #2366d1;
}
#result {
margin-top: 18px;
color: #4fa3ff;
font-weight: bold;
min-height: 24px;
letter-spacing: 0.2px;
}
@media (max-width: 600px) {
.container {
max-width: 98vw;
padding: 18px 4vw 18px 4vw;
}
h2 {
font-size: 1.2em;
}
label {
font-size: 1em;
}
}
</style>
</head>
<body>
<div class="container">
<h2>Select Series to Show in Dropdown</h2>
<div id="series-list"></div>
<button onclick="save()">Save</button>
<div id="result"></div>
</div>
<script>
let allSeries = [];
function load() {
fetch('/sn0w/series').then(r=>r.json()).then(data=>{
allSeries = data.series;
fetch('/sn0w/visible_series').then(r2=>r2.json()).then(vs=>{
let div = document.getElementById('series-list');
div.innerHTML = '';
let visible = vs.visible_series;
// If none selected, select all by default
if (!visible || visible.length === 0) visible = allSeries;
allSeries.forEach(s=>{
let checked = visible.includes(s) ? 'checked' : '';
// Create the Danbooru link
let tag = encodeURIComponent(s.replace(/ /g, "_"));
let link = `<a href="https://danbooru.donmai.us/posts?tags=${tag}" target="_blank" style="margin-left:8px;color:#4fa3ff;text-decoration:underline;font-size:0.95em;">link</a>`;
div.innerHTML += `<label><input type="checkbox" value="${s}" ${checked}>${s}${link}</label>`;
});
});
});
}
function save() {
let cbs = document.querySelectorAll('#series-list input[type=checkbox]');
let vals = [];
cbs.forEach(cb=>{ if(cb.checked) vals.push(cb.value); });
fetch('/sn0w/visible_series', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({visible_series: vals})
}).then(r=>r.json()).then(data=>{
document.getElementById('result').innerText = 'Saved!';
});
}
window.onload = load;
</script>
</body>
</html>
"""
path = os.path.join(os.path.dirname(os.path.realpath(__file__)), "web", "characters", "index.html")
with open(path, "r", encoding="utf-8") as f:
html = f.read()
return web.Response(text=html, content_type="text/html")
+66 -12
View File
@@ -438,6 +438,7 @@ class MessageHolder:
class CharacterDBLoader:
def __init__(self):
start = time.perf_counter()
self.logger = Logger()
self.db_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "web", "characters", "characters.db")
self.conn = sqlite3.connect(self.db_path, check_same_thread=False) # Allow multi-thread access
@@ -446,8 +447,11 @@ class CharacterDBLoader:
self.logger.log(f"Connected to database at {self.db_path}", "DEBUG")
self._ensure_table()
self._custom_characters = self._load_custom_characters()
duration = time.perf_counter() - start
self.logger.log(f"CharacterDBLoader __init__ took {duration:.4f} seconds", "DEBUG")
def _ensure_table(self):
start = time.perf_counter()
with self.lock:
self.cursor.execute("""
CREATE TABLE IF NOT EXISTS characters (
@@ -461,9 +465,12 @@ class CharacterDBLoader:
)
""")
self.conn.commit()
duration = time.perf_counter() - start
self.logger.log(f"CharacterDBLoader _ensure_table took {duration:.4f} seconds", "DEBUG")
def _load_custom_characters(self):
"""Load custom characters from JSON and return as dict."""
start = time.perf_counter()
base_dir = os.path.dirname(os.path.dirname(self.db_path))
custom_json_path = os.path.join(base_dir, "settings/custom_characters.json")
custom_dict = {}
@@ -490,6 +497,8 @@ class CharacterDBLoader:
self.logger.log(f"Error loading custom characters: {e}", "ERROR")
else:
self.logger.log("Custom characters JSON file not found", "DEBUG")
duration = time.perf_counter() - start
self.logger.log(f"CharacterDBLoader _load_custom_characters took {duration:.4f} seconds", "DEBUG")
return custom_dict
def _extract_series_name(self, character_name):
@@ -504,10 +513,11 @@ class CharacterDBLoader:
def _get_db_characters(self):
"""Get characters from DB."""
start = time.perf_counter()
with self.lock:
self.cursor.execute("SELECT * FROM characters")
rows = self.cursor.fetchall()
return {
result = {
row[0]: {
"gender": row[1],
"series": row[2],
@@ -518,9 +528,13 @@ class CharacterDBLoader:
}
for row in rows
}
duration = time.perf_counter() - start
self.logger.log(f"CharacterDBLoader _get_db_characters took {duration:.4f} seconds", "DEBUG")
return result
def get_all_characters(self):
"""Get all characters from DB and custom merged."""
start = time.perf_counter()
db_chars = self._get_db_characters()
merged = db_chars.copy()
for name, char in self._custom_characters.items():
@@ -540,55 +554,90 @@ class CharacterDBLoader:
self.logger.log(
f"Merged {len(db_chars)} DB characters with {len(self._custom_characters)} custom characters", "DEBUG"
)
duration = time.perf_counter() - start
self.logger.log(f"CharacterDBLoader get_all_characters took {duration:.4f} seconds", "DEBUG")
return merged
def get_characters_by_series(self, series_name):
"""Get characters by series from merged data."""
start = time.perf_counter()
all_chars = self.get_all_characters()
return {name: char for name, char in all_chars.items() if char["series"] == series_name}
result = {name: char for name, char in all_chars.items() if char["series"] == series_name}
duration = time.perf_counter() - start
self.logger.log(f"CharacterDBLoader get_characters_by_series took {duration:.4f} seconds", "DEBUG")
return result
def get_all_series(self):
"""Get all series from merged data."""
start = time.perf_counter()
all_chars = self.get_all_characters()
series_set = set(char["series"] for char in all_chars.values() if char["series"])
return sorted(series_set)
result = sorted(series_set)
duration = time.perf_counter() - start
self.logger.log(f"CharacterDBLoader get_all_series took {duration:.4f} seconds", "DEBUG")
return result
def get_filtered_characters(self, visible_series=None):
"""Get filtered characters from merged data."""
start = time.perf_counter()
all_chars = self.get_all_characters()
if visible_series:
return {
result = {
name: char
for name, char in all_chars.items()
if char.get("is_custom") or char["series"] in visible_series
}
return all_chars
else:
result = all_chars
duration = time.perf_counter() - start
self.logger.log(f"CharacterDBLoader get_filtered_characters took {duration:.4f} seconds", "DEBUG")
return result
def load_visible_series(self):
"""Load visible series from JSON file"""
start = time.perf_counter()
base_dir = os.path.dirname(os.path.dirname(self.db_path))
visible_series_path = os.path.join(base_dir, "settings/visible_series.json")
try:
with open(visible_series_path, "r", encoding="utf-8") as f:
visible_series = json.load(f)
content = f.read().strip()
if not content:
raise FileNotFoundError("File is empty")
visible_series = json.loads(content)
self.logger.log(f"Loaded {len(visible_series) if visible_series else 0} visible series", "DEBUG")
return set(visible_series) if visible_series else None
result = set(visible_series) if visible_series else None
except FileNotFoundError:
self.logger.log(f"Visible series file not found at: {visible_series_path}", "WARNING")
return None
self.logger.log(f"Visible series file not found or empty at: {visible_series_path}", "WARNING")
all_series = self.get_all_series()
os.makedirs(os.path.dirname(visible_series_path), exist_ok=True)
with open(visible_series_path, "w", encoding="utf-8") as f:
json.dump(all_series, f)
result = set(all_series)
except Exception as e:
self.logger.log(f"Error reading visible series file: {e}", "ERROR")
return None
all_series = self.get_all_series()
os.makedirs(os.path.dirname(visible_series_path), exist_ok=True)
with open(visible_series_path, "w", encoding="utf-8") as f:
json.dump(all_series, f)
result = set(all_series)
duration = time.perf_counter() - start
self.logger.log(f"CharacterDBLoader load_visible_series took {duration:.4f} seconds", "DEBUG")
return result
def get_visible_characters(self, include_default=True):
"""Get characters filtered by visible series or only custom if include_default is False"""
start = time.perf_counter()
if not include_default:
return self._custom_characters
result = self._custom_characters
else:
visible_series = self.load_visible_series()
return self.get_filtered_characters(visible_series)
result = self.get_filtered_characters(visible_series)
duration = time.perf_counter() - start
self.logger.log(f"CharacterDBLoader get_visible_characters took {duration:.4f} seconds", "DEBUG")
return result
def insert_character(self, name, gender, series="", associated_string="", prompt="", clothing_tags="", is_custom=0):
start = time.perf_counter()
# Only insert if not custom, or handle custom separately
if is_custom:
self.logger.log(f"Skipping insert for custom character: {name}", "DEBUG")
@@ -606,11 +655,16 @@ class CharacterDBLoader:
(name, gender, series, associated_string, prompt, clothing_tags, is_custom),
)
self.conn.commit()
duration = time.perf_counter() - start
self.logger.log(f"CharacterDBLoader insert_character took {duration:.4f} seconds", "DEBUG")
def close(self):
start = time.perf_counter()
self.logger.log("Closing database connection", "DEBUG")
with self.lock:
self.conn.close()
duration = time.perf_counter() - start
self.logger.log(f"CharacterDBLoader close took {duration:.4f} seconds", "DEBUG")
API_PREFIX = "/sn0w"
File diff suppressed because one or more lines are too long