Add help button which opens GitHub issue w/ system report | explicitly log with electron-log & scrub logs
This commit is contained in:
@@ -9,6 +9,7 @@ import {
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { BookOpenIcon, BugIcon } from "lucide-react";
|
||||
import { IpcClient } from "@/ipc/ipc_client";
|
||||
import { useState } from "react";
|
||||
|
||||
interface HelpDialogProps {
|
||||
isOpen: boolean;
|
||||
@@ -16,6 +17,60 @@ interface 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 (
|
||||
<Dialog open={isOpen} onOpenChange={onClose}>
|
||||
<DialogContent>
|
||||
@@ -46,17 +101,15 @@ export function HelpDialog({ isOpen, onClose }: HelpDialogProps) {
|
||||
<div className="flex flex-col space-y-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() =>
|
||||
IpcClient.getInstance().openExternalUrl(
|
||||
"https://github.com/dyad-sh/dyad/issues/new"
|
||||
)
|
||||
}
|
||||
onClick={handleReportBug}
|
||||
disabled={isLoading}
|
||||
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>
|
||||
<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.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -20,7 +20,6 @@ export function ProviderSettingsGrid({
|
||||
const navigate = useNavigate();
|
||||
|
||||
const handleProviderClick = (provider: ModelProvider) => {
|
||||
console.log("PROVIDER", provider);
|
||||
navigate({
|
||||
to: providerSettingsRoute.id,
|
||||
params: { provider },
|
||||
|
||||
@@ -9,6 +9,9 @@ import path from "node:path";
|
||||
import fs from "node:fs";
|
||||
import { getDyadAppPath, getUserDataPath } from "../paths/paths";
|
||||
import { eq } from "drizzle-orm";
|
||||
import log from "electron-log";
|
||||
|
||||
const logger = log.scope("db");
|
||||
|
||||
// Database connection factory
|
||||
let _db: ReturnType<typeof drizzle> | null = null;
|
||||
@@ -29,7 +32,7 @@ export function initializeDatabase(): BetterSQLite3Database<typeof schema> & {
|
||||
if (_db) return _db as any;
|
||||
|
||||
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
|
||||
try {
|
||||
@@ -38,14 +41,12 @@ export function initializeDatabase(): BetterSQLite3Database<typeof schema> & {
|
||||
const stats = fs.statSync(dbPath);
|
||||
// If the file is very small, it might be corrupted
|
||||
if (stats.size < 100) {
|
||||
console.log(
|
||||
"Database file exists but may be corrupted. Removing it..."
|
||||
);
|
||||
logger.log("Database file exists but may be corrupted. Removing it...");
|
||||
fs.unlinkSync(dbPath);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error checking database file:", error);
|
||||
logger.error("Error checking database file:", error);
|
||||
}
|
||||
|
||||
fs.mkdirSync(getUserDataPath(), { recursive: true });
|
||||
@@ -62,21 +63,15 @@ export function initializeDatabase(): BetterSQLite3Database<typeof schema> & {
|
||||
_db = drizzle(sqlite, { schema });
|
||||
|
||||
try {
|
||||
// Run migrations programmatically
|
||||
|
||||
const migrationsFolder = path.join(__dirname, "..", "..", "drizzle");
|
||||
|
||||
console.log("MIGRATIONS FOLDER INITIALIZE", migrationsFolder);
|
||||
|
||||
// Verify migrations folder exists
|
||||
if (!fs.existsSync(migrationsFolder)) {
|
||||
console.error("Migrations folder not found:", migrationsFolder);
|
||||
logger.error("Migrations folder not found:", migrationsFolder);
|
||||
} else {
|
||||
console.log("Running migrations from:", migrationsFolder);
|
||||
logger.log("Running migrations from:", migrationsFolder);
|
||||
migrate(_db, { migrationsFolder });
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Migration error:", error);
|
||||
logger.error("Migration error:", error);
|
||||
}
|
||||
|
||||
return _db as any;
|
||||
@@ -86,7 +81,7 @@ export function initializeDatabase(): BetterSQLite3Database<typeof schema> & {
|
||||
try {
|
||||
initializeDatabase();
|
||||
} 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> & {
|
||||
|
||||
@@ -36,15 +36,14 @@ import fixPath from "fix-path";
|
||||
import { getGitAuthor } from "../utils/git_author";
|
||||
import killPort from "kill-port";
|
||||
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
|
||||
// to find node/pnpm.
|
||||
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({
|
||||
appPath,
|
||||
appId,
|
||||
@@ -96,7 +95,7 @@ async function executeAppLocalNode({
|
||||
// Log output
|
||||
process.stdout?.on("data", (data) => {
|
||||
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", {
|
||||
type: "stdout",
|
||||
message,
|
||||
@@ -106,7 +105,7 @@ async function executeAppLocalNode({
|
||||
|
||||
process.stderr?.on("data", (data) => {
|
||||
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", {
|
||||
type: "stderr",
|
||||
message,
|
||||
@@ -116,7 +115,7 @@ async function executeAppLocalNode({
|
||||
|
||||
// Handle process exit/close
|
||||
process.on("close", (code, signal) => {
|
||||
console.log(
|
||||
logger.log(
|
||||
`App ${appId} (PID: ${process.pid}) process closed with code ${code}, signal ${signal}.`
|
||||
);
|
||||
removeAppIfCurrentProcess(appId, process);
|
||||
@@ -124,7 +123,7 @@ async function executeAppLocalNode({
|
||||
|
||||
// Handle errors during process lifecycle (e.g., command not found)
|
||||
process.on("error", (err) => {
|
||||
console.error(
|
||||
logger.error(
|
||||
`Error in app ${appId} (PID: ${process.pid}) process: ${err.message}`
|
||||
);
|
||||
removeAppIfCurrentProcess(appId, process);
|
||||
@@ -196,7 +195,7 @@ export function registerAppHandlers() {
|
||||
author: await getGitAuthor(),
|
||||
});
|
||||
} 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 {
|
||||
files = getFilesRecursively(appPath, appPath);
|
||||
} 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
|
||||
}
|
||||
|
||||
@@ -266,10 +265,7 @@ export function registerAppHandlers() {
|
||||
const contents = fs.readFileSync(fullPath, "utf-8");
|
||||
return contents;
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Error reading file ${filePath} for app ${appId}:`,
|
||||
error
|
||||
);
|
||||
logger.error(`Error reading file ${filePath} for app ${appId}:`, error);
|
||||
throw new Error("Failed to read file");
|
||||
}
|
||||
}
|
||||
@@ -292,7 +288,7 @@ export function registerAppHandlers() {
|
||||
return withLock(appId, async () => {
|
||||
// Check if app is already running
|
||||
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
|
||||
return { success: true, message: "App already running." };
|
||||
}
|
||||
@@ -305,7 +301,7 @@ export function registerAppHandlers() {
|
||||
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);
|
||||
try {
|
||||
@@ -313,7 +309,7 @@ export function registerAppHandlers() {
|
||||
|
||||
return { success: true, processId: currentProcessId };
|
||||
} 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
|
||||
if (
|
||||
runningApps.has(appId) &&
|
||||
@@ -328,15 +324,15 @@ export function registerAppHandlers() {
|
||||
);
|
||||
|
||||
ipcMain.handle("stop-app", async (_, { appId }: { appId: number }) => {
|
||||
console.log(
|
||||
`Attempting to stop app ${appId} (local-node only). Current running apps: ${runningApps.size}`
|
||||
logger.log(
|
||||
`Attempting to stop app ${appId}. Current running apps: ${runningApps.size}`
|
||||
);
|
||||
return withLock(appId, async () => {
|
||||
const appInfo = runningApps.get(appId);
|
||||
|
||||
if (!appInfo) {
|
||||
console.log(
|
||||
`App ${appId} not found in running apps map (local-node). Assuming already stopped.`
|
||||
logger.log(
|
||||
`App ${appId} not found in running apps map. Assuming already stopped.`
|
||||
);
|
||||
return {
|
||||
success: true,
|
||||
@@ -345,13 +341,13 @@ export function registerAppHandlers() {
|
||||
}
|
||||
|
||||
const { process, processId } = appInfo;
|
||||
console.log(
|
||||
logger.log(
|
||||
`Found running app ${appId} with processId ${processId} (PID: ${process.pid}). Attempting to stop.`
|
||||
);
|
||||
|
||||
// Check if the process is already exited or closed
|
||||
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.`
|
||||
);
|
||||
runningApps.delete(appId); // Ensure cleanup if somehow missed
|
||||
@@ -367,7 +363,7 @@ export function registerAppHandlers() {
|
||||
|
||||
return { success: true };
|
||||
} catch (error: any) {
|
||||
console.error(
|
||||
logger.error(
|
||||
`Error stopping app ${appId} (PID: ${process.pid}, processId: ${processId}):`,
|
||||
error
|
||||
);
|
||||
@@ -384,24 +380,21 @@ export function registerAppHandlers() {
|
||||
event: Electron.IpcMainInvokeEvent,
|
||||
{ appId }: { appId: number }
|
||||
) => {
|
||||
// Static server worker is NOT terminated here anymore
|
||||
|
||||
logger.log(`Restarting app ${appId}`);
|
||||
return withLock(appId, async () => {
|
||||
try {
|
||||
// First stop the app if it's running
|
||||
const appInfo = runningApps.get(appId);
|
||||
if (appInfo) {
|
||||
const { process, processId } = appInfo;
|
||||
console.log(
|
||||
`Stopping local-node app ${appId} (processId ${processId}) before restart` // Adjusted log
|
||||
logger.log(
|
||||
`Stopping app ${appId} (processId ${processId}) before restart`
|
||||
);
|
||||
|
||||
await killProcess(process);
|
||||
runningApps.delete(appId);
|
||||
} else {
|
||||
console.log(
|
||||
`App ${appId} not running in local-node mode, proceeding to start.`
|
||||
);
|
||||
logger.log(`App ${appId} not running. Proceeding to start.`);
|
||||
}
|
||||
|
||||
// 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);
|
||||
console.debug(
|
||||
logger.debug(
|
||||
`Executing app ${appId} in path ${app.path} after restart request`
|
||||
); // Adjusted log
|
||||
|
||||
@@ -425,7 +418,7 @@ export function registerAppHandlers() {
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error(`Error restarting app ${appId}:`, error);
|
||||
logger.error(`Error restarting app ${appId}:`, error);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
@@ -461,7 +454,7 @@ export function registerAppHandlers() {
|
||||
timestamp: commit.commit.author.timestamp,
|
||||
})) satisfies Version[];
|
||||
} 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}`);
|
||||
}
|
||||
});
|
||||
@@ -552,7 +545,7 @@ export function registerAppHandlers() {
|
||||
|
||||
return { success: true };
|
||||
} catch (error: any) {
|
||||
console.error(
|
||||
logger.error(
|
||||
`Error reverting to version ${previousVersionId} for app ${appId}:`,
|
||||
error
|
||||
);
|
||||
@@ -587,7 +580,7 @@ export function registerAppHandlers() {
|
||||
|
||||
return { success: true };
|
||||
} catch (error: any) {
|
||||
console.error(
|
||||
logger.error(
|
||||
`Error checking out version ${versionId} for app ${appId}:`,
|
||||
error
|
||||
);
|
||||
@@ -614,7 +607,7 @@ export function registerAppHandlers() {
|
||||
try {
|
||||
return await extractCodebase(appPath, maxFiles);
|
||||
} catch (error) {
|
||||
console.error(`Error extracting codebase for app ${appId}:`, error);
|
||||
logger.error(`Error extracting codebase for app ${appId}:`, error);
|
||||
throw new Error(
|
||||
`Failed to extract codebase: ${(error as any).message}`
|
||||
);
|
||||
@@ -673,10 +666,7 @@ export function registerAppHandlers() {
|
||||
|
||||
return { success: true };
|
||||
} catch (error: any) {
|
||||
console.error(
|
||||
`Error writing file ${filePath} for app ${appId}:`,
|
||||
error
|
||||
);
|
||||
logger.error(`Error writing file ${filePath} for app ${appId}:`, error);
|
||||
throw new Error(`Failed to write file: ${error.message}`);
|
||||
}
|
||||
}
|
||||
@@ -699,14 +689,11 @@ export function registerAppHandlers() {
|
||||
if (runningApps.has(appId)) {
|
||||
const appInfo = runningApps.get(appId)!;
|
||||
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);
|
||||
runningApps.delete(appId);
|
||||
} catch (error: any) {
|
||||
console.error(
|
||||
`Error stopping local-node app ${appId} before deletion:`,
|
||||
error
|
||||
); // Adjusted log
|
||||
logger.error(`Error stopping app ${appId} before deletion:`, error); // Adjusted log
|
||||
// Continue with deletion even if stopping fails
|
||||
}
|
||||
}
|
||||
@@ -716,7 +703,7 @@ export function registerAppHandlers() {
|
||||
try {
|
||||
await fsPromises.rm(appPath, { recursive: true, force: true });
|
||||
} 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}`);
|
||||
}
|
||||
|
||||
@@ -726,7 +713,7 @@ export function registerAppHandlers() {
|
||||
// Note: Associated chats will cascade delete if that's set up in the schema
|
||||
return { success: true };
|
||||
} 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}`);
|
||||
}
|
||||
});
|
||||
@@ -776,10 +763,7 @@ export function registerAppHandlers() {
|
||||
await killProcess(appInfo.process);
|
||||
runningApps.delete(appId);
|
||||
} catch (error: any) {
|
||||
console.error(
|
||||
`Error stopping app ${appId} before renaming:`,
|
||||
error
|
||||
);
|
||||
logger.error(`Error stopping app ${appId} before renaming:`, error);
|
||||
throw new Error(
|
||||
`Failed to stop app before renaming: ${error.message}`
|
||||
);
|
||||
@@ -807,7 +791,7 @@ export function registerAppHandlers() {
|
||||
// Move the files
|
||||
await fsPromises.rename(oldAppPath, newAppPath);
|
||||
} catch (error: any) {
|
||||
console.error(
|
||||
logger.error(
|
||||
`Error moving app files from ${oldAppPath} to ${newAppPath}:`,
|
||||
error
|
||||
);
|
||||
@@ -833,14 +817,14 @@ export function registerAppHandlers() {
|
||||
try {
|
||||
await fsPromises.rename(newAppPath, oldAppPath);
|
||||
} catch (rollbackError) {
|
||||
console.error(
|
||||
logger.error(
|
||||
`Failed to rollback file move during rename error:`,
|
||||
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}`);
|
||||
}
|
||||
});
|
||||
@@ -848,16 +832,9 @@ export function registerAppHandlers() {
|
||||
);
|
||||
|
||||
ipcMain.handle("reset-all", async () => {
|
||||
console.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;
|
||||
}
|
||||
logger.log("start: resetting all apps and settings.");
|
||||
// Stop all running apps first
|
||||
console.log("stopping all running apps...");
|
||||
logger.log("stopping all running apps...");
|
||||
const runningAppIds = Array.from(runningApps.keys());
|
||||
for (const appId of runningAppIds) {
|
||||
try {
|
||||
@@ -865,12 +842,12 @@ export function registerAppHandlers() {
|
||||
await killProcess(appInfo.process);
|
||||
runningApps.delete(appId);
|
||||
} 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
|
||||
}
|
||||
}
|
||||
console.log("all running apps stopped.");
|
||||
console.log("deleting database...");
|
||||
logger.log("all running apps stopped.");
|
||||
logger.log("deleting database...");
|
||||
// 1. Drop the database by deleting the SQLite file
|
||||
const dbPath = getDatabasePath();
|
||||
if (fs.existsSync(dbPath)) {
|
||||
@@ -879,31 +856,31 @@ export function registerAppHandlers() {
|
||||
db.$client.close();
|
||||
}
|
||||
await fsPromises.unlink(dbPath);
|
||||
console.log(`Database file deleted: ${dbPath}`);
|
||||
logger.log(`Database file deleted: ${dbPath}`);
|
||||
}
|
||||
console.log("database deleted.");
|
||||
console.log("deleting settings...");
|
||||
logger.log("database deleted.");
|
||||
logger.log("deleting settings...");
|
||||
// 2. Remove settings
|
||||
const userDataPath = getUserDataPath();
|
||||
const settingsPath = path.join(userDataPath, "user-settings.json");
|
||||
|
||||
if (fs.existsSync(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
|
||||
// Doing this last because it's the most time-consuming and the least important
|
||||
// in terms of resetting the app state.
|
||||
console.log("removing all app files...");
|
||||
logger.log("removing all app files...");
|
||||
const dyadAppPath = getDyadAppPath(".");
|
||||
if (fs.existsSync(dyadAppPath)) {
|
||||
await fsPromises.rm(dyadAppPath, { recursive: true, force: true });
|
||||
// Recreate the base directory
|
||||
await fsPromises.mkdir(dyadAppPath, { recursive: true });
|
||||
}
|
||||
console.log("all app files removed.");
|
||||
console.log("reset all complete.");
|
||||
logger.log("all app files removed.");
|
||||
logger.log("reset all complete.");
|
||||
return { success: true, message: "Successfully reset everything" };
|
||||
});
|
||||
|
||||
|
||||
@@ -12,6 +12,9 @@ import { processFullResponseActions } from "../processors/response_processor";
|
||||
import { streamTestResponse } from "./testing_chat_handlers";
|
||||
import { getTestResponse } from "./testing_chat_handlers";
|
||||
import { getModelClient } from "../utils/get_model_client";
|
||||
import log from "electron-log";
|
||||
|
||||
const logger = log.scope("chat_stream_handlers");
|
||||
|
||||
// Track active streams for cancellation
|
||||
const activeStreams = new Map<number, AbortController>();
|
||||
@@ -125,12 +128,12 @@ export function registerChatStreamHandlers() {
|
||||
const appPath = getDyadAppPath(updatedChat.app.path);
|
||||
try {
|
||||
codebaseInfo = await extractCodebase(appPath);
|
||||
console.log(`Extracted codebase information from ${appPath}`);
|
||||
logger.log(`Extracted codebase information from ${appPath}`);
|
||||
} catch (error) {
|
||||
console.error("Error extracting codebase:", error);
|
||||
logger.error("Error extracting codebase:", error);
|
||||
}
|
||||
}
|
||||
console.log(
|
||||
logger.log(
|
||||
"codebaseInfo: length",
|
||||
codebaseInfo.length,
|
||||
"estimated tokens",
|
||||
@@ -170,7 +173,7 @@ export function registerChatStreamHandlers() {
|
||||
},
|
||||
],
|
||||
onError: (error) => {
|
||||
console.error("Error streaming text:", error);
|
||||
logger.error("Error streaming text:", error);
|
||||
const message =
|
||||
(error as any)?.error?.message || JSON.stringify(error);
|
||||
event.sender.send(
|
||||
@@ -204,7 +207,7 @@ export function registerChatStreamHandlers() {
|
||||
|
||||
// If the stream was aborted, exit early
|
||||
if (abortController.signal.aborted) {
|
||||
console.log(`Stream for chat ${req.chatId} was aborted`);
|
||||
logger.log(`Stream for chat ${req.chatId} was aborted`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -222,10 +225,10 @@ export function registerChatStreamHandlers() {
|
||||
role: "assistant",
|
||||
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);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
logger.error(
|
||||
`Error saving partial response for chat ${chatId}:`,
|
||||
error
|
||||
);
|
||||
@@ -305,7 +308,7 @@ export function registerChatStreamHandlers() {
|
||||
// Return the chat ID for backwards compatibility
|
||||
return req.chatId;
|
||||
} catch (error) {
|
||||
console.error("[MAIN] API error:", error);
|
||||
logger.error("Error calling LLM:", error);
|
||||
event.sender.send(
|
||||
"chat:response:error",
|
||||
`Sorry, there was an error processing your request: ${error}`
|
||||
@@ -324,9 +327,9 @@ export function registerChatStreamHandlers() {
|
||||
// Abort the stream
|
||||
abortController.abort();
|
||||
activeStreams.delete(chatId);
|
||||
console.log(`Aborted stream for chat ${chatId}`);
|
||||
logger.log(`Aborted stream for chat ${chatId}`);
|
||||
} 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
|
||||
|
||||
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 { eq } from "drizzle-orm";
|
||||
import { GithubUser } from "../../lib/schemas";
|
||||
import log from "electron-log";
|
||||
|
||||
const logger = log.scope("github_handlers");
|
||||
|
||||
// --- GitHub Device Flow Constants ---
|
||||
// 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 };
|
||||
} catch (err) {
|
||||
console.error("[GitHub Handler] Failed to get GitHub username:", err);
|
||||
logger.error("[GitHub Handler] Failed to get GitHub username:", err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -78,15 +81,13 @@ export async function getGithubUser(): Promise<GithubUser | null> {
|
||||
|
||||
async function pollForAccessToken(event: IpcMainInvokeEvent) {
|
||||
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;
|
||||
}
|
||||
|
||||
const { deviceCode, interval } = currentFlowState;
|
||||
|
||||
console.log(
|
||||
`[GitHub Handler] Polling for token with device code: ${deviceCode}`
|
||||
);
|
||||
logger.debug("[GitHub Handler] Polling for token with device code");
|
||||
event.sender.send("github:flow-update", {
|
||||
message: "Polling GitHub for authorization...",
|
||||
});
|
||||
@@ -108,10 +109,7 @@ async function pollForAccessToken(event: IpcMainInvokeEvent) {
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok && data.access_token) {
|
||||
// --- SUCCESS ---
|
||||
console.log(
|
||||
"[GitHub Handler] Successfully obtained GitHub Access Token."
|
||||
); // TODO: Store this token securely!
|
||||
logger.log("Successfully obtained GitHub Access Token.");
|
||||
event.sender.send("github:flow-success", {
|
||||
message: "Successfully connected!",
|
||||
});
|
||||
@@ -120,13 +118,13 @@ async function pollForAccessToken(event: IpcMainInvokeEvent) {
|
||||
value: data.access_token,
|
||||
},
|
||||
});
|
||||
// TODO: Associate token with appId if provided
|
||||
|
||||
stopPolling();
|
||||
return;
|
||||
} else if (data.error) {
|
||||
switch (data.error) {
|
||||
case "authorization_pending":
|
||||
console.log("[GitHub Handler] Authorization pending...");
|
||||
logger.debug("Authorization pending...");
|
||||
event.sender.send("github:flow-update", {
|
||||
message: "Waiting for user authorization...",
|
||||
});
|
||||
@@ -138,9 +136,7 @@ async function pollForAccessToken(event: IpcMainInvokeEvent) {
|
||||
break;
|
||||
case "slow_down":
|
||||
const newInterval = interval + 5;
|
||||
console.log(
|
||||
`[GitHub Handler] Slow down requested. New interval: ${newInterval}s`
|
||||
);
|
||||
logger.debug(`Slow down requested. New interval: ${newInterval}s`);
|
||||
currentFlowState.interval = newInterval; // Update interval
|
||||
event.sender.send("github:flow-update", {
|
||||
message: `GitHub asked to slow down. Retrying in ${newInterval}s...`,
|
||||
@@ -151,24 +147,22 @@ async function pollForAccessToken(event: IpcMainInvokeEvent) {
|
||||
);
|
||||
break;
|
||||
case "expired_token":
|
||||
console.error("[GitHub Handler] Device code expired.");
|
||||
logger.error("Device code expired.");
|
||||
event.sender.send("github:flow-error", {
|
||||
error: "Verification code expired. Please try again.",
|
||||
});
|
||||
stopPolling();
|
||||
break;
|
||||
case "access_denied":
|
||||
console.error("[GitHub Handler] Access denied by user.");
|
||||
logger.error("Access denied by user.");
|
||||
event.sender.send("github:flow-error", {
|
||||
error: "Authorization denied by user.",
|
||||
});
|
||||
stopPolling();
|
||||
break;
|
||||
default:
|
||||
console.error(
|
||||
`[GitHub Handler] Unknown GitHub error: ${
|
||||
data.error_description || data.error
|
||||
}`
|
||||
logger.error(
|
||||
`Unknown GitHub error: ${data.error_description || data.error}`
|
||||
);
|
||||
event.sender.send("github:flow-error", {
|
||||
error: `GitHub authorization error: ${
|
||||
@@ -182,10 +176,7 @@ async function pollForAccessToken(event: IpcMainInvokeEvent) {
|
||||
throw new Error(`Unknown response structure: ${JSON.stringify(data)}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"[GitHub Handler] Error polling for GitHub access token:",
|
||||
error
|
||||
);
|
||||
logger.error("Error polling for GitHub access token:", error);
|
||||
event.sender.send("github:flow-error", {
|
||||
error: `Network or unexpected error during polling: ${
|
||||
error instanceof Error ? error.message : String(error)
|
||||
@@ -202,12 +193,9 @@ function stopPolling() {
|
||||
}
|
||||
currentFlowState.isPolling = false;
|
||||
currentFlowState.timeoutId = null;
|
||||
// Maybe keep window reference for a bit if needed, or clear it
|
||||
// currentFlowState.window = null;
|
||||
console.log("[GitHub Handler] Polling stopped.");
|
||||
|
||||
logger.debug("[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 ---
|
||||
@@ -216,15 +204,11 @@ function handleStartGithubFlow(
|
||||
event: IpcMainInvokeEvent,
|
||||
args: { appId: number | null }
|
||||
) {
|
||||
console.log(
|
||||
`[GitHub Handler] Received github:start-flow for appId: ${args.appId}`
|
||||
);
|
||||
logger.debug(`Received github:start-flow for appId: ${args.appId}`);
|
||||
|
||||
// If a flow is already in progress, maybe cancel it or send an error
|
||||
if (currentFlowState && currentFlowState.isPolling) {
|
||||
console.warn(
|
||||
"[GitHub Handler] Another GitHub flow is already in progress."
|
||||
);
|
||||
logger.warn("Another GitHub flow is already in progress.");
|
||||
event.sender.send("github:flow-error", {
|
||||
error: "Another connection process is already active.",
|
||||
});
|
||||
@@ -234,7 +218,7 @@ function handleStartGithubFlow(
|
||||
// Store the window that initiated the request
|
||||
const window = BrowserWindow.fromWebContents(event.sender);
|
||||
if (!window) {
|
||||
console.error("[GitHub Handler] Could not get BrowserWindow instance.");
|
||||
logger.error("Could not get BrowserWindow instance.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -272,7 +256,7 @@ function handleStartGithubFlow(
|
||||
return res.json();
|
||||
})
|
||||
.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
|
||||
|
||||
currentFlowState.deviceCode = data.device_code;
|
||||
@@ -293,10 +277,7 @@ function handleStartGithubFlow(
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(
|
||||
"[GitHub Handler] Error initiating GitHub device flow:",
|
||||
error
|
||||
);
|
||||
logger.error("Error initiating GitHub device flow:", error);
|
||||
event.sender.send("github:flow-error", {
|
||||
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 ---
|
||||
async function handleIsRepoAvailable(
|
||||
event: IpcMainInvokeEvent,
|
||||
@@ -453,7 +425,6 @@ async function handlePushToGithub(
|
||||
// --- Registration ---
|
||||
export function registerGithubHandlers() {
|
||||
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:create-repo", handleCreateRepo);
|
||||
ipcMain.handle("github:push", handlePushToGithub);
|
||||
|
||||
@@ -1,51 +1,28 @@
|
||||
import { ipcMain, app } from "electron";
|
||||
import { exec, execSync, spawn } from "child_process";
|
||||
import { exec, execSync } from "child_process";
|
||||
import { platform, arch } from "os";
|
||||
import { NodeSystemInfo } from "../ipc_types";
|
||||
import fixPath from "fix-path";
|
||||
import { runShellCommand } from "../utils/runShellCommand";
|
||||
import log from "electron-log";
|
||||
|
||||
function checkCommandExists(command: string): Promise<string | null> {
|
||||
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
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
const logger = log.scope("node_handlers");
|
||||
|
||||
export function registerNodeHandlers() {
|
||||
ipcMain.handle("nodejs-status", async (): Promise<NodeSystemInfo> => {
|
||||
logger.log(
|
||||
"handling ipc: nodejs-status for platform:",
|
||||
platform(),
|
||||
"and arch:",
|
||||
arch()
|
||||
);
|
||||
// Run checks in parallel
|
||||
const [nodeVersion, pnpmVersion] = await Promise.all([
|
||||
checkCommandExists("node --version"),
|
||||
runShellCommand("node --version"),
|
||||
// First, check if pnpm is installed.
|
||||
// If not, try to install it using corepack.
|
||||
// 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)"
|
||||
),
|
||||
]);
|
||||
@@ -66,7 +43,7 @@ export function registerNodeHandlers() {
|
||||
});
|
||||
|
||||
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") {
|
||||
const newPath = execSync("cmd /c echo %PATH%", {
|
||||
encoding: "utf8",
|
||||
@@ -75,6 +52,6 @@ export function registerNodeHandlers() {
|
||||
} else {
|
||||
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,
|
||||
processFullResponseActions,
|
||||
} 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)
|
||||
// const placeholderProposal: Proposal = { ... };
|
||||
@@ -33,7 +36,7 @@ const getProposalHandler = async (
|
||||
_event: IpcMainInvokeEvent,
|
||||
{ chatId }: { chatId: number }
|
||||
): Promise<ProposalResult | null> => {
|
||||
console.log(`IPC: get-proposal called for chatId: ${chatId}`);
|
||||
logger.log(`IPC: get-proposal called for chatId: ${chatId}`);
|
||||
|
||||
try {
|
||||
// Find the latest ASSISTANT message for the chat
|
||||
@@ -56,7 +59,7 @@ const getProposalHandler = async (
|
||||
|
||||
if (latestAssistantMessage?.content && latestAssistantMessage.id) {
|
||||
const messageId = latestAssistantMessage.id; // Get the message ID
|
||||
console.log(
|
||||
logger.log(
|
||||
`Found latest assistant message (ID: ${messageId}), parsing content...`
|
||||
);
|
||||
const messageContent = latestAssistantMessage.content;
|
||||
@@ -78,20 +81,25 @@ const getProposalHandler = async (
|
||||
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
|
||||
} else {
|
||||
console.log(
|
||||
logger.log(
|
||||
"No relevant tags found in the latest assistant message content."
|
||||
);
|
||||
return null; // No proposal could be generated
|
||||
}
|
||||
} else {
|
||||
console.log(`No assistant message found for chatId: ${chatId}`);
|
||||
logger.log(`No assistant message found for chatId: ${chatId}`);
|
||||
return null; // No message found
|
||||
}
|
||||
} 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
|
||||
}
|
||||
};
|
||||
@@ -101,7 +109,7 @@ const approveProposalHandler = async (
|
||||
_event: IpcMainInvokeEvent,
|
||||
{ chatId, messageId }: { chatId: number; messageId: number }
|
||||
): Promise<{ success: boolean; error?: string }> => {
|
||||
console.log(
|
||||
logger.log(
|
||||
`IPC: approve-proposal called for chatId: ${chatId}, messageId: ${messageId}`
|
||||
);
|
||||
|
||||
@@ -119,7 +127,7 @@ const approveProposalHandler = async (
|
||||
});
|
||||
|
||||
if (!messageToApprove?.content) {
|
||||
console.error(
|
||||
logger.error(
|
||||
`Assistant message not found for chatId: ${chatId}, messageId: ${messageId}`
|
||||
);
|
||||
return { success: false, error: "Assistant message not found." };
|
||||
@@ -137,7 +145,7 @@ const approveProposalHandler = async (
|
||||
);
|
||||
|
||||
if (processResult.error) {
|
||||
console.error(
|
||||
logger.error(
|
||||
`Error processing actions for message ${messageId}:`,
|
||||
processResult.error
|
||||
);
|
||||
@@ -151,10 +159,7 @@ const approveProposalHandler = async (
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Error approving proposal for messageId ${messageId}:`,
|
||||
error
|
||||
);
|
||||
logger.error(`Error approving proposal for messageId ${messageId}:`, error);
|
||||
return {
|
||||
success: false,
|
||||
error: (error as Error)?.message || "Unknown error",
|
||||
@@ -167,7 +172,7 @@ const rejectProposalHandler = async (
|
||||
_event: IpcMainInvokeEvent,
|
||||
{ chatId, messageId }: { chatId: number; messageId: number }
|
||||
): Promise<{ success: boolean; error?: string }> => {
|
||||
console.log(
|
||||
logger.log(
|
||||
`IPC: reject-proposal called for chatId: ${chatId}, messageId: ${messageId}`
|
||||
);
|
||||
|
||||
@@ -183,7 +188,7 @@ const rejectProposalHandler = async (
|
||||
});
|
||||
|
||||
if (!messageToReject) {
|
||||
console.error(
|
||||
logger.error(
|
||||
`Assistant message not found for chatId: ${chatId}, messageId: ${messageId}`
|
||||
);
|
||||
return { success: false, error: "Assistant message not found." };
|
||||
@@ -195,13 +200,10 @@ const rejectProposalHandler = async (
|
||||
.set({ approvalState: "rejected" })
|
||||
.where(eq(messages.id, messageId));
|
||||
|
||||
console.log(`Message ${messageId} marked as rejected.`);
|
||||
logger.log(`Message ${messageId} marked as rejected.`);
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error(
|
||||
`Error rejecting proposal for messageId ${messageId}:`,
|
||||
error
|
||||
);
|
||||
logger.error(`Error rejecting proposal for messageId ${messageId}:`, error);
|
||||
return {
|
||||
success: false,
|
||||
error: (error as Error)?.message || "Unknown error",
|
||||
@@ -214,5 +216,4 @@ export function registerProposalHandlers() {
|
||||
ipcMain.handle("get-proposal", getProposalHandler);
|
||||
ipcMain.handle("approve-proposal", approveProposalHandler);
|
||||
ipcMain.handle("reject-proposal", rejectProposalHandler);
|
||||
console.log("Registered proposal IPC handlers (get, approve, reject)");
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { ipcMain, shell } from "electron";
|
||||
import log from "electron-log";
|
||||
|
||||
const logger = log.scope("shell_handlers");
|
||||
|
||||
export function registerShellHandlers() {
|
||||
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
|
||||
if (url && (url.startsWith("http://") || url.startsWith("https://"))) {
|
||||
await shell.openExternal(url);
|
||||
logger.debug("Opened external URL:", url);
|
||||
return { success: true };
|
||||
}
|
||||
console.error("Attempted to open invalid or non-http URL:", url);
|
||||
return {
|
||||
success: false,
|
||||
error: "Invalid URL provided. Only http/https URLs are allowed.",
|
||||
};
|
||||
logger.error("Attempted to open invalid or non-http URL:", url);
|
||||
return {
|
||||
success: false,
|
||||
error: "Invalid URL provided. Only http/https URLs are allowed.",
|
||||
};
|
||||
} 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 };
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
Message,
|
||||
Version,
|
||||
SystemDebugInfo,
|
||||
} from "./ipc_types";
|
||||
import type { CodeProposal, ProposalResult } from "@/lib/schemas";
|
||||
import { showError } from "@/lib/toast";
|
||||
@@ -670,4 +671,15 @@ export class IpcClient {
|
||||
}
|
||||
}
|
||||
// --- 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 { registerNodeHandlers } from "./handlers/node_handlers";
|
||||
import { registerProposalHandlers } from "./handlers/proposal_handlers";
|
||||
import { registerDebugHandlers } from "./handlers/debug_handlers";
|
||||
|
||||
export function registerIpcHandlers() {
|
||||
// Register all IPC handlers by category
|
||||
@@ -19,4 +20,5 @@ export function registerIpcHandlers() {
|
||||
registerGithubHandlers();
|
||||
registerNodeHandlers();
|
||||
registerProposalHandlers();
|
||||
registerDebugHandlers();
|
||||
}
|
||||
|
||||
@@ -76,3 +76,16 @@ export interface NodeSystemInfo {
|
||||
pnpmVersion: string | null;
|
||||
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 log from "electron-log";
|
||||
|
||||
console.log = log.log;
|
||||
console.error = log.error;
|
||||
console.warn = log.warn;
|
||||
console.info = log.info;
|
||||
console.debug = log.debug;
|
||||
log.errorHandler.startCatching();
|
||||
log.eventLogger.startLogging();
|
||||
log.log("HELLO WORLD");
|
||||
|
||||
updateElectronApp(); // additional configuration options available
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ const validInvokeChannels = [
|
||||
"get-proposal",
|
||||
"approve-proposal",
|
||||
"reject-proposal",
|
||||
"get-system-debug-info",
|
||||
] as const;
|
||||
|
||||
// Add valid receive channels
|
||||
|
||||
Reference in New Issue
Block a user