diff --git a/src/components/chat/ChatInput.tsx b/src/components/chat/ChatInput.tsx index a45b83c..07de978 100644 --- a/src/components/chat/ChatInput.tsx +++ b/src/components/chat/ChatInput.tsx @@ -58,7 +58,10 @@ import { useVersions } from "@/hooks/useVersions"; import { useAttachments } from "@/hooks/useAttachments"; import { AttachmentsList } from "./AttachmentsList"; import { DragDropOverlay } from "./DragDropOverlay"; - +import { + showError as showErrorToast, + showUncommittedFilesWarning, +} from "@/lib/toast"; const showTokenBarAtom = atom(false); export function ChatInput({ chatId }: { chatId?: number }) { @@ -189,6 +192,9 @@ export function ChatInput({ chatId }: { chatId?: number }) { console.error("Failed to approve proposal:", result.error); setError(result.error || "Failed to approve proposal"); } + if (result.uncommittedFiles) { + showUncommittedFilesWarning(result.uncommittedFiles); + } } catch (err) { console.error("Error approving proposal:", err); setError((err as Error)?.message || "An error occurred while approving"); diff --git a/src/hooks/useStreamChat.ts b/src/hooks/useStreamChat.ts index c606ed1..282a162 100644 --- a/src/hooks/useStreamChat.ts +++ b/src/hooks/useStreamChat.ts @@ -14,7 +14,7 @@ import { useChats } from "./useChats"; import { useLoadApp } from "./useLoadApp"; import { selectedAppIdAtom } from "@/atoms/appAtoms"; import { useVersions } from "./useVersions"; -import { showError } from "@/lib/toast"; +import { showError, showUncommittedFilesWarning } from "@/lib/toast"; import { useProposal } from "./useProposal"; import { useSearch } from "@tanstack/react-router"; import { useRunApp } from "./useRunApp"; @@ -87,6 +87,9 @@ export function useStreamChat({ setIsPreviewOpen(true); refreshAppIframe(); } + if (response.uncommittedFiles) { + showUncommittedFilesWarning(response.uncommittedFiles); + } refreshProposal(chatId); // Keep the same as below diff --git a/src/ipc/handlers/chat_stream_handlers.ts b/src/ipc/handlers/chat_stream_handlers.ts index 99eed0c..5d87b4f 100644 --- a/src/ipc/handlers/chat_stream_handlers.ts +++ b/src/ipc/handlers/chat_stream_handlers.ts @@ -486,6 +486,7 @@ This conversation includes one or more image attachments. When the user uploads event.sender.send("chat:response:end", { chatId: req.chatId, updatedFiles: status.updatedFiles ?? false, + uncommittedFiles: status.uncommittedFiles, } satisfies ChatResponseEnd); } else { event.sender.send("chat:response:end", { diff --git a/src/ipc/handlers/proposal_handlers.ts b/src/ipc/handlers/proposal_handlers.ts index 0658a88..674b674 100644 --- a/src/ipc/handlers/proposal_handlers.ts +++ b/src/ipc/handlers/proposal_handlers.ts @@ -331,7 +331,11 @@ const getProposalHandler = async ( const approveProposalHandler = async ( _event: IpcMainInvokeEvent, { chatId, messageId }: { chatId: number; messageId: number } -): Promise<{ success: boolean; error?: string }> => { +): Promise<{ + success: boolean; + error?: string; + uncommittedFiles?: string[]; +}> => { logger.log( `IPC: approve-proposal called for chatId: ${chatId}, messageId: ${messageId}` ); @@ -380,7 +384,7 @@ const approveProposalHandler = async ( }; } - return { success: true }; + return { success: true, uncommittedFiles: processResult.uncommittedFiles }; } catch (error) { logger.error(`Error approving proposal for messageId ${messageId}:`, error); return { diff --git a/src/ipc/ipc_client.ts b/src/ipc/ipc_client.ts index 6717b2c..5b30806 100644 --- a/src/ipc/ipc_client.ts +++ b/src/ipc/ipc_client.ts @@ -111,10 +111,10 @@ export class IpcClient { }); this.ipcRenderer.on("chat:response:end", (payload) => { - const { chatId, updatedFiles } = payload as unknown as ChatResponseEnd; + const { chatId } = payload as unknown as ChatResponseEnd; const callbacks = this.chatStreams.get(chatId); if (callbacks) { - callbacks.onEnd({ chatId, updatedFiles }); + callbacks.onEnd(payload as unknown as ChatResponseEnd); console.debug("chat:response:end"); this.chatStreams.delete(chatId); } else { @@ -734,13 +734,21 @@ export class IpcClient { }: { chatId: number; messageId: number; - }): Promise<{ success: boolean; error?: string }> { + }): Promise<{ + success: boolean; + error?: string; + uncommittedFiles?: string[]; + }> { try { const result = await this.ipcRenderer.invoke("approve-proposal", { chatId, messageId, }); - return result as { success: boolean; error?: string }; + return result as { + success: boolean; + error?: string; + uncommittedFiles?: string[]; + }; } catch (error) { showError(error); return { success: false, error: (error as Error).message }; diff --git a/src/ipc/ipc_types.ts b/src/ipc/ipc_types.ts index b62a2f9..dcc0cbb 100644 --- a/src/ipc/ipc_types.ts +++ b/src/ipc/ipc_types.ts @@ -24,6 +24,7 @@ export interface ChatStreamParams { export interface ChatResponseEnd { chatId: number; updatedFiles: boolean; + uncommittedFiles?: string[]; } export interface CreateAppParams { diff --git a/src/ipc/processors/response_processor.ts b/src/ipc/processors/response_processor.ts index 8489158..8d820b1 100644 --- a/src/ipc/processors/response_processor.ts +++ b/src/ipc/processors/response_processor.ts @@ -175,7 +175,11 @@ export async function processFullResponseActions( chatSummary, messageId, }: { chatSummary: string | undefined; messageId: number } -): Promise<{ updatedFiles?: boolean; error?: string }> { +): Promise<{ + updatedFiles?: boolean; + error?: string; + uncommittedFiles?: string[]; +}> { logger.log("processFullResponseActions for chatId", chatId); // Get the app associated with the chat const chatWithApp = await db.query.chats.findFirst({ @@ -453,6 +457,13 @@ export async function processFullResponseActions( }) .where(eq(messages.id, messageId)); } + + // Check for any uncommitted changes after the commit + const statusMatrix = await git.statusMatrix({ fs, dir: appPath }); + const uncommittedFiles = statusMatrix + .filter((row) => row[1] !== 1 || row[2] !== 1 || row[3] !== 1) + .map((row) => row[0]); // Get just the file paths + logger.log("mark as approved: hasChanges", hasChanges); // Update the message to approved await db @@ -461,7 +472,12 @@ export async function processFullResponseActions( approvalState: "approved", }) .where(eq(messages.id, messageId)); - return { updatedFiles: hasChanges }; + + return { + updatedFiles: hasChanges, + uncommittedFiles: + uncommittedFiles.length > 0 ? uncommittedFiles : undefined, + }; } catch (error: unknown) { logger.error("Error processing files:", error); return { error: (error as any).toString() }; diff --git a/src/lib/toast.ts b/src/lib/toast.ts index 4c5c054..adab74f 100644 --- a/src/lib/toast.ts +++ b/src/lib/toast.ts @@ -21,6 +21,15 @@ export const showError = (message: any) => { console.error(message); }; +/** + * Show a warning toast + * @param message The warning message to display + */ +export const showWarning = (message: string) => { + toast.warning(message); + console.warn(message); +}; + /** * Show an info toast * @param message The info message to display @@ -49,5 +58,12 @@ export const showLoading = ( }); }; +export const showUncommittedFilesWarning = (files: string[]) => { + showWarning( + `Some changed files were not committed. Please use git to manually commit them. + \n\n${files.join("\n")}` + ); +}; + // Re-export for direct use export { toast };