v1.9.151: Postural Recovery Hysteresis Fix & Browser Delete Confirmation - Added delete_item API and keyboard bindings
This commit is contained in:
@@ -248,6 +248,35 @@ async def get_video_poster(request):
|
||||
traceback.print_exc()
|
||||
return web.Response(status=500, text=str(e))
|
||||
|
||||
async def delete_item(request):
|
||||
data = await request.json()
|
||||
relative_path_str = data.get("path", "")
|
||||
filename = data.get("filename", "")
|
||||
if not filename:
|
||||
return web.Response(status=400, text="Filename is required.")
|
||||
|
||||
try:
|
||||
file_path = get_safe_path(output_dir, os.path.join(relative_path_str, filename))
|
||||
|
||||
if file_path.is_file():
|
||||
os.remove(file_path)
|
||||
# Also cleanup potential cache files
|
||||
cache_thumb = get_safe_path(image_thumb_dir, os.path.join(relative_path_str, f"{file_path.stem}.jpg"))
|
||||
if cache_thumb.exists(): os.remove(cache_thumb)
|
||||
cache_poster = get_safe_path(video_poster_dir, os.path.join(relative_path_str, f"{file_path.stem}.jpg"))
|
||||
if cache_poster.exists(): os.remove(cache_poster)
|
||||
|
||||
return web.json_response({"status": "success", "message": f"File {filename} deleted."})
|
||||
elif file_path.is_dir():
|
||||
shutil.rmtree(file_path)
|
||||
return web.json_response({"status": "success", "message": f"Directory {filename} deleted."})
|
||||
else:
|
||||
return web.Response(status=404, text="Item not found")
|
||||
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
return web.Response(status=500, text=str(e))
|
||||
|
||||
async def get_batch_metadata(request):
|
||||
data = await request.json()
|
||||
path = data.get("path", "")
|
||||
@@ -336,6 +365,7 @@ server.PromptServer.instance.app.router.add_post('/api/aiia/v1/browser/get_batch
|
||||
server.PromptServer.instance.app.router.add_get('/api/aiia/v1/browser/thumbnail', get_thumbnail)
|
||||
server.PromptServer.instance.app.router.add_get('/api/aiia/v1/browser/poster', get_video_poster)
|
||||
server.PromptServer.instance.app.router.add_get('/api/aiia/v1/browser/get_workflow', get_workflow)
|
||||
server.PromptServer.instance.app.router.add_post('/api/aiia/v1/browser/delete_item', delete_item)
|
||||
|
||||
NODE_CLASS_MAPPINGS = {}
|
||||
NODE_DISPLAY_NAME_MAPPINGS = {}
|
||||
+25
-2
@@ -76,7 +76,7 @@ class AIIABrowserDialog extends ComfyDialog {
|
||||
this.iconView = { resizeObserver: null, columns: 0, rowHeight: 0, activeNodes: new Map(), nodePool: [], mediaCache: new Map() };
|
||||
this.applyFocusRaf = null;
|
||||
this.iconLoadTimers = new Map();
|
||||
|
||||
|
||||
this.outsideClickListener = null;
|
||||
|
||||
this.tooltipImage = $el("img.aiia-tooltip-image");
|
||||
@@ -300,6 +300,25 @@ class AIIABrowserDialog extends ComfyDialog {
|
||||
}
|
||||
}
|
||||
}
|
||||
async deleteItem(itemName) {
|
||||
if (!itemName) return;
|
||||
if (!confirm(`Are you sure you want to delete "${itemName}"? This action cannot be undone.`)) return;
|
||||
try {
|
||||
const res = await api.fetchApi('/aiia/v1/browser/delete_item', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ path: this.currentPath, filename: itemName })
|
||||
});
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`);
|
||||
const result = await res.json();
|
||||
if (result.status === 'success') {
|
||||
this.setFocus(null, null);
|
||||
this.refreshCurrentDirectory();
|
||||
}
|
||||
} catch (e) {
|
||||
alert(`Error deleting item: ${e.message}`);
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
updateTooltipSizeLimits() { const rect = this.contentPanel.getBoundingClientRect(); const maxSize = Math.min(rect.width, rect.height) * 0.30; this.tooltipElement.style.setProperty('--aiia-tooltip-media-max-size', `${maxSize}px`); }
|
||||
updateSortControls() { this.sortKeySelect.value = this.sortKey; this.sortDirButton.textContent = this.sortDir === 'asc' ? '▲' : '▼'; this.sortDirButton.dataset.tooltipText = `Sort ${this.sortDir === 'asc' ? 'Descending' : 'Ascending'}`; }
|
||||
showPathInput() { this.breadcrumbs.style.display = 'none'; this.pathInputContainer.style.display = 'flex'; this.pathInput.value = this.currentPath; this.pathInput.focus(); this.pathInput.select(); }
|
||||
@@ -1313,11 +1332,15 @@ class AIIABrowserDialog extends ComfyDialog {
|
||||
|
||||
if ((target.tagName === 'INPUT' && target !== this.pathInput) || target.tagName === 'SELECT') return;
|
||||
|
||||
const keyMap = { 'ArrowUp': 'moveFocus', 'ArrowDown': 'moveFocus', 'ArrowLeft': 'moveFocus', 'ArrowRight': 'moveFocus', 'Enter': 'activateFocusedItem', ' ': 'toggleTooltipForFocusedItem', 'Escape': 'close' };
|
||||
const keyMap = { 'ArrowUp': 'moveFocus', 'ArrowDown': 'moveFocus', 'ArrowLeft': 'moveFocus', 'ArrowRight': 'moveFocus', 'Enter': 'activateFocusedItem', ' ': 'toggleTooltipForFocusedItem', 'Escape': 'close', 'Delete': 'deleteFocusedItem' };
|
||||
if (keyMap[e.key]) {
|
||||
e.preventDefault(); e.stopPropagation();
|
||||
this.isKeyboardNavigating = true;
|
||||
if (keyMap[e.key] === 'moveFocus') this.moveFocus(e.key);
|
||||
else if (keyMap[e.key] === 'deleteFocusedItem') {
|
||||
const item = this.getAllNavigableItems()[this.currentFocusIndex];
|
||||
if (item) this.deleteItem(item.name);
|
||||
}
|
||||
else this[keyMap[e.key]]();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,7 +161,7 @@ class Audio2Motion:
|
||||
|
||||
return res_kp_seq
|
||||
|
||||
def _update_kp_cond(self, res_kp_seq, idx):
|
||||
def _update_kp_cond(self, res_kp_seq, idx, step_len=0):
|
||||
# [v1.9.144] Unconditional State Tracking
|
||||
# We process these outside the fix_kp_cond branches to ensure monitoring never stops.
|
||||
last_pose = res_kp_seq[:, idx-1]
|
||||
@@ -175,11 +175,25 @@ class Audio2Motion:
|
||||
self.global_time += 1
|
||||
delta_p = pitch_deg - self.s_pitch_deg
|
||||
|
||||
# [v1.9.149] Pulse Heartbeat (Every Chunk)
|
||||
# 1:1 log frequency to eliminate blind spots
|
||||
# [v1.9.150] Detailed Diagnostic Pulse
|
||||
# Always output current frame pitch and silence status
|
||||
real_f = res_kp_seq.shape[1]
|
||||
tag = "[Postural HEARTBEAT]" if self.look_up_timer <= 50 else "[RECOVERY ACTIVE]"
|
||||
print(f"{tag} Frame {real_f} | Raw={pitch_deg:.2f}° | Delta={delta_p:+.2f}° | Timer={self.look_up_timer}/50")
|
||||
print(f"{tag} Frame {real_f:04d} | Delta={delta_p:+.2f}° | Silence={self.silence_frames:03d} | Timer={self.look_up_timer}/50")
|
||||
|
||||
# 5. Postural Auto-Correction Logic [v1.9.150 Robustness Patch]
|
||||
# Shorten silence threshold to 25 frames (1 second) to combat jittery audio
|
||||
# Only trigger if notably higher than source (> 2.0 deg Higher -> Delta < -2.0)
|
||||
if self.silence_frames >= 25 and delta_p < -2.0:
|
||||
self.look_up_timer += step_len
|
||||
if self.look_up_timer > 50:
|
||||
print(f"[正在执行纠偏] Frame {real_f} | Delta={delta_p:+.2f}° | Active Down-Pull engaged.")
|
||||
else:
|
||||
# ONLY RESET if we are back in the safe zone (Hysteresis)
|
||||
if delta_p >= -0.5:
|
||||
if self.look_up_timer > 50:
|
||||
print(f"[Postural] Recovery Finished (Delta={delta_p:+.2f}°). Timer reset.")
|
||||
self.look_up_timer = 0
|
||||
|
||||
if self.fix_kp_cond == 0: # 不重置
|
||||
# 1. Silence Intensity Boost
|
||||
@@ -213,23 +227,10 @@ class Audio2Motion:
|
||||
brow_indices = [217, 218, 220]
|
||||
brow_jitter = np.random.normal(0, 0.015 * boost_factor, len(brow_indices)).astype(np.float32)
|
||||
|
||||
# 5. Postural Auto-Correction (Anti-Stall) - [v1.9.149 Frame-Sync Fix]
|
||||
if self.silence_frames > 50 and delta_p < -2.0:
|
||||
# v1.149: Increment by frames added, not just 1.
|
||||
self.look_up_timer += self.valid_clip_len
|
||||
if self.look_up_timer > 50:
|
||||
diag_tag = "[正在执行纠偏]"
|
||||
print(f"{diag_tag} Frame {real_f} | Delta={delta_p:+.2f}° | Push-Down Active")
|
||||
else:
|
||||
# Reset only if significantly recovered (Hysteresis)
|
||||
if delta_p > -0.5:
|
||||
if self.look_up_timer > 50:
|
||||
print(f"[Postural] Recovery Finished at Delta={delta_p:+.2f}°. Timer Reset.")
|
||||
self.look_up_timer = 0
|
||||
|
||||
gravity_vec = np.ones_like(last_pose) * 0.05
|
||||
if self.look_up_timer > 50:
|
||||
gravity_vec[0, 1:67] = 0.20
|
||||
|
||||
# 6. Integration
|
||||
next_pose = last_pose + noise
|
||||
for i, idx_in_kp in enumerate(brow_indices):
|
||||
@@ -342,7 +343,7 @@ class Audio2Motion:
|
||||
self.clip_idx += 1
|
||||
|
||||
idx = res_kp_seq.shape[1] - self.overlap_v2
|
||||
self._update_kp_cond(res_kp_seq, idx)
|
||||
self._update_kp_cond(res_kp_seq, idx, step_len=step_len)
|
||||
|
||||
return res_kp_seq
|
||||
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "aiia"
|
||||
description = "The Ultimate AI Audio/Video toolkit for ComfyUI. Features an enhanced Ditto (with optimizations that outperform official demos and other SOTA talking head models in lip-sync accuracy and natural motion), EchoMimic V3 & FLOAT, VibeVoice & CosyVoice 3.0 (Zero-Shot Voice Cloning), Multi-Role Podcast Generation, and a powerful Media Browser."
|
||||
version = "1.9.149"
|
||||
version = "1.9.151"
|
||||
license = {file = "LICENSE"}
|
||||
readme = "README.md"
|
||||
authors = [
|
||||
|
||||
Reference in New Issue
Block a user