Fix caption cache timing, commit path tracking, and clean up start script
- Move caption cache refresh before UI render so indicators show on load - Return actual dest paths from commit_batch/commit_global to fix caption-on-apply silently failing when files are renamed on collision - Simplify start.sh to only run NiceGUI (remove Streamlit) - Add requests to requirements.txt Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
58
engine.py
58
engine.py
@@ -450,23 +450,25 @@ class SorterEngine:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def commit_global(output_root, cleanup_mode, operation="Copy", source_root=None, profile=None):
|
def commit_global(output_root, cleanup_mode, operation="Copy", source_root=None, profile=None):
|
||||||
"""Commits ALL staged files and fixes permissions."""
|
"""Commits ALL staged files and fixes permissions.
|
||||||
|
Returns dict mapping original_path -> {dest, cat} for committed files."""
|
||||||
data = SorterEngine.get_staged_data()
|
data = SorterEngine.get_staged_data()
|
||||||
|
committed = {}
|
||||||
|
|
||||||
# Save folder tags BEFORE processing (so we can restore them later)
|
# Save folder tags BEFORE processing (so we can restore them later)
|
||||||
if source_root:
|
if source_root:
|
||||||
SorterEngine.save_folder_tags(source_root, profile)
|
SorterEngine.save_folder_tags(source_root, profile)
|
||||||
|
|
||||||
conn = sqlite3.connect(SorterEngine.DB_PATH)
|
conn = sqlite3.connect(SorterEngine.DB_PATH)
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
if not os.path.exists(output_root): os.makedirs(output_root, exist_ok=True)
|
if not os.path.exists(output_root): os.makedirs(output_root, exist_ok=True)
|
||||||
|
|
||||||
# 1. Process all Staged Items
|
# 1. Process all Staged Items
|
||||||
for old_p, info in data.items():
|
for old_p, info in data.items():
|
||||||
if os.path.exists(old_p):
|
if os.path.exists(old_p):
|
||||||
final_dst = os.path.join(output_root, info['name'])
|
final_dst = os.path.join(output_root, info['name'])
|
||||||
|
|
||||||
if os.path.exists(final_dst):
|
if os.path.exists(final_dst):
|
||||||
root, ext = os.path.splitext(info['name'])
|
root, ext = os.path.splitext(info['name'])
|
||||||
c = 1
|
c = 1
|
||||||
@@ -478,12 +480,15 @@ class SorterEngine:
|
|||||||
shutil.copy2(old_p, final_dst)
|
shutil.copy2(old_p, final_dst)
|
||||||
else:
|
else:
|
||||||
shutil.move(old_p, final_dst)
|
shutil.move(old_p, final_dst)
|
||||||
|
|
||||||
# --- FIX PERMISSIONS ---
|
# --- FIX PERMISSIONS ---
|
||||||
SorterEngine.fix_permissions(final_dst)
|
SorterEngine.fix_permissions(final_dst)
|
||||||
|
|
||||||
|
# Track actual destination
|
||||||
|
committed[old_p] = {"dest": final_dst, "cat": info['cat']}
|
||||||
|
|
||||||
# Log History
|
# Log History
|
||||||
cursor.execute("INSERT OR REPLACE INTO processed_log VALUES (?, ?, ?)",
|
cursor.execute("INSERT OR REPLACE INTO processed_log VALUES (?, ?, ?)",
|
||||||
(old_p, info['cat'], operation))
|
(old_p, info['cat'], operation))
|
||||||
|
|
||||||
# 2. Global Cleanup
|
# 2. Global Cleanup
|
||||||
@@ -495,16 +500,17 @@ class SorterEngine:
|
|||||||
unused_dir = os.path.join(source_root, "unused")
|
unused_dir = os.path.join(source_root, "unused")
|
||||||
os.makedirs(unused_dir, exist_ok=True)
|
os.makedirs(unused_dir, exist_ok=True)
|
||||||
dest_unused = os.path.join(unused_dir, os.path.basename(img_p))
|
dest_unused = os.path.join(unused_dir, os.path.basename(img_p))
|
||||||
|
|
||||||
shutil.move(img_p, dest_unused)
|
shutil.move(img_p, dest_unused)
|
||||||
SorterEngine.fix_permissions(dest_unused)
|
SorterEngine.fix_permissions(dest_unused)
|
||||||
|
|
||||||
elif cleanup_mode == "Delete":
|
elif cleanup_mode == "Delete":
|
||||||
os.remove(img_p)
|
os.remove(img_p)
|
||||||
|
|
||||||
cursor.execute("DELETE FROM staging_area")
|
cursor.execute("DELETE FROM staging_area")
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
return committed
|
||||||
|
|
||||||
# --- 6. CORE UTILITIES (SYNC & UNDO) ---
|
# --- 6. CORE UTILITIES (SYNC & UNDO) ---
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -616,21 +622,23 @@ class SorterEngine:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def commit_batch(file_list, output_root, cleanup_mode, operation="Copy"):
|
def commit_batch(file_list, output_root, cleanup_mode, operation="Copy"):
|
||||||
"""Commits files and fixes permissions."""
|
"""Commits files and fixes permissions.
|
||||||
|
Returns dict mapping original_path -> actual_dest_path for committed files."""
|
||||||
data = SorterEngine.get_staged_data()
|
data = SorterEngine.get_staged_data()
|
||||||
conn = sqlite3.connect(SorterEngine.DB_PATH)
|
conn = sqlite3.connect(SorterEngine.DB_PATH)
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
committed = {}
|
||||||
|
|
||||||
if not os.path.exists(output_root): os.makedirs(output_root, exist_ok=True)
|
if not os.path.exists(output_root): os.makedirs(output_root, exist_ok=True)
|
||||||
|
|
||||||
for file_path in file_list:
|
for file_path in file_list:
|
||||||
if not os.path.exists(file_path): continue
|
if not os.path.exists(file_path): continue
|
||||||
|
|
||||||
# --- CASE A: Tagged ---
|
# --- CASE A: Tagged ---
|
||||||
if file_path in data and data[file_path]['marked']:
|
if file_path in data and data[file_path]['marked']:
|
||||||
info = data[file_path]
|
info = data[file_path]
|
||||||
final_dst = os.path.join(output_root, info['name'])
|
final_dst = os.path.join(output_root, info['name'])
|
||||||
|
|
||||||
# Collision Check
|
# Collision Check
|
||||||
if os.path.exists(final_dst):
|
if os.path.exists(final_dst):
|
||||||
root, ext = os.path.splitext(info['name'])
|
root, ext = os.path.splitext(info['name'])
|
||||||
@@ -638,7 +646,7 @@ class SorterEngine:
|
|||||||
while os.path.exists(final_dst):
|
while os.path.exists(final_dst):
|
||||||
final_dst = os.path.join(output_root, f"{root}_{c}{ext}")
|
final_dst = os.path.join(output_root, f"{root}_{c}{ext}")
|
||||||
c += 1
|
c += 1
|
||||||
|
|
||||||
# Perform Action
|
# Perform Action
|
||||||
if operation == "Copy":
|
if operation == "Copy":
|
||||||
shutil.copy2(file_path, final_dst)
|
shutil.copy2(file_path, final_dst)
|
||||||
@@ -648,26 +656,30 @@ class SorterEngine:
|
|||||||
# --- FIX PERMISSIONS ---
|
# --- FIX PERMISSIONS ---
|
||||||
SorterEngine.fix_permissions(final_dst)
|
SorterEngine.fix_permissions(final_dst)
|
||||||
|
|
||||||
|
# Track actual destination
|
||||||
|
committed[file_path] = {"dest": final_dst, "cat": info['cat']}
|
||||||
|
|
||||||
# Update DB
|
# Update DB
|
||||||
cursor.execute("DELETE FROM staging_area WHERE original_path = ?", (file_path,))
|
cursor.execute("DELETE FROM staging_area WHERE original_path = ?", (file_path,))
|
||||||
cursor.execute("INSERT OR REPLACE INTO processed_log VALUES (?, ?, ?)",
|
cursor.execute("INSERT OR REPLACE INTO processed_log VALUES (?, ?, ?)",
|
||||||
(file_path, info['cat'], operation))
|
(file_path, info['cat'], operation))
|
||||||
|
|
||||||
# --- CASE B: Cleanup ---
|
# --- CASE B: Cleanup ---
|
||||||
elif cleanup_mode != "Keep":
|
elif cleanup_mode != "Keep":
|
||||||
if cleanup_mode == "Move to Unused":
|
if cleanup_mode == "Move to Unused":
|
||||||
unused_dir = os.path.join(os.path.dirname(file_path), "unused")
|
unused_dir = os.path.join(os.path.dirname(file_path), "unused")
|
||||||
os.makedirs(unused_dir, exist_ok=True)
|
os.makedirs(unused_dir, exist_ok=True)
|
||||||
dest_unused = os.path.join(unused_dir, os.path.basename(file_path))
|
dest_unused = os.path.join(unused_dir, os.path.basename(file_path))
|
||||||
|
|
||||||
shutil.move(file_path, dest_unused)
|
shutil.move(file_path, dest_unused)
|
||||||
SorterEngine.fix_permissions(dest_unused) # Fix here too
|
SorterEngine.fix_permissions(dest_unused) # Fix here too
|
||||||
|
|
||||||
elif cleanup_mode == "Delete":
|
elif cleanup_mode == "Delete":
|
||||||
os.remove(file_path)
|
os.remove(file_path)
|
||||||
|
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
return committed
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def rename_category(old_name, new_name):
|
def rename_category(old_name, new_name):
|
||||||
|
|||||||
@@ -273,10 +273,10 @@ def load_images():
|
|||||||
state.page = 0
|
state.page = 0
|
||||||
|
|
||||||
refresh_staged_info()
|
refresh_staged_info()
|
||||||
refresh_ui()
|
# Load caption data before rendering so indicators appear on cards
|
||||||
# Refresh caption cache in background (non-blocking)
|
|
||||||
state.refresh_caption_cache()
|
state.refresh_caption_cache()
|
||||||
state.load_caption_settings()
|
state.load_caption_settings()
|
||||||
|
refresh_ui()
|
||||||
|
|
||||||
# ==========================================
|
# ==========================================
|
||||||
# PAIRING MODE FUNCTIONS
|
# PAIRING MODE FUNCTIONS
|
||||||
@@ -715,24 +715,16 @@ async def action_apply_page():
|
|||||||
ui.notify("No images on current page", type='warning')
|
ui.notify("No images on current page", type='warning')
|
||||||
return
|
return
|
||||||
|
|
||||||
# Get tagged images and their categories before commit (they'll be moved/copied)
|
committed = SorterEngine.commit_batch(batch, state.output_dir, state.cleanup_mode, state.batch_mode)
|
||||||
tagged_batch = []
|
|
||||||
for img_path in batch:
|
|
||||||
if img_path in state.staged_data:
|
|
||||||
info = state.staged_data[img_path]
|
|
||||||
# Calculate destination path
|
|
||||||
dest_path = os.path.join(state.output_dir, info['name'])
|
|
||||||
tagged_batch.append((img_path, info['cat'], dest_path))
|
|
||||||
|
|
||||||
SorterEngine.commit_batch(batch, state.output_dir, state.cleanup_mode, state.batch_mode)
|
# Caption on apply if enabled - uses actual dest paths from commit
|
||||||
|
if state.caption_on_apply and committed:
|
||||||
# Caption on apply if enabled
|
|
||||||
if state.caption_on_apply and tagged_batch:
|
|
||||||
state.load_caption_settings()
|
state.load_caption_settings()
|
||||||
caption_count = 0
|
caption_count = 0
|
||||||
for orig_path, category, dest_path in tagged_batch:
|
for orig_path, info in committed.items():
|
||||||
|
dest_path = info['dest']
|
||||||
if os.path.exists(dest_path):
|
if os.path.exists(dest_path):
|
||||||
prompt = SorterEngine.get_category_prompt(state.profile_name, category)
|
prompt = SorterEngine.get_category_prompt(state.profile_name, info['cat'])
|
||||||
caption, error = await run.io_bound(
|
caption, error = await run.io_bound(
|
||||||
SorterEngine.caption_image_vllm,
|
SorterEngine.caption_image_vllm,
|
||||||
dest_path, prompt, state.caption_settings
|
dest_path, prompt, state.caption_settings
|
||||||
@@ -753,14 +745,7 @@ async def action_apply_global():
|
|||||||
"""Apply all staged changes globally."""
|
"""Apply all staged changes globally."""
|
||||||
ui.notify("Starting global apply... This may take a while.", type='info')
|
ui.notify("Starting global apply... This may take a while.", type='info')
|
||||||
|
|
||||||
# Capture staged data before commit for captioning
|
committed = await run.io_bound(
|
||||||
staged_before_commit = {}
|
|
||||||
if state.caption_on_apply:
|
|
||||||
for img_path, info in state.staged_data.items():
|
|
||||||
dest_path = os.path.join(state.output_dir, info['name'])
|
|
||||||
staged_before_commit[img_path] = {'cat': info['cat'], 'dest': dest_path}
|
|
||||||
|
|
||||||
await run.io_bound(
|
|
||||||
SorterEngine.commit_global,
|
SorterEngine.commit_global,
|
||||||
state.output_dir,
|
state.output_dir,
|
||||||
state.cleanup_mode,
|
state.cleanup_mode,
|
||||||
@@ -769,13 +754,13 @@ async def action_apply_global():
|
|||||||
state.profile_name
|
state.profile_name
|
||||||
)
|
)
|
||||||
|
|
||||||
# Caption on apply if enabled
|
# Caption on apply if enabled - uses actual dest paths from commit
|
||||||
if state.caption_on_apply and staged_before_commit:
|
if state.caption_on_apply and committed:
|
||||||
state.load_caption_settings()
|
state.load_caption_settings()
|
||||||
ui.notify(f"Captioning {len(staged_before_commit)} images...", type='info')
|
ui.notify(f"Captioning {len(committed)} images...", type='info')
|
||||||
|
|
||||||
caption_count = 0
|
caption_count = 0
|
||||||
for orig_path, info in staged_before_commit.items():
|
for orig_path, info in committed.items():
|
||||||
dest_path = info['dest']
|
dest_path = info['dest']
|
||||||
if os.path.exists(dest_path):
|
if os.path.exists(dest_path):
|
||||||
prompt = SorterEngine.get_category_prompt(state.profile_name, info['cat'])
|
prompt = SorterEngine.get_category_prompt(state.profile_name, info['cat'])
|
||||||
|
|||||||
33
start.sh
Normal file → Executable file
33
start.sh
Normal file → Executable file
@@ -1,18 +1,25 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# 1. Navigate to app directory
|
# NiceSorter - Start Script
|
||||||
cd /app
|
# Runs the NiceGUI gallery interface on port 8080
|
||||||
|
|
||||||
# 2. Install dependencies (Including NiceGUI if missing)
|
set -e
|
||||||
# This checks your requirements.txt AND ensures nicegui is present
|
|
||||||
pip install --no-cache-dir -r requirements.txt
|
|
||||||
|
|
||||||
# 3. Start NiceGUI in the Background (&)
|
# Navigate to app directory if running in container
|
||||||
# This runs silently while the script continues
|
if [ -d "/app" ]; then
|
||||||
echo "🚀 Starting NiceGUI on Port 8080..."
|
cd /app
|
||||||
python3 gallery_app.py &
|
fi
|
||||||
|
|
||||||
# 4. Start Streamlit in the Foreground
|
# Install dependencies if requirements.txt exists
|
||||||
# This keeps the container running
|
if [ -f "requirements.txt" ]; then
|
||||||
echo "🚀 Starting Streamlit on Port 8501..."
|
echo "📦 Installing dependencies..."
|
||||||
streamlit run app.py --server.port=8501 --server.address=0.0.0.0
|
pip install --no-cache-dir -q -r requirements.txt
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Initialize database
|
||||||
|
echo "🗄️ Initializing database..."
|
||||||
|
python3 -c "from engine import SorterEngine; SorterEngine.init_db()"
|
||||||
|
|
||||||
|
# Start NiceGUI
|
||||||
|
echo "🚀 Starting NiceSorter on http://0.0.0.0:8080"
|
||||||
|
exec python3 gallery_app.py
|
||||||
|
|||||||
Reference in New Issue
Block a user