Files Changed
diff --git a/src/db/schema.ts b/src/db/schema.ts
index d0d14a0..9084c96 100644
--- a/src/db/schema.ts
+++ b/src/db/schema.ts
@@ -35,7 +35,7 @@ export const messages = sqliteTable("messages", {
role: text("role", { enum: ["user", "assistant"] }).notNull(),
content: text("content").notNull(),
approvalState: text("approval_state", {
- enum: ["approved", "rejected", "pending"],
+ enum: ["approved", "rejected"],
}),
createdAt: integer("created_at", { mode: "timestamp" })
.notNull()
diff --git a/src/ipc/handlers/proposal_handlers.ts b/src/ipc/handlers/proposal_handlers.ts
index 8d665d7..ef3d7b2 100644
--- a/src/ipc/handlers/proposal_handlers.ts
+++ b/src/ipc/handlers/proposal_handlers.ts
@@ -1,43 +1,86 @@
import { ipcMain, type IpcMainInvokeEvent } from "electron";
import type { Proposal } from "@/lib/schemas";
+import { db } from "../../db";
+import { messages } from "../../db/schema";
+import { desc, eq, and } from "drizzle-orm";
+import path from "node:path"; // Import path for basename
+// Import tag parsers
+import {
+ getDyadChatSummaryTag,
+ getDyadWriteTags,
+} from "../processors/response_processor";
-// Placeholder Proposal data
-const placeholderProposal: Proposal = {
- title: "Review: Example Refactoring (from IPC)",
- securityRisks: [
- {
- type: "warning",
- title: "Potential XSS Vulnerability",
- description: "User input is directly rendered without sanitization.",
- },
- {
- type: "danger",
- title: "Hardcoded API Key",
- description: "API key found in plain text in configuration file.",
- },
- ],
- filesChanged: [
- {
- name: "ChatInput.tsx",
- path: "src/components/chat/ChatInput.tsx",
- summary: "Added review actions and details section.",
- },
- {
- name: "api.ts",
- path: "src/lib/api.ts",
- summary: "Refactored API call structure.",
- },
- ],
-};
+// Placeholder Proposal data (can be removed or kept for reference)
+// const placeholderProposal: Proposal = { ... };
+
+// Type guard for the parsed proposal structure
+interface ParsedProposal {
+ title: string;
+ files: string[];
+}
+
+function isParsedProposal(obj: any): obj is ParsedProposal {
+ return (
+ obj &&
+ typeof obj === "object" &&
+ typeof obj.title === "string" &&
+ Array.isArray(obj.files) &&
+ obj.files.every((file: any) => typeof file === "string")
+ );
+}
const getProposalHandler = async (
_event: IpcMainInvokeEvent,
{ chatId }: { chatId: number }
-): Promise
=> {
+): Promise => {
console.log(`IPC: get-proposal called for chatId: ${chatId}`);
- // Simulate async operation
- await new Promise((resolve) => setTimeout(resolve, 500)); // 500ms delay
- return placeholderProposal;
+
+ try {
+ // Find the latest ASSISTANT message for the chat
+ const latestAssistantMessage = await db.query.messages.findFirst({
+ where: and(eq(messages.chatId, chatId), eq(messages.role, "assistant")),
+ orderBy: [desc(messages.createdAt)],
+ columns: {
+ content: true, // Fetch the content to parse
+ },
+ });
+
+ if (latestAssistantMessage?.content) {
+ console.log("Found latest assistant message, parsing content...");
+ const messageContent = latestAssistantMessage.content;
+
+ // Parse tags directly from message content
+ const proposalTitle = getDyadChatSummaryTag(messageContent);
+ const proposalFiles = getDyadWriteTags(messageContent); // Gets { path: string, content: string }[]
+
+ // Check if we have enough information to create a proposal
+ if (proposalTitle || proposalFiles.length > 0) {
+ const proposal: Proposal = {
+ // Use parsed title or a default title if summary tag is missing but write tags exist
+ title: proposalTitle ?? "Proposed File Changes",
+ securityRisks: [], // Keep empty
+ filesChanged: proposalFiles.map((tag) => ({
+ name: path.basename(tag.path),
+ path: tag.path,
+ summary: tag.description ?? "(no change summary found)", // Generic summary
+ })),
+ };
+ console.log("Generated proposal on the fly:", proposal);
+ return proposal;
+ } else {
+ console.log(
+ "No relevant tags found in the latest assistant message content."
+ );
+ return null; // No proposal could be generated
+ }
+ } else {
+ console.log(`No assistant message found for chatId: ${chatId}`);
+ return null; // No message found
+ }
+ } catch (error) {
+ console.error(`Error processing proposal for chatId ${chatId}:`, error);
+ return null; // Indicate DB or processing error
+ }
};
// Function to register proposal-related handlers
diff --git a/src/ipc/processors/response_processor.ts b/src/ipc/processors/response_processor.ts
index 0210273..b18cdb6 100644
--- a/src/ipc/processors/response_processor.ts
+++ b/src/ipc/processors/response_processor.ts
@@ -11,20 +11,42 @@ import { getGitAuthor } from "../utils/git_author";
export function getDyadWriteTags(fullResponse: string): {
path: string;
content: string;
+ description?: string;
}[] {
- const dyadWriteRegex =
- /]*>([\s\S]*?)<\/dyad-write>/g;
+ const dyadWriteRegex = /]*)>([\s\S]*?)<\/dyad-write>/gi;
+ const pathRegex = /path="([^"]+)"/;
+ const descriptionRegex = /description="([^"]+)"/;
+
let match;
- const tags: { path: string; content: string }[] = [];
+ const tags: { path: string; content: string; description?: string }[] = [];
+
while ((match = dyadWriteRegex.exec(fullResponse)) !== null) {
- const content = match[2].trim().split("\n");
- if (content[0].startsWith("```")) {
- content.shift();
+ const attributesString = match[1];
+ let content = match[2].trim();
+
+ const pathMatch = pathRegex.exec(attributesString);
+ const descriptionMatch = descriptionRegex.exec(attributesString);
+
+ if (pathMatch && pathMatch[1]) {
+ const path = pathMatch[1];
+ const description = descriptionMatch?.[1];
+
+ const contentLines = content.split("\n");
+ if (contentLines[0]?.startsWith("```")) {
+ contentLines.shift();
+ }
+ if (contentLines[contentLines.length - 1]?.startsWith("```")) {
+ contentLines.pop();
+ }
+ content = contentLines.join("\n");
+
+ tags.push({ path, content, description });
+ } else {
+ console.warn(
+ "Found tag without a valid 'path' attribute:",
+ match[0]
+ );
}
- if (content[content.length - 1].startsWith("```")) {
- content.pop();
- }
- tags.push({ path: match[1], content: content.join("\n") });
}
return tags;
}
@@ -65,6 +87,16 @@ export function getDyadAddDependencyTags(fullResponse: string): string[] {
return packages;
}
+export function getDyadChatSummaryTag(fullResponse: string): string | null {
+ const dyadChatSummaryRegex =
+ /([\s\S]*?)<\/dyad-chat-summary>/g;
+ const match = dyadChatSummaryRegex.exec(fullResponse);
+ if (match && match[1]) {
+ return match[1].trim();
+ }
+ return null;
+}
+
export async function processFullResponseActions(
fullResponse: string,
chatId: number,
@@ -206,6 +238,7 @@ export async function processFullResponseActions(
if (deletedFiles.length > 0)
changes.push(`deleted ${deletedFiles.length} file(s)`);
+ // Use chat summary, if provided, or default for commit message
await git.commit({
fs,
dir: appPath,