Unverified 提交 c71638a5 authored 作者: Will Chen's avatar Will Chen 提交者: GitHub

Simplify handlers & IPC client: move from Result pattern to throwing errors (#120)

上级 26305ee0
...@@ -92,11 +92,7 @@ export function ChatList({ show }: { show?: boolean }) { ...@@ -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) { ...@@ -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) { ...@@ -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"} ...@@ -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"; ...@@ -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 }) { ...@@ -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 }) { ...@@ -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,13 +375,17 @@ function SummarizeInNewChatButton() { ...@@ -389,13 +375,17 @@ function SummarizeInNewChatButton() {
console.error("No app id found"); console.error("No app id found");
return; return;
} }
const newChatId = await IpcClient.getInstance().createChat(appId); try {
// navigate to new chat const newChatId = await IpcClient.getInstance().createChat(appId);
await navigate({ to: "/chat", search: { id: newChatId } }); // navigate to new chat
await streamMessage({ await navigate({ to: "/chat", search: { id: newChatId } });
prompt: "Summarize from chat-id=" + chatId, await streamMessage({
chatId: newChatId, prompt: "Summarize from chat-id=" + chatId,
}); chatId: newChatId,
});
} catch (err) {
showError(err);
}
}; };
return ( return (
<TooltipProvider> <TooltipProvider>
......
...@@ -89,14 +89,13 @@ export const MessagesList = forwardRef<HTMLDivElement, MessagesListProps>( ...@@ -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"; ...@@ -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) => { ...@@ -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() { ...@@ -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);
setError(null);
if (result.success) {
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() { ...@@ -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);
setError(null);
if (result.success) {
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"; ...@@ -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,75 +139,80 @@ async function killProcessOnPort(port: number): Promise<void> { ...@@ -137,75 +139,80 @@ async function killProcessOnPort(port: number): Promise<void> {
} }
export function registerAppHandlers() { export function registerAppHandlers() {
ipcMain.handle("create-app", async (_, params: CreateAppParams) => { handle(
const appPath = params.name; "create-app",
const fullAppPath = getDyadAppPath(appPath); async (
if (fs.existsSync(fullAppPath)) { _,
throw new Error(`App already exists at: ${fullAppPath}`); params: CreateAppParams,
} ): Promise<{ app: any; chatId: number }> => {
// Create a new app const appPath = params.name;
const [app] = await db const fullAppPath = getDyadAppPath(appPath);
.insert(apps) if (fs.existsSync(fullAppPath)) {
.values({ throw new Error(`App already exists at: ${fullAppPath}`);
name: params.name, }
// Use the name as the path for now // Create a new app
path: appPath, const [app] = await db
}) .insert(apps)
.returning(); .values({
name: params.name,
// Create an initial chat for this app // Use the name as the path for now
const [chat] = await db path: appPath,
.insert(chats) })
.values({ .returning();
appId: app.id,
})
.returning();
// Start async operations in background
try {
// Copy scaffold asynchronously
await copyDirectoryRecursive(
path.join(__dirname, "..", "..", "scaffold"),
fullAppPath,
);
// Initialize git repo and create first commit
await git.init({
fs: fs,
dir: fullAppPath,
defaultBranch: "main",
});
// Stage all files // Create an initial chat for this app
await git.add({ const [chat] = await db
fs: fs, .insert(chats)
dir: fullAppPath, .values({
filepath: ".", appId: app.id,
}); })
.returning();
// Create initial commit // Start async operations in background
const commitHash = await git.commit({ try {
fs: fs, // Copy scaffold asynchronously
dir: fullAppPath, await copyDirectoryRecursive(
message: "Init from react vite template", path.join(__dirname, "..", "..", "scaffold"),
author: await getGitAuthor(), fullAppPath,
}); );
// Initialize git repo and create first commit
await git.init({
fs: fs,
dir: fullAppPath,
defaultBranch: "main",
});
// Update chat with initial commit hash // Stage all files
await db await git.add({
.update(chats) fs: fs,
.set({ dir: fullAppPath,
initialCommitHash: commitHash, filepath: ".",
}) });
.where(eq(chats.id, chat.id));
} catch (error) {
logger.error("Error in background app initialization:", error);
}
// })();
return { app, chatId: chat.id }; // Create initial commit
}); const commitHash = await git.commit({
fs: fs,
dir: fullAppPath,
message: "Init from react vite template",
author: await getGitAuthor(),
});
// Update chat with initial commit hash
await db
.update(chats)
.set({
initialCommitHash: commitHash,
})
.where(eq(chats.id, chat.id));
} catch (error) {
logger.error("Error in background app initialization:", error);
}
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() { ...@@ -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() { ...@@ -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() { ...@@ -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,56 +340,56 @@ export function registerAppHandlers() { ...@@ -333,56 +340,56 @@ export function registerAppHandlers() {
}, },
); );
ipcMain.handle("stop-app", async (_, { appId }: { appId: number }) => { ipcMain.handle(
logger.log( "stop-app",
`Attempting to stop app ${appId}. Current running apps: ${runningApps.size}`, async (_, { appId }: { appId: number }): Promise<void> => {
);
return withLock(appId, async () => {
const appInfo = runningApps.get(appId);
if (!appInfo) {
logger.log(
`App ${appId} not found in running apps map. Assuming already stopped.`,
);
return {
success: true,
message: "App not running.",
};
}
const { process, processId } = appInfo;
logger.log( logger.log(
`Found running app ${appId} with processId ${processId} (PID: ${process.pid}). Attempting to stop.`, `Attempting to stop app ${appId}. Current running apps: ${runningApps.size}`,
); );
return withLock(appId, async () => {
const appInfo = runningApps.get(appId);
if (!appInfo) {
logger.log(
`App ${appId} not found in running apps map. Assuming already stopped.`,
);
return;
}
// Check if the process is already exited or closed const { process, processId } = appInfo;
if (process.exitCode !== null || process.signalCode !== null) {
logger.log( logger.log(
`Process for app ${appId} (PID: ${process.pid}) already exited (code: ${process.exitCode}, signal: ${process.signalCode}). Cleaning up map.`, `Found running app ${appId} with processId ${processId} (PID: ${process.pid}). Attempting to stop.`,
); );
runningApps.delete(appId); // Ensure cleanup if somehow missed
return { success: true, message: "Process already exited." };
}
try { // Check if the process is already exited or closed
// Use the killProcess utility to stop the process if (process.exitCode !== null || process.signalCode !== null) {
await killProcess(process); 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
return;
}
// Now, safely remove the app from the map *after* confirming closure try {
removeAppIfCurrentProcess(appId, process); // Use the killProcess utility to stop the process
await killProcess(process);
return { success: true }; // Now, safely remove the app from the map *after* confirming closure
} catch (error: any) { removeAppIfCurrentProcess(appId, process);
logger.error(
`Error stopping app ${appId} (PID: ${process.pid}, processId: ${processId}):`, return;
error, } catch (error: any) {
); logger.error(
// Attempt cleanup even if an error occurred during the stop process `Error stopping app ${appId} (PID: ${process.pid}, processId: ${processId}):`,
removeAppIfCurrentProcess(appId, process); error,
throw new Error(`Failed to stop app ${appId}: ${error.message}`); );
} // Attempt cleanup even if an error occurred during the stop process
}); removeAppIfCurrentProcess(appId, process);
}); 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() { ...@@ -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() { ...@@ -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() { ...@@ -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() { ...@@ -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,52 +520,57 @@ export function registerAppHandlers() { ...@@ -513,52 +520,57 @@ export function registerAppHandlers() {
}, },
); );
ipcMain.handle("delete-app", async (_, { appId }: { appId: number }) => { ipcMain.handle(
// Static server worker is NOT terminated here anymore "delete-app",
async (_, { appId }: { appId: number }): Promise<void> => {
// Static server worker is NOT terminated here anymore
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({
where: eq(apps.id, appId), where: eq(apps.id, appId),
}); });
if (!app) { if (!app) {
throw new Error("App not found"); throw new Error("App not found");
} }
// Stop the app if it's running // Stop the app if it's running
if (runningApps.has(appId)) { if (runningApps.has(appId)) {
const appInfo = runningApps.get(appId)!; const appInfo = runningApps.get(appId)!;
try {
logger.log(`Stopping app ${appId} before deletion.`); // Adjusted log
await killProcess(appInfo.process);
runningApps.delete(appId);
} catch (error: any) {
logger.error(`Error stopping app ${appId} before deletion:`, error); // Adjusted log
// Continue with deletion even if stopping fails
}
}
// Delete app files
const appPath = getDyadAppPath(app.path);
try { try {
logger.log(`Stopping app ${appId} before deletion.`); // Adjusted log await fsPromises.rm(appPath, { recursive: true, force: true });
await killProcess(appInfo.process);
runningApps.delete(appId);
} catch (error: any) { } catch (error: any) {
logger.error(`Error stopping app ${appId} before deletion:`, error); // Adjusted log logger.error(`Error deleting app files for app ${appId}:`, error);
// Continue with deletion even if stopping fails throw new Error(`Failed to delete app files: ${error.message}`);
} }
}
// Delete app files
const appPath = getDyadAppPath(app.path);
try {
await fsPromises.rm(appPath, { recursive: true, force: true });
} catch (error: any) {
logger.error(`Error deleting app files for app ${appId}:`, error);
throw new Error(`Failed to delete app files: ${error.message}`);
}
// Delete app from database // Delete app from database
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() { ...@@ -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() { ...@@ -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() { ...@@ -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() { ...@@ -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() { ...@@ -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"; ...@@ -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,54 +76,38 @@ export function registerChatHandlers() { ...@@ -74,54 +76,38 @@ export function registerChatHandlers() {
return chat; return chat;
}); });
ipcMain.handle( handle("get-chats", async (_, appId?: number): Promise<ChatSummary[]> => {
"get-chats", // If appId is provided, filter chats for that app
async (_, appId?: number): Promise<ChatSummary[]> => { const query = appId
// If appId is provided, filter chats for that app ? db.query.chats.findMany({
const query = appId where: eq(chats.appId, appId),
? db.query.chats.findMany({ columns: {
where: eq(chats.appId, appId), id: true,
columns: { title: true,
id: true, createdAt: true,
title: true, appId: true,
createdAt: true, },
appId: true, orderBy: [desc(chats.createdAt)],
}, })
orderBy: [desc(chats.createdAt)], : db.query.chats.findMany({
}) columns: {
: db.query.chats.findMany({ id: true,
columns: { title: true,
id: true, createdAt: true,
title: true, appId: true,
createdAt: true, },
appId: true, orderBy: [desc(chats.createdAt)],
}, });
orderBy: [desc(chats.createdAt)],
});
const allChats = await query; const allChats = await query;
return allChats; return allChats;
}, });
);
ipcMain.handle("delete-chat", async (_, chatId: number) => { handle("delete-chat", async (_, chatId: number): Promise<void> => {
try { await db.delete(chats).where(eq(chats.id, chatId));
// 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-messages", async (_, chatId: number): Promise<void> => {
try { 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 };
}
}); });
} }
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( ...@@ -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,49 +323,44 @@ async function handleIsRepoAvailable( ...@@ -323,49 +323,44 @@ 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) { throw new Error("Not authenticated with GitHub.");
return { success: false, 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; if (!owner) {
if (!owner) { const userRes = await fetch("https://api.github.com/user", {
const userRes = await fetch("https://api.github.com/user", { headers: { Authorization: `Bearer ${accessToken}` },
headers: { Authorization: `Bearer ${accessToken}` },
});
const user = await userRes.json();
owner = user.login;
}
// Create repo
const createUrl = org
? `https://api.github.com/orgs/${owner}/repos`
: `https://api.github.com/user/repos`;
const res = await fetch(createUrl, {
method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
Accept: "application/vnd.github+json",
},
body: JSON.stringify({
name: repo,
private: true,
}),
}); });
if (!res.ok) { const user = await userRes.json();
const data = await res.json(); owner = user.login;
return { success: false, error: data.message || "Failed to create repo" }; }
} // Create repo
// Store org and repo in the app's DB row (apps table) const createUrl = org
await updateAppGithubRepo(appId, owner, repo); ? `https://api.github.com/orgs/${owner}/repos`
return { success: true }; : `https://api.github.com/user/repos`;
} catch (err: any) { const res = await fetch(createUrl, {
return { success: false, error: err.message || "Unknown error" }; method: "POST",
headers: {
Authorization: `Bearer ${accessToken}`,
"Content-Type": "application/json",
Accept: "application/vnd.github+json",
},
body: JSON.stringify({
name: repo,
private: true,
}),
});
if (!res.ok) {
const data = await res.json();
throw new Error(data.message || "Failed to create repo");
} }
// Store org and repo in the app's DB row (apps table)
await updateAppGithubRepo(appId, owner, repo);
} }
// --- GitHub Push Handler --- // --- GitHub Push Handler ---
...@@ -420,36 +415,26 @@ async function handlePushToGithub( ...@@ -420,36 +415,26 @@ 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
const app = await db.query.apps.findFirst({
where: eq(apps.id, appId),
});
if (!app) {
return { success: false, error: "App not found" };
}
// Update app in database to remove GitHub repo and org // Get the app from the database
await db const app = await db.query.apps.findFirst({
.update(apps) where: eq(apps.id, appId),
.set({ });
githubRepo: null,
githubOrg: null,
})
.where(eq(apps.id, appId));
return { success: true }; if (!app) {
} catch (error) { throw new Error("App not found");
logger.error(`Error disconnecting GitHub repo: ${error}`);
return {
success: false,
error: error instanceof Error ? error.message : String(error),
};
} }
// Update app in database to remove GitHub repo and org
await db
.update(apps)
.set({
githubRepo: null,
githubOrg: null,
})
.where(eq(apps.id, appId));
} }
// --- Registration --- // --- Registration ---
......
...@@ -18,28 +18,24 @@ export interface LMStudioModel { ...@@ -18,28 +18,24 @@ 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", );
); if (!modelsResponse.ok) {
if (!modelsResponse.ok) { throw new Error("Failed to fetch models from LM Studio");
throw new Error("Failed to fetch models from LM Studio");
}
const modelsJson = await modelsResponse.json();
const downloadedModels = modelsJson.data as LMStudioModel[];
const models: LocalModel[] = downloadedModels
.filter((model: any) => model.type === "llm")
.map((model: any) => ({
modelName: model.id,
displayName: model.id,
provider: "lmstudio",
}));
logger.info(`Successfully fetched ${models.length} models from LM Studio`);
return { models, error: null };
} catch {
return { models: [], error: "Failed to fetch models from LM Studio" };
} }
const modelsJson = await modelsResponse.json();
const downloadedModels = modelsJson.data as LMStudioModel[];
const models: LocalModel[] = downloadedModels
.filter((model: any) => model.type === "llm")
.map((model: any) => ({
modelName: model.id,
displayName: model.id,
provider: "lmstudio",
}));
logger.info(`Successfully fetched ${models.length} models from LM Studio`);
return { models };
} }
export function registerLMStudioHandlers() { export function registerLMStudioHandlers() {
......
...@@ -47,20 +47,17 @@ export async function fetchOllamaModels(): Promise<LocalModelListResponse> { ...@@ -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 { "Could not connect to Ollama. Make sure it's running at http://localhost:11434",
models: [], );
error:
"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");
} }
} }
......
import { ipcMain, type IpcMainInvokeEvent } from "electron"; import { type IpcMainInvokeEvent } from "electron";
import type { import type {
CodeProposal, CodeProposal,
ProposalResult, ProposalResult,
...@@ -29,9 +29,9 @@ import { ...@@ -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,115 +317,83 @@ const approveProposalHandler = async ( ...@@ -317,115 +317,83 @@ 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( // 1. Fetch the specific assistant message
`IPC: approve-proposal called for chatId: ${chatId}, messageId: ${messageId}`, const messageToApprove = await db.query.messages.findFirst({
); where: and(
eq(messages.id, messageId),
try { eq(messages.chatId, chatId),
// 1. Fetch the specific assistant message eq(messages.role, "assistant"),
const messageToApprove = await db.query.messages.findFirst({ ),
where: and( columns: {
eq(messages.id, messageId), content: true,
eq(messages.chatId, chatId), },
eq(messages.role, "assistant"), });
),
columns: {
content: true,
},
});
if (!messageToApprove?.content) {
logger.error(
`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 if (!messageToApprove?.content) {
const chatSummary = getDyadChatSummaryTag(messageToApprove.content); throw new Error(
const processResult = await processFullResponseActions( `Assistant message not found for chatId: ${chatId}, messageId: ${messageId}`,
messageToApprove.content,
chatId,
{
chatSummary: chatSummary ?? undefined,
messageId,
}, // Pass summary if found
); );
}
if (processResult.error) { // 2. Process the actions defined in the message content
logger.error( const chatSummary = getDyadChatSummaryTag(messageToApprove.content);
`Error processing actions for message ${messageId}:`, const processResult = await processFullResponseActions(
processResult.error, messageToApprove.content,
); chatId,
// Optionally: Update message state to 'error' or similar? {
// For now, just return error to frontend chatSummary: chatSummary ?? undefined,
return { messageId,
success: false, }, // Pass summary if found
error: `Action processing failed: ${processResult.error}`, );
};
}
return { success: true, uncommittedFiles: processResult.uncommittedFiles }; if (processResult.error) {
} catch (error) { throw new Error(
logger.error(`Error approving proposal for messageId ${messageId}:`, error); `Error processing actions for message ${messageId}: ${processResult.error}`,
return { );
success: false,
error: (error as Error)?.message || "Unknown error",
};
} }
return { uncommittedFiles: processResult.uncommittedFiles };
}; };
// 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( eq(messages.id, messageId),
eq(messages.id, messageId), eq(messages.chatId, chatId),
eq(messages.chatId, chatId), eq(messages.role, "assistant"),
eq(messages.role, "assistant"), ),
), columns: { id: true },
columns: { id: true }, });
});
if (!messageToReject) {
logger.error(
`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' if (!messageToReject) {
await db throw new Error(
.update(messages) `Assistant message not found for chatId: ${chatId}, messageId: ${messageId}`,
.set({ approvalState: "rejected" }) );
.where(eq(messages.id, messageId));
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",
};
} }
// 2. Update the message's approval state to 'rejected'
await db
.update(messages)
.set({ approvalState: "rejected" })
.where(eq(messages.id, messageId));
logger.log(`Message ${messageId} marked as rejected.`);
}; };
// 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);
} }
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) { ...@@ -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"; ...@@ -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>) => {
......
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://"))) {
await shell.openExternal(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 };
} }
if (!url.startsWith("http://") && !url.startsWith("https://")) {
throw new Error("Attempted to open invalid or non-http URL: " + url);
}
await shell.openExternal(url);
logger.debug("Opened external URL:", url);
}); });
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) { throw new Error("No file path provided.");
logger.error("Attempted to show item with empty path");
return {
success: false,
error: "No file path provided.",
};
}
shell.showItemInFolder(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 };
} }
shell.showItemInFolder(fullPath);
logger.debug("Showed item in folder:", fullPath);
}); });
} }
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 () => { const supabase = await getSupabaseClient();
try { return supabase.getProjects();
const supabase = await getSupabaseClient();
// Call the API according to supabase-management-js structure
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 { await db
// Here you could store the project-app association in your database .update(apps)
// For example: .set({ supabaseProjectId: project })
await db .where(eq(apps.id, app));
.update(apps)
.set({ supabaseProjectId: project })
.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", await db
async (_, { app }: { app: number }) => { .update(apps)
try { .set({ supabaseProjectId: null })
await db .where(eq(apps.id, app));
.update(apps)
.set({ supabaseProjectId: null })
.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;
}
},
);
} }
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,91 +14,82 @@ import { getSupabaseContext } from "../../supabase_admin/supabase_context"; ...@@ -15,91 +14,82 @@ 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 { const chat = await db.query.chats.findFirst({
// Get the chat with messages where: eq(chats.id, req.chatId),
const chat = await db.query.chats.findFirst({ with: {
where: eq(chats.id, req.chatId), messages: {
with: { orderBy: (messages, { asc }) => [asc(messages.createdAt)],
messages: {
orderBy: (messages, { asc }) => [asc(messages.createdAt)],
},
app: true,
}, },
}); app: true,
},
});
if (!chat) { if (!chat) {
throw new Error(`Chat not found: ${req.chatId}`); throw new Error(`Chat not found: ${req.chatId}`);
} }
// Prepare message history for token counting // Prepare message history for token counting
const messageHistory = chat.messages const messageHistory = chat.messages
.map((message) => message.content) .map((message) => message.content)
.join(""); .join("");
const messageHistoryTokens = estimateTokens(messageHistory); const messageHistoryTokens = estimateTokens(messageHistory);
// Count input tokens // Count input tokens
const inputTokens = estimateTokens(req.input); const inputTokens = estimateTokens(req.input);
// Count system prompt tokens // Count system prompt tokens
let systemPrompt = SYSTEM_PROMPT; let systemPrompt = SYSTEM_PROMPT;
let supabaseContext = ""; let supabaseContext = "";
if (chat.app?.supabaseProjectId) { if (chat.app?.supabaseProjectId) {
systemPrompt += "\n\n" + SUPABASE_AVAILABLE_SYSTEM_PROMPT; systemPrompt += "\n\n" + SUPABASE_AVAILABLE_SYSTEM_PROMPT;
supabaseContext = await getSupabaseContext({ supabaseContext = await getSupabaseContext({
supabaseProjectId: chat.app.supabaseProjectId, supabaseProjectId: chat.app.supabaseProjectId,
}); });
} else { } else {
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 = "";
let codebaseTokens = 0; let codebaseTokens = 0;
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
const totalTokens = const totalTokens =
messageHistoryTokens + messageHistoryTokens +
inputTokens + inputTokens +
systemPromptTokens + systemPromptTokens +
codebaseTokens; codebaseTokens;
return { return {
totalTokens, totalTokens,
messageHistoryTokens, messageHistoryTokens,
codebaseTokens, codebaseTokens,
inputTokens, inputTokens,
systemPromptTokens, systemPromptTokens,
contextWindow: getContextWindow(), contextWindow: getContextWindow(),
}; };
} catch (error) {
logger.error("Error counting tokens:", error);
throw error;
}
}, },
); );
} }
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,49 +13,37 @@ interface UploadToSignedUrlParams { ...@@ -11,49 +13,37 @@ interface UploadToSignedUrlParams {
} }
export function registerUploadHandlers() { export function registerUploadHandlers() {
ipcMain.handle( handle("upload-to-signed-url", async (_, params: UploadToSignedUrlParams) => {
"upload-to-signed-url", const { url, contentType, data } = params;
async (_, params: UploadToSignedUrlParams) => { logger.debug("IPC: upload-to-signed-url called");
const { url, contentType, data } = params;
logger.debug("IPC: upload-to-signed-url called"); // Validate the signed URL
if (!url || typeof url !== "string" || !url.startsWith("https://")) {
try { throw new Error("Invalid signed URL provided");
// Validate the signed URL }
if (!url || typeof url !== "string" || !url.startsWith("https://")) {
throw new Error("Invalid signed URL provided"); // Validate content type
} if (!contentType || typeof contentType !== "string") {
throw new Error("Invalid content type provided");
// Validate content type }
if (!contentType || typeof contentType !== "string") {
throw new Error("Invalid content type provided"); // Perform the upload to the signed URL
} const response = await fetch(url, {
method: "PUT",
// Perform the upload to the signed URL headers: {
const response = await fetch(url, { "Content-Type": contentType,
method: "PUT", },
headers: { body: JSON.stringify(data),
"Content-Type": contentType, });
},
body: JSON.stringify(data), if (!response.ok) {
}); throw new Error(
`Upload failed with status ${response.status}: ${response.statusText}`,
if (!response.ok) { );
throw new Error( }
`Upload failed with status ${response.status}: ${response.statusText}`,
); 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"; ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 }> { appId,
try { });
const result = await this.ipcRenderer.invoke("github:disconnect", {
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 { ...@@ -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 { ...@@ -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 }> { app,
try { });
const result = await this.ipcRenderer.invoke(
"supabase:unset-app-project",
{
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( return response?.models || [];
"local-models:list-ollama",
);
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 { ...@@ -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 { ...@@ -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() { ...@@ -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() { ...@@ -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(
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论