Simplify handlers & IPC client: move from Result pattern to throwing errors (#120)
This commit is contained in:
@@ -92,11 +92,7 @@ export function ChatList({ show }: { show?: boolean }) {
|
|||||||
|
|
||||||
const handleDeleteChat = async (chatId: number) => {
|
const handleDeleteChat = async (chatId: number) => {
|
||||||
try {
|
try {
|
||||||
const result = await IpcClient.getInstance().deleteChat(chatId);
|
await IpcClient.getInstance().deleteChat(chatId);
|
||||||
if (!result.success) {
|
|
||||||
showError("Failed to delete chat");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
showSuccess("Chat deleted successfully");
|
showSuccess("Chat deleted successfully");
|
||||||
|
|
||||||
// If the deleted chat was selected, navigate to home
|
// If the deleted chat was selected, navigate to home
|
||||||
|
|||||||
@@ -153,18 +153,14 @@ export function GitHubConnector({ appId, folderName }: GitHubConnectorProps) {
|
|||||||
setIsCreatingRepo(true);
|
setIsCreatingRepo(true);
|
||||||
setCreateRepoSuccess(false);
|
setCreateRepoSuccess(false);
|
||||||
try {
|
try {
|
||||||
const result = await IpcClient.getInstance().createGithubRepo(
|
await IpcClient.getInstance().createGithubRepo(
|
||||||
githubOrg,
|
githubOrg,
|
||||||
repoName,
|
repoName,
|
||||||
appId!,
|
appId!,
|
||||||
);
|
);
|
||||||
if (result.success) {
|
|
||||||
setCreateRepoSuccess(true);
|
setCreateRepoSuccess(true);
|
||||||
setRepoCheckError(null);
|
setRepoCheckError(null);
|
||||||
refreshApp();
|
refreshApp();
|
||||||
} else {
|
|
||||||
setCreateRepoError(result.error || "Failed to create repository.");
|
|
||||||
}
|
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
setCreateRepoError(err.message || "Failed to create repository.");
|
setCreateRepoError(err.message || "Failed to create repository.");
|
||||||
} finally {
|
} finally {
|
||||||
@@ -180,12 +176,8 @@ export function GitHubConnector({ appId, folderName }: GitHubConnectorProps) {
|
|||||||
setIsDisconnecting(true);
|
setIsDisconnecting(true);
|
||||||
setDisconnectError(null);
|
setDisconnectError(null);
|
||||||
try {
|
try {
|
||||||
const result = await IpcClient.getInstance().disconnectGithubRepo(appId);
|
await IpcClient.getInstance().disconnectGithubRepo(appId);
|
||||||
if (result.success) {
|
|
||||||
refreshApp();
|
refreshApp();
|
||||||
} else {
|
|
||||||
setDisconnectError(result.error || "Failed to disconnect repository.");
|
|
||||||
}
|
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
setDisconnectError(err.message || "Failed to disconnect repository.");
|
setDisconnectError(err.message || "Failed to disconnect repository.");
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -171,17 +171,12 @@ ${debugInfo.logs.slice(-3_500) || "No logs available"}
|
|||||||
|
|
||||||
const { uploadUrl, filename } = await response.json();
|
const { uploadUrl, filename } = await response.json();
|
||||||
|
|
||||||
// Upload to the signed URL using IPC
|
await IpcClient.getInstance().uploadToSignedUrl(
|
||||||
const uploadResult = await IpcClient.getInstance().uploadToSignedUrl(
|
|
||||||
uploadUrl,
|
uploadUrl,
|
||||||
"application/json",
|
"application/json",
|
||||||
chatLogsJson,
|
chatLogsJson,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!uploadResult.success) {
|
|
||||||
throw new Error(`Failed to upload logs: ${uploadResult.error}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract session ID (filename without extension)
|
// Extract session ID (filename without extension)
|
||||||
const sessionId = filename.replace(".json", "");
|
const sessionId = filename.replace(".json", "");
|
||||||
setSessionId(sessionId);
|
setSessionId(sessionId);
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ import { useVersions } from "@/hooks/useVersions";
|
|||||||
import { useAttachments } from "@/hooks/useAttachments";
|
import { useAttachments } from "@/hooks/useAttachments";
|
||||||
import { AttachmentsList } from "./AttachmentsList";
|
import { AttachmentsList } from "./AttachmentsList";
|
||||||
import { DragDropOverlay } from "./DragDropOverlay";
|
import { DragDropOverlay } from "./DragDropOverlay";
|
||||||
import { showUncommittedFilesWarning } from "@/lib/toast";
|
import { showError, showUncommittedFilesWarning } from "@/lib/toast";
|
||||||
const showTokenBarAtom = atom(false);
|
const showTokenBarAtom = atom(false);
|
||||||
|
|
||||||
export function ChatInput({ chatId }: { chatId?: number }) {
|
export function ChatInput({ chatId }: { chatId?: number }) {
|
||||||
@@ -182,13 +182,6 @@ export function ChatInput({ chatId }: { chatId?: number }) {
|
|||||||
chatId,
|
chatId,
|
||||||
messageId,
|
messageId,
|
||||||
});
|
});
|
||||||
if (result.success) {
|
|
||||||
console.log("Proposal approved successfully");
|
|
||||||
// TODO: Maybe refresh proposal state or show confirmation?
|
|
||||||
} else {
|
|
||||||
console.error("Failed to approve proposal:", result.error);
|
|
||||||
setError(result.error || "Failed to approve proposal");
|
|
||||||
}
|
|
||||||
if (result.uncommittedFiles) {
|
if (result.uncommittedFiles) {
|
||||||
showUncommittedFilesWarning(result.uncommittedFiles);
|
showUncommittedFilesWarning(result.uncommittedFiles);
|
||||||
}
|
}
|
||||||
@@ -215,17 +208,10 @@ export function ChatInput({ chatId }: { chatId?: number }) {
|
|||||||
setIsRejecting(true);
|
setIsRejecting(true);
|
||||||
posthog.capture("chat:reject");
|
posthog.capture("chat:reject");
|
||||||
try {
|
try {
|
||||||
const result = await IpcClient.getInstance().rejectProposal({
|
await IpcClient.getInstance().rejectProposal({
|
||||||
chatId,
|
chatId,
|
||||||
messageId,
|
messageId,
|
||||||
});
|
});
|
||||||
if (result.success) {
|
|
||||||
console.log("Proposal rejected successfully");
|
|
||||||
// TODO: Maybe refresh proposal state or show confirmation?
|
|
||||||
} else {
|
|
||||||
console.error("Failed to reject proposal:", result.error);
|
|
||||||
setError(result.error || "Failed to reject proposal");
|
|
||||||
}
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Error rejecting proposal:", err);
|
console.error("Error rejecting proposal:", err);
|
||||||
setError((err as Error)?.message || "An error occurred while rejecting");
|
setError((err as Error)?.message || "An error occurred while rejecting");
|
||||||
@@ -389,6 +375,7 @@ function SummarizeInNewChatButton() {
|
|||||||
console.error("No app id found");
|
console.error("No app id found");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
try {
|
||||||
const newChatId = await IpcClient.getInstance().createChat(appId);
|
const newChatId = await IpcClient.getInstance().createChat(appId);
|
||||||
// navigate to new chat
|
// navigate to new chat
|
||||||
await navigate({ to: "/chat", search: { id: newChatId } });
|
await navigate({ to: "/chat", search: { id: newChatId } });
|
||||||
@@ -396,6 +383,9 @@ function SummarizeInNewChatButton() {
|
|||||||
prompt: "Summarize from chat-id=" + chatId,
|
prompt: "Summarize from chat-id=" + chatId,
|
||||||
chatId: newChatId,
|
chatId: newChatId,
|
||||||
});
|
});
|
||||||
|
} catch (err) {
|
||||||
|
showError(err);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
|
|||||||
@@ -89,14 +89,13 @@ export const MessagesList = forwardRef<HTMLDivElement, MessagesListProps>(
|
|||||||
await revertVersion({
|
await revertVersion({
|
||||||
versionId: chat.initialCommitHash,
|
versionId: chat.initialCommitHash,
|
||||||
});
|
});
|
||||||
const result =
|
try {
|
||||||
await IpcClient.getInstance().deleteMessages(
|
await IpcClient.getInstance().deleteMessages(
|
||||||
selectedChatId,
|
selectedChatId,
|
||||||
);
|
);
|
||||||
if (result.success) {
|
|
||||||
setMessages([]);
|
setMessages([]);
|
||||||
} else {
|
} catch (err) {
|
||||||
showError(result.error);
|
showError(err);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
showWarning(
|
showWarning(
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { ChevronRight, Circle } from "lucide-react";
|
|||||||
import "@/components/chat/monaco";
|
import "@/components/chat/monaco";
|
||||||
import { IpcClient } from "@/ipc/ipc_client";
|
import { IpcClient } from "@/ipc/ipc_client";
|
||||||
import { useSettings } from "@/hooks/useSettings";
|
import { useSettings } from "@/hooks/useSettings";
|
||||||
|
import { showError } from "@/lib/toast";
|
||||||
|
|
||||||
interface FileEditorProps {
|
interface FileEditorProps {
|
||||||
appId: number | null;
|
appId: number | null;
|
||||||
@@ -132,8 +133,7 @@ export const FileEditor = ({ appId, filePath }: FileEditorProps) => {
|
|||||||
needsSaveRef.current = false;
|
needsSaveRef.current = false;
|
||||||
setDisplayUnsavedChanges(false);
|
setDisplayUnsavedChanges(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error saving file:", error);
|
showError(error);
|
||||||
// Could add error notification here
|
|
||||||
} finally {
|
} finally {
|
||||||
isSavingRef.current = false;
|
isSavingRef.current = false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,14 +42,8 @@ export function useSupabase() {
|
|||||||
async (projectId: string, appId: number) => {
|
async (projectId: string, appId: number) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const result = await ipcClient.setSupabaseAppProject(projectId, appId);
|
await ipcClient.setSupabaseAppProject(projectId, appId);
|
||||||
|
|
||||||
if (result.success) {
|
|
||||||
setError(null);
|
setError(null);
|
||||||
return result;
|
|
||||||
} else {
|
|
||||||
throw new Error("Failed to set project for app");
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error setting Supabase project for app:", error);
|
console.error("Error setting Supabase project for app:", error);
|
||||||
setError(error instanceof Error ? error : new Error(String(error)));
|
setError(error instanceof Error ? error : new Error(String(error)));
|
||||||
@@ -68,14 +62,8 @@ export function useSupabase() {
|
|||||||
async (appId: number) => {
|
async (appId: number) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
const result = await ipcClient.unsetSupabaseAppProject(appId);
|
await ipcClient.unsetSupabaseAppProject(appId);
|
||||||
|
|
||||||
if (result.success) {
|
|
||||||
setError(null);
|
setError(null);
|
||||||
return result;
|
|
||||||
} else {
|
|
||||||
throw new Error("Failed to unset project for app");
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error unsetting Supabase project for app:", error);
|
console.error("Error unsetting Supabase project for app:", error);
|
||||||
setError(error instanceof Error ? error : new Error(String(error)));
|
setError(error instanceof Error ? error : new Error(String(error)));
|
||||||
|
|||||||
@@ -32,8 +32,10 @@ import killPort from "kill-port";
|
|||||||
import util from "util";
|
import util from "util";
|
||||||
import log from "electron-log";
|
import log from "electron-log";
|
||||||
import { getSupabaseProjectName } from "../../supabase_admin/supabase_management_client";
|
import { getSupabaseProjectName } from "../../supabase_admin/supabase_management_client";
|
||||||
|
import { createLoggedHandler } from "./safe_handle";
|
||||||
|
|
||||||
const logger = log.scope("app_handlers");
|
const logger = log.scope("app_handlers");
|
||||||
|
const handle = createLoggedHandler(logger);
|
||||||
|
|
||||||
// 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.
|
||||||
@@ -137,7 +139,12 @@ async function killProcessOnPort(port: number): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function registerAppHandlers() {
|
export function registerAppHandlers() {
|
||||||
ipcMain.handle("create-app", async (_, params: CreateAppParams) => {
|
handle(
|
||||||
|
"create-app",
|
||||||
|
async (
|
||||||
|
_,
|
||||||
|
params: CreateAppParams,
|
||||||
|
): Promise<{ app: any; chatId: number }> => {
|
||||||
const appPath = params.name;
|
const appPath = params.name;
|
||||||
const fullAppPath = getDyadAppPath(appPath);
|
const fullAppPath = getDyadAppPath(appPath);
|
||||||
if (fs.existsSync(fullAppPath)) {
|
if (fs.existsSync(fullAppPath)) {
|
||||||
@@ -200,12 +207,12 @@ export function registerAppHandlers() {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("Error in background app initialization:", error);
|
logger.error("Error in background app initialization:", error);
|
||||||
}
|
}
|
||||||
// })();
|
|
||||||
|
|
||||||
return { app, chatId: chat.id };
|
return { app, chatId: chat.id };
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
ipcMain.handle("get-app", async (_, appId: number): Promise<App> => {
|
handle("get-app", async (_, appId: number): Promise<App> => {
|
||||||
const app = await db.query.apps.findFirst({
|
const app = await db.query.apps.findFirst({
|
||||||
where: eq(apps.id, appId),
|
where: eq(apps.id, appId),
|
||||||
});
|
});
|
||||||
@@ -281,6 +288,7 @@ export function registerAppHandlers() {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Do NOT use handle for this, it contains sensitive information.
|
||||||
ipcMain.handle("get-env-vars", async () => {
|
ipcMain.handle("get-env-vars", async () => {
|
||||||
const envVars: Record<string, string | undefined> = {};
|
const envVars: Record<string, string | undefined> = {};
|
||||||
for (const key of ALLOWED_ENV_VARS) {
|
for (const key of ALLOWED_ENV_VARS) {
|
||||||
@@ -294,13 +302,12 @@ export function registerAppHandlers() {
|
|||||||
async (
|
async (
|
||||||
event: Electron.IpcMainInvokeEvent,
|
event: Electron.IpcMainInvokeEvent,
|
||||||
{ appId }: { appId: number },
|
{ appId }: { appId: number },
|
||||||
) => {
|
): Promise<void> => {
|
||||||
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)) {
|
||||||
logger.debug(`App ${appId} is already running.`);
|
logger.debug(`App ${appId} is already running.`);
|
||||||
// Potentially return the existing process info or confirm status
|
return;
|
||||||
return { success: true, message: "App already running." };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const app = await db.query.apps.findFirst({
|
const app = await db.query.apps.findFirst({
|
||||||
@@ -315,9 +322,9 @@ export function registerAppHandlers() {
|
|||||||
|
|
||||||
const appPath = getDyadAppPath(app.path);
|
const appPath = getDyadAppPath(app.path);
|
||||||
try {
|
try {
|
||||||
const currentProcessId = await executeApp({ appPath, appId, event });
|
await executeApp({ appPath, appId, event });
|
||||||
|
|
||||||
return { success: true, processId: currentProcessId };
|
return;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
logger.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
|
||||||
@@ -333,7 +340,9 @@ export function registerAppHandlers() {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
ipcMain.handle("stop-app", async (_, { appId }: { appId: number }) => {
|
ipcMain.handle(
|
||||||
|
"stop-app",
|
||||||
|
async (_, { appId }: { appId: number }): Promise<void> => {
|
||||||
logger.log(
|
logger.log(
|
||||||
`Attempting to stop app ${appId}. Current running apps: ${runningApps.size}`,
|
`Attempting to stop app ${appId}. Current running apps: ${runningApps.size}`,
|
||||||
);
|
);
|
||||||
@@ -344,10 +353,7 @@ export function registerAppHandlers() {
|
|||||||
logger.log(
|
logger.log(
|
||||||
`App ${appId} not found in running apps map. Assuming already stopped.`,
|
`App ${appId} not found in running apps map. Assuming already stopped.`,
|
||||||
);
|
);
|
||||||
return {
|
return;
|
||||||
success: true,
|
|
||||||
message: "App not running.",
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { process, processId } = appInfo;
|
const { process, processId } = appInfo;
|
||||||
@@ -361,7 +367,7 @@ export function registerAppHandlers() {
|
|||||||
`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
|
||||||
return { success: true, message: "Process already exited." };
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -371,7 +377,7 @@ export function registerAppHandlers() {
|
|||||||
// Now, safely remove the app from the map *after* confirming closure
|
// Now, safely remove the app from the map *after* confirming closure
|
||||||
removeAppIfCurrentProcess(appId, process);
|
removeAppIfCurrentProcess(appId, process);
|
||||||
|
|
||||||
return { success: true };
|
return;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
logger.error(
|
logger.error(
|
||||||
`Error stopping app ${appId} (PID: ${process.pid}, processId: ${processId}):`,
|
`Error stopping app ${appId} (PID: ${process.pid}, processId: ${processId}):`,
|
||||||
@@ -382,7 +388,8 @@ export function registerAppHandlers() {
|
|||||||
throw new Error(`Failed to stop app ${appId}: ${error.message}`);
|
throw new Error(`Failed to stop app ${appId}: ${error.message}`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
ipcMain.handle(
|
ipcMain.handle(
|
||||||
"restart-app",
|
"restart-app",
|
||||||
@@ -392,7 +399,7 @@ export function registerAppHandlers() {
|
|||||||
appId,
|
appId,
|
||||||
removeNodeModules,
|
removeNodeModules,
|
||||||
}: { appId: number; removeNodeModules?: boolean },
|
}: { appId: number; removeNodeModules?: boolean },
|
||||||
) => {
|
): Promise<void> => {
|
||||||
logger.log(`Restarting app ${appId}`);
|
logger.log(`Restarting app ${appId}`);
|
||||||
return withLock(appId, async () => {
|
return withLock(appId, async () => {
|
||||||
try {
|
try {
|
||||||
@@ -447,7 +454,7 @@ export function registerAppHandlers() {
|
|||||||
|
|
||||||
await executeApp({ appPath, appId, event }); // This will handle starting either mode
|
await executeApp({ appPath, appId, event }); // This will handle starting either mode
|
||||||
|
|
||||||
return { success: true };
|
return;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(`Error restarting app ${appId}:`, error);
|
logger.error(`Error restarting app ${appId}:`, error);
|
||||||
throw error;
|
throw error;
|
||||||
@@ -465,7 +472,7 @@ export function registerAppHandlers() {
|
|||||||
filePath,
|
filePath,
|
||||||
content,
|
content,
|
||||||
}: { appId: number; filePath: string; content: string },
|
}: { appId: number; filePath: string; content: string },
|
||||||
) => {
|
): Promise<void> => {
|
||||||
const app = await db.query.apps.findFirst({
|
const app = await db.query.apps.findFirst({
|
||||||
where: eq(apps.id, appId),
|
where: eq(apps.id, appId),
|
||||||
});
|
});
|
||||||
@@ -505,7 +512,7 @@ export function registerAppHandlers() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return { success: true };
|
return;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
logger.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}`);
|
throw new Error(`Failed to write file: ${error.message}`);
|
||||||
@@ -513,7 +520,9 @@ export function registerAppHandlers() {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
ipcMain.handle("delete-app", async (_, { appId }: { appId: number }) => {
|
ipcMain.handle(
|
||||||
|
"delete-app",
|
||||||
|
async (_, { appId }: { appId: number }): Promise<void> => {
|
||||||
// Static server worker is NOT terminated here anymore
|
// Static server worker is NOT terminated here anymore
|
||||||
|
|
||||||
return withLock(appId, async () => {
|
return withLock(appId, async () => {
|
||||||
@@ -552,13 +561,16 @@ export function registerAppHandlers() {
|
|||||||
try {
|
try {
|
||||||
await db.delete(apps).where(eq(apps.id, appId));
|
await db.delete(apps).where(eq(apps.id, appId));
|
||||||
// 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;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
logger.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}`,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
|
||||||
ipcMain.handle(
|
ipcMain.handle(
|
||||||
"rename-app",
|
"rename-app",
|
||||||
@@ -569,7 +581,7 @@ export function registerAppHandlers() {
|
|||||||
appName,
|
appName,
|
||||||
appPath,
|
appPath,
|
||||||
}: { appId: number; appName: string; appPath: string },
|
}: { appId: number; appName: string; appPath: string },
|
||||||
) => {
|
): Promise<void> => {
|
||||||
return withLock(appId, async () => {
|
return withLock(appId, async () => {
|
||||||
// Check if app exists
|
// Check if app exists
|
||||||
const app = await db.query.apps.findFirst({
|
const app = await db.query.apps.findFirst({
|
||||||
@@ -642,7 +654,7 @@ export function registerAppHandlers() {
|
|||||||
|
|
||||||
// Update app in database
|
// Update app in database
|
||||||
try {
|
try {
|
||||||
const [updatedApp] = await db
|
await db
|
||||||
.update(apps)
|
.update(apps)
|
||||||
.set({
|
.set({
|
||||||
name: appName,
|
name: appName,
|
||||||
@@ -651,7 +663,7 @@ export function registerAppHandlers() {
|
|||||||
.where(eq(apps.id, appId))
|
.where(eq(apps.id, appId))
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
return { success: true, app: updatedApp };
|
return;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
// Attempt to rollback the file move
|
// Attempt to rollback the file move
|
||||||
if (newAppPath !== oldAppPath) {
|
if (newAppPath !== oldAppPath) {
|
||||||
@@ -672,7 +684,7 @@ export function registerAppHandlers() {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
ipcMain.handle("reset-all", async () => {
|
ipcMain.handle("reset-all", async (): Promise<void> => {
|
||||||
logger.log("start: resetting all apps and settings.");
|
logger.log("start: resetting all apps and settings.");
|
||||||
// Stop all running apps first
|
// Stop all running apps first
|
||||||
logger.log("stopping all running apps...");
|
logger.log("stopping all running apps...");
|
||||||
@@ -722,10 +734,9 @@ export function registerAppHandlers() {
|
|||||||
}
|
}
|
||||||
logger.log("all app files removed.");
|
logger.log("all app files removed.");
|
||||||
logger.log("reset all complete.");
|
logger.log("reset all complete.");
|
||||||
return { success: true, message: "Successfully reset everything" };
|
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle("get-app-version", async () => {
|
ipcMain.handle("get-app-version", async (): Promise<{ version: string }> => {
|
||||||
// Read version from package.json at project root
|
// Read version from package.json at project root
|
||||||
const packageJsonPath = path.resolve(__dirname, "..", "..", "package.json");
|
const packageJsonPath = path.resolve(__dirname, "..", "..", "package.json");
|
||||||
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
||||||
|
|||||||
@@ -5,14 +5,16 @@ import { desc, eq } from "drizzle-orm";
|
|||||||
import type { ChatSummary } from "../../lib/schemas";
|
import type { ChatSummary } from "../../lib/schemas";
|
||||||
import * as git from "isomorphic-git";
|
import * as git from "isomorphic-git";
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
|
import { createLoggedHandler } from "./safe_handle";
|
||||||
|
|
||||||
import log from "electron-log";
|
import log from "electron-log";
|
||||||
import { getDyadAppPath } from "../../paths/paths";
|
import { getDyadAppPath } from "../../paths/paths";
|
||||||
|
|
||||||
const logger = log.scope("chat_handlers");
|
const logger = log.scope("chat_handlers");
|
||||||
|
const handle = createLoggedHandler(logger);
|
||||||
|
|
||||||
export function registerChatHandlers() {
|
export function registerChatHandlers() {
|
||||||
ipcMain.handle("create-chat", async (_, appId: number) => {
|
handle("create-chat", async (_, appId: number): Promise<number> => {
|
||||||
// Get the app's path first
|
// Get the app's path first
|
||||||
const app = await db.query.apps.findFirst({
|
const app = await db.query.apps.findFirst({
|
||||||
where: eq(apps.id, appId),
|
where: eq(apps.id, appId),
|
||||||
@@ -74,9 +76,7 @@ export function registerChatHandlers() {
|
|||||||
return chat;
|
return chat;
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle(
|
handle("get-chats", async (_, appId?: number): Promise<ChatSummary[]> => {
|
||||||
"get-chats",
|
|
||||||
async (_, appId?: number): Promise<ChatSummary[]> => {
|
|
||||||
// If appId is provided, filter chats for that app
|
// If appId is provided, filter chats for that app
|
||||||
const query = appId
|
const query = appId
|
||||||
? db.query.chats.findMany({
|
? db.query.chats.findMany({
|
||||||
@@ -101,27 +101,13 @@ export function registerChatHandlers() {
|
|||||||
|
|
||||||
const allChats = await query;
|
const allChats = await query;
|
||||||
return allChats;
|
return allChats;
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
ipcMain.handle("delete-chat", async (_, chatId: number) => {
|
|
||||||
try {
|
|
||||||
// Delete the chat and its associated messages
|
|
||||||
await db.delete(chats).where(eq(chats.id, chatId));
|
|
||||||
return { success: true };
|
|
||||||
} catch (error) {
|
|
||||||
logger.error("Error deleting chat:", error);
|
|
||||||
return { success: false, error: (error as Error).message };
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle("delete-messages", async (_, chatId: number) => {
|
handle("delete-chat", async (_, chatId: number): Promise<void> => {
|
||||||
try {
|
await db.delete(chats).where(eq(chats.id, chatId));
|
||||||
|
});
|
||||||
|
|
||||||
|
handle("delete-messages", async (_, chatId: number): Promise<void> => {
|
||||||
await db.delete(messages).where(eq(messages.chatId, chatId));
|
await db.delete(messages).where(eq(messages.chatId, chatId));
|
||||||
return { success: true };
|
|
||||||
} catch (error) {
|
|
||||||
logger.error("Error deleting messages:", error);
|
|
||||||
return { success: false, error: (error as Error).message };
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,21 @@
|
|||||||
import { ipcMain } from "electron";
|
|
||||||
import { db } from "../../db";
|
import { db } from "../../db";
|
||||||
import { messages, apps, chats } from "../../db/schema";
|
import { messages, apps, chats } from "../../db/schema";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import { getDyadAppPath } from "../../paths/paths";
|
import { getDyadAppPath } from "../../paths/paths";
|
||||||
import { executeAddDependency } from "../processors/executeAddDependency";
|
import { executeAddDependency } from "../processors/executeAddDependency";
|
||||||
|
import { createLoggedHandler } from "./safe_handle";
|
||||||
|
import log from "electron-log";
|
||||||
|
|
||||||
|
const logger = log.scope("dependency_handlers");
|
||||||
|
const handle = createLoggedHandler(logger);
|
||||||
|
|
||||||
export function registerDependencyHandlers() {
|
export function registerDependencyHandlers() {
|
||||||
ipcMain.handle(
|
handle(
|
||||||
"chat:add-dep",
|
"chat:add-dep",
|
||||||
async (
|
async (
|
||||||
_event,
|
_event,
|
||||||
{ chatId, packages }: { chatId: number; packages: string[] },
|
{ chatId, packages }: { chatId: number; packages: string[] },
|
||||||
) => {
|
): Promise<void> => {
|
||||||
// Find the message from the database
|
// Find the message from the database
|
||||||
const foundMessages = await db.query.messages.findMany({
|
const foundMessages = await db.query.messages.findMany({
|
||||||
where: eq(messages.chatId, chatId),
|
where: eq(messages.chatId, chatId),
|
||||||
|
|||||||
@@ -285,7 +285,7 @@ function handleStartGithubFlow(
|
|||||||
async function handleIsRepoAvailable(
|
async function handleIsRepoAvailable(
|
||||||
event: IpcMainInvokeEvent,
|
event: IpcMainInvokeEvent,
|
||||||
{ org, repo }: { org: string; repo: string },
|
{ org, repo }: { org: string; repo: string },
|
||||||
) {
|
): Promise<{ available: boolean; error?: string }> {
|
||||||
try {
|
try {
|
||||||
// Get access token from settings
|
// Get access token from settings
|
||||||
const settings = readSettings();
|
const settings = readSettings();
|
||||||
@@ -323,13 +323,12 @@ async function handleIsRepoAvailable(
|
|||||||
async function handleCreateRepo(
|
async function handleCreateRepo(
|
||||||
event: IpcMainInvokeEvent,
|
event: IpcMainInvokeEvent,
|
||||||
{ org, repo, appId }: { org: string; repo: string; appId: number },
|
{ org, repo, appId }: { org: string; repo: string; appId: number },
|
||||||
) {
|
): Promise<void> {
|
||||||
try {
|
|
||||||
// Get access token from settings
|
// Get access token from settings
|
||||||
const settings = readSettings();
|
const settings = readSettings();
|
||||||
const accessToken = settings.githubAccessToken?.value;
|
const accessToken = settings.githubAccessToken?.value;
|
||||||
if (!accessToken) {
|
if (!accessToken) {
|
||||||
return { success: false, error: "Not authenticated with GitHub." };
|
throw new Error("Not authenticated with GitHub.");
|
||||||
}
|
}
|
||||||
// If org is empty, create for the authenticated user
|
// If org is empty, create for the authenticated user
|
||||||
let owner = org;
|
let owner = org;
|
||||||
@@ -358,14 +357,10 @@ async function handleCreateRepo(
|
|||||||
});
|
});
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
return { success: false, error: data.message || "Failed to create repo" };
|
throw new Error(data.message || "Failed to create repo");
|
||||||
}
|
}
|
||||||
// Store org and repo in the app's DB row (apps table)
|
// Store org and repo in the app's DB row (apps table)
|
||||||
await updateAppGithubRepo(appId, owner, repo);
|
await updateAppGithubRepo(appId, owner, repo);
|
||||||
return { success: true };
|
|
||||||
} catch (err: any) {
|
|
||||||
return { success: false, error: err.message || "Unknown error" };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- GitHub Push Handler ---
|
// --- GitHub Push Handler ---
|
||||||
@@ -420,8 +415,7 @@ async function handlePushToGithub(
|
|||||||
async function handleDisconnectGithubRepo(
|
async function handleDisconnectGithubRepo(
|
||||||
event: IpcMainInvokeEvent,
|
event: IpcMainInvokeEvent,
|
||||||
{ appId }: { appId: number },
|
{ appId }: { appId: number },
|
||||||
) {
|
): Promise<void> {
|
||||||
try {
|
|
||||||
logger.log(`Disconnecting GitHub repo for appId: ${appId}`);
|
logger.log(`Disconnecting GitHub repo for appId: ${appId}`);
|
||||||
|
|
||||||
// Get the app from the database
|
// Get the app from the database
|
||||||
@@ -430,7 +424,7 @@ async function handleDisconnectGithubRepo(
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!app) {
|
if (!app) {
|
||||||
return { success: false, error: "App not found" };
|
throw new Error("App not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update app in database to remove GitHub repo and org
|
// Update app in database to remove GitHub repo and org
|
||||||
@@ -441,15 +435,6 @@ async function handleDisconnectGithubRepo(
|
|||||||
githubOrg: null,
|
githubOrg: null,
|
||||||
})
|
})
|
||||||
.where(eq(apps.id, appId));
|
.where(eq(apps.id, appId));
|
||||||
|
|
||||||
return { success: true };
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(`Error disconnecting GitHub repo: ${error}`);
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: error instanceof Error ? error.message : String(error),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Registration ---
|
// --- Registration ---
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ export interface LMStudioModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchLMStudioModels(): Promise<LocalModelListResponse> {
|
export async function fetchLMStudioModels(): Promise<LocalModelListResponse> {
|
||||||
try {
|
|
||||||
const modelsResponse: Response = await fetch(
|
const modelsResponse: Response = await fetch(
|
||||||
"http://localhost:1234/api/v0/models",
|
"http://localhost:1234/api/v0/models",
|
||||||
);
|
);
|
||||||
@@ -36,10 +35,7 @@ export async function fetchLMStudioModels(): Promise<LocalModelListResponse> {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
logger.info(`Successfully fetched ${models.length} models from LM Studio`);
|
logger.info(`Successfully fetched ${models.length} models from LM Studio`);
|
||||||
return { models, error: null };
|
return { models };
|
||||||
} catch {
|
|
||||||
return { models: [], error: "Failed to fetch models from LM Studio" };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function registerLMStudioHandlers() {
|
export function registerLMStudioHandlers() {
|
||||||
|
|||||||
@@ -47,20 +47,17 @@ export async function fetchOllamaModels(): Promise<LocalModelListResponse> {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
logger.info(`Successfully fetched ${models.length} models from Ollama`);
|
logger.info(`Successfully fetched ${models.length} models from Ollama`);
|
||||||
return { models, error: null };
|
return { models };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (
|
if (
|
||||||
error instanceof TypeError &&
|
error instanceof TypeError &&
|
||||||
(error as Error).message.includes("fetch failed")
|
(error as Error).message.includes("fetch failed")
|
||||||
) {
|
) {
|
||||||
logger.error("Could not connect to Ollama");
|
throw new Error(
|
||||||
return {
|
|
||||||
models: [],
|
|
||||||
error:
|
|
||||||
"Could not connect to Ollama. Make sure it's running at http://localhost:11434",
|
"Could not connect to Ollama. Make sure it's running at http://localhost:11434",
|
||||||
};
|
);
|
||||||
}
|
}
|
||||||
return { models: [], error: "Failed to fetch models from Ollama" };
|
throw new Error("Failed to fetch models from Ollama");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ipcMain, type IpcMainInvokeEvent } from "electron";
|
import { type IpcMainInvokeEvent } from "electron";
|
||||||
import type {
|
import type {
|
||||||
CodeProposal,
|
CodeProposal,
|
||||||
ProposalResult,
|
ProposalResult,
|
||||||
@@ -29,9 +29,9 @@ import {
|
|||||||
import { extractCodebase } from "../../utils/codebase";
|
import { extractCodebase } from "../../utils/codebase";
|
||||||
import { getDyadAppPath } from "../../paths/paths";
|
import { getDyadAppPath } from "../../paths/paths";
|
||||||
import { withLock } from "../utils/lock_utils";
|
import { withLock } from "../utils/lock_utils";
|
||||||
|
import { createLoggedHandler } from "./safe_handle";
|
||||||
const logger = log.scope("proposal_handlers");
|
const logger = log.scope("proposal_handlers");
|
||||||
|
const handle = createLoggedHandler(logger);
|
||||||
// Cache for codebase token counts
|
// Cache for codebase token counts
|
||||||
interface CodebaseTokenCache {
|
interface CodebaseTokenCache {
|
||||||
chatId: number;
|
chatId: number;
|
||||||
@@ -317,15 +317,8 @@ const approveProposalHandler = async (
|
|||||||
_event: IpcMainInvokeEvent,
|
_event: IpcMainInvokeEvent,
|
||||||
{ chatId, messageId }: { chatId: number; messageId: number },
|
{ chatId, messageId }: { chatId: number; messageId: number },
|
||||||
): Promise<{
|
): Promise<{
|
||||||
success: boolean;
|
|
||||||
error?: string;
|
|
||||||
uncommittedFiles?: string[];
|
uncommittedFiles?: string[];
|
||||||
}> => {
|
}> => {
|
||||||
logger.log(
|
|
||||||
`IPC: approve-proposal called for chatId: ${chatId}, messageId: ${messageId}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 1. Fetch the specific assistant message
|
// 1. Fetch the specific assistant message
|
||||||
const messageToApprove = await db.query.messages.findFirst({
|
const messageToApprove = await db.query.messages.findFirst({
|
||||||
where: and(
|
where: and(
|
||||||
@@ -339,10 +332,9 @@ const approveProposalHandler = async (
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!messageToApprove?.content) {
|
if (!messageToApprove?.content) {
|
||||||
logger.error(
|
throw new 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." };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Process the actions defined in the message content
|
// 2. Process the actions defined in the message content
|
||||||
@@ -357,38 +349,23 @@ const approveProposalHandler = async (
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (processResult.error) {
|
if (processResult.error) {
|
||||||
logger.error(
|
throw new Error(
|
||||||
`Error processing actions for message ${messageId}:`,
|
`Error processing actions for message ${messageId}: ${processResult.error}`,
|
||||||
processResult.error,
|
|
||||||
);
|
);
|
||||||
// Optionally: Update message state to 'error' or similar?
|
|
||||||
// For now, just return error to frontend
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: `Action processing failed: ${processResult.error}`,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return { success: true, uncommittedFiles: processResult.uncommittedFiles };
|
return { uncommittedFiles: processResult.uncommittedFiles };
|
||||||
} catch (error) {
|
|
||||||
logger.error(`Error approving proposal for messageId ${messageId}:`, error);
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: (error as Error)?.message || "Unknown error",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handler to reject a proposal (just update message state)
|
// Handler to reject a proposal (just update message state)
|
||||||
const rejectProposalHandler = async (
|
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<void> => {
|
||||||
logger.log(
|
logger.log(
|
||||||
`IPC: reject-proposal called for chatId: ${chatId}, messageId: ${messageId}`,
|
`IPC: reject-proposal called for chatId: ${chatId}, messageId: ${messageId}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
|
||||||
// 1. Verify the message exists and is an assistant message
|
// 1. Verify the message exists and is an assistant message
|
||||||
const messageToReject = await db.query.messages.findFirst({
|
const messageToReject = await db.query.messages.findFirst({
|
||||||
where: and(
|
where: and(
|
||||||
@@ -400,10 +377,9 @@ const rejectProposalHandler = async (
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!messageToReject) {
|
if (!messageToReject) {
|
||||||
logger.error(
|
throw new 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." };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Update the message's approval state to 'rejected'
|
// 2. Update the message's approval state to 'rejected'
|
||||||
@@ -413,19 +389,11 @@ const rejectProposalHandler = async (
|
|||||||
.where(eq(messages.id, messageId));
|
.where(eq(messages.id, messageId));
|
||||||
|
|
||||||
logger.log(`Message ${messageId} marked as rejected.`);
|
logger.log(`Message ${messageId} marked as rejected.`);
|
||||||
return { success: true };
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(`Error rejecting proposal for messageId ${messageId}:`, error);
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: (error as Error)?.message || "Unknown error",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Function to register proposal-related handlers
|
// Function to register proposal-related handlers
|
||||||
export function registerProposalHandlers() {
|
export function registerProposalHandlers() {
|
||||||
ipcMain.handle("get-proposal", getProposalHandler);
|
handle("get-proposal", getProposalHandler);
|
||||||
ipcMain.handle("approve-proposal", approveProposalHandler);
|
handle("approve-proposal", approveProposalHandler);
|
||||||
ipcMain.handle("reject-proposal", rejectProposalHandler);
|
handle("reject-proposal", rejectProposalHandler);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { ipcMain, IpcMainInvokeEvent } from "electron";
|
import { ipcMain, IpcMainInvokeEvent } from "electron";
|
||||||
import log from "electron-log";
|
import log from "electron-log";
|
||||||
|
|
||||||
export function createSafeHandler(logger: log.LogFunctions) {
|
export function createLoggedHandler(logger: log.LogFunctions) {
|
||||||
return (
|
return (
|
||||||
channel: string,
|
channel: string,
|
||||||
fn: (event: IpcMainInvokeEvent, ...args: any[]) => Promise<any>,
|
fn: (event: IpcMainInvokeEvent, ...args: any[]) => Promise<any>,
|
||||||
@@ -9,8 +9,11 @@ export function createSafeHandler(logger: log.LogFunctions) {
|
|||||||
ipcMain.handle(
|
ipcMain.handle(
|
||||||
channel,
|
channel,
|
||||||
async (event: IpcMainInvokeEvent, ...args: any[]) => {
|
async (event: IpcMainInvokeEvent, ...args: any[]) => {
|
||||||
|
logger.log(`IPC: ${channel} called with args: ${JSON.stringify(args)}`);
|
||||||
try {
|
try {
|
||||||
return await fn(event, ...args);
|
const result = await fn(event, ...args);
|
||||||
|
logger.log(`IPC: ${channel} returned: ${JSON.stringify(result)}`);
|
||||||
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(
|
logger.error(
|
||||||
`Error in ${fn.name}: args: ${JSON.stringify(args)}`,
|
`Error in ${fn.name}: args: ${JSON.stringify(args)}`,
|
||||||
|
|||||||
@@ -4,11 +4,13 @@ import { writeSettings } from "../../main/settings";
|
|||||||
import { readSettings } from "../../main/settings";
|
import { readSettings } from "../../main/settings";
|
||||||
|
|
||||||
export function registerSettingsHandlers() {
|
export function registerSettingsHandlers() {
|
||||||
|
// Intentionally do NOT use handle because it could log sensitive data from the return value.
|
||||||
ipcMain.handle("get-user-settings", async () => {
|
ipcMain.handle("get-user-settings", async () => {
|
||||||
const settings = readSettings();
|
const settings = readSettings();
|
||||||
return settings;
|
return settings;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Intentionally do NOT use handle because it could log sensitive data from the args.
|
||||||
ipcMain.handle(
|
ipcMain.handle(
|
||||||
"set-user-settings",
|
"set-user-settings",
|
||||||
async (_, settings: Partial<UserSettings>) => {
|
async (_, settings: Partial<UserSettings>) => {
|
||||||
|
|||||||
@@ -1,45 +1,29 @@
|
|||||||
import { ipcMain, shell } from "electron";
|
import { shell } from "electron";
|
||||||
import log from "electron-log";
|
import log from "electron-log";
|
||||||
|
import { createLoggedHandler } from "./safe_handle";
|
||||||
|
|
||||||
const logger = log.scope("shell_handlers");
|
const logger = log.scope("shell_handlers");
|
||||||
|
const handle = createLoggedHandler(logger);
|
||||||
|
|
||||||
export function registerShellHandlers() {
|
export function registerShellHandlers() {
|
||||||
ipcMain.handle("open-external-url", async (_event, url: string) => {
|
handle("open-external-url", async (_event, url: string) => {
|
||||||
try {
|
if (!url) {
|
||||||
// Basic validation to ensure it's a http/https url
|
throw new Error("No URL provided.");
|
||||||
if (url && (url.startsWith("http://") || url.startsWith("https://"))) {
|
}
|
||||||
|
if (!url.startsWith("http://") && !url.startsWith("https://")) {
|
||||||
|
throw new Error("Attempted to open invalid or non-http URL: " + url);
|
||||||
|
}
|
||||||
await shell.openExternal(url);
|
await shell.openExternal(url);
|
||||||
logger.debug("Opened external URL:", url);
|
logger.debug("Opened external URL:", url);
|
||||||
return { success: true };
|
|
||||||
}
|
|
||||||
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) {
|
|
||||||
logger.error(`Failed to open external URL ${url}:`, error);
|
|
||||||
return { success: false, error: (error as Error).message };
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle("show-item-in-folder", async (_event, fullPath: string) => {
|
handle("show-item-in-folder", async (_event, fullPath: string) => {
|
||||||
try {
|
|
||||||
// Validate that a path was provided
|
// Validate that a path was provided
|
||||||
if (!fullPath) {
|
if (!fullPath) {
|
||||||
logger.error("Attempted to show item with empty path");
|
throw new Error("No file path provided.");
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: "No file path provided.",
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
shell.showItemInFolder(fullPath);
|
shell.showItemInFolder(fullPath);
|
||||||
logger.debug("Showed item in folder:", fullPath);
|
logger.debug("Showed item in folder:", fullPath);
|
||||||
return { success: true };
|
|
||||||
} catch (error) {
|
|
||||||
logger.error(`Failed to show item in folder ${fullPath}:`, error);
|
|
||||||
return { success: false, error: (error as Error).message };
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,63 +1,39 @@
|
|||||||
import { ipcMain } from "electron";
|
|
||||||
import log from "electron-log";
|
import log from "electron-log";
|
||||||
import { db } from "../../db";
|
import { db } from "../../db";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import { apps } from "../../db/schema";
|
import { apps } from "../../db/schema";
|
||||||
import { getSupabaseClient } from "../../supabase_admin/supabase_management_client";
|
import { getSupabaseClient } from "../../supabase_admin/supabase_management_client";
|
||||||
|
import { createLoggedHandler } from "./safe_handle";
|
||||||
|
|
||||||
const logger = log.scope("supabase_handlers");
|
const logger = log.scope("supabase_handlers");
|
||||||
|
const handle = createLoggedHandler(logger);
|
||||||
|
|
||||||
export function registerSupabaseHandlers() {
|
export function registerSupabaseHandlers() {
|
||||||
// List all Supabase projects
|
handle("supabase:list-projects", async () => {
|
||||||
ipcMain.handle("supabase:list-projects", async () => {
|
|
||||||
try {
|
|
||||||
const supabase = await getSupabaseClient();
|
const supabase = await getSupabaseClient();
|
||||||
// Call the API according to supabase-management-js structure
|
return supabase.getProjects();
|
||||||
const projects = await supabase.getProjects();
|
|
||||||
return projects;
|
|
||||||
} catch (error) {
|
|
||||||
logger.error("Error listing Supabase projects:", error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set app project - links a Dyad app to a Supabase project
|
// Set app project - links a Dyad app to a Supabase project
|
||||||
ipcMain.handle(
|
handle(
|
||||||
"supabase:set-app-project",
|
"supabase:set-app-project",
|
||||||
async (_, { project, app }: { project: string; app: number }) => {
|
async (_, { project, app }: { project: string; app: number }) => {
|
||||||
try {
|
|
||||||
// Here you could store the project-app association in your database
|
|
||||||
// For example:
|
|
||||||
await db
|
await db
|
||||||
.update(apps)
|
.update(apps)
|
||||||
.set({ supabaseProjectId: project })
|
.set({ supabaseProjectId: project })
|
||||||
.where(eq(apps.id, app));
|
.where(eq(apps.id, app));
|
||||||
|
|
||||||
logger.info(`Associated app ${app} with Supabase project ${project}`);
|
logger.info(`Associated app ${app} with Supabase project ${project}`);
|
||||||
return { success: true, appId: app, projectId: project };
|
|
||||||
} catch (error) {
|
|
||||||
logger.error("Error setting Supabase project for app:", error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// Unset app project - removes the link between a Dyad app and a Supabase project
|
// Unset app project - removes the link between a Dyad app and a Supabase project
|
||||||
ipcMain.handle(
|
handle("supabase:unset-app-project", async (_, { app }: { app: number }) => {
|
||||||
"supabase:unset-app-project",
|
|
||||||
async (_, { app }: { app: number }) => {
|
|
||||||
try {
|
|
||||||
await db
|
await db
|
||||||
.update(apps)
|
.update(apps)
|
||||||
.set({ supabaseProjectId: null })
|
.set({ supabaseProjectId: null })
|
||||||
.where(eq(apps.id, app));
|
.where(eq(apps.id, app));
|
||||||
|
|
||||||
logger.info(`Removed Supabase project association for app ${app}`);
|
logger.info(`Removed Supabase project association for app ${app}`);
|
||||||
return { success: true, appId: app };
|
});
|
||||||
} catch (error) {
|
|
||||||
logger.error("Error unsetting Supabase project for app:", error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { ipcMain } from "electron";
|
|
||||||
import { db } from "../../db";
|
import { db } from "../../db";
|
||||||
import { chats } from "../../db/schema";
|
import { chats } from "../../db/schema";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
@@ -15,15 +14,16 @@ import { getSupabaseContext } from "../../supabase_admin/supabase_context";
|
|||||||
import { TokenCountParams } from "../ipc_types";
|
import { TokenCountParams } from "../ipc_types";
|
||||||
import { TokenCountResult } from "../ipc_types";
|
import { TokenCountResult } from "../ipc_types";
|
||||||
import { estimateTokens, getContextWindow } from "../utils/token_utils";
|
import { estimateTokens, getContextWindow } from "../utils/token_utils";
|
||||||
|
import { createLoggedHandler } from "./safe_handle";
|
||||||
|
|
||||||
const logger = log.scope("token_count_handlers");
|
const logger = log.scope("token_count_handlers");
|
||||||
|
|
||||||
|
const handle = createLoggedHandler(logger);
|
||||||
|
|
||||||
export function registerTokenCountHandlers() {
|
export function registerTokenCountHandlers() {
|
||||||
ipcMain.handle(
|
handle(
|
||||||
"chat:count-tokens",
|
"chat:count-tokens",
|
||||||
async (event, req: TokenCountParams): Promise<TokenCountResult> => {
|
async (event, req: TokenCountParams): Promise<TokenCountResult> => {
|
||||||
try {
|
|
||||||
// Get the chat with messages
|
|
||||||
const chat = await db.query.chats.findFirst({
|
const chat = await db.query.chats.findFirst({
|
||||||
where: eq(chats.id, req.chatId),
|
where: eq(chats.id, req.chatId),
|
||||||
with: {
|
with: {
|
||||||
@@ -60,9 +60,7 @@ export function registerTokenCountHandlers() {
|
|||||||
systemPrompt += "\n\n" + SUPABASE_NOT_AVAILABLE_SYSTEM_PROMPT;
|
systemPrompt += "\n\n" + SUPABASE_NOT_AVAILABLE_SYSTEM_PROMPT;
|
||||||
}
|
}
|
||||||
|
|
||||||
const systemPromptTokens = estimateTokens(
|
const systemPromptTokens = estimateTokens(systemPrompt + supabaseContext);
|
||||||
systemPrompt + supabaseContext,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Extract codebase information if app is associated with the chat
|
// Extract codebase information if app is associated with the chat
|
||||||
let codebaseInfo = "";
|
let codebaseInfo = "";
|
||||||
@@ -70,15 +68,11 @@ export function registerTokenCountHandlers() {
|
|||||||
|
|
||||||
if (chat.app) {
|
if (chat.app) {
|
||||||
const appPath = getDyadAppPath(chat.app.path);
|
const appPath = getDyadAppPath(chat.app.path);
|
||||||
try {
|
|
||||||
codebaseInfo = await extractCodebase(appPath);
|
codebaseInfo = await extractCodebase(appPath);
|
||||||
codebaseTokens = estimateTokens(codebaseInfo);
|
codebaseTokens = estimateTokens(codebaseInfo);
|
||||||
logger.log(
|
logger.log(
|
||||||
`Extracted codebase information from ${appPath}, tokens: ${codebaseTokens}`,
|
`Extracted codebase information from ${appPath}, tokens: ${codebaseTokens}`,
|
||||||
);
|
);
|
||||||
} catch (error) {
|
|
||||||
logger.error("Error extracting codebase:", error);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate total tokens
|
// Calculate total tokens
|
||||||
@@ -96,10 +90,6 @@ export function registerTokenCountHandlers() {
|
|||||||
systemPromptTokens,
|
systemPromptTokens,
|
||||||
contextWindow: getContextWindow(),
|
contextWindow: getContextWindow(),
|
||||||
};
|
};
|
||||||
} catch (error) {
|
|
||||||
logger.error("Error counting tokens:", error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import { ipcMain } from "electron";
|
|
||||||
import log from "electron-log";
|
import log from "electron-log";
|
||||||
import fetch from "node-fetch";
|
import fetch from "node-fetch";
|
||||||
|
import { createLoggedHandler } from "./safe_handle";
|
||||||
|
|
||||||
const logger = log.scope("upload_handlers");
|
const logger = log.scope("upload_handlers");
|
||||||
|
|
||||||
|
const handle = createLoggedHandler(logger);
|
||||||
|
|
||||||
interface UploadToSignedUrlParams {
|
interface UploadToSignedUrlParams {
|
||||||
url: string;
|
url: string;
|
||||||
contentType: string;
|
contentType: string;
|
||||||
@@ -11,13 +13,10 @@ interface UploadToSignedUrlParams {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function registerUploadHandlers() {
|
export function registerUploadHandlers() {
|
||||||
ipcMain.handle(
|
handle("upload-to-signed-url", async (_, params: UploadToSignedUrlParams) => {
|
||||||
"upload-to-signed-url",
|
|
||||||
async (_, params: UploadToSignedUrlParams) => {
|
|
||||||
const { url, contentType, data } = params;
|
const { url, contentType, data } = params;
|
||||||
logger.debug("IPC: upload-to-signed-url called");
|
logger.debug("IPC: upload-to-signed-url called");
|
||||||
|
|
||||||
try {
|
|
||||||
// Validate the signed URL
|
// Validate the signed URL
|
||||||
if (!url || typeof url !== "string" || !url.startsWith("https://")) {
|
if (!url || typeof url !== "string" || !url.startsWith("https://")) {
|
||||||
throw new Error("Invalid signed URL provided");
|
throw new Error("Invalid signed URL provided");
|
||||||
@@ -44,16 +43,7 @@ export function registerUploadHandlers() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
logger.debug("Successfully uploaded data to signed URL");
|
logger.debug("Successfully uploaded data to signed URL");
|
||||||
return { success: true };
|
});
|
||||||
} catch (error) {
|
|
||||||
logger.error("Failed to upload to signed URL:", error);
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error: error instanceof Error ? error.message : String(error),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
logger.debug("Registered upload IPC handlers");
|
logger.debug("Registered upload IPC handlers");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,11 +10,11 @@ import { promises as fsPromises } from "node:fs";
|
|||||||
import { withLock } from "../utils/lock_utils";
|
import { withLock } from "../utils/lock_utils";
|
||||||
import { getGitAuthor } from "../utils/git_author";
|
import { getGitAuthor } from "../utils/git_author";
|
||||||
import log from "electron-log";
|
import log from "electron-log";
|
||||||
import { createSafeHandler } from "./safe_handle";
|
import { createLoggedHandler } from "./safe_handle";
|
||||||
|
|
||||||
const logger = log.scope("version_handlers");
|
const logger = log.scope("version_handlers");
|
||||||
|
|
||||||
const handle = createSafeHandler(logger);
|
const handle = createLoggedHandler(logger);
|
||||||
|
|
||||||
export function registerVersionHandlers() {
|
export function registerVersionHandlers() {
|
||||||
handle("list-versions", async (_, { appId }: { appId: number }) => {
|
handle("list-versions", async (_, { appId }: { appId: number }) => {
|
||||||
|
|||||||
@@ -150,23 +150,11 @@ export class IpcClient {
|
|||||||
|
|
||||||
// Create a new app with an initial chat
|
// Create a new app with an initial chat
|
||||||
public async createApp(params: CreateAppParams): Promise<CreateAppResult> {
|
public async createApp(params: CreateAppParams): Promise<CreateAppResult> {
|
||||||
try {
|
return this.ipcRenderer.invoke("create-app", params);
|
||||||
const result = await this.ipcRenderer.invoke("create-app", params);
|
|
||||||
return result as CreateAppResult;
|
|
||||||
} catch (error) {
|
|
||||||
showError(error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getApp(appId: number): Promise<App> {
|
public async getApp(appId: number): Promise<App> {
|
||||||
try {
|
return this.ipcRenderer.invoke("get-app", appId);
|
||||||
const data = await this.ipcRenderer.invoke("get-app", appId);
|
|
||||||
return data;
|
|
||||||
} catch (error) {
|
|
||||||
showError(error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getChat(chatId: number): Promise<Chat> {
|
public async getChat(chatId: number): Promise<Chat> {
|
||||||
@@ -192,28 +180,14 @@ export class IpcClient {
|
|||||||
|
|
||||||
// Get all apps
|
// Get all apps
|
||||||
public async listApps(): Promise<ListAppsResponse> {
|
public async listApps(): Promise<ListAppsResponse> {
|
||||||
try {
|
return this.ipcRenderer.invoke("list-apps");
|
||||||
const data = await this.ipcRenderer.invoke("list-apps");
|
|
||||||
return data;
|
|
||||||
} catch (error) {
|
|
||||||
showError(error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read a file from an app directory
|
|
||||||
public async readAppFile(appId: number, filePath: string): Promise<string> {
|
public async readAppFile(appId: number, filePath: string): Promise<string> {
|
||||||
try {
|
return this.ipcRenderer.invoke("read-app-file", {
|
||||||
const content = await this.ipcRenderer.invoke("read-app-file", {
|
|
||||||
appId,
|
appId,
|
||||||
filePath,
|
filePath,
|
||||||
});
|
});
|
||||||
return content as string;
|
|
||||||
} catch (error) {
|
|
||||||
// No toast because sometimes the file will disappear.
|
|
||||||
console.error(error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Edit a file in an app directory
|
// Edit a file in an app directory
|
||||||
@@ -221,18 +195,12 @@ export class IpcClient {
|
|||||||
appId: number,
|
appId: number,
|
||||||
filePath: string,
|
filePath: string,
|
||||||
content: string,
|
content: string,
|
||||||
): Promise<{ success: boolean }> {
|
): Promise<void> {
|
||||||
try {
|
await this.ipcRenderer.invoke("edit-app-file", {
|
||||||
const result = await this.ipcRenderer.invoke("edit-app-file", {
|
|
||||||
appId,
|
appId,
|
||||||
filePath,
|
filePath,
|
||||||
content,
|
content,
|
||||||
});
|
});
|
||||||
return result as { success: boolean };
|
|
||||||
} catch (error) {
|
|
||||||
showError(error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// New method for streaming responses
|
// New method for streaming responses
|
||||||
@@ -321,91 +289,38 @@ export class IpcClient {
|
|||||||
|
|
||||||
// Create a new chat for an app
|
// Create a new chat for an app
|
||||||
public async createChat(appId: number): Promise<number> {
|
public async createChat(appId: number): Promise<number> {
|
||||||
try {
|
return this.ipcRenderer.invoke("create-chat", appId);
|
||||||
const chatId = await this.ipcRenderer.invoke("create-chat", appId);
|
|
||||||
return chatId as number;
|
|
||||||
} catch (error) {
|
|
||||||
showError(error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async deleteChat(
|
public async deleteChat(chatId: number): Promise<void> {
|
||||||
chatId: number,
|
await this.ipcRenderer.invoke("delete-chat", chatId);
|
||||||
): Promise<{ success: boolean; error?: string }> {
|
|
||||||
try {
|
|
||||||
const result = await this.ipcRenderer.invoke("delete-chat", chatId);
|
|
||||||
return result as { success: boolean; error?: string };
|
|
||||||
} catch (error) {
|
|
||||||
showError(error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async deleteMessages(
|
public async deleteMessages(chatId: number): Promise<void> {
|
||||||
chatId: number,
|
await this.ipcRenderer.invoke("delete-messages", chatId);
|
||||||
): Promise<{ success: boolean; error?: string }> {
|
|
||||||
try {
|
|
||||||
const result = await this.ipcRenderer.invoke("delete-messages", chatId);
|
|
||||||
return result as { success: boolean; error?: string };
|
|
||||||
} catch (error) {
|
|
||||||
showError(error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open an external URL using the default browser
|
// Open an external URL using the default browser
|
||||||
public async openExternalUrl(
|
public async openExternalUrl(url: string): Promise<void> {
|
||||||
url: string,
|
await this.ipcRenderer.invoke("open-external-url", url);
|
||||||
): Promise<{ success: boolean; error?: string }> {
|
|
||||||
try {
|
|
||||||
const result = await this.ipcRenderer.invoke("open-external-url", url);
|
|
||||||
return result as { success: boolean; error?: string };
|
|
||||||
} catch (error) {
|
|
||||||
showError(error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async showItemInFolder(
|
public async showItemInFolder(fullPath: string): Promise<void> {
|
||||||
fullPath: string,
|
await this.ipcRenderer.invoke("show-item-in-folder", fullPath);
|
||||||
): Promise<{ success: boolean; error?: string }> {
|
|
||||||
try {
|
|
||||||
const result = await this.ipcRenderer.invoke(
|
|
||||||
"show-item-in-folder",
|
|
||||||
fullPath,
|
|
||||||
);
|
|
||||||
return result as { success: boolean; error?: string };
|
|
||||||
} catch (error) {
|
|
||||||
showError(error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run an app
|
// Run an app
|
||||||
public async runApp(
|
public async runApp(
|
||||||
appId: number,
|
appId: number,
|
||||||
onOutput: (output: AppOutput) => void,
|
onOutput: (output: AppOutput) => void,
|
||||||
): Promise<{ success: boolean }> {
|
): Promise<void> {
|
||||||
try {
|
await this.ipcRenderer.invoke("run-app", { appId });
|
||||||
const result = await this.ipcRenderer.invoke("run-app", { appId });
|
|
||||||
this.appStreams.set(appId, { onOutput });
|
this.appStreams.set(appId, { onOutput });
|
||||||
return result;
|
|
||||||
} catch (error) {
|
|
||||||
showError(error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop a running app
|
// Stop a running app
|
||||||
public async stopApp(appId: number): Promise<{ success: boolean }> {
|
public async stopApp(appId: number): Promise<void> {
|
||||||
try {
|
await this.ipcRenderer.invoke("stop-app", { appId });
|
||||||
const result = await this.ipcRenderer.invoke("stop-app", { appId });
|
|
||||||
return result;
|
|
||||||
} catch (error) {
|
|
||||||
showError(error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restart a running app
|
// Restart a running app
|
||||||
@@ -514,14 +429,8 @@ export class IpcClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Delete an app and all its files
|
// Delete an app and all its files
|
||||||
public async deleteApp(appId: number): Promise<{ success: boolean }> {
|
public async deleteApp(appId: number): Promise<void> {
|
||||||
try {
|
await this.ipcRenderer.invoke("delete-app", { appId });
|
||||||
const result = await this.ipcRenderer.invoke("delete-app", { appId });
|
|
||||||
return result as { success: boolean };
|
|
||||||
} catch (error) {
|
|
||||||
showError(error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rename an app (update name and path)
|
// Rename an app (update name and path)
|
||||||
@@ -533,29 +442,17 @@ export class IpcClient {
|
|||||||
appId: number;
|
appId: number;
|
||||||
appName: string;
|
appName: string;
|
||||||
appPath: string;
|
appPath: string;
|
||||||
}): Promise<{ success: boolean; app: App }> {
|
}): Promise<void> {
|
||||||
try {
|
await this.ipcRenderer.invoke("rename-app", {
|
||||||
const result = await this.ipcRenderer.invoke("rename-app", {
|
|
||||||
appId,
|
appId,
|
||||||
appName,
|
appName,
|
||||||
appPath,
|
appPath,
|
||||||
});
|
});
|
||||||
return result as { success: boolean; app: App };
|
|
||||||
} catch (error) {
|
|
||||||
showError(error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset all - removes all app files, settings, and drops the database
|
// Reset all - removes all app files, settings, and drops the database
|
||||||
public async resetAll(): Promise<{ success: boolean; message: string }> {
|
public async resetAll(): Promise<void> {
|
||||||
try {
|
await this.ipcRenderer.invoke("reset-all");
|
||||||
const result = await this.ipcRenderer.invoke("reset-all");
|
|
||||||
return result as { success: boolean; message: string };
|
|
||||||
} catch (error) {
|
|
||||||
showError(error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async addDependency({
|
public async addDependency({
|
||||||
@@ -565,26 +462,15 @@ export class IpcClient {
|
|||||||
chatId: number;
|
chatId: number;
|
||||||
packages: string[];
|
packages: string[];
|
||||||
}): Promise<void> {
|
}): Promise<void> {
|
||||||
try {
|
|
||||||
await this.ipcRenderer.invoke("chat:add-dep", {
|
await this.ipcRenderer.invoke("chat:add-dep", {
|
||||||
chatId,
|
chatId,
|
||||||
packages,
|
packages,
|
||||||
});
|
});
|
||||||
} catch (error) {
|
|
||||||
showError(error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check Node.js and npm status
|
// Check Node.js and npm status
|
||||||
public async getNodejsStatus(): Promise<NodeSystemInfo> {
|
public async getNodejsStatus(): Promise<NodeSystemInfo> {
|
||||||
try {
|
return this.ipcRenderer.invoke("nodejs-status");
|
||||||
const result = await this.ipcRenderer.invoke("nodejs-status");
|
|
||||||
return result;
|
|
||||||
} catch (error) {
|
|
||||||
showError(error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- GitHub Device Flow ---
|
// --- GitHub Device Flow ---
|
||||||
@@ -631,11 +517,6 @@ export class IpcClient {
|
|||||||
this.ipcRenderer.removeListener("github:flow-error", listener);
|
this.ipcRenderer.removeListener("github:flow-error", listener);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Implement cancel method if needed
|
|
||||||
// public cancelGithubDeviceFlow(): void {
|
|
||||||
// this.ipcRenderer.sendMessage("github:cancel-flow");
|
|
||||||
// }
|
|
||||||
// --- End GitHub Device Flow ---
|
// --- End GitHub Device Flow ---
|
||||||
|
|
||||||
// --- GitHub Repo Management ---
|
// --- GitHub Repo Management ---
|
||||||
@@ -643,32 +524,22 @@ export class IpcClient {
|
|||||||
org: string,
|
org: string,
|
||||||
repo: string,
|
repo: string,
|
||||||
): Promise<{ available: boolean; error?: string }> {
|
): Promise<{ available: boolean; error?: string }> {
|
||||||
try {
|
return this.ipcRenderer.invoke("github:is-repo-available", {
|
||||||
const result = await this.ipcRenderer.invoke("github:is-repo-available", {
|
|
||||||
org,
|
org,
|
||||||
repo,
|
repo,
|
||||||
});
|
});
|
||||||
return result;
|
|
||||||
} catch (error: any) {
|
|
||||||
return { available: false, error: error.message || "Unknown error" };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async createGithubRepo(
|
public async createGithubRepo(
|
||||||
org: string,
|
org: string,
|
||||||
repo: string,
|
repo: string,
|
||||||
appId: number,
|
appId: number,
|
||||||
): Promise<{ success: boolean; error?: string }> {
|
): Promise<void> {
|
||||||
try {
|
await this.ipcRenderer.invoke("github:create-repo", {
|
||||||
const result = await this.ipcRenderer.invoke("github:create-repo", {
|
|
||||||
org,
|
org,
|
||||||
repo,
|
repo,
|
||||||
appId,
|
appId,
|
||||||
});
|
});
|
||||||
return result;
|
|
||||||
} catch (error: any) {
|
|
||||||
return { success: false, error: error.message || "Unknown error" };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sync (push) local repo to GitHub
|
// Sync (push) local repo to GitHub
|
||||||
@@ -684,30 +555,17 @@ export class IpcClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async disconnectGithubRepo(
|
public async disconnectGithubRepo(appId: number): Promise<void> {
|
||||||
appId: number,
|
await this.ipcRenderer.invoke("github:disconnect", {
|
||||||
): Promise<{ success: boolean; error?: string }> {
|
|
||||||
try {
|
|
||||||
const result = await this.ipcRenderer.invoke("github:disconnect", {
|
|
||||||
appId,
|
appId,
|
||||||
});
|
});
|
||||||
return result as { success: boolean; error?: string };
|
|
||||||
} catch (error) {
|
|
||||||
showError(error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// --- End GitHub Repo Management ---
|
// --- End GitHub Repo Management ---
|
||||||
|
|
||||||
// Get the main app version
|
// Get the main app version
|
||||||
public async getAppVersion(): Promise<string> {
|
public async getAppVersion(): Promise<string> {
|
||||||
try {
|
|
||||||
const result = await this.ipcRenderer.invoke("get-app-version");
|
const result = await this.ipcRenderer.invoke("get-app-version");
|
||||||
return result.version as string;
|
return result.version as string;
|
||||||
} catch (error) {
|
|
||||||
showError(error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get proposal details
|
// Get proposal details
|
||||||
@@ -734,24 +592,12 @@ export class IpcClient {
|
|||||||
chatId: number;
|
chatId: number;
|
||||||
messageId: number;
|
messageId: number;
|
||||||
}): Promise<{
|
}): Promise<{
|
||||||
success: boolean;
|
|
||||||
error?: string;
|
|
||||||
uncommittedFiles?: string[];
|
uncommittedFiles?: string[];
|
||||||
}> {
|
}> {
|
||||||
try {
|
return this.ipcRenderer.invoke("approve-proposal", {
|
||||||
const result = await this.ipcRenderer.invoke("approve-proposal", {
|
|
||||||
chatId,
|
chatId,
|
||||||
messageId,
|
messageId,
|
||||||
});
|
});
|
||||||
return result as {
|
|
||||||
success: boolean;
|
|
||||||
error?: string;
|
|
||||||
uncommittedFiles?: string[];
|
|
||||||
};
|
|
||||||
} catch (error) {
|
|
||||||
showError(error);
|
|
||||||
return { success: false, error: (error as Error).message };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async rejectProposal({
|
public async rejectProposal({
|
||||||
@@ -760,132 +606,66 @@ export class IpcClient {
|
|||||||
}: {
|
}: {
|
||||||
chatId: number;
|
chatId: number;
|
||||||
messageId: number;
|
messageId: number;
|
||||||
}): Promise<{ success: boolean; error?: string }> {
|
}): Promise<void> {
|
||||||
try {
|
await this.ipcRenderer.invoke("reject-proposal", {
|
||||||
const result = await this.ipcRenderer.invoke("reject-proposal", {
|
|
||||||
chatId,
|
chatId,
|
||||||
messageId,
|
messageId,
|
||||||
});
|
});
|
||||||
return result as { success: boolean; error?: string };
|
|
||||||
} catch (error) {
|
|
||||||
showError(error);
|
|
||||||
return { success: false, error: (error as Error).message };
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// --- End Proposal Management ---
|
// --- End Proposal Management ---
|
||||||
|
|
||||||
// --- Supabase Management ---
|
// --- Supabase Management ---
|
||||||
public async listSupabaseProjects(): Promise<any[]> {
|
public async listSupabaseProjects(): Promise<any[]> {
|
||||||
try {
|
return this.ipcRenderer.invoke("supabase:list-projects");
|
||||||
const projects = await this.ipcRenderer.invoke("supabase:list-projects");
|
|
||||||
return projects;
|
|
||||||
} catch (error) {
|
|
||||||
showError(error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async setSupabaseAppProject(
|
public async setSupabaseAppProject(
|
||||||
project: string,
|
project: string,
|
||||||
app: number,
|
app: number,
|
||||||
): Promise<{ success: boolean; appId: number; projectId: string }> {
|
): Promise<void> {
|
||||||
try {
|
await this.ipcRenderer.invoke("supabase:set-app-project", {
|
||||||
const result = await this.ipcRenderer.invoke("supabase:set-app-project", {
|
|
||||||
project,
|
project,
|
||||||
app,
|
app,
|
||||||
});
|
});
|
||||||
return result;
|
|
||||||
} catch (error) {
|
|
||||||
showError(error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async unsetSupabaseAppProject(
|
public async unsetSupabaseAppProject(app: number): Promise<void> {
|
||||||
app: number,
|
await this.ipcRenderer.invoke("supabase:unset-app-project", {
|
||||||
): Promise<{ success: boolean; appId: number }> {
|
|
||||||
try {
|
|
||||||
const result = await this.ipcRenderer.invoke(
|
|
||||||
"supabase:unset-app-project",
|
|
||||||
{
|
|
||||||
app,
|
app,
|
||||||
},
|
});
|
||||||
);
|
|
||||||
return result;
|
|
||||||
} catch (error) {
|
|
||||||
showError(error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// --- End Supabase Management ---
|
// --- End Supabase Management ---
|
||||||
|
|
||||||
// Get system debug information
|
|
||||||
public async getSystemDebugInfo(): Promise<SystemDebugInfo> {
|
public async getSystemDebugInfo(): Promise<SystemDebugInfo> {
|
||||||
try {
|
return this.ipcRenderer.invoke("get-system-debug-info");
|
||||||
const data = await this.ipcRenderer.invoke("get-system-debug-info");
|
|
||||||
return data as SystemDebugInfo;
|
|
||||||
} catch (error) {
|
|
||||||
showError(error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getChatLogs(chatId: number): Promise<ChatLogsData> {
|
public async getChatLogs(chatId: number): Promise<ChatLogsData> {
|
||||||
try {
|
return this.ipcRenderer.invoke("get-chat-logs", chatId);
|
||||||
const data = await this.ipcRenderer.invoke("get-chat-logs", chatId);
|
|
||||||
return data as ChatLogsData;
|
|
||||||
} catch (error) {
|
|
||||||
showError(error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async uploadToSignedUrl(
|
public async uploadToSignedUrl(
|
||||||
url: string,
|
url: string,
|
||||||
contentType: string,
|
contentType: string,
|
||||||
data: any,
|
data: any,
|
||||||
): Promise<{ success: boolean; error?: string }> {
|
): Promise<void> {
|
||||||
try {
|
await this.ipcRenderer.invoke("upload-to-signed-url", {
|
||||||
const result = await this.ipcRenderer.invoke("upload-to-signed-url", {
|
|
||||||
url,
|
url,
|
||||||
contentType,
|
contentType,
|
||||||
data,
|
data,
|
||||||
});
|
});
|
||||||
return result as { success: boolean; error?: string };
|
|
||||||
} catch (error) {
|
|
||||||
showError(error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async listLocalOllamaModels(): Promise<LocalModel[]> {
|
public async listLocalOllamaModels(): Promise<LocalModel[]> {
|
||||||
try {
|
const response = await this.ipcRenderer.invoke("local-models:list-ollama");
|
||||||
const response = await this.ipcRenderer.invoke(
|
|
||||||
"local-models:list-ollama",
|
|
||||||
);
|
|
||||||
return response?.models || [];
|
return response?.models || [];
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof Error) {
|
|
||||||
throw new Error(`Failed to fetch Ollama models: ${error.message}`);
|
|
||||||
}
|
|
||||||
throw new Error("Failed to fetch Ollama models: Unknown error occurred");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async listLocalLMStudioModels(): Promise<LocalModel[]> {
|
public async listLocalLMStudioModels(): Promise<LocalModel[]> {
|
||||||
try {
|
|
||||||
const response = await this.ipcRenderer.invoke(
|
const response = await this.ipcRenderer.invoke(
|
||||||
"local-models:list-lmstudio",
|
"local-models:list-lmstudio",
|
||||||
);
|
);
|
||||||
return response?.models || [];
|
return response?.models || [];
|
||||||
} catch (error) {
|
|
||||||
if (error instanceof Error) {
|
|
||||||
throw new Error(`Failed to fetch LM Studio models: ${error.message}`);
|
|
||||||
}
|
|
||||||
throw new Error(
|
|
||||||
"Failed to fetch LM Studio models: Unknown error occurred",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Listen for deep link events
|
// Listen for deep link events
|
||||||
|
|||||||
@@ -111,7 +111,6 @@ export interface LocalModel {
|
|||||||
|
|
||||||
export type LocalModelListResponse = {
|
export type LocalModelListResponse = {
|
||||||
models: LocalModel[];
|
models: LocalModel[];
|
||||||
error: string | null;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface TokenCountParams {
|
export interface TokenCountParams {
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import {
|
|||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import { GitHubConnector } from "@/components/GitHubConnector";
|
import { GitHubConnector } from "@/components/GitHubConnector";
|
||||||
import { SupabaseConnector } from "@/components/SupabaseConnector";
|
import { SupabaseConnector } from "@/components/SupabaseConnector";
|
||||||
|
import { showError } from "@/lib/toast";
|
||||||
|
|
||||||
export default function AppDetailsPage() {
|
export default function AppDetailsPage() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -62,7 +63,7 @@ export default function AppDetailsPage() {
|
|||||||
await refreshApps();
|
await refreshApps();
|
||||||
navigate({ to: "/", search: {} });
|
navigate({ to: "/", search: {} });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Failed to delete app:", error);
|
showError(error);
|
||||||
} finally {
|
} finally {
|
||||||
setIsDeleting(false);
|
setIsDeleting(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,12 +27,8 @@ export default function SettingsPage() {
|
|||||||
setIsResetting(true);
|
setIsResetting(true);
|
||||||
try {
|
try {
|
||||||
const ipcClient = IpcClient.getInstance();
|
const ipcClient = IpcClient.getInstance();
|
||||||
const result = await ipcClient.resetAll();
|
await ipcClient.resetAll();
|
||||||
if (result.success) {
|
|
||||||
showSuccess("Successfully reset everything. Restart the application.");
|
showSuccess("Successfully reset everything. Restart the application.");
|
||||||
} else {
|
|
||||||
showError(result.message || "Failed to reset everything.");
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error resetting:", error);
|
console.error("Error resetting:", error);
|
||||||
showError(
|
showError(
|
||||||
|
|||||||
Reference in New Issue
Block a user