diff --git a/js/speech_length_calculator.js b/js/speech_length_calculator.js
deleted file mode 100644
index dfcf565..0000000
--- a/js/speech_length_calculator.js
+++ /dev/null
@@ -1,336 +0,0 @@
-import { app } from "../../scripts/app.js";
-import { ComfyWidgets } from "../../scripts/widgets.js";
-
-// 1. DEFINE CSS STYLES GLOBALLY ONCE
-// We use DOM elements instead of Canvas drawing so it works flawlessly in ComfyUI V3
-const cssStyles = `
- .slc-ui-container {
- width: 100%;
- height: 100%;
- min-height: 260px; /* Increased from 220px to prevent V1 cropping */
- background-color: rgba(15, 15, 19, 0.7);
- border: 1px solid rgba(255, 255, 255, 0.1);
- border-radius: 8px;
- padding: 12px;
- box-sizing: border-box;
- display: flex;
- flex-direction: column;
- font-family: sans-serif;
- color: #ffffff;
- overflow: hidden;
- pointer-events: auto; /* allows text selection */
- }
- .slc-empty {
- flex: 1;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- }
- .slc-empty-title { color: #aaaaaa; font-size: 14px; font-weight: 500; margin-bottom: 4px; }
- .slc-empty-sub { color: #777777; font-size: 11px; }
- .slc-headers {
- display: flex;
- gap: 8px;
- margin-bottom: 12px;
- }
- .slc-header-block {
- flex: 1;
- background-color: rgba(0, 0, 0, 0.4);
- border-radius: 6px;
- padding: 8px 0;
- display: flex;
- flex-direction: column;
- align-items: center;
- }
- .slc-header-title { color: #888888; font-size: 9px; font-weight: bold; margin-bottom: 4px; }
- .slc-header-value { color: #ffffff; font-size: 16px; font-weight: bold; }
- .slc-cards {
- display: flex;
- flex-direction: column;
- gap: 8px;
- }
- .slc-card {
- display: flex;
- justify-content: space-between;
- align-items: center;
- background-color: rgba(0, 0, 0, 0.25);
- border: 1px solid rgba(255, 255, 255, 0.05);
- border-radius: 4px;
- padding: 8px 12px 8px 16px;
- position: relative;
- overflow: hidden;
- }
- .slc-card::before {
- content: '';
- position: absolute;
- left: 0;
- top: 0;
- bottom: 0;
- width: 4px;
- }
- .slc-card.slow::before { background-color: #93c5fd; }
- .slc-card.avg::before { background-color: #86efac; }
- .slc-card.fast::before { background-color: #fca5a5; }
-
- .slc-card-left { display: flex; flex-direction: column; }
- .slc-card-speed { font-size: 12px; font-weight: bold; margin-bottom: 2px; }
- .slc-card.slow .slc-card-speed { color: #93c5fd; }
- .slc-card.avg .slc-card-speed { color: #86efac; }
- .slc-card.fast .slc-card-speed { color: #fca5a5; }
- .slc-card-wpm { font-size: 10px; color: #aaaaaa; }
-
- .slc-card-right { display: flex; flex-direction: column; align-items: flex-end; }
- .slc-card-time { font-size: 13px; font-weight: bold; color: #ffffff; margin-bottom: 2px; }
- .slc-card-frames { font-size: 11px; font-family: monospace; color: #888888; }
-
- .slc-legend {
- margin-top: auto;
- padding-top: 8px;
- color: #999999;
- font-size: 10px;
- font-weight: 500;
- }
-`;
-
-// Inject styles to document head safely
-if (!document.getElementById("speech-length-calculator-styles")) {
- const styleEl = document.createElement("style");
- styleEl.id = "speech-length-calculator-styles";
- styleEl.textContent = cssStyles;
- document.head.appendChild(styleEl);
-}
-
-// 2. HELPER TO GENERATE THE HTML CONTENT
-function buildUIHTML(statsData) {
- if (!statsData || statsData.empty) {
- return `
-
-
Awaiting Script
-
Wrap spoken text inside "quotes"
-
- `;
- }
-
- return `
-
-
-
-
-
-
${statsData.slow.time}
-
${statsData.slow.frames} frames
-
-
-
-
-
-
${statsData.avg.time}
-
${statsData.avg.frames} frames
-
-
-
-
-
-
${statsData.fast.time}
-
${statsData.fast.frames} frames
-
-
-
- WPM = Words Per Minute
- `;
-}
-
-app.registerExtension({
- name: "Comfy.SpeechLengthCalculator",
- async beforeRegisterNodeDef(nodeType, nodeData, app) {
- if (nodeData.name === "SpeechLengthCalculator") {
-
- const onNodeCreated = nodeType.prototype.onNodeCreated;
- nodeType.prototype.onNodeCreated = function () {
- const r = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined;
-
- // 3. CREATE THE DOM WIDGET
- const uiContainer = document.createElement("div");
- uiContainer.className = "slc-ui-container";
- uiContainer.innerHTML = buildUIHTML({ empty: true });
-
- // Attach to node: standard addDOMWidget works natively in both V1 and V3 Frontends
- let statsWidget;
- if (typeof this.addDOMWidget === "function") {
- statsWidget = this.addDOMWidget("Stats", "HTML", uiContainer, {
- serialize: false,
- hideOnZoom: false
- });
- } else {
- // Fallback for very old unpatched V1 installations
- statsWidget = ComfyWidgets["STRING"](this, "Stats", ["STRING", { multiline: true }], app).widget;
- if (statsWidget.inputEl) {
- statsWidget.inputEl.style.display = "none";
- if (statsWidget.inputEl.parentNode) {
- statsWidget.inputEl.parentNode.appendChild(uiContainer);
- }
- }
- }
-
- // Tell LiteGraph exactly how much vertical space to hold for our UI
- statsWidget.computeSize = function() {
- return [0, 280]; // Increased from 240 to prevent bottom cropping in V1
- };
-
- // Ensure the node is wide enough on initial creation
- requestAnimationFrame(() => {
- const minWidth = 340;
- if (this.size[0] < minWidth) this.size[0] = minWidth;
-
- // Fine tune initial height to account for the larger computeSize and make text box taller
- if (this.size[1] < 500) this.size[1] = 500;
-
- this.setDirtyCanvas(true, true);
- });
-
- // Fetch text from input link OR widget
- this._getCurrentText = () => {
- const inputSlot = this.inputs && this.inputs.find(i => i.name === "text_input" || (i.name === "text" && i.link));
- if (inputSlot && inputSlot.link) {
- const link = app.graph.links[inputSlot.link];
- if (link) {
- const sourceNode = app.graph.getNodeById(link.origin_id);
- if (sourceNode && sourceNode.widgets) {
- const w = sourceNode.widgets.find(w => w.name === "value" || w.name === "text" || w.name === "Text" || w.type === "customtext" || w.type === "STRING");
- if (w && typeof w.value === "string") return w.value;
- }
- }
- }
- const textWidget = this.widgets && this.widgets.find(w => w.name === "text");
- return textWidget ? (textWidget.value || "") : "";
- };
-
- this._lastState = { text: null, fps: null, addTime: null };
-
- const updateStats = () => {
- const fpsWidget = this.widgets && this.widgets.find(w => w.name === "fps");
- const additionalTimeWidget = this.widgets && this.widgets.find(w => w.name === "additional_time");
-
- if (!fpsWidget) return;
-
- const text = this._getCurrentText();
- const fps = fpsWidget.value || 24;
- const additionalTime = additionalTimeWidget ? parseFloat(additionalTimeWidget.value) || 0 : 0;
-
- // Skip expensive calculations if nothing changed
- if (this._lastState.text === text &&
- this._lastState.fps === fps &&
- this._lastState.addTime === additionalTime) {
- return;
- }
-
- this._lastState.text = text;
- this._lastState.fps = fps;
- this._lastState.addTime = additionalTime;
-
- const regex = /"([^"]*)"|'([^']*)'|“([^”]*)”|‘([^’]*)’/g;
- let match;
- let quotedText = "";
- while ((match = regex.exec(text)) !== null) {
- quotedText += (match[1] || match[2] || match[3] || match[4] || "") + " ";
- }
-
- const words = quotedText.trim().split(/\s+/).filter(w => w.length > 0);
- const wordCount = words.length;
-
- const formatTime = (wpm) => {
- const baseMins = wordCount / wpm;
- const totalSecs = (baseMins * 60) + additionalTime;
-
- const mins = Math.floor(totalSecs / 60);
- let secs = totalSecs % 60;
- secs = Math.ceil(secs * 10) / 10;
- const frames = Math.ceil(totalSecs * fps);
-
- const secsStr = secs.toFixed(1);
- const timeStr = mins > 0 ? `${mins}m ${secsStr}s` : `${secsStr}s`;
-
- return {
- time: timeStr,
- frames: frames.toString()
- };
- };
-
- const statsData = {
- empty: (wordCount === 0 && additionalTime === 0),
- wordCount: wordCount,
- additionalTime: additionalTime,
- slow: formatTime(100),
- avg: formatTime(130),
- fast: formatTime(160)
- };
-
- // 4. INJECT HTML TO THE DOM
- // Replaces the heavy canvas redrawing!
- uiContainer.innerHTML = buildUIHTML(statsData);
-
- this.setDirtyCanvas(true, false);
- };
-
- // We still use onDrawBackground as an efficient silent trigger
- // to auto-update in case upstream nodes silently change their text
- const onDrawBackground = this.onDrawBackground;
- this.onDrawBackground = function(ctx) {
- if (onDrawBackground) onDrawBackground.apply(this, arguments);
- updateStats();
- };
-
- // Bind events to update in real time based on node interactions
- setTimeout(() => {
- const textWidget = this.widgets && this.widgets.find(w => w.name === "text");
- const fpsWidget = this.widgets && this.widgets.find(w => w.name === "fps");
- const additionalTimeWidget = this.widgets && this.widgets.find(w => w.name === "additional_time");
-
- if (textWidget) {
- const origCallback = textWidget.callback;
- textWidget.callback = function() {
- if (origCallback) origCallback.apply(this, arguments);
- updateStats();
- }
- }
- if (fpsWidget) {
- const origFpsCallback = fpsWidget.callback;
- fpsWidget.callback = function() {
- if (origFpsCallback) origFpsCallback.apply(this, arguments);
- updateStats();
- }
- }
- if (additionalTimeWidget) {
- const origAddCallback = additionalTimeWidget.callback;
- additionalTimeWidget.callback = function() {
- if (origAddCallback) origAddCallback.apply(this, arguments);
- updateStats();
- }
- }
- updateStats();
- }, 100);
-
- return r;
- };
- }
- }
-});
\ No newline at end of file