Add help button which opens GitHub issue w/ system report | explicitly log with electron-log & scrub logs
This commit is contained in:
@@ -11,10 +11,9 @@ import path from "path";
|
|||||||
import { fileURLToPath } from "url";
|
import { fileURLToPath } from "url";
|
||||||
import { dirname } from "path";
|
import { dirname } from "path";
|
||||||
import { isIgnored } from "isomorphic-git";
|
import { isIgnored } from "isomorphic-git";
|
||||||
|
import log from "electron-log";
|
||||||
|
|
||||||
// Setup ESM compatibility
|
const logger = log.scope("extract-codebase");
|
||||||
// const __filename = fileURLToPath(import.meta.url);
|
|
||||||
// const __dirname = dirname(__filename);
|
|
||||||
|
|
||||||
// File extensions to include
|
// File extensions to include
|
||||||
const ALLOWED_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".css"];
|
const ALLOWED_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".css"];
|
||||||
@@ -28,7 +27,7 @@ async function isGitIgnored(
|
|||||||
const relativePath = path.relative(baseDir, filePath);
|
const relativePath = path.relative(baseDir, filePath);
|
||||||
return await isIgnored({ fs, dir: baseDir, filepath: relativePath });
|
return await isIgnored({ fs, dir: baseDir, filepath: relativePath });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error checking if path is git ignored: ${filePath}`, error);
|
logger.error(`Error checking if path is git ignored: ${filePath}`, error);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -84,7 +83,7 @@ function formatFile(filePath: string, baseDir: string): string {
|
|||||||
.extname(filePath)
|
.extname(filePath)
|
||||||
.substring(1)}\n${content}\n\`\`\`\n\n`;
|
.substring(1)}\n${content}\n\`\`\`\n\n`;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error reading file: ${filePath}`, error);
|
logger.error(`Error reading file: ${filePath}`, error);
|
||||||
return `## File: ${filePath}\nError reading file: ${error}\n\n`;
|
return `## File: ${filePath}\nError reading file: ${error}\n\n`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -115,9 +114,3 @@ async function main() {
|
|||||||
fs.writeFileSync(outputFile, output);
|
fs.writeFileSync(outputFile, output);
|
||||||
console.log(`Extraction complete. Output written to ${outputFile}`);
|
console.log(`Extraction complete. Output written to ${outputFile}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run the script
|
|
||||||
main().catch((error) => {
|
|
||||||
console.error("Error:", error);
|
|
||||||
process.exit(1);
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { BookOpenIcon, BugIcon } from "lucide-react";
|
import { BookOpenIcon, BugIcon } from "lucide-react";
|
||||||
import { IpcClient } from "@/ipc/ipc_client";
|
import { IpcClient } from "@/ipc/ipc_client";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
interface HelpDialogProps {
|
interface HelpDialogProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@@ -16,6 +17,60 @@ interface HelpDialogProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function HelpDialog({ isOpen, onClose }: HelpDialogProps) {
|
export function HelpDialog({ isOpen, onClose }: HelpDialogProps) {
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
|
const handleReportBug = async () => {
|
||||||
|
setIsLoading(true);
|
||||||
|
try {
|
||||||
|
// Get system debug info
|
||||||
|
const debugInfo = await IpcClient.getInstance().getSystemDebugInfo();
|
||||||
|
|
||||||
|
// Create a formatted issue body with the debug info
|
||||||
|
const issueBody = `
|
||||||
|
## Bug Description
|
||||||
|
<!-- Please describe the issue you're experiencing -->
|
||||||
|
|
||||||
|
## Steps to Reproduce
|
||||||
|
<!-- Please list the steps to reproduce the issue -->
|
||||||
|
|
||||||
|
## Expected Behavior
|
||||||
|
<!-- What did you expect to happen? -->
|
||||||
|
|
||||||
|
## Actual Behavior
|
||||||
|
<!-- What actually happened? -->
|
||||||
|
|
||||||
|
## System Information
|
||||||
|
- Dyad Version: ${debugInfo.dyadVersion}
|
||||||
|
- Platform: ${debugInfo.platform}
|
||||||
|
- Architecture: ${debugInfo.architecture}
|
||||||
|
- Node Version: ${debugInfo.nodeVersion || "Not available"}
|
||||||
|
- PNPM Version: ${debugInfo.pnpmVersion || "Not available"}
|
||||||
|
- Node Path: ${debugInfo.nodePath || "Not available"}
|
||||||
|
|
||||||
|
## Logs
|
||||||
|
\`\`\`
|
||||||
|
${debugInfo.logs.slice(-3_500) || "No logs available"}
|
||||||
|
\`\`\`
|
||||||
|
`;
|
||||||
|
|
||||||
|
// Create the GitHub issue URL with the pre-filled body
|
||||||
|
const encodedBody = encodeURIComponent(issueBody);
|
||||||
|
const encodedTitle = encodeURIComponent("[bug] <add title>");
|
||||||
|
const githubIssueUrl = `https://github.com/dyad-sh/dyad/issues/new?title=${encodedTitle}&labels=bug,filed-from-app&body=${encodedBody}`;
|
||||||
|
|
||||||
|
// Open the pre-filled GitHub issue page
|
||||||
|
IpcClient.getInstance().openExternalUrl(githubIssueUrl);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to prepare bug report:", error);
|
||||||
|
// Fallback to opening the regular GitHub issue page
|
||||||
|
IpcClient.getInstance().openExternalUrl(
|
||||||
|
"https://github.com/dyad-sh/dyad/issues/new"
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
@@ -46,17 +101,15 @@ export function HelpDialog({ isOpen, onClose }: HelpDialogProps) {
|
|||||||
<div className="flex flex-col space-y-2">
|
<div className="flex flex-col space-y-2">
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
onClick={() =>
|
onClick={handleReportBug}
|
||||||
IpcClient.getInstance().openExternalUrl(
|
disabled={isLoading}
|
||||||
"https://github.com/dyad-sh/dyad/issues/new"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
className="w-full py-6 bg-(--background-lightest)"
|
className="w-full py-6 bg-(--background-lightest)"
|
||||||
>
|
>
|
||||||
<BugIcon className="mr-2 h-5 w-5" /> Report a Bug
|
<BugIcon className="mr-2 h-5 w-5" />{" "}
|
||||||
|
{isLoading ? "Preparing Report..." : "Report a Bug"}
|
||||||
</Button>
|
</Button>
|
||||||
<p className="text-sm text-muted-foreground px-2">
|
<p className="text-sm text-muted-foreground px-2">
|
||||||
We’ll auto-fill your report with system info and logs. You can
|
We'll auto-fill your report with system info and logs. You can
|
||||||
review it for any sensitive info before submitting.
|
review it for any sensitive info before submitting.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ export function ProviderSettingsGrid({
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const handleProviderClick = (provider: ModelProvider) => {
|
const handleProviderClick = (provider: ModelProvider) => {
|
||||||
console.log("PROVIDER", provider);
|
|
||||||
navigate({
|
navigate({
|
||||||
to: providerSettingsRoute.id,
|
to: providerSettingsRoute.id,
|
||||||
params: { provider },
|
params: { provider },
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ import path from "node:path";
|
|||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import { getDyadAppPath, getUserDataPath } from "../paths/paths";
|
import { getDyadAppPath, getUserDataPath } from "../paths/paths";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
|
import log from "electron-log";
|
||||||
|
|
||||||
|
const logger = log.scope("db");
|
||||||
|
|
||||||
// Database connection factory
|
// Database connection factory
|
||||||
let _db: ReturnType<typeof drizzle> | null = null;
|
let _db: ReturnType<typeof drizzle> | null = null;
|
||||||
@@ -29,7 +32,7 @@ export function initializeDatabase(): BetterSQLite3Database<typeof schema> & {
|
|||||||
if (_db) return _db as any;
|
if (_db) return _db as any;
|
||||||
|
|
||||||
const dbPath = getDatabasePath();
|
const dbPath = getDatabasePath();
|
||||||
console.log("Initializing database at:", dbPath);
|
logger.log("Initializing database at:", dbPath);
|
||||||
|
|
||||||
// Check if the database file exists and remove it if it has issues
|
// Check if the database file exists and remove it if it has issues
|
||||||
try {
|
try {
|
||||||
@@ -38,14 +41,12 @@ export function initializeDatabase(): BetterSQLite3Database<typeof schema> & {
|
|||||||
const stats = fs.statSync(dbPath);
|
const stats = fs.statSync(dbPath);
|
||||||
// If the file is very small, it might be corrupted
|
// If the file is very small, it might be corrupted
|
||||||
if (stats.size < 100) {
|
if (stats.size < 100) {
|
||||||
console.log(
|
logger.log("Database file exists but may be corrupted. Removing it...");
|
||||||
"Database file exists but may be corrupted. Removing it..."
|
|
||||||
);
|
|
||||||
fs.unlinkSync(dbPath);
|
fs.unlinkSync(dbPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error checking database file:", error);
|
logger.error("Error checking database file:", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
fs.mkdirSync(getUserDataPath(), { recursive: true });
|
fs.mkdirSync(getUserDataPath(), { recursive: true });
|
||||||
@@ -62,21 +63,15 @@ export function initializeDatabase(): BetterSQLite3Database<typeof schema> & {
|
|||||||
_db = drizzle(sqlite, { schema });
|
_db = drizzle(sqlite, { schema });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Run migrations programmatically
|
|
||||||
|
|
||||||
const migrationsFolder = path.join(__dirname, "..", "..", "drizzle");
|
const migrationsFolder = path.join(__dirname, "..", "..", "drizzle");
|
||||||
|
|
||||||
console.log("MIGRATIONS FOLDER INITIALIZE", migrationsFolder);
|
|
||||||
|
|
||||||
// Verify migrations folder exists
|
|
||||||
if (!fs.existsSync(migrationsFolder)) {
|
if (!fs.existsSync(migrationsFolder)) {
|
||||||
console.error("Migrations folder not found:", migrationsFolder);
|
logger.error("Migrations folder not found:", migrationsFolder);
|
||||||
} else {
|
} else {
|
||||||
console.log("Running migrations from:", migrationsFolder);
|
logger.log("Running migrations from:", migrationsFolder);
|
||||||
migrate(_db, { migrationsFolder });
|
migrate(_db, { migrationsFolder });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Migration error:", error);
|
logger.error("Migration error:", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
return _db as any;
|
return _db as any;
|
||||||
@@ -86,7 +81,7 @@ export function initializeDatabase(): BetterSQLite3Database<typeof schema> & {
|
|||||||
try {
|
try {
|
||||||
initializeDatabase();
|
initializeDatabase();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to initialize database:", error);
|
logger.error("Failed to initialize database:", error);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const db = _db as any as BetterSQLite3Database<typeof schema> & {
|
export const db = _db as any as BetterSQLite3Database<typeof schema> & {
|
||||||
|
|||||||
@@ -36,15 +36,14 @@ import fixPath from "fix-path";
|
|||||||
import { getGitAuthor } from "../utils/git_author";
|
import { getGitAuthor } from "../utils/git_author";
|
||||||
import killPort from "kill-port";
|
import killPort from "kill-port";
|
||||||
import util from "util";
|
import util from "util";
|
||||||
|
import log from "electron-log";
|
||||||
|
|
||||||
|
const logger = log.scope("app_handlers");
|
||||||
|
|
||||||
// Needed, otherwise electron in MacOS/Linux will not be able
|
// Needed, otherwise electron in MacOS/Linux will not be able
|
||||||
// to find node/pnpm.
|
// to find node/pnpm.
|
||||||
fixPath();
|
fixPath();
|
||||||
|
|
||||||
// Keep track of the static file server worker
|
|
||||||
let staticServerWorker: Worker | null = null;
|
|
||||||
let staticServerPort: number | null = null;
|
|
||||||
// let staticServerRootDir: string | null = null; // Store the root dir it's serving - Removed
|
|
||||||
|
|
||||||
async function executeApp({
|
async function executeApp({
|
||||||
appPath,
|
appPath,
|
||||||
appId,
|
appId,
|
||||||
@@ -96,7 +95,7 @@ async function executeAppLocalNode({
|
|||||||
// Log output
|
// Log output
|
||||||
process.stdout?.on("data", (data) => {
|
process.stdout?.on("data", (data) => {
|
||||||
const message = util.stripVTControlCharacters(data.toString());
|
const message = util.stripVTControlCharacters(data.toString());
|
||||||
console.log(`App ${appId} (PID: ${process.pid}) stdout: ${message}`);
|
logger.debug(`App ${appId} (PID: ${process.pid}) stdout: ${message}`);
|
||||||
event.sender.send("app:output", {
|
event.sender.send("app:output", {
|
||||||
type: "stdout",
|
type: "stdout",
|
||||||
message,
|
message,
|
||||||
@@ -106,7 +105,7 @@ async function executeAppLocalNode({
|
|||||||
|
|
||||||
process.stderr?.on("data", (data) => {
|
process.stderr?.on("data", (data) => {
|
||||||
const message = util.stripVTControlCharacters(data.toString());
|
const message = util.stripVTControlCharacters(data.toString());
|
||||||
console.error(`App ${appId} (PID: ${process.pid}) stderr: ${message}`);
|
logger.error(`App ${appId} (PID: ${process.pid}) stderr: ${message}`);
|
||||||
event.sender.send("app:output", {
|
event.sender.send("app:output", {
|
||||||
type: "stderr",
|
type: "stderr",
|
||||||
message,
|
message,
|
||||||
@@ -116,7 +115,7 @@ async function executeAppLocalNode({
|
|||||||
|
|
||||||
// Handle process exit/close
|
// Handle process exit/close
|
||||||
process.on("close", (code, signal) => {
|
process.on("close", (code, signal) => {
|
||||||
console.log(
|
logger.log(
|
||||||
`App ${appId} (PID: ${process.pid}) process closed with code ${code}, signal ${signal}.`
|
`App ${appId} (PID: ${process.pid}) process closed with code ${code}, signal ${signal}.`
|
||||||
);
|
);
|
||||||
removeAppIfCurrentProcess(appId, process);
|
removeAppIfCurrentProcess(appId, process);
|
||||||
@@ -124,7 +123,7 @@ async function executeAppLocalNode({
|
|||||||
|
|
||||||
// Handle errors during process lifecycle (e.g., command not found)
|
// Handle errors during process lifecycle (e.g., command not found)
|
||||||
process.on("error", (err) => {
|
process.on("error", (err) => {
|
||||||
console.error(
|
logger.error(
|
||||||
`Error in app ${appId} (PID: ${process.pid}) process: ${err.message}`
|
`Error in app ${appId} (PID: ${process.pid}) process: ${err.message}`
|
||||||
);
|
);
|
||||||
removeAppIfCurrentProcess(appId, process);
|
removeAppIfCurrentProcess(appId, process);
|
||||||
@@ -196,7 +195,7 @@ export function registerAppHandlers() {
|
|||||||
author: await getGitAuthor(),
|
author: await getGitAuthor(),
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error in background app initialization:", error);
|
logger.error("Error in background app initialization:", error);
|
||||||
}
|
}
|
||||||
// })();
|
// })();
|
||||||
|
|
||||||
@@ -219,7 +218,7 @@ export function registerAppHandlers() {
|
|||||||
try {
|
try {
|
||||||
files = getFilesRecursively(appPath, appPath);
|
files = getFilesRecursively(appPath, appPath);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error reading files for app ${appId}:`, error);
|
logger.error(`Error reading files for app ${appId}:`, error);
|
||||||
// Return app even if files couldn't be read
|
// Return app even if files couldn't be read
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -266,10 +265,7 @@ export function registerAppHandlers() {
|
|||||||
const contents = fs.readFileSync(fullPath, "utf-8");
|
const contents = fs.readFileSync(fullPath, "utf-8");
|
||||||
return contents;
|
return contents;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(
|
logger.error(`Error reading file ${filePath} for app ${appId}:`, error);
|
||||||
`Error reading file ${filePath} for app ${appId}:`,
|
|
||||||
error
|
|
||||||
);
|
|
||||||
throw new Error("Failed to read file");
|
throw new Error("Failed to read file");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -292,7 +288,7 @@ export function registerAppHandlers() {
|
|||||||
return withLock(appId, async () => {
|
return withLock(appId, async () => {
|
||||||
// Check if app is already running
|
// Check if app is already running
|
||||||
if (runningApps.has(appId)) {
|
if (runningApps.has(appId)) {
|
||||||
console.debug(`App ${appId} is already running.`);
|
logger.debug(`App ${appId} is already running.`);
|
||||||
// Potentially return the existing process info or confirm status
|
// Potentially return the existing process info or confirm status
|
||||||
return { success: true, message: "App already running." };
|
return { success: true, message: "App already running." };
|
||||||
}
|
}
|
||||||
@@ -305,7 +301,7 @@ export function registerAppHandlers() {
|
|||||||
throw new Error("App not found");
|
throw new Error("App not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
console.debug(`Starting app ${appId} in path ${app.path}`);
|
logger.debug(`Starting app ${appId} in path ${app.path}`);
|
||||||
|
|
||||||
const appPath = getDyadAppPath(app.path);
|
const appPath = getDyadAppPath(app.path);
|
||||||
try {
|
try {
|
||||||
@@ -313,7 +309,7 @@ export function registerAppHandlers() {
|
|||||||
|
|
||||||
return { success: true, processId: currentProcessId };
|
return { success: true, processId: currentProcessId };
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(`Error running app ${appId}:`, error);
|
logger.error(`Error running app ${appId}:`, error);
|
||||||
// Ensure cleanup if error happens during setup but before process events are handled
|
// Ensure cleanup if error happens during setup but before process events are handled
|
||||||
if (
|
if (
|
||||||
runningApps.has(appId) &&
|
runningApps.has(appId) &&
|
||||||
@@ -328,15 +324,15 @@ export function registerAppHandlers() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
ipcMain.handle("stop-app", async (_, { appId }: { appId: number }) => {
|
ipcMain.handle("stop-app", async (_, { appId }: { appId: number }) => {
|
||||||
console.log(
|
logger.log(
|
||||||
`Attempting to stop app ${appId} (local-node only). Current running apps: ${runningApps.size}`
|
`Attempting to stop app ${appId}. Current running apps: ${runningApps.size}`
|
||||||
);
|
);
|
||||||
return withLock(appId, async () => {
|
return withLock(appId, async () => {
|
||||||
const appInfo = runningApps.get(appId);
|
const appInfo = runningApps.get(appId);
|
||||||
|
|
||||||
if (!appInfo) {
|
if (!appInfo) {
|
||||||
console.log(
|
logger.log(
|
||||||
`App ${appId} not found in running apps map (local-node). Assuming already stopped.`
|
`App ${appId} not found in running apps map. Assuming already stopped.`
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
@@ -345,13 +341,13 @@ export function registerAppHandlers() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { process, processId } = appInfo;
|
const { process, processId } = appInfo;
|
||||||
console.log(
|
logger.log(
|
||||||
`Found running app ${appId} with processId ${processId} (PID: ${process.pid}). Attempting to stop.`
|
`Found running app ${appId} with processId ${processId} (PID: ${process.pid}). Attempting to stop.`
|
||||||
);
|
);
|
||||||
|
|
||||||
// Check if the process is already exited or closed
|
// Check if the process is already exited or closed
|
||||||
if (process.exitCode !== null || process.signalCode !== null) {
|
if (process.exitCode !== null || process.signalCode !== null) {
|
||||||
console.log(
|
logger.log(
|
||||||
`Process for app ${appId} (PID: ${process.pid}) already exited (code: ${process.exitCode}, signal: ${process.signalCode}). Cleaning up map.`
|
`Process for app ${appId} (PID: ${process.pid}) already exited (code: ${process.exitCode}, signal: ${process.signalCode}). Cleaning up map.`
|
||||||
);
|
);
|
||||||
runningApps.delete(appId); // Ensure cleanup if somehow missed
|
runningApps.delete(appId); // Ensure cleanup if somehow missed
|
||||||
@@ -367,7 +363,7 @@ export function registerAppHandlers() {
|
|||||||
|
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(
|
logger.error(
|
||||||
`Error stopping app ${appId} (PID: ${process.pid}, processId: ${processId}):`,
|
`Error stopping app ${appId} (PID: ${process.pid}, processId: ${processId}):`,
|
||||||
error
|
error
|
||||||
);
|
);
|
||||||
@@ -384,24 +380,21 @@ export function registerAppHandlers() {
|
|||||||
event: Electron.IpcMainInvokeEvent,
|
event: Electron.IpcMainInvokeEvent,
|
||||||
{ appId }: { appId: number }
|
{ appId }: { appId: number }
|
||||||
) => {
|
) => {
|
||||||
// Static server worker is NOT terminated here anymore
|
logger.log(`Restarting app ${appId}`);
|
||||||
|
|
||||||
return withLock(appId, async () => {
|
return withLock(appId, async () => {
|
||||||
try {
|
try {
|
||||||
// First stop the app if it's running
|
// First stop the app if it's running
|
||||||
const appInfo = runningApps.get(appId);
|
const appInfo = runningApps.get(appId);
|
||||||
if (appInfo) {
|
if (appInfo) {
|
||||||
const { process, processId } = appInfo;
|
const { process, processId } = appInfo;
|
||||||
console.log(
|
logger.log(
|
||||||
`Stopping local-node app ${appId} (processId ${processId}) before restart` // Adjusted log
|
`Stopping app ${appId} (processId ${processId}) before restart`
|
||||||
);
|
);
|
||||||
|
|
||||||
await killProcess(process);
|
await killProcess(process);
|
||||||
runningApps.delete(appId);
|
runningApps.delete(appId);
|
||||||
} else {
|
} else {
|
||||||
console.log(
|
logger.log(`App ${appId} not running. Proceeding to start.`);
|
||||||
`App ${appId} not running in local-node mode, proceeding to start.`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Kill any orphaned process on port 32100 (in case previous run left it)
|
// Kill any orphaned process on port 32100 (in case previous run left it)
|
||||||
@@ -417,7 +410,7 @@ export function registerAppHandlers() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const appPath = getDyadAppPath(app.path);
|
const appPath = getDyadAppPath(app.path);
|
||||||
console.debug(
|
logger.debug(
|
||||||
`Executing app ${appId} in path ${app.path} after restart request`
|
`Executing app ${appId} in path ${app.path} after restart request`
|
||||||
); // Adjusted log
|
); // Adjusted log
|
||||||
|
|
||||||
@@ -425,7 +418,7 @@ export function registerAppHandlers() {
|
|||||||
|
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error restarting app ${appId}:`, error);
|
logger.error(`Error restarting app ${appId}:`, error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -461,7 +454,7 @@ export function registerAppHandlers() {
|
|||||||
timestamp: commit.commit.author.timestamp,
|
timestamp: commit.commit.author.timestamp,
|
||||||
})) satisfies Version[];
|
})) satisfies Version[];
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(`Error listing versions for app ${appId}:`, error);
|
logger.error(`Error listing versions for app ${appId}:`, error);
|
||||||
throw new Error(`Failed to list versions: ${error.message}`);
|
throw new Error(`Failed to list versions: ${error.message}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -552,7 +545,7 @@ export function registerAppHandlers() {
|
|||||||
|
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(
|
logger.error(
|
||||||
`Error reverting to version ${previousVersionId} for app ${appId}:`,
|
`Error reverting to version ${previousVersionId} for app ${appId}:`,
|
||||||
error
|
error
|
||||||
);
|
);
|
||||||
@@ -587,7 +580,7 @@ export function registerAppHandlers() {
|
|||||||
|
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(
|
logger.error(
|
||||||
`Error checking out version ${versionId} for app ${appId}:`,
|
`Error checking out version ${versionId} for app ${appId}:`,
|
||||||
error
|
error
|
||||||
);
|
);
|
||||||
@@ -614,7 +607,7 @@ export function registerAppHandlers() {
|
|||||||
try {
|
try {
|
||||||
return await extractCodebase(appPath, maxFiles);
|
return await extractCodebase(appPath, maxFiles);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error extracting codebase for app ${appId}:`, error);
|
logger.error(`Error extracting codebase for app ${appId}:`, error);
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Failed to extract codebase: ${(error as any).message}`
|
`Failed to extract codebase: ${(error as any).message}`
|
||||||
);
|
);
|
||||||
@@ -673,10 +666,7 @@ export function registerAppHandlers() {
|
|||||||
|
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(
|
logger.error(`Error writing file ${filePath} for app ${appId}:`, error);
|
||||||
`Error writing file ${filePath} for app ${appId}:`,
|
|
||||||
error
|
|
||||||
);
|
|
||||||
throw new Error(`Failed to write file: ${error.message}`);
|
throw new Error(`Failed to write file: ${error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -699,14 +689,11 @@ export function registerAppHandlers() {
|
|||||||
if (runningApps.has(appId)) {
|
if (runningApps.has(appId)) {
|
||||||
const appInfo = runningApps.get(appId)!;
|
const appInfo = runningApps.get(appId)!;
|
||||||
try {
|
try {
|
||||||
console.log(`Stopping local-node app ${appId} before deletion.`); // Adjusted log
|
logger.log(`Stopping app ${appId} before deletion.`); // Adjusted log
|
||||||
await killProcess(appInfo.process);
|
await killProcess(appInfo.process);
|
||||||
runningApps.delete(appId);
|
runningApps.delete(appId);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(
|
logger.error(`Error stopping app ${appId} before deletion:`, error); // Adjusted log
|
||||||
`Error stopping local-node app ${appId} before deletion:`,
|
|
||||||
error
|
|
||||||
); // Adjusted log
|
|
||||||
// Continue with deletion even if stopping fails
|
// Continue with deletion even if stopping fails
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -716,7 +703,7 @@ export function registerAppHandlers() {
|
|||||||
try {
|
try {
|
||||||
await fsPromises.rm(appPath, { recursive: true, force: true });
|
await fsPromises.rm(appPath, { recursive: true, force: true });
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(`Error deleting app files for app ${appId}:`, error);
|
logger.error(`Error deleting app files for app ${appId}:`, error);
|
||||||
throw new Error(`Failed to delete app files: ${error.message}`);
|
throw new Error(`Failed to delete app files: ${error.message}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -726,7 +713,7 @@ export function registerAppHandlers() {
|
|||||||
// Note: Associated chats will cascade delete if that's set up in the schema
|
// Note: Associated chats will cascade delete if that's set up in the schema
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(`Error deleting app ${appId} from database:`, error);
|
logger.error(`Error deleting app ${appId} from database:`, error);
|
||||||
throw new Error(`Failed to delete app from database: ${error.message}`);
|
throw new Error(`Failed to delete app from database: ${error.message}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -776,10 +763,7 @@ export function registerAppHandlers() {
|
|||||||
await killProcess(appInfo.process);
|
await killProcess(appInfo.process);
|
||||||
runningApps.delete(appId);
|
runningApps.delete(appId);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(
|
logger.error(`Error stopping app ${appId} before renaming:`, error);
|
||||||
`Error stopping app ${appId} before renaming:`,
|
|
||||||
error
|
|
||||||
);
|
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Failed to stop app before renaming: ${error.message}`
|
`Failed to stop app before renaming: ${error.message}`
|
||||||
);
|
);
|
||||||
@@ -807,7 +791,7 @@ export function registerAppHandlers() {
|
|||||||
// Move the files
|
// Move the files
|
||||||
await fsPromises.rename(oldAppPath, newAppPath);
|
await fsPromises.rename(oldAppPath, newAppPath);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(
|
logger.error(
|
||||||
`Error moving app files from ${oldAppPath} to ${newAppPath}:`,
|
`Error moving app files from ${oldAppPath} to ${newAppPath}:`,
|
||||||
error
|
error
|
||||||
);
|
);
|
||||||
@@ -833,14 +817,14 @@ export function registerAppHandlers() {
|
|||||||
try {
|
try {
|
||||||
await fsPromises.rename(newAppPath, oldAppPath);
|
await fsPromises.rename(newAppPath, oldAppPath);
|
||||||
} catch (rollbackError) {
|
} catch (rollbackError) {
|
||||||
console.error(
|
logger.error(
|
||||||
`Failed to rollback file move during rename error:`,
|
`Failed to rollback file move during rename error:`,
|
||||||
rollbackError
|
rollbackError
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.error(`Error updating app ${appId} in database:`, error);
|
logger.error(`Error updating app ${appId} in database:`, error);
|
||||||
throw new Error(`Failed to update app in database: ${error.message}`);
|
throw new Error(`Failed to update app in database: ${error.message}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -848,16 +832,9 @@ export function registerAppHandlers() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
ipcMain.handle("reset-all", async () => {
|
ipcMain.handle("reset-all", async () => {
|
||||||
console.log("start: resetting all apps and settings.");
|
logger.log("start: resetting all apps and settings.");
|
||||||
// Terminate static server worker if it's running
|
|
||||||
if (staticServerWorker) {
|
|
||||||
console.log(`Terminating static server worker on reset-all command.`);
|
|
||||||
await staticServerWorker.terminate();
|
|
||||||
staticServerWorker = null;
|
|
||||||
staticServerPort = null;
|
|
||||||
}
|
|
||||||
// Stop all running apps first
|
// Stop all running apps first
|
||||||
console.log("stopping all running apps...");
|
logger.log("stopping all running apps...");
|
||||||
const runningAppIds = Array.from(runningApps.keys());
|
const runningAppIds = Array.from(runningApps.keys());
|
||||||
for (const appId of runningAppIds) {
|
for (const appId of runningAppIds) {
|
||||||
try {
|
try {
|
||||||
@@ -865,12 +842,12 @@ export function registerAppHandlers() {
|
|||||||
await killProcess(appInfo.process);
|
await killProcess(appInfo.process);
|
||||||
runningApps.delete(appId);
|
runningApps.delete(appId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error stopping app ${appId} during reset:`, error);
|
logger.error(`Error stopping app ${appId} during reset:`, error);
|
||||||
// Continue with reset even if stopping fails
|
// Continue with reset even if stopping fails
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log("all running apps stopped.");
|
logger.log("all running apps stopped.");
|
||||||
console.log("deleting database...");
|
logger.log("deleting database...");
|
||||||
// 1. Drop the database by deleting the SQLite file
|
// 1. Drop the database by deleting the SQLite file
|
||||||
const dbPath = getDatabasePath();
|
const dbPath = getDatabasePath();
|
||||||
if (fs.existsSync(dbPath)) {
|
if (fs.existsSync(dbPath)) {
|
||||||
@@ -879,31 +856,31 @@ export function registerAppHandlers() {
|
|||||||
db.$client.close();
|
db.$client.close();
|
||||||
}
|
}
|
||||||
await fsPromises.unlink(dbPath);
|
await fsPromises.unlink(dbPath);
|
||||||
console.log(`Database file deleted: ${dbPath}`);
|
logger.log(`Database file deleted: ${dbPath}`);
|
||||||
}
|
}
|
||||||
console.log("database deleted.");
|
logger.log("database deleted.");
|
||||||
console.log("deleting settings...");
|
logger.log("deleting settings...");
|
||||||
// 2. Remove settings
|
// 2. Remove settings
|
||||||
const userDataPath = getUserDataPath();
|
const userDataPath = getUserDataPath();
|
||||||
const settingsPath = path.join(userDataPath, "user-settings.json");
|
const settingsPath = path.join(userDataPath, "user-settings.json");
|
||||||
|
|
||||||
if (fs.existsSync(settingsPath)) {
|
if (fs.existsSync(settingsPath)) {
|
||||||
await fsPromises.unlink(settingsPath);
|
await fsPromises.unlink(settingsPath);
|
||||||
console.log(`Settings file deleted: ${settingsPath}`);
|
logger.log(`Settings file deleted: ${settingsPath}`);
|
||||||
}
|
}
|
||||||
console.log("settings deleted.");
|
logger.log("settings deleted.");
|
||||||
// 3. Remove all app files recursively
|
// 3. Remove all app files recursively
|
||||||
// Doing this last because it's the most time-consuming and the least important
|
// Doing this last because it's the most time-consuming and the least important
|
||||||
// in terms of resetting the app state.
|
// in terms of resetting the app state.
|
||||||
console.log("removing all app files...");
|
logger.log("removing all app files...");
|
||||||
const dyadAppPath = getDyadAppPath(".");
|
const dyadAppPath = getDyadAppPath(".");
|
||||||
if (fs.existsSync(dyadAppPath)) {
|
if (fs.existsSync(dyadAppPath)) {
|
||||||
await fsPromises.rm(dyadAppPath, { recursive: true, force: true });
|
await fsPromises.rm(dyadAppPath, { recursive: true, force: true });
|
||||||
// Recreate the base directory
|
// Recreate the base directory
|
||||||
await fsPromises.mkdir(dyadAppPath, { recursive: true });
|
await fsPromises.mkdir(dyadAppPath, { recursive: true });
|
||||||
}
|
}
|
||||||
console.log("all app files removed.");
|
logger.log("all app files removed.");
|
||||||
console.log("reset all complete.");
|
logger.log("reset all complete.");
|
||||||
return { success: true, message: "Successfully reset everything" };
|
return { success: true, message: "Successfully reset everything" };
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ import { processFullResponseActions } from "../processors/response_processor";
|
|||||||
import { streamTestResponse } from "./testing_chat_handlers";
|
import { streamTestResponse } from "./testing_chat_handlers";
|
||||||
import { getTestResponse } from "./testing_chat_handlers";
|
import { getTestResponse } from "./testing_chat_handlers";
|
||||||
import { getModelClient } from "../utils/get_model_client";
|
import { getModelClient } from "../utils/get_model_client";
|
||||||
|
import log from "electron-log";
|
||||||
|
|
||||||
|
const logger = log.scope("chat_stream_handlers");
|
||||||
|
|
||||||
// Track active streams for cancellation
|
// Track active streams for cancellation
|
||||||
const activeStreams = new Map<number, AbortController>();
|
const activeStreams = new Map<number, AbortController>();
|
||||||
@@ -125,12 +128,12 @@ export function registerChatStreamHandlers() {
|
|||||||
const appPath = getDyadAppPath(updatedChat.app.path);
|
const appPath = getDyadAppPath(updatedChat.app.path);
|
||||||
try {
|
try {
|
||||||
codebaseInfo = await extractCodebase(appPath);
|
codebaseInfo = await extractCodebase(appPath);
|
||||||
console.log(`Extracted codebase information from ${appPath}`);
|
logger.log(`Extracted codebase information from ${appPath}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error extracting codebase:", error);
|
logger.error("Error extracting codebase:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log(
|
logger.log(
|
||||||
"codebaseInfo: length",
|
"codebaseInfo: length",
|
||||||
codebaseInfo.length,
|
codebaseInfo.length,
|
||||||
"estimated tokens",
|
"estimated tokens",
|
||||||
@@ -170,7 +173,7 @@ export function registerChatStreamHandlers() {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
onError: (error) => {
|
onError: (error) => {
|
||||||
console.error("Error streaming text:", error);
|
logger.error("Error streaming text:", error);
|
||||||
const message =
|
const message =
|
||||||
(error as any)?.error?.message || JSON.stringify(error);
|
(error as any)?.error?.message || JSON.stringify(error);
|
||||||
event.sender.send(
|
event.sender.send(
|
||||||
@@ -204,7 +207,7 @@ export function registerChatStreamHandlers() {
|
|||||||
|
|
||||||
// If the stream was aborted, exit early
|
// If the stream was aborted, exit early
|
||||||
if (abortController.signal.aborted) {
|
if (abortController.signal.aborted) {
|
||||||
console.log(`Stream for chat ${req.chatId} was aborted`);
|
logger.log(`Stream for chat ${req.chatId} was aborted`);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -222,10 +225,10 @@ export function registerChatStreamHandlers() {
|
|||||||
role: "assistant",
|
role: "assistant",
|
||||||
content: `${partialResponse}\n\n[Response cancelled by user]`,
|
content: `${partialResponse}\n\n[Response cancelled by user]`,
|
||||||
});
|
});
|
||||||
console.log(`Saved partial response for chat ${chatId}`);
|
logger.log(`Saved partial response for chat ${chatId}`);
|
||||||
partialResponses.delete(chatId);
|
partialResponses.delete(chatId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(
|
logger.error(
|
||||||
`Error saving partial response for chat ${chatId}:`,
|
`Error saving partial response for chat ${chatId}:`,
|
||||||
error
|
error
|
||||||
);
|
);
|
||||||
@@ -305,7 +308,7 @@ export function registerChatStreamHandlers() {
|
|||||||
// Return the chat ID for backwards compatibility
|
// Return the chat ID for backwards compatibility
|
||||||
return req.chatId;
|
return req.chatId;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[MAIN] API error:", error);
|
logger.error("Error calling LLM:", error);
|
||||||
event.sender.send(
|
event.sender.send(
|
||||||
"chat:response:error",
|
"chat:response:error",
|
||||||
`Sorry, there was an error processing your request: ${error}`
|
`Sorry, there was an error processing your request: ${error}`
|
||||||
@@ -324,9 +327,9 @@ export function registerChatStreamHandlers() {
|
|||||||
// Abort the stream
|
// Abort the stream
|
||||||
abortController.abort();
|
abortController.abort();
|
||||||
activeStreams.delete(chatId);
|
activeStreams.delete(chatId);
|
||||||
console.log(`Aborted stream for chat ${chatId}`);
|
logger.log(`Aborted stream for chat ${chatId}`);
|
||||||
} else {
|
} else {
|
||||||
console.warn(`No active stream found for chat ${chatId}`);
|
logger.warn(`No active stream found for chat ${chatId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send the end event to the renderer
|
// Send the end event to the renderer
|
||||||
|
|||||||
94
src/ipc/handlers/debug_handlers.ts
Normal file
94
src/ipc/handlers/debug_handlers.ts
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
import { ipcMain, app } from "electron";
|
||||||
|
import { platform, arch } from "os";
|
||||||
|
import { SystemDebugInfo } from "../ipc_types";
|
||||||
|
import { readSettings } from "../../main/settings";
|
||||||
|
import { execSync } from "child_process";
|
||||||
|
import log from "electron-log";
|
||||||
|
import path from "path";
|
||||||
|
import fs from "fs";
|
||||||
|
import { runShellCommand } from "../utils/runShellCommand";
|
||||||
|
|
||||||
|
export function registerDebugHandlers() {
|
||||||
|
ipcMain.handle(
|
||||||
|
"get-system-debug-info",
|
||||||
|
async (): Promise<SystemDebugInfo> => {
|
||||||
|
console.log("IPC: get-system-debug-info called");
|
||||||
|
|
||||||
|
// Get Node.js and pnpm versions
|
||||||
|
let nodeVersion: string | null = null;
|
||||||
|
let pnpmVersion: string | null = null;
|
||||||
|
let nodePath: string | null = null;
|
||||||
|
try {
|
||||||
|
nodeVersion = await runShellCommand("node --version");
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to get Node.js version:", err);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
pnpmVersion = await runShellCommand("pnpm --version");
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to get pnpm version:", err);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (platform() === "win32") {
|
||||||
|
nodePath = await runShellCommand("where.exe node");
|
||||||
|
} else {
|
||||||
|
nodePath = await runShellCommand("which node");
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to get node path:", err);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get Dyad version from package.json
|
||||||
|
const packageJsonPath = path.resolve(
|
||||||
|
__dirname,
|
||||||
|
"..",
|
||||||
|
"..",
|
||||||
|
"package.json"
|
||||||
|
);
|
||||||
|
let dyadVersion = "unknown";
|
||||||
|
try {
|
||||||
|
const packageJson = JSON.parse(
|
||||||
|
fs.readFileSync(packageJsonPath, "utf8")
|
||||||
|
);
|
||||||
|
dyadVersion = packageJson.version;
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to read package.json:", err);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get telemetry info from settings
|
||||||
|
const settings = readSettings();
|
||||||
|
const telemetryId = settings.telemetryUserId || "unknown";
|
||||||
|
|
||||||
|
// Get logs from electron-log
|
||||||
|
let logs = "";
|
||||||
|
try {
|
||||||
|
const logPath = log.transports.file.getFile().path;
|
||||||
|
if (fs.existsSync(logPath)) {
|
||||||
|
const logContent = fs.readFileSync(logPath, "utf8");
|
||||||
|
const logLines = logContent.split("\n");
|
||||||
|
logs = logLines.slice(-100).join("\n");
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Failed to read log file:", err);
|
||||||
|
logs = `Error reading logs: ${err}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
nodeVersion,
|
||||||
|
pnpmVersion,
|
||||||
|
nodePath,
|
||||||
|
telemetryId,
|
||||||
|
telemetryConsent: settings.telemetryConsent || "unknown",
|
||||||
|
telemetryUrl: "https://us.i.posthog.com", // Hardcoded from renderer.tsx
|
||||||
|
dyadVersion,
|
||||||
|
platform: process.platform,
|
||||||
|
architecture: arch(),
|
||||||
|
logs,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
console.log("Registered debug IPC handlers");
|
||||||
|
}
|
||||||
@@ -16,6 +16,9 @@ import { db } from "../../db";
|
|||||||
import { apps } from "../../db/schema";
|
import { apps } from "../../db/schema";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import { GithubUser } from "../../lib/schemas";
|
import { GithubUser } from "../../lib/schemas";
|
||||||
|
import log from "electron-log";
|
||||||
|
|
||||||
|
const logger = log.scope("github_handlers");
|
||||||
|
|
||||||
// --- GitHub Device Flow Constants ---
|
// --- GitHub Device Flow Constants ---
|
||||||
// TODO: Fetch this securely, e.g., from environment variables or a config file
|
// TODO: Fetch this securely, e.g., from environment variables or a config file
|
||||||
@@ -65,7 +68,7 @@ export async function getGithubUser(): Promise<GithubUser | null> {
|
|||||||
});
|
});
|
||||||
return { email };
|
return { email };
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("[GitHub Handler] Failed to get GitHub username:", err);
|
logger.error("[GitHub Handler] Failed to get GitHub username:", err);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -78,15 +81,13 @@ export async function getGithubUser(): Promise<GithubUser | null> {
|
|||||||
|
|
||||||
async function pollForAccessToken(event: IpcMainInvokeEvent) {
|
async function pollForAccessToken(event: IpcMainInvokeEvent) {
|
||||||
if (!currentFlowState || !currentFlowState.isPolling) {
|
if (!currentFlowState || !currentFlowState.isPolling) {
|
||||||
console.log("[GitHub Handler] Polling stopped or no active flow.");
|
logger.debug("[GitHub Handler] Polling stopped or no active flow.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { deviceCode, interval } = currentFlowState;
|
const { deviceCode, interval } = currentFlowState;
|
||||||
|
|
||||||
console.log(
|
logger.debug("[GitHub Handler] Polling for token with device code");
|
||||||
`[GitHub Handler] Polling for token with device code: ${deviceCode}`
|
|
||||||
);
|
|
||||||
event.sender.send("github:flow-update", {
|
event.sender.send("github:flow-update", {
|
||||||
message: "Polling GitHub for authorization...",
|
message: "Polling GitHub for authorization...",
|
||||||
});
|
});
|
||||||
@@ -108,10 +109,7 @@ async function pollForAccessToken(event: IpcMainInvokeEvent) {
|
|||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
if (response.ok && data.access_token) {
|
if (response.ok && data.access_token) {
|
||||||
// --- SUCCESS ---
|
logger.log("Successfully obtained GitHub Access Token.");
|
||||||
console.log(
|
|
||||||
"[GitHub Handler] Successfully obtained GitHub Access Token."
|
|
||||||
); // TODO: Store this token securely!
|
|
||||||
event.sender.send("github:flow-success", {
|
event.sender.send("github:flow-success", {
|
||||||
message: "Successfully connected!",
|
message: "Successfully connected!",
|
||||||
});
|
});
|
||||||
@@ -120,13 +118,13 @@ async function pollForAccessToken(event: IpcMainInvokeEvent) {
|
|||||||
value: data.access_token,
|
value: data.access_token,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
// TODO: Associate token with appId if provided
|
|
||||||
stopPolling();
|
stopPolling();
|
||||||
return;
|
return;
|
||||||
} else if (data.error) {
|
} else if (data.error) {
|
||||||
switch (data.error) {
|
switch (data.error) {
|
||||||
case "authorization_pending":
|
case "authorization_pending":
|
||||||
console.log("[GitHub Handler] Authorization pending...");
|
logger.debug("Authorization pending...");
|
||||||
event.sender.send("github:flow-update", {
|
event.sender.send("github:flow-update", {
|
||||||
message: "Waiting for user authorization...",
|
message: "Waiting for user authorization...",
|
||||||
});
|
});
|
||||||
@@ -138,9 +136,7 @@ async function pollForAccessToken(event: IpcMainInvokeEvent) {
|
|||||||
break;
|
break;
|
||||||
case "slow_down":
|
case "slow_down":
|
||||||
const newInterval = interval + 5;
|
const newInterval = interval + 5;
|
||||||
console.log(
|
logger.debug(`Slow down requested. New interval: ${newInterval}s`);
|
||||||
`[GitHub Handler] Slow down requested. New interval: ${newInterval}s`
|
|
||||||
);
|
|
||||||
currentFlowState.interval = newInterval; // Update interval
|
currentFlowState.interval = newInterval; // Update interval
|
||||||
event.sender.send("github:flow-update", {
|
event.sender.send("github:flow-update", {
|
||||||
message: `GitHub asked to slow down. Retrying in ${newInterval}s...`,
|
message: `GitHub asked to slow down. Retrying in ${newInterval}s...`,
|
||||||
@@ -151,24 +147,22 @@ async function pollForAccessToken(event: IpcMainInvokeEvent) {
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case "expired_token":
|
case "expired_token":
|
||||||
console.error("[GitHub Handler] Device code expired.");
|
logger.error("Device code expired.");
|
||||||
event.sender.send("github:flow-error", {
|
event.sender.send("github:flow-error", {
|
||||||
error: "Verification code expired. Please try again.",
|
error: "Verification code expired. Please try again.",
|
||||||
});
|
});
|
||||||
stopPolling();
|
stopPolling();
|
||||||
break;
|
break;
|
||||||
case "access_denied":
|
case "access_denied":
|
||||||
console.error("[GitHub Handler] Access denied by user.");
|
logger.error("Access denied by user.");
|
||||||
event.sender.send("github:flow-error", {
|
event.sender.send("github:flow-error", {
|
||||||
error: "Authorization denied by user.",
|
error: "Authorization denied by user.",
|
||||||
});
|
});
|
||||||
stopPolling();
|
stopPolling();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
console.error(
|
logger.error(
|
||||||
`[GitHub Handler] Unknown GitHub error: ${
|
`Unknown GitHub error: ${data.error_description || data.error}`
|
||||||
data.error_description || data.error
|
|
||||||
}`
|
|
||||||
);
|
);
|
||||||
event.sender.send("github:flow-error", {
|
event.sender.send("github:flow-error", {
|
||||||
error: `GitHub authorization error: ${
|
error: `GitHub authorization error: ${
|
||||||
@@ -182,10 +176,7 @@ async function pollForAccessToken(event: IpcMainInvokeEvent) {
|
|||||||
throw new Error(`Unknown response structure: ${JSON.stringify(data)}`);
|
throw new Error(`Unknown response structure: ${JSON.stringify(data)}`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(
|
logger.error("Error polling for GitHub access token:", error);
|
||||||
"[GitHub Handler] Error polling for GitHub access token:",
|
|
||||||
error
|
|
||||||
);
|
|
||||||
event.sender.send("github:flow-error", {
|
event.sender.send("github:flow-error", {
|
||||||
error: `Network or unexpected error during polling: ${
|
error: `Network or unexpected error during polling: ${
|
||||||
error instanceof Error ? error.message : String(error)
|
error instanceof Error ? error.message : String(error)
|
||||||
@@ -202,12 +193,9 @@ function stopPolling() {
|
|||||||
}
|
}
|
||||||
currentFlowState.isPolling = false;
|
currentFlowState.isPolling = false;
|
||||||
currentFlowState.timeoutId = null;
|
currentFlowState.timeoutId = null;
|
||||||
// Maybe keep window reference for a bit if needed, or clear it
|
|
||||||
// currentFlowState.window = null;
|
logger.debug("[GitHub Handler] Polling stopped.");
|
||||||
console.log("[GitHub Handler] Polling stopped.");
|
|
||||||
}
|
}
|
||||||
// Setting to null signifies no active flow
|
|
||||||
// currentFlowState = null; // Decide if you want to clear immediately or allow potential restart
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- IPC Handlers ---
|
// --- IPC Handlers ---
|
||||||
@@ -216,15 +204,11 @@ function handleStartGithubFlow(
|
|||||||
event: IpcMainInvokeEvent,
|
event: IpcMainInvokeEvent,
|
||||||
args: { appId: number | null }
|
args: { appId: number | null }
|
||||||
) {
|
) {
|
||||||
console.log(
|
logger.debug(`Received github:start-flow for appId: ${args.appId}`);
|
||||||
`[GitHub Handler] Received github:start-flow for appId: ${args.appId}`
|
|
||||||
);
|
|
||||||
|
|
||||||
// If a flow is already in progress, maybe cancel it or send an error
|
// If a flow is already in progress, maybe cancel it or send an error
|
||||||
if (currentFlowState && currentFlowState.isPolling) {
|
if (currentFlowState && currentFlowState.isPolling) {
|
||||||
console.warn(
|
logger.warn("Another GitHub flow is already in progress.");
|
||||||
"[GitHub Handler] Another GitHub flow is already in progress."
|
|
||||||
);
|
|
||||||
event.sender.send("github:flow-error", {
|
event.sender.send("github:flow-error", {
|
||||||
error: "Another connection process is already active.",
|
error: "Another connection process is already active.",
|
||||||
});
|
});
|
||||||
@@ -234,7 +218,7 @@ function handleStartGithubFlow(
|
|||||||
// Store the window that initiated the request
|
// Store the window that initiated the request
|
||||||
const window = BrowserWindow.fromWebContents(event.sender);
|
const window = BrowserWindow.fromWebContents(event.sender);
|
||||||
if (!window) {
|
if (!window) {
|
||||||
console.error("[GitHub Handler] Could not get BrowserWindow instance.");
|
logger.error("Could not get BrowserWindow instance.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,7 +256,7 @@ function handleStartGithubFlow(
|
|||||||
return res.json();
|
return res.json();
|
||||||
})
|
})
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
console.log("[GitHub Handler] Received device code response:", data);
|
logger.info("Received device code response");
|
||||||
if (!currentFlowState) return; // Flow might have been cancelled
|
if (!currentFlowState) return; // Flow might have been cancelled
|
||||||
|
|
||||||
currentFlowState.deviceCode = data.device_code;
|
currentFlowState.deviceCode = data.device_code;
|
||||||
@@ -293,10 +277,7 @@ function handleStartGithubFlow(
|
|||||||
);
|
);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error(
|
logger.error("Error initiating GitHub device flow:", error);
|
||||||
"[GitHub Handler] Error initiating GitHub device flow:",
|
|
||||||
error
|
|
||||||
);
|
|
||||||
event.sender.send("github:flow-error", {
|
event.sender.send("github:flow-error", {
|
||||||
error: `Failed to start GitHub connection: ${error.message}`,
|
error: `Failed to start GitHub connection: ${error.message}`,
|
||||||
});
|
});
|
||||||
@@ -305,15 +286,6 @@ function handleStartGithubFlow(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optional: Handle cancellation from renderer
|
|
||||||
// function handleCancelGithubFlow(event: IpcMainEvent) {
|
|
||||||
// console.log('[GitHub Handler] Received github:cancel-flow');
|
|
||||||
// stopPolling();
|
|
||||||
// currentFlowState = null; // Clear state on cancel
|
|
||||||
// // Optionally send confirmation back
|
|
||||||
// event.sender.send('github:flow-cancelled', { message: 'GitHub flow cancelled.' });
|
|
||||||
// }
|
|
||||||
|
|
||||||
// --- GitHub Repo Availability Handler ---
|
// --- GitHub Repo Availability Handler ---
|
||||||
async function handleIsRepoAvailable(
|
async function handleIsRepoAvailable(
|
||||||
event: IpcMainInvokeEvent,
|
event: IpcMainInvokeEvent,
|
||||||
@@ -453,7 +425,6 @@ async function handlePushToGithub(
|
|||||||
// --- Registration ---
|
// --- Registration ---
|
||||||
export function registerGithubHandlers() {
|
export function registerGithubHandlers() {
|
||||||
ipcMain.handle("github:start-flow", handleStartGithubFlow);
|
ipcMain.handle("github:start-flow", handleStartGithubFlow);
|
||||||
// ipcMain.on('github:cancel-flow', handleCancelGithubFlow); // Uncomment if you add cancellation
|
|
||||||
ipcMain.handle("github:is-repo-available", handleIsRepoAvailable);
|
ipcMain.handle("github:is-repo-available", handleIsRepoAvailable);
|
||||||
ipcMain.handle("github:create-repo", handleCreateRepo);
|
ipcMain.handle("github:create-repo", handleCreateRepo);
|
||||||
ipcMain.handle("github:push", handlePushToGithub);
|
ipcMain.handle("github:push", handlePushToGithub);
|
||||||
|
|||||||
@@ -1,51 +1,28 @@
|
|||||||
import { ipcMain, app } from "electron";
|
import { ipcMain, app } from "electron";
|
||||||
import { exec, execSync, spawn } from "child_process";
|
import { exec, execSync } from "child_process";
|
||||||
import { platform, arch } from "os";
|
import { platform, arch } from "os";
|
||||||
import { NodeSystemInfo } from "../ipc_types";
|
import { NodeSystemInfo } from "../ipc_types";
|
||||||
import fixPath from "fix-path";
|
import fixPath from "fix-path";
|
||||||
|
import { runShellCommand } from "../utils/runShellCommand";
|
||||||
|
import log from "electron-log";
|
||||||
|
|
||||||
function checkCommandExists(command: string): Promise<string | null> {
|
const logger = log.scope("node_handlers");
|
||||||
return new Promise((resolve) => {
|
|
||||||
let output = "";
|
|
||||||
const process = spawn(command, {
|
|
||||||
shell: true,
|
|
||||||
stdio: ["ignore", "pipe", "pipe"], // ignore stdin, pipe stdout/stderr
|
|
||||||
});
|
|
||||||
|
|
||||||
process.stdout?.on("data", (data) => {
|
|
||||||
output += data.toString();
|
|
||||||
});
|
|
||||||
|
|
||||||
process.stderr?.on("data", (data) => {
|
|
||||||
// Log stderr but don't treat it as a failure unless the exit code is non-zero
|
|
||||||
console.warn(`Stderr from "${command}": ${data.toString().trim()}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
process.on("error", (error) => {
|
|
||||||
console.error(`Error executing command "${command}":`, error.message);
|
|
||||||
resolve(null); // Command execution failed
|
|
||||||
});
|
|
||||||
|
|
||||||
process.on("close", (code) => {
|
|
||||||
if (code === 0) {
|
|
||||||
resolve(output.trim()); // Command succeeded, return trimmed output
|
|
||||||
} else {
|
|
||||||
console.error(`Command "${command}" failed with code ${code}`);
|
|
||||||
resolve(null); // Command failed
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function registerNodeHandlers() {
|
export function registerNodeHandlers() {
|
||||||
ipcMain.handle("nodejs-status", async (): Promise<NodeSystemInfo> => {
|
ipcMain.handle("nodejs-status", async (): Promise<NodeSystemInfo> => {
|
||||||
|
logger.log(
|
||||||
|
"handling ipc: nodejs-status for platform:",
|
||||||
|
platform(),
|
||||||
|
"and arch:",
|
||||||
|
arch()
|
||||||
|
);
|
||||||
// Run checks in parallel
|
// Run checks in parallel
|
||||||
const [nodeVersion, pnpmVersion] = await Promise.all([
|
const [nodeVersion, pnpmVersion] = await Promise.all([
|
||||||
checkCommandExists("node --version"),
|
runShellCommand("node --version"),
|
||||||
// First, check if pnpm is installed.
|
// First, check if pnpm is installed.
|
||||||
// If not, try to install it using corepack.
|
// If not, try to install it using corepack.
|
||||||
// If both fail, then pnpm is not available.
|
// If both fail, then pnpm is not available.
|
||||||
checkCommandExists(
|
runShellCommand(
|
||||||
"pnpm --version || (corepack enable pnpm && pnpm --version) || (npm install -g pnpm@latest-10 && pnpm --version)"
|
"pnpm --version || (corepack enable pnpm && pnpm --version) || (npm install -g pnpm@latest-10 && pnpm --version)"
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
@@ -66,7 +43,7 @@ export function registerNodeHandlers() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle("reload-env-path", async (): Promise<void> => {
|
ipcMain.handle("reload-env-path", async (): Promise<void> => {
|
||||||
console.debug("Reloading env path, previously:", process.env.PATH);
|
logger.debug("Reloading env path, previously:", process.env.PATH);
|
||||||
if (platform() === "win32") {
|
if (platform() === "win32") {
|
||||||
const newPath = execSync("cmd /c echo %PATH%", {
|
const newPath = execSync("cmd /c echo %PATH%", {
|
||||||
encoding: "utf8",
|
encoding: "utf8",
|
||||||
@@ -75,6 +52,6 @@ export function registerNodeHandlers() {
|
|||||||
} else {
|
} else {
|
||||||
fixPath();
|
fixPath();
|
||||||
}
|
}
|
||||||
console.debug("Reloaded env path, now:", process.env.PATH);
|
logger.debug("Reloaded env path, now:", process.env.PATH);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ import {
|
|||||||
getDyadWriteTags,
|
getDyadWriteTags,
|
||||||
processFullResponseActions,
|
processFullResponseActions,
|
||||||
} from "../processors/response_processor";
|
} from "../processors/response_processor";
|
||||||
|
import log from "electron-log";
|
||||||
|
|
||||||
|
const logger = log.scope("proposal_handlers");
|
||||||
|
|
||||||
// Placeholder Proposal data (can be removed or kept for reference)
|
// Placeholder Proposal data (can be removed or kept for reference)
|
||||||
// const placeholderProposal: Proposal = { ... };
|
// const placeholderProposal: Proposal = { ... };
|
||||||
@@ -33,7 +36,7 @@ const getProposalHandler = async (
|
|||||||
_event: IpcMainInvokeEvent,
|
_event: IpcMainInvokeEvent,
|
||||||
{ chatId }: { chatId: number }
|
{ chatId }: { chatId: number }
|
||||||
): Promise<ProposalResult | null> => {
|
): Promise<ProposalResult | null> => {
|
||||||
console.log(`IPC: get-proposal called for chatId: ${chatId}`);
|
logger.log(`IPC: get-proposal called for chatId: ${chatId}`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Find the latest ASSISTANT message for the chat
|
// Find the latest ASSISTANT message for the chat
|
||||||
@@ -56,7 +59,7 @@ const getProposalHandler = async (
|
|||||||
|
|
||||||
if (latestAssistantMessage?.content && latestAssistantMessage.id) {
|
if (latestAssistantMessage?.content && latestAssistantMessage.id) {
|
||||||
const messageId = latestAssistantMessage.id; // Get the message ID
|
const messageId = latestAssistantMessage.id; // Get the message ID
|
||||||
console.log(
|
logger.log(
|
||||||
`Found latest assistant message (ID: ${messageId}), parsing content...`
|
`Found latest assistant message (ID: ${messageId}), parsing content...`
|
||||||
);
|
);
|
||||||
const messageContent = latestAssistantMessage.content;
|
const messageContent = latestAssistantMessage.content;
|
||||||
@@ -78,20 +81,25 @@ const getProposalHandler = async (
|
|||||||
summary: tag.description ?? "(no change summary found)", // Generic summary
|
summary: tag.description ?? "(no change summary found)", // Generic summary
|
||||||
})),
|
})),
|
||||||
};
|
};
|
||||||
console.log("Generated proposal on the fly:", proposal);
|
logger.log(
|
||||||
|
"Generated code proposal. title=",
|
||||||
|
proposal.title,
|
||||||
|
"files=",
|
||||||
|
proposal.filesChanged.length
|
||||||
|
);
|
||||||
return { proposal, chatId, messageId }; // Return proposal and messageId
|
return { proposal, chatId, messageId }; // Return proposal and messageId
|
||||||
} else {
|
} else {
|
||||||
console.log(
|
logger.log(
|
||||||
"No relevant tags found in the latest assistant message content."
|
"No relevant tags found in the latest assistant message content."
|
||||||
);
|
);
|
||||||
return null; // No proposal could be generated
|
return null; // No proposal could be generated
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log(`No assistant message found for chatId: ${chatId}`);
|
logger.log(`No assistant message found for chatId: ${chatId}`);
|
||||||
return null; // No message found
|
return null; // No message found
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error processing proposal for chatId ${chatId}:`, error);
|
logger.error(`Error processing proposal for chatId ${chatId}:`, error);
|
||||||
return null; // Indicate DB or processing error
|
return null; // Indicate DB or processing error
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -101,7 +109,7 @@ const approveProposalHandler = async (
|
|||||||
_event: IpcMainInvokeEvent,
|
_event: IpcMainInvokeEvent,
|
||||||
{ chatId, messageId }: { chatId: number; messageId: number }
|
{ chatId, messageId }: { chatId: number; messageId: number }
|
||||||
): Promise<{ success: boolean; error?: string }> => {
|
): Promise<{ success: boolean; error?: string }> => {
|
||||||
console.log(
|
logger.log(
|
||||||
`IPC: approve-proposal called for chatId: ${chatId}, messageId: ${messageId}`
|
`IPC: approve-proposal called for chatId: ${chatId}, messageId: ${messageId}`
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -119,7 +127,7 @@ const approveProposalHandler = async (
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!messageToApprove?.content) {
|
if (!messageToApprove?.content) {
|
||||||
console.error(
|
logger.error(
|
||||||
`Assistant message not found for chatId: ${chatId}, messageId: ${messageId}`
|
`Assistant message not found for chatId: ${chatId}, messageId: ${messageId}`
|
||||||
);
|
);
|
||||||
return { success: false, error: "Assistant message not found." };
|
return { success: false, error: "Assistant message not found." };
|
||||||
@@ -137,7 +145,7 @@ const approveProposalHandler = async (
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (processResult.error) {
|
if (processResult.error) {
|
||||||
console.error(
|
logger.error(
|
||||||
`Error processing actions for message ${messageId}:`,
|
`Error processing actions for message ${messageId}:`,
|
||||||
processResult.error
|
processResult.error
|
||||||
);
|
);
|
||||||
@@ -151,10 +159,7 @@ const approveProposalHandler = async (
|
|||||||
|
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(
|
logger.error(`Error approving proposal for messageId ${messageId}:`, error);
|
||||||
`Error approving proposal for messageId ${messageId}:`,
|
|
||||||
error
|
|
||||||
);
|
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: (error as Error)?.message || "Unknown error",
|
error: (error as Error)?.message || "Unknown error",
|
||||||
@@ -167,7 +172,7 @@ const rejectProposalHandler = async (
|
|||||||
_event: IpcMainInvokeEvent,
|
_event: IpcMainInvokeEvent,
|
||||||
{ chatId, messageId }: { chatId: number; messageId: number }
|
{ chatId, messageId }: { chatId: number; messageId: number }
|
||||||
): Promise<{ success: boolean; error?: string }> => {
|
): Promise<{ success: boolean; error?: string }> => {
|
||||||
console.log(
|
logger.log(
|
||||||
`IPC: reject-proposal called for chatId: ${chatId}, messageId: ${messageId}`
|
`IPC: reject-proposal called for chatId: ${chatId}, messageId: ${messageId}`
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -183,7 +188,7 @@ const rejectProposalHandler = async (
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!messageToReject) {
|
if (!messageToReject) {
|
||||||
console.error(
|
logger.error(
|
||||||
`Assistant message not found for chatId: ${chatId}, messageId: ${messageId}`
|
`Assistant message not found for chatId: ${chatId}, messageId: ${messageId}`
|
||||||
);
|
);
|
||||||
return { success: false, error: "Assistant message not found." };
|
return { success: false, error: "Assistant message not found." };
|
||||||
@@ -195,13 +200,10 @@ const rejectProposalHandler = async (
|
|||||||
.set({ approvalState: "rejected" })
|
.set({ approvalState: "rejected" })
|
||||||
.where(eq(messages.id, messageId));
|
.where(eq(messages.id, messageId));
|
||||||
|
|
||||||
console.log(`Message ${messageId} marked as rejected.`);
|
logger.log(`Message ${messageId} marked as rejected.`);
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(
|
logger.error(`Error rejecting proposal for messageId ${messageId}:`, error);
|
||||||
`Error rejecting proposal for messageId ${messageId}:`,
|
|
||||||
error
|
|
||||||
);
|
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: (error as Error)?.message || "Unknown error",
|
error: (error as Error)?.message || "Unknown error",
|
||||||
@@ -214,5 +216,4 @@ export function registerProposalHandlers() {
|
|||||||
ipcMain.handle("get-proposal", getProposalHandler);
|
ipcMain.handle("get-proposal", getProposalHandler);
|
||||||
ipcMain.handle("approve-proposal", approveProposalHandler);
|
ipcMain.handle("approve-proposal", approveProposalHandler);
|
||||||
ipcMain.handle("reject-proposal", rejectProposalHandler);
|
ipcMain.handle("reject-proposal", rejectProposalHandler);
|
||||||
console.log("Registered proposal IPC handlers (get, approve, reject)");
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import { ipcMain, shell } from "electron";
|
import { ipcMain, shell } from "electron";
|
||||||
|
import log from "electron-log";
|
||||||
|
|
||||||
|
const logger = log.scope("shell_handlers");
|
||||||
|
|
||||||
export function registerShellHandlers() {
|
export function registerShellHandlers() {
|
||||||
ipcMain.handle("open-external-url", async (_event, url: string) => {
|
ipcMain.handle("open-external-url", async (_event, url: string) => {
|
||||||
@@ -6,15 +9,16 @@ export function registerShellHandlers() {
|
|||||||
// Basic validation to ensure it's a http/https url
|
// Basic validation to ensure it's a http/https url
|
||||||
if (url && (url.startsWith("http://") || url.startsWith("https://"))) {
|
if (url && (url.startsWith("http://") || url.startsWith("https://"))) {
|
||||||
await shell.openExternal(url);
|
await shell.openExternal(url);
|
||||||
|
logger.debug("Opened external URL:", url);
|
||||||
return { success: true };
|
return { success: true };
|
||||||
}
|
}
|
||||||
console.error("Attempted to open invalid or non-http URL:", url);
|
logger.error("Attempted to open invalid or non-http URL:", url);
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error: "Invalid URL provided. Only http/https URLs are allowed.",
|
error: "Invalid URL provided. Only http/https URLs are allowed.",
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Failed to open external URL ${url}:`, error);
|
logger.error(`Failed to open external URL ${url}:`, error);
|
||||||
return { success: false, error: (error as Error).message };
|
return { success: false, error: (error as Error).message };
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
import type { IpcMainInvokeEvent } from "electron";
|
|
||||||
import { shell } from "electron";
|
|
||||||
|
|
||||||
export async function handleShellOpenExternal(
|
|
||||||
_event: IpcMainInvokeEvent,
|
|
||||||
url: string
|
|
||||||
): Promise<void> {
|
|
||||||
// Basic validation to ensure it's likely a URL
|
|
||||||
if (url && (url.startsWith("http://") || url.startsWith("https://"))) {
|
|
||||||
await shell.openExternal(url);
|
|
||||||
} else {
|
|
||||||
console.error(`Invalid URL attempt blocked: ${url}`);
|
|
||||||
// Optionally, you could throw an error back to the renderer
|
|
||||||
// throw new Error("Invalid or insecure URL provided.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -16,6 +16,7 @@ import type {
|
|||||||
NodeSystemInfo,
|
NodeSystemInfo,
|
||||||
Message,
|
Message,
|
||||||
Version,
|
Version,
|
||||||
|
SystemDebugInfo,
|
||||||
} from "./ipc_types";
|
} from "./ipc_types";
|
||||||
import type { CodeProposal, ProposalResult } from "@/lib/schemas";
|
import type { CodeProposal, ProposalResult } from "@/lib/schemas";
|
||||||
import { showError } from "@/lib/toast";
|
import { showError } from "@/lib/toast";
|
||||||
@@ -670,4 +671,15 @@ export class IpcClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// --- End Proposal Management ---
|
// --- End Proposal Management ---
|
||||||
|
|
||||||
|
// Get system debug information
|
||||||
|
public async getSystemDebugInfo(): Promise<SystemDebugInfo> {
|
||||||
|
try {
|
||||||
|
const result = await this.ipcRenderer.invoke("get-system-debug-info");
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
showError(error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { registerDependencyHandlers } from "./handlers/dependency_handlers";
|
|||||||
import { registerGithubHandlers } from "./handlers/github_handlers";
|
import { registerGithubHandlers } from "./handlers/github_handlers";
|
||||||
import { registerNodeHandlers } from "./handlers/node_handlers";
|
import { registerNodeHandlers } from "./handlers/node_handlers";
|
||||||
import { registerProposalHandlers } from "./handlers/proposal_handlers";
|
import { registerProposalHandlers } from "./handlers/proposal_handlers";
|
||||||
|
import { registerDebugHandlers } from "./handlers/debug_handlers";
|
||||||
|
|
||||||
export function registerIpcHandlers() {
|
export function registerIpcHandlers() {
|
||||||
// Register all IPC handlers by category
|
// Register all IPC handlers by category
|
||||||
@@ -19,4 +20,5 @@ export function registerIpcHandlers() {
|
|||||||
registerGithubHandlers();
|
registerGithubHandlers();
|
||||||
registerNodeHandlers();
|
registerNodeHandlers();
|
||||||
registerProposalHandlers();
|
registerProposalHandlers();
|
||||||
|
registerDebugHandlers();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,3 +76,16 @@ export interface NodeSystemInfo {
|
|||||||
pnpmVersion: string | null;
|
pnpmVersion: string | null;
|
||||||
nodeDownloadUrl: string;
|
nodeDownloadUrl: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SystemDebugInfo {
|
||||||
|
nodeVersion: string | null;
|
||||||
|
pnpmVersion: string | null;
|
||||||
|
nodePath: string | null;
|
||||||
|
telemetryId: string;
|
||||||
|
telemetryConsent: string;
|
||||||
|
telemetryUrl: string;
|
||||||
|
dyadVersion: string;
|
||||||
|
platform: string;
|
||||||
|
architecture: string;
|
||||||
|
logs: string;
|
||||||
|
}
|
||||||
|
|||||||
41
src/ipc/utils/runShellCommand.ts
Normal file
41
src/ipc/utils/runShellCommand.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { spawn } from "child_process";
|
||||||
|
import log from "electron-log";
|
||||||
|
|
||||||
|
const logger = log.scope("runShellCommand");
|
||||||
|
|
||||||
|
export function runShellCommand(command: string): Promise<string | null> {
|
||||||
|
logger.log(`Running command: ${command}`);
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
let output = "";
|
||||||
|
const process = spawn(command, {
|
||||||
|
shell: true,
|
||||||
|
stdio: ["ignore", "pipe", "pipe"], // ignore stdin, pipe stdout/stderr
|
||||||
|
});
|
||||||
|
|
||||||
|
process.stdout?.on("data", (data) => {
|
||||||
|
output += data.toString();
|
||||||
|
});
|
||||||
|
|
||||||
|
process.stderr?.on("data", (data) => {
|
||||||
|
// Log stderr but don't treat it as a failure unless the exit code is non-zero
|
||||||
|
logger.warn(`Stderr from "${command}": ${data.toString().trim()}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
process.on("error", (error) => {
|
||||||
|
logger.error(`Error executing command "${command}":`, error.message);
|
||||||
|
resolve(null); // Command execution failed
|
||||||
|
});
|
||||||
|
|
||||||
|
process.on("close", (code) => {
|
||||||
|
if (code === 0) {
|
||||||
|
logger.debug(
|
||||||
|
`Command "${command}" succeeded with code ${code}: ${output.trim()}`
|
||||||
|
);
|
||||||
|
resolve(output.trim()); // Command succeeded, return trimmed output
|
||||||
|
} else {
|
||||||
|
logger.error(`Command "${command}" failed with code ${code}`);
|
||||||
|
resolve(null); // Command failed
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -7,11 +7,9 @@ import started from "electron-squirrel-startup";
|
|||||||
import { updateElectronApp } from "update-electron-app";
|
import { updateElectronApp } from "update-electron-app";
|
||||||
import log from "electron-log";
|
import log from "electron-log";
|
||||||
|
|
||||||
console.log = log.log;
|
log.errorHandler.startCatching();
|
||||||
console.error = log.error;
|
log.eventLogger.startLogging();
|
||||||
console.warn = log.warn;
|
log.log("HELLO WORLD");
|
||||||
console.info = log.info;
|
|
||||||
console.debug = log.debug;
|
|
||||||
|
|
||||||
updateElectronApp(); // additional configuration options available
|
updateElectronApp(); // additional configuration options available
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ const validInvokeChannels = [
|
|||||||
"get-proposal",
|
"get-proposal",
|
||||||
"approve-proposal",
|
"approve-proposal",
|
||||||
"reject-proposal",
|
"reject-proposal",
|
||||||
|
"get-system-debug-info",
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
// Add valid receive channels
|
// Add valid receive channels
|
||||||
|
|||||||
Reference in New Issue
Block a user