Skip to content

Commit 7dfaec1

Browse files
committed
feat: backend integration and status indicator
1 parent aa5ba52 commit 7dfaec1

File tree

4 files changed

+211
-72
lines changed

4 files changed

+211
-72
lines changed

src/index.css

Lines changed: 98 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,101 @@
1+
:root {
2+
--bg-primary: #0f0f0f;
3+
--bg-secondary: #1a1a1a;
4+
--text-primary: #f5f5f5;
5+
--text-secondary: #a0a0a0;
6+
--accent: #6366f1;
7+
--success: #22c55e;
8+
--error: #ef4444;
9+
}
10+
11+
* {
12+
margin: 0;
13+
padding: 0;
14+
box-sizing: border-box;
15+
}
16+
117
body {
2-
font-family:
3-
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial,
4-
sans-serif;
5-
margin: auto;
6-
max-width: 38rem;
18+
font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont, Roboto, sans-serif;
19+
background: var(--bg-primary);
20+
color: var(--text-primary);
21+
min-height: 100vh;
22+
}
23+
24+
.app-container {
25+
display: flex;
26+
flex-direction: column;
27+
min-height: 100vh;
28+
}
29+
30+
.app-header {
31+
display: flex;
32+
align-items: center;
33+
justify-content: space-between;
34+
padding: 1rem 2rem;
35+
background: var(--bg-secondary);
36+
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
37+
}
38+
39+
.app-header h1 {
40+
font-size: 1.5rem;
41+
font-weight: 600;
42+
background: linear-gradient(135deg, var(--accent), #a855f7);
43+
-webkit-background-clip: text;
44+
-webkit-text-fill-color: transparent;
45+
background-clip: text;
46+
}
47+
48+
.status-indicator {
49+
display: flex;
50+
align-items: center;
51+
gap: 0.5rem;
52+
padding: 0.5rem 1rem;
53+
background: rgba(255, 255, 255, 0.05);
54+
border-radius: 9999px;
55+
border: 1px solid rgba(255, 255, 255, 0.1);
56+
}
57+
58+
.status-dot {
59+
width: 10px;
60+
height: 10px;
61+
border-radius: 50%;
62+
transition: all 0.3s ease;
63+
}
64+
65+
.status-dot.online {
66+
background: var(--success);
67+
box-shadow: 0 0 10px var(--success);
68+
}
69+
70+
.status-dot.offline {
71+
background: var(--error);
72+
animation: pulse 1.5s ease-in-out infinite;
73+
}
74+
75+
@keyframes pulse {
76+
0%, 100% {
77+
opacity: 1;
78+
}
79+
50% {
80+
opacity: 0.5;
81+
}
82+
}
83+
84+
.status-text {
85+
font-size: 0.875rem;
86+
color: var(--text-secondary);
87+
}
88+
89+
.app-main {
90+
flex: 1;
91+
display: flex;
92+
flex-direction: column;
93+
align-items: center;
94+
justify-content: center;
795
padding: 2rem;
896
}
97+
98+
.app-main p {
99+
color: var(--text-secondary);
100+
font-size: 1.1rem;
101+
}

src/main.ts

Lines changed: 50 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,73 @@
1-
import { app, BrowserWindow } from 'electron';
2-
import path from 'node:path';
3-
import started from 'electron-squirrel-startup';
1+
import { app, BrowserWindow, ipcMain } from "electron";
2+
import path from "node:path";
3+
import started from "electron-squirrel-startup";
4+
import { getBackendManager } from "./backend-manager";
45

5-
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
6-
if (started) {
7-
app.quit();
8-
}
6+
if (started) app.quit();
7+
8+
const backendManager = getBackendManager({
9+
host: "127.0.0.1",
10+
port: 8000,
11+
debug: true,
12+
});
13+
14+
let mainWindow: BrowserWindow | null = null;
915

1016
const createWindow = () => {
11-
// Create the browser window.
12-
const mainWindow = new BrowserWindow({
17+
mainWindow = new BrowserWindow({
1318
width: 800,
1419
height: 600,
1520
webPreferences: {
16-
preload: path.join(__dirname, 'preload.js'),
21+
preload: path.join(__dirname, "preload.js"),
22+
contextIsolation: true,
1723
},
1824
});
1925

20-
// and load the index.html of the app.
2126
if (MAIN_WINDOW_VITE_DEV_SERVER_URL) {
2227
mainWindow.loadURL(MAIN_WINDOW_VITE_DEV_SERVER_URL);
2328
} else {
2429
mainWindow.loadFile(
25-
path.join(__dirname, `../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`),
30+
path.join(__dirname, `../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`)
2631
);
2732
}
2833

29-
// Open the DevTools.
3034
mainWindow.webContents.openDevTools();
3135
};
3236

33-
// This method will be called when Electron has finished
34-
// initialization and is ready to create browser windows.
35-
// Some APIs can only be used after this event occurs.
36-
app.on('ready', createWindow);
37-
38-
// Quit when all windows are closed, except on macOS. There, it's common
39-
// for applications and their menu bar to stay active until the user quits
40-
// explicitly with Cmd + Q.
41-
app.on('window-all-closed', () => {
42-
if (process.platform !== 'darwin') {
43-
app.quit();
44-
}
37+
// IPC handler for backend status
38+
ipcMain.handle("backend:status", () => {
39+
return {
40+
status: backendManager.getStatus(),
41+
isRunning: backendManager.getStatus() === "running",
42+
url: backendManager.getServerUrl(),
43+
};
4544
});
4645

47-
app.on('activate', () => {
48-
// On OS X it's common to re-create a window in the app when the
49-
// dock icon is clicked and there are no other windows open.
50-
if (BrowserWindow.getAllWindows().length === 0) {
51-
createWindow();
52-
}
46+
app.on("ready", () => {
47+
createWindow();
48+
console.log("Window created - starting backend...");
49+
50+
// Start backend and notify renderer when ready
51+
backendManager.start().then(async () => {
52+
const ready = await backendManager.waitForReady();
53+
if (ready) {
54+
console.log(`Backend ready at: ${backendManager.getServerUrl()}`);
55+
mainWindow?.webContents.send("backend:ready");
56+
} else {
57+
console.error("Backend failed to become ready");
58+
}
59+
});
60+
});
61+
62+
app.on("window-all-closed", async () => {
63+
await backendManager.stop();
64+
if (process.platform !== "darwin") app.quit();
5365
});
5466

55-
// In this file you can include the rest of your app's specific main process
56-
// code. You can also put them in separate files and import them here.
67+
app.on("activate", () => {
68+
if (BrowserWindow.getAllWindows().length === 0) createWindow();
69+
});
70+
71+
app.on("before-quit", async () => {
72+
await backendManager.stop();
73+
});

src/preload.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,9 @@
1-
// See the Electron documentation for details on how to use preload scripts:
2-
// https://www.electronjs.org/docs/latest/tutorial/process-model#preload-scripts
1+
import { contextBridge, ipcRenderer } from "electron";
2+
3+
contextBridge.exposeInMainWorld("electronAPI", {
4+
getBackendStatus: () => ipcRenderer.invoke("backend:status"),
5+
onBackendReady: (callback: () => void) => {
6+
ipcRenderer.on("backend:ready", callback);
7+
return () => ipcRenderer.removeListener("backend:ready", callback);
8+
},
9+
});

src/renderer.tsx

Lines changed: 54 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,61 @@
1-
/**
2-
* This file will automatically be loaded by vite and run in the "renderer" context.
3-
* To learn more about the differences between the "main" and the "renderer" context in
4-
* Electron, visit:
5-
*
6-
* https://electronjs.org/docs/tutorial/process-model
7-
*
8-
* By default, Node.js integration in this file is disabled. When enabling Node.js integration
9-
* in a renderer process, please be aware of potential security implications. You can read
10-
* more about security risks here:
11-
*
12-
* https://electronjs.org/docs/tutorial/security
13-
*
14-
* To enable Node.js integration in this file, open up `main.ts` and enable the `nodeIntegration`
15-
* flag:
16-
*
17-
* ```
18-
* // Create the browser window.
19-
* mainWindow = new BrowserWindow({
20-
* width: 800,
21-
* height: 600,
22-
* webPreferences: {
23-
* nodeIntegration: true
24-
* }
25-
* });
26-
* ```
27-
*/
28-
291
import "./index.css";
302
import ReactDOM from "react-dom/client";
3+
import { useState, useEffect } from "react";
4+
5+
declare global {
6+
interface Window {
7+
electronAPI: {
8+
getBackendStatus: () => Promise<{ status: string; isRunning: boolean; url: string }>;
9+
onBackendReady: (callback: () => void) => () => void;
10+
};
11+
}
12+
}
3113

32-
const App = () => (
33-
<div>
34-
<h1>Hello from React!</h1>
14+
const StatusIndicator = ({ isOnline }: { isOnline: boolean }) => (
15+
<div className="status-indicator">
16+
<span
17+
className={`status-dot ${isOnline ? "online" : "offline"}`}
18+
aria-label={isOnline ? "Backend online" : "Backend offline"}
19+
/>
20+
<span className="status-text">
21+
Backend: {isOnline ? "Online" : "Connecting..."}
22+
</span>
3523
</div>
3624
);
3725

38-
const root = ReactDOM.createRoot(document.getElementById("root"));
26+
const App = () => {
27+
const [isBackendOnline, setIsBackendOnline] = useState(false);
28+
29+
useEffect(() => {
30+
// Guard: only interact with API if available
31+
if (!window.electronAPI) return;
32+
33+
// Non-blocking status check
34+
window.electronAPI
35+
.getBackendStatus()
36+
.then((status) => setIsBackendOnline(status.isRunning))
37+
.catch(() => {}); // Ignore errors on initial check
38+
39+
// Listen for backend ready event
40+
const cleanup = window.electronAPI.onBackendReady(() => {
41+
setIsBackendOnline(true);
42+
});
43+
44+
return cleanup;
45+
}, []);
46+
47+
return (
48+
<div className="app-container">
49+
<header className="app-header">
50+
<h1>TrainKit</h1>
51+
<StatusIndicator isOnline={isBackendOnline} />
52+
</header>
53+
<main className="app-main">
54+
<p>Dataset preparation toolkit for AI image training</p>
55+
</main>
56+
</div>
57+
);
58+
};
59+
60+
const root = ReactDOM.createRoot(document.getElementById("root")!);
3961
root.render(<App />);

0 commit comments

Comments
 (0)