Skip to content

Commit eed524c

Browse files
committed
fix: Implement whisper ai setup fixes. Generation still broken
feat: Implement Python process pool and worker management - Added PythonProcessPool class to manage multiple Python worker processes, enabling concurrent task execution with health monitoring. - Introduced PythonWorker class for individual worker processes, handling task execution and communication with Python scripts. - Implemented TaskRouter class for intelligent task distribution based on worker roles and task types. - Added styles for dialog and button components in application.css to enhance UI consistency.
1 parent 33c8d53 commit eed524c

File tree

18 files changed

+2335
-147
lines changed

18 files changed

+2335
-147
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
{
2+
"name": "Planner Mode",
3+
"description": "A mode that collaborates with me to create, refine, and finalize detailed plans before writing any code.",
4+
"instructions": [
5+
"You are an expert technical planner and collaborator.",
6+
"Your primary purpose is to help me develop, structure, and refine detailed project or feature plans step-by-step.",
7+
"Before starting any plan, ask clarifying questions to understand the context, goals, scope, and constraints.",
8+
"Then create a structured, detailed plan with clear sections such as: Goal, Context, Approach, Step-by-step Plan, Dependencies, Risks, and Timeline (if relevant).",
9+
"Keep the plan concise but specific enough that another engineer could begin implementing it.",
10+
"Always confirm whether I’m satisfied or would like further refinement before marking the plan as final.",
11+
"Never write or suggest code unless I explicitly ask for it.",
12+
"Keep the tone professional and collaborative, as if co-designing with a lead engineer.",
13+
"If we are working on a large project, maintain continuity — remember past context and refine iteratively based on my feedback.",
14+
"When I approve a plan, you can summarize the finalized version in a clean, formatted block for documentation."
15+
],
16+
"behavior": {
17+
"allow_code_suggestions": false,
18+
"enable_contextual_knowledge": true,
19+
"use_repo_context": true,
20+
"ask_follow_up_questions": true,
21+
"confirm_before_completion": true
22+
},
23+
"examples": [
24+
{
25+
"input": "Let's plan a new feature for user profiles in the mobile app.",
26+
"output": "Sure! Before I begin, can you tell me what aspects of user profiles you want to focus on (e.g., bio, photos, achievements, or privacy settings)?"
27+
},
28+
{
29+
"input": "Let's make a plan for a muscle recovery tracker in Project Yoked.",
30+
"output": "Got it. Here’s an initial outline:\n\n**Goal:** Allow users to track muscle recovery visually and log rest periods.\n**Approach:** Use D3.js for SVG-based diagrams synced with backend recovery data.\n**Steps:**\n1. Define muscle group schema in backend (FastAPI + SQLite/PostgreSQL).\n2. Expose API endpoints for updating recovery states.\n3. Integrate with React/D3 component for interactive visualization.\n...\n\nWould you like me to expand any of these sections or add estimated timeframes?"
31+
}
32+
]
33+
}

.github/workflows/build-macos.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ on:
1515

1616
jobs:
1717
build-x64:
18-
runs-on: macos-12
18+
runs-on: macos-14
1919
if: github.event.inputs.architecture == 'both' || github.event.inputs.architecture == 'x64'
2020

2121
steps:

EncodeForge/pom.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,10 @@
331331
<linuxPackageName>encodeforge</linuxPackageName>
332332
<linuxAppCategory>AudioVideo</linuxAppCategory>
333333
<linuxPackageDeps>ffmpeg</linuxPackageDeps>
334+
<jlinkOptions>
335+
<option>--add-modules</option>
336+
<option>java.base,java.desktop,java.logging,java.xml,java.prefs,java.scripting,java.sql,java.rmi,java.naming,java.datatransfer</option>
337+
</jlinkOptions>
334338
</configuration>
335339
</execution>
336340

EncodeForge/src/main/java/com/encodeforge/MainApp.java

Lines changed: 86 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -106,10 +106,19 @@ public void init() throws Exception {
106106
* Initialize runtime components (Python bridge, etc.)
107107
*/
108108
private void initializeRuntime() throws IOException {
109-
// Initialize Python bridge (will use bundled or system Python)
110-
pythonBridge = new PythonBridge(dependencyManager);
111-
pythonBridge.start();
112-
logger.info("Python bridge initialized");
109+
try {
110+
// Initialize Python bridge (will use bundled or system Python)
111+
pythonBridge = new PythonBridge(dependencyManager);
112+
pythonBridge.start();
113+
logger.info("Python bridge initialized");
114+
} catch (IOException e) {
115+
// Check if this is a Python not found error
116+
if (e.getMessage() != null && e.getMessage().contains("Python not found")) {
117+
logger.error("Python not found", e);
118+
throw new RuntimeException("Python not found. Please install Python 3.8 or higher.", e);
119+
}
120+
throw e;
121+
}
113122
}
114123

115124
/**
@@ -207,6 +216,15 @@ public void start(Stage primaryStage) {
207216
// Check for updates on startup (background, non-blocking)
208217
checkForUpdatesOnStartup();
209218

219+
} catch (RuntimeException e) {
220+
// Check if this is a Python not found error
221+
if (e.getMessage() != null && e.getMessage().contains("Python not found")) {
222+
logger.error("Python not found", e);
223+
showPythonNotFoundDialog();
224+
} else {
225+
logger.error("Failed to start application", e);
226+
showErrorAndExit("Failed to start application", e);
227+
}
210228
} catch (IOException e) {
211229
logger.error("Failed to load application UI", e);
212230
showErrorAndExit("Failed to load application interface", e);
@@ -270,6 +288,70 @@ private void showErrorAndExit(String message, Exception e) {
270288
alert.showAndWait();
271289
Platform.exit();
272290
}
291+
292+
/**
293+
* Show dialog when Python is not found
294+
*/
295+
private void showPythonNotFoundDialog() {
296+
javafx.scene.control.Alert alert = new javafx.scene.control.Alert(
297+
javafx.scene.control.Alert.AlertType.ERROR
298+
);
299+
alert.setTitle("Python Not Found");
300+
alert.setHeaderText("Python Interpreter Required");
301+
302+
// Create styled content
303+
javafx.scene.layout.VBox contentBox = new javafx.scene.layout.VBox(10);
304+
305+
javafx.scene.text.Text warningText = new javafx.scene.text.Text(
306+
"EncodeForge requires Python 3.8 or higher to run.\n\n" +
307+
"Please install Python and try again."
308+
);
309+
warningText.setStyle("-fx-fill: #ffffff; -fx-font-size: 12px;");
310+
warningText.setWrappingWidth(450);
311+
312+
// Add platform-specific download links
313+
String os = System.getProperty("os.name").toLowerCase();
314+
String downloadText = "";
315+
if (os.contains("win")) {
316+
downloadText = "Download Python:\nhttps://www.python.org/downloads/\n\n" +
317+
"Recommended: Python 3.12 (latest stable)";
318+
} else if (os.contains("mac")) {
319+
downloadText = "Install Python:\nbrew install [email protected]\n\n" +
320+
"Or download from: https://www.python.org/downloads/";
321+
} else {
322+
downloadText = "Install Python:\nsudo apt-get install python3.12\n\n" +
323+
"Or: https://www.python.org/downloads/";
324+
}
325+
326+
javafx.scene.text.Text downloadInfo = new javafx.scene.text.Text(downloadText);
327+
downloadInfo.setStyle("-fx-fill: #a0a0a0; -fx-font-size: 11px;");
328+
downloadInfo.setWrappingWidth(450);
329+
330+
contentBox.getChildren().addAll(warningText, downloadInfo);
331+
alert.getDialogPane().setContent(contentBox);
332+
333+
// Style the dialog
334+
javafx.scene.control.DialogPane dialogPane = alert.getDialogPane();
335+
dialogPane.getStylesheets().add(getClass().getResource("/styles/application.css").toExternalForm());
336+
dialogPane.getStyleClass().add("dialog-pane");
337+
338+
javafx.scene.control.ButtonType openWebsiteButton = new javafx.scene.control.ButtonType("Open Download Page");
339+
javafx.scene.control.ButtonType closeButton = new javafx.scene.control.ButtonType("Close",
340+
javafx.scene.control.ButtonBar.ButtonData.CANCEL_CLOSE);
341+
alert.getButtonTypes().setAll(openWebsiteButton, closeButton);
342+
343+
alert.showAndWait().ifPresent(response -> {
344+
if (response == openWebsiteButton) {
345+
try {
346+
java.awt.Desktop.getDesktop().browse(new java.net.URI("https://www.python.org/downloads/"));
347+
} catch (Exception ex) {
348+
logger.error("Failed to open browser", ex);
349+
}
350+
}
351+
});
352+
353+
Platform.exit();
354+
}
273355

274356
public static void main(String[] args) {
275357
launch(args);

EncodeForge/src/main/java/com/encodeforge/controller/MainController.java

Lines changed: 96 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -340,13 +340,23 @@ public void initialize() {
340340
// Set default mode to encoder
341341
handleEncoderMode();
342342

343-
// Run consolidated status check for faster startup
344-
CompletableFuture.runAsync(this::updateAllStatus);
343+
// Defer status check to let Python backend fully initialize
344+
// This improves startup performance by showing UI first
345+
CompletableFuture.runAsync(() -> {
346+
try {
347+
// Wait a moment for Python backend to be ready
348+
Thread.sleep(500);
349+
updateAllStatus();
350+
} catch (InterruptedException e) {
351+
Thread.currentThread().interrupt();
352+
logger.debug("Status check interrupted");
353+
}
354+
});
345355

346356
// Check for ongoing conversions from previous session
347357
CompletableFuture.runAsync(this::checkForOngoingConversions);
348358

349-
logger.info("Main Controller initialized (async status check running)");
359+
logger.info("Main Controller initialized (async status check deferred)");
350360
}
351361

352362
private void setupSubtitleFileSelector() {
@@ -1696,6 +1706,7 @@ private void openSettings(String category) {
16961706
controller.setSettings(settings);
16971707
controller.setPythonBridge(pythonBridge);
16981708
controller.setDependencyManager(dependencyManager); // Pass DependencyManager for FFmpeg detection
1709+
controller.setStatusManager(statusManager); // Pass StatusManager for status display
16991710

17001711
// Navigate to specific category if provided
17011712
if (category != null) {
@@ -2521,86 +2532,84 @@ private void resetProcessingState() {
25212532
* FFmpeg detection now uses Java-side DependencyManager instead of Python.
25222533
*/
25232534
private void updateAllStatus() {
2524-
new Thread(() -> {
2535+
try {
2536+
logger.info("Starting consolidated status check");
2537+
2538+
// First, sync settings with Python backend
25252539
try {
2526-
logger.info("Starting consolidated status check");
2527-
2528-
// First, sync settings with Python backend
2529-
try {
2530-
JsonObject updateSettings = new JsonObject();
2531-
updateSettings.addProperty("action", "update_settings");
2532-
updateSettings.add("settings", settings.toJson());
2533-
pythonBridge.sendCommand(updateSettings);
2534-
logger.info("Settings synchronized with Python backend");
2535-
} catch (Exception e) {
2536-
logger.error("Failed to sync settings with Python backend", e);
2537-
}
2538-
2539-
// Check FFmpeg using Java DependencyManager (not Python)
2540-
boolean ffmpegAvailable = false;
2541-
String ffmpegVersion = "Not Found";
2542-
try {
2543-
ffmpegAvailable = dependencyManager.checkFFmpeg().get();
2544-
if (ffmpegAvailable) {
2545-
java.nio.file.Path ffmpegPath = dependencyManager.getInstalledFFmpegPath();
2546-
if (ffmpegPath != null) {
2547-
ffmpegVersion = "Found";
2548-
logger.info("FFmpeg detected via DependencyManager at: {}", ffmpegPath);
2549-
} else {
2550-
// FFmpeg found in system PATH
2551-
ffmpegVersion = "System PATH";
2552-
logger.info("FFmpeg detected in system PATH");
2553-
}
2554-
}
2555-
} catch (Exception e) {
2556-
logger.error("Error checking FFmpeg via DependencyManager", e);
2557-
}
2558-
2559-
// Get Python backend status (Whisper, providers, etc)
2560-
JsonObject response = pythonBridge.getAllStatus();
2561-
2562-
if (response.has("status") && response.get("status").getAsString().equals("error")) {
2563-
logger.error("Error getting Python status: " + response.get("message").getAsString());
2564-
}
2565-
2566-
// Update FFmpeg info in response (override Python's check with Java's)
2567-
if (response.has("ffmpeg")) {
2568-
JsonObject ffmpegInfo = response.getAsJsonObject("ffmpeg");
2569-
ffmpegInfo.addProperty("available", ffmpegAvailable);
2570-
if (ffmpegAvailable && ffmpegInfo.has("version")) {
2571-
ffmpegVersion = ffmpegInfo.get("version").getAsString();
2540+
JsonObject updateSettings = new JsonObject();
2541+
updateSettings.addProperty("action", "update_settings");
2542+
updateSettings.add("settings", settings.toJson());
2543+
pythonBridge.sendCommand(updateSettings);
2544+
logger.info("Settings synchronized with Python backend");
2545+
} catch (Exception e) {
2546+
logger.error("Failed to sync settings with Python backend", e);
2547+
}
2548+
2549+
// Check FFmpeg using Java DependencyManager (not Python)
2550+
boolean ffmpegAvailable = false;
2551+
String ffmpegVersion = "Not Found";
2552+
try {
2553+
ffmpegAvailable = dependencyManager.checkFFmpeg().get();
2554+
if (ffmpegAvailable) {
2555+
java.nio.file.Path ffmpegPath = dependencyManager.getInstalledFFmpegPath();
2556+
if (ffmpegPath != null) {
2557+
ffmpegVersion = "Found";
2558+
logger.info("FFmpeg detected via DependencyManager at: {}", ffmpegPath);
2559+
} else {
2560+
// FFmpeg found in system PATH
2561+
ffmpegVersion = "System PATH";
2562+
logger.info("FFmpeg detected in system PATH");
25722563
}
25732564
}
2574-
2575-
// Update StatusManager with the consolidated response
2576-
com.encodeforge.util.StatusManager.getInstance().updateFromResponse(response);
2577-
2578-
// Update FFmpeg status separately (using Java check)
2579-
final boolean finalFfmpegAvailable = ffmpegAvailable;
2580-
final String finalFfmpegVersion = ffmpegVersion;
2581-
2582-
// Update UI on JavaFX thread
2583-
Platform.runLater(() -> {
2584-
updateFFmpegStatus(finalFfmpegAvailable, finalFfmpegVersion);
2585-
updateUIFromStatus();
2586-
});
2587-
2588-
logger.info("Consolidated status check completed successfully");
2589-
2590-
} catch (IOException | TimeoutException e) {
2591-
logger.error("Error checking status", e);
2592-
Platform.runLater(() -> {
2593-
log("ERROR checking status: " + e.getMessage());
2594-
updateFFmpegStatus(false, "Error");
2595-
});
25962565
} catch (Exception e) {
2597-
logger.error("Unexpected error checking status", e);
2598-
Platform.runLater(() -> {
2599-
log("ERROR: " + e.getMessage());
2600-
updateFFmpegStatus(false, "Error");
2601-
});
2566+
logger.error("Error checking FFmpeg via DependencyManager", e);
26022567
}
2603-
}).start();
2568+
2569+
// Get Python backend status (Whisper, providers, etc)
2570+
JsonObject response = pythonBridge.getAllStatus();
2571+
2572+
if (response.has("status") && response.get("status").getAsString().equals("error")) {
2573+
logger.error("Error getting Python status: " + response.get("message").getAsString());
2574+
}
2575+
2576+
// Update FFmpeg info in response (override Python's check with Java's)
2577+
if (response.has("ffmpeg")) {
2578+
JsonObject ffmpegInfo = response.getAsJsonObject("ffmpeg");
2579+
ffmpegInfo.addProperty("available", ffmpegAvailable);
2580+
if (ffmpegAvailable && ffmpegInfo.has("version")) {
2581+
ffmpegVersion = ffmpegInfo.get("version").getAsString();
2582+
}
2583+
}
2584+
2585+
// Update StatusManager with the consolidated response
2586+
com.encodeforge.util.StatusManager.getInstance().updateFromResponse(response);
2587+
2588+
// Update FFmpeg status separately (using Java check)
2589+
final boolean finalFfmpegAvailable = ffmpegAvailable;
2590+
final String finalFfmpegVersion = ffmpegVersion;
2591+
2592+
// Update UI on JavaFX thread
2593+
Platform.runLater(() -> {
2594+
updateFFmpegStatus(finalFfmpegAvailable, finalFfmpegVersion);
2595+
updateUIFromStatus();
2596+
});
2597+
2598+
logger.info("Consolidated status check completed successfully");
2599+
2600+
} catch (IOException | TimeoutException e) {
2601+
logger.error("Error checking status", e);
2602+
Platform.runLater(() -> {
2603+
log("ERROR checking status: " + e.getMessage());
2604+
updateFFmpegStatus(false, "Error");
2605+
});
2606+
} catch (Exception e) {
2607+
logger.error("Unexpected error checking status", e);
2608+
Platform.runLater(() -> {
2609+
log("ERROR: " + e.getMessage());
2610+
updateFFmpegStatus(false, "Error");
2611+
});
2612+
}
26042613
}
26052614

26062615
/**
@@ -3785,6 +3794,15 @@ private void handleConfigureWhisper() {
37853794

37863795
// Refresh status after installation
37873796
if (setupDialog.isInstallationComplete()) {
3797+
// Reset Python core to detect newly installed Whisper
3798+
try {
3799+
pythonBridge.resetCore();
3800+
logger.info("Python core reset after Whisper installation");
3801+
} catch (Exception e) {
3802+
logger.warn("Failed to reset Python core", e);
3803+
}
3804+
3805+
// Refresh status to update UI
37883806
CompletableFuture.runAsync(this::updateAllStatus);
37893807
showInfo("Whisper Installed", "AI subtitle generation is now available!");
37903808
}

0 commit comments

Comments
 (0)