8
e2e-tests/dyad_tags_parsing.spec.ts
Normal file
8
e2e-tests/dyad_tags_parsing.spec.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import { testSkipIfWindows } from "./helpers/test_helper";
|
||||||
|
|
||||||
|
testSkipIfWindows("dyad tags handles nested < tags", async ({ po }) => {
|
||||||
|
await po.setUp({ autoApprove: true });
|
||||||
|
await po.importApp("minimal");
|
||||||
|
await po.sendPrompt("tc=dyad-write-angle");
|
||||||
|
await po.snapshotAppFiles();
|
||||||
|
});
|
||||||
5
e2e-tests/fixtures/dyad-write-angle.md
Normal file
5
e2e-tests/fixtures/dyad-write-angle.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
BEFORE TAG
|
||||||
|
<dyad-write path="src/foo/bar.tsx" description="page to use <a> and <b> tags.">
|
||||||
|
// BEGINNING OF FILE
|
||||||
|
</dyad-write>
|
||||||
|
AFTER TAG
|
||||||
@@ -0,0 +1,191 @@
|
|||||||
|
=== .gitignore ===
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
dist-ssr
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# Editor directories and files
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.suo
|
||||||
|
*.ntvs*
|
||||||
|
*.njsproj
|
||||||
|
*.sln
|
||||||
|
*.sw?
|
||||||
|
|
||||||
|
|
||||||
|
=== file1.txt ===
|
||||||
|
A file (2)
|
||||||
|
|
||||||
|
=== index.html ===
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>dyad-generated-app</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
||||||
|
|
||||||
|
=== package.json ===
|
||||||
|
{
|
||||||
|
"name": "vite_react_shadcn_ts",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"build:dev": "vite build --mode development",
|
||||||
|
"lint": "eslint .",
|
||||||
|
"preview": "vite preview"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"react": "^18.3.1",
|
||||||
|
"react-dom": "^18.3.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^22.5.5",
|
||||||
|
"@types/react": "^18.3.3",
|
||||||
|
"@types/react-dom": "^18.3.0",
|
||||||
|
"@vitejs/plugin-react-swc": "^3.9.0",
|
||||||
|
"typescript": "^5.5.3",
|
||||||
|
"vite": "^6.3.4"
|
||||||
|
},
|
||||||
|
"packageManager": "<scrubbed>"
|
||||||
|
}
|
||||||
|
|
||||||
|
=== src/App.tsx ===
|
||||||
|
const App = () => <div>Minimal imported app</div>;
|
||||||
|
|
||||||
|
export default App;
|
||||||
|
|
||||||
|
|
||||||
|
=== src/foo/bar.tsx ===
|
||||||
|
// BEGINNING OF FILE
|
||||||
|
|
||||||
|
=== src/main.tsx ===
|
||||||
|
import { createRoot } from "react-dom/client";
|
||||||
|
import App from "./App.tsx";
|
||||||
|
|
||||||
|
createRoot(document.getElementById("root")!).render(<App />);
|
||||||
|
|
||||||
|
|
||||||
|
=== src/vite-env.d.ts ===
|
||||||
|
/// <reference types="vite/client" />
|
||||||
|
|
||||||
|
|
||||||
|
=== tsconfig.app.json ===
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2020",
|
||||||
|
"useDefineForClassFields": true,
|
||||||
|
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": false,
|
||||||
|
"noUnusedLocals": false,
|
||||||
|
"noUnusedParameters": false,
|
||||||
|
"noImplicitAny": false,
|
||||||
|
"noFallthroughCasesInSwitch": false,
|
||||||
|
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
=== tsconfig.json ===
|
||||||
|
{
|
||||||
|
"files": [],
|
||||||
|
"references": [
|
||||||
|
{ "path": "./tsconfig.app.json" },
|
||||||
|
{ "path": "./tsconfig.node.json" }
|
||||||
|
],
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
},
|
||||||
|
"noImplicitAny": false,
|
||||||
|
"noUnusedParameters": false,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"allowJs": true,
|
||||||
|
"noUnusedLocals": false,
|
||||||
|
"strictNullChecks": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
=== tsconfig.node.json ===
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"lib": ["ES2023"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"skipLibCheck": true,
|
||||||
|
|
||||||
|
/* Bundler mode */
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"noEmit": true,
|
||||||
|
|
||||||
|
/* Linting */
|
||||||
|
"strict": true,
|
||||||
|
"noUnusedLocals": false,
|
||||||
|
"noUnusedParameters": false,
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
|
},
|
||||||
|
"include": ["vite.config.ts"]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
=== vite.config.ts ===
|
||||||
|
import { defineConfig } from "vite";
|
||||||
|
import react from "@vitejs/plugin-react-swc";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
export default defineConfig(() => ({
|
||||||
|
server: {
|
||||||
|
host: "::",
|
||||||
|
port: 8080,
|
||||||
|
},
|
||||||
|
plugins: [react()],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
"@": path.resolve(__dirname, "./src"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import git from "isomorphic-git";
|
import git from "isomorphic-git";
|
||||||
import { db } from "../db";
|
import { db } from "../db";
|
||||||
|
import { cleanFullResponse } from "@/ipc/utils/cleanFullResponse";
|
||||||
|
|
||||||
// Mock fs with default export
|
// Mock fs with default export
|
||||||
vi.mock("node:fs", async () => {
|
vi.mock("node:fs", async () => {
|
||||||
@@ -139,6 +140,110 @@ console.log("TodoItem");`,
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should handle missing description", () => {
|
||||||
|
const result = getDyadWriteTags(`
|
||||||
|
<dyad-write path="src/pages/locations/neighborhoods/louisville/Highlands.tsx">
|
||||||
|
import React from 'react';
|
||||||
|
</dyad-write>
|
||||||
|
`);
|
||||||
|
expect(result).toEqual([
|
||||||
|
{
|
||||||
|
path: "src/pages/locations/neighborhoods/louisville/Highlands.tsx",
|
||||||
|
description: undefined,
|
||||||
|
content: `import React from 'react';`,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle extra space", () => {
|
||||||
|
const result = getDyadWriteTags(
|
||||||
|
cleanFullResponse(`
|
||||||
|
<dyad-write path="src/pages/locations/neighborhoods/louisville/Highlands.tsx" description="Updating Highlands neighborhood page to use <a> tags." >
|
||||||
|
import React from 'react';
|
||||||
|
</dyad-write>
|
||||||
|
`),
|
||||||
|
);
|
||||||
|
expect(result).toEqual([
|
||||||
|
{
|
||||||
|
path: "src/pages/locations/neighborhoods/louisville/Highlands.tsx",
|
||||||
|
description: "Updating Highlands neighborhood page to use <a> tags.",
|
||||||
|
content: `import React from 'react';`,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle nested tags", () => {
|
||||||
|
const result = getDyadWriteTags(
|
||||||
|
cleanFullResponse(`
|
||||||
|
BEFORE TAG
|
||||||
|
<dyad-write path="src/pages/locations/neighborhoods/louisville/Highlands.tsx" description="Updating Highlands neighborhood page to use <a> tags.">
|
||||||
|
import React from 'react';
|
||||||
|
</dyad-write>
|
||||||
|
AFTER TAG
|
||||||
|
`),
|
||||||
|
);
|
||||||
|
expect(result).toEqual([
|
||||||
|
{
|
||||||
|
path: "src/pages/locations/neighborhoods/louisville/Highlands.tsx",
|
||||||
|
description: "Updating Highlands neighborhood page to use <a> tags.",
|
||||||
|
content: `import React from 'react';`,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle nested tags after preprocessing", () => {
|
||||||
|
// Simulate the preprocessing step that cleanFullResponse would do
|
||||||
|
const inputWithNestedTags = `
|
||||||
|
BEFORE TAG
|
||||||
|
<dyad-write path="src/pages/locations/neighborhoods/louisville/Highlands.tsx" description="Updating Highlands neighborhood page to use <a> tags.">
|
||||||
|
import React from 'react';
|
||||||
|
</dyad-write>
|
||||||
|
AFTER TAG
|
||||||
|
`;
|
||||||
|
|
||||||
|
const cleanedInput = cleanFullResponse(inputWithNestedTags);
|
||||||
|
|
||||||
|
const result = getDyadWriteTags(cleanedInput);
|
||||||
|
expect(result).toEqual([
|
||||||
|
{
|
||||||
|
path: "src/pages/locations/neighborhoods/louisville/Highlands.tsx",
|
||||||
|
description: "Updating Highlands neighborhood page to use <a> tags.",
|
||||||
|
content: `import React from 'react';`,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle multiple nested tags after preprocessing", () => {
|
||||||
|
const inputWithMultipleNestedTags = `<dyad-write path="src/file.tsx" description="Testing <div> and <span> and <a> tags.">content</dyad-write>`;
|
||||||
|
|
||||||
|
// This simulates what cleanFullResponse should do
|
||||||
|
const cleanedInput = cleanFullResponse(inputWithMultipleNestedTags);
|
||||||
|
const result = getDyadWriteTags(cleanedInput);
|
||||||
|
expect(result).toEqual([
|
||||||
|
{
|
||||||
|
path: "src/file.tsx",
|
||||||
|
description: "Testing <div> and <span> and <a> tags.",
|
||||||
|
content: `content`,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle nested tags in multiple attributes", () => {
|
||||||
|
const inputWithNestedInMultipleAttrs = `<dyad-write path="src/<component>.tsx" description="Testing <div> tags.">content</dyad-write>`;
|
||||||
|
|
||||||
|
// This simulates what cleanFullResponse should do
|
||||||
|
const cleanedInput = cleanFullResponse(inputWithNestedInMultipleAttrs);
|
||||||
|
|
||||||
|
const result = getDyadWriteTags(cleanedInput);
|
||||||
|
expect(result).toEqual([
|
||||||
|
{
|
||||||
|
path: "src/<component>.tsx",
|
||||||
|
description: "Testing <div> tags.",
|
||||||
|
content: `content`,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
it("should return an array of dyad-write tags", () => {
|
it("should return an array of dyad-write tags", () => {
|
||||||
const result = getDyadWriteTags(
|
const result = getDyadWriteTags(
|
||||||
`I'll create a simple todo list app using React, TypeScript, and shadcn/ui components. Let's get started!
|
`I'll create a simple todo list app using React, TypeScript, and shadcn/ui components. Let's get started!
|
||||||
|
|||||||
89
src/__tests__/cleanFullResponse.test.ts
Normal file
89
src/__tests__/cleanFullResponse.test.ts
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
import { cleanFullResponse } from "@/ipc/utils/cleanFullResponse";
|
||||||
|
import { describe, it, expect } from "vitest";
|
||||||
|
|
||||||
|
describe("cleanFullResponse", () => {
|
||||||
|
it("should replace < characters in dyad-write attributes", () => {
|
||||||
|
const input = `<dyad-write path="src/file.tsx" description="Testing <a> tags.">content</dyad-write>`;
|
||||||
|
const expected = `<dyad-write path="src/file.tsx" description="Testing <a> tags.">content</dyad-write>`;
|
||||||
|
|
||||||
|
const result = cleanFullResponse(input);
|
||||||
|
expect(result).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should replace < characters in multiple attributes", () => {
|
||||||
|
const input = `<dyad-write path="src/<component>.tsx" description="Testing <div> tags.">content</dyad-write>`;
|
||||||
|
const expected = `<dyad-write path="src/<component>.tsx" description="Testing <div> tags.">content</dyad-write>`;
|
||||||
|
|
||||||
|
const result = cleanFullResponse(input);
|
||||||
|
expect(result).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle multiple nested HTML tags in a single attribute", () => {
|
||||||
|
const input = `<dyad-write path="src/file.tsx" description="Testing <div> and <span> and <a> tags.">content</dyad-write>`;
|
||||||
|
const expected = `<dyad-write path="src/file.tsx" description="Testing <div> and <span> and <a> tags.">content</dyad-write>`;
|
||||||
|
|
||||||
|
const result = cleanFullResponse(input);
|
||||||
|
expect(result).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle complex example with mixed content", () => {
|
||||||
|
const input = `
|
||||||
|
BEFORE TAG
|
||||||
|
<dyad-write path="src/pages/locations/neighborhoods/louisville/Highlands.tsx" description="Updating Highlands neighborhood page to use <a> tags.">
|
||||||
|
import React from 'react';
|
||||||
|
</dyad-write>
|
||||||
|
AFTER TAG
|
||||||
|
`;
|
||||||
|
|
||||||
|
const expected = `
|
||||||
|
BEFORE TAG
|
||||||
|
<dyad-write path="src/pages/locations/neighborhoods/louisville/Highlands.tsx" description="Updating Highlands neighborhood page to use <a> tags.">
|
||||||
|
import React from 'react';
|
||||||
|
</dyad-write>
|
||||||
|
AFTER TAG
|
||||||
|
`;
|
||||||
|
|
||||||
|
const result = cleanFullResponse(input);
|
||||||
|
expect(result).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle other dyad tag types", () => {
|
||||||
|
const input = `<dyad-rename from="src/<old>.tsx" to="src/<new>.tsx"></dyad-rename>`;
|
||||||
|
const expected = `<dyad-rename from="src/<old>.tsx" to="src/<new>.tsx"></dyad-rename>`;
|
||||||
|
|
||||||
|
const result = cleanFullResponse(input);
|
||||||
|
expect(result).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle dyad-delete tags", () => {
|
||||||
|
const input = `<dyad-delete path="src/<component>.tsx"></dyad-delete>`;
|
||||||
|
const expected = `<dyad-delete path="src/<component>.tsx"></dyad-delete>`;
|
||||||
|
|
||||||
|
const result = cleanFullResponse(input);
|
||||||
|
expect(result).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should not affect content outside dyad tags", () => {
|
||||||
|
const input = `Some text with <regular> HTML tags. <dyad-write path="test.tsx" description="With <nested> tags.">content</dyad-write> More <html> here.`;
|
||||||
|
const expected = `Some text with <regular> HTML tags. <dyad-write path="test.tsx" description="With <nested> tags.">content</dyad-write> More <html> here.`;
|
||||||
|
|
||||||
|
const result = cleanFullResponse(input);
|
||||||
|
expect(result).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle empty attributes", () => {
|
||||||
|
const input = `<dyad-write path="src/file.tsx">content</dyad-write>`;
|
||||||
|
const expected = `<dyad-write path="src/file.tsx">content</dyad-write>`;
|
||||||
|
|
||||||
|
const result = cleanFullResponse(input);
|
||||||
|
expect(result).toBe(expected);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle attributes without < characters", () => {
|
||||||
|
const input = `<dyad-write path="src/file.tsx" description="Normal description">content</dyad-write>`;
|
||||||
|
const expected = `<dyad-write path="src/file.tsx" description="Normal description">content</dyad-write>`;
|
||||||
|
|
||||||
|
const result = cleanFullResponse(input);
|
||||||
|
expect(result).toBe(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -25,7 +25,7 @@ import {
|
|||||||
getSupabaseClientCode,
|
getSupabaseClientCode,
|
||||||
} from "../../supabase_admin/supabase_context";
|
} from "../../supabase_admin/supabase_context";
|
||||||
import { SUMMARIZE_CHAT_SYSTEM_PROMPT } from "../../prompts/summarize_chat_system_prompt";
|
import { SUMMARIZE_CHAT_SYSTEM_PROMPT } from "../../prompts/summarize_chat_system_prompt";
|
||||||
import * as fs from "fs";
|
import fs from "node:fs";
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
import * as os from "os";
|
import * as os from "os";
|
||||||
import * as crypto from "crypto";
|
import * as crypto from "crypto";
|
||||||
@@ -38,6 +38,7 @@ import { GoogleGenerativeAIProviderOptions } from "@ai-sdk/google";
|
|||||||
import { getExtraProviderOptions } from "../utils/thinking_utils";
|
import { getExtraProviderOptions } from "../utils/thinking_utils";
|
||||||
|
|
||||||
import { safeSend } from "../utils/safe_sender";
|
import { safeSend } from "../utils/safe_sender";
|
||||||
|
import { cleanFullResponse } from "../utils/cleanFullResponse";
|
||||||
|
|
||||||
const logger = log.scope("chat_stream_handlers");
|
const logger = log.scope("chat_stream_handlers");
|
||||||
|
|
||||||
@@ -258,7 +259,6 @@ ${componentSnippet}
|
|||||||
abortController,
|
abortController,
|
||||||
updatedChat,
|
updatedChat,
|
||||||
);
|
);
|
||||||
fullResponse = cleanThinkingByEscapingDyadTags(fullResponse);
|
|
||||||
} else {
|
} else {
|
||||||
// Normal AI processing for non-test prompts
|
// Normal AI processing for non-test prompts
|
||||||
const settings = readSettings();
|
const settings = readSettings();
|
||||||
@@ -515,6 +515,7 @@ This conversation includes one or more image attachments. When the user uploads
|
|||||||
}
|
}
|
||||||
|
|
||||||
fullResponse += chunk;
|
fullResponse += chunk;
|
||||||
|
fullResponse = cleanFullResponse(fullResponse);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
fullResponse.includes("$$SUPABASE_CLIENT_CODE$$") &&
|
fullResponse.includes("$$SUPABASE_CLIENT_CODE$$") &&
|
||||||
@@ -815,25 +816,6 @@ async function prepareMessageWithAttachments(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function cleanThinkingByEscapingDyadTags(text: string): string {
|
|
||||||
// Extract content inside <think> </think> tags
|
|
||||||
const thinkRegex = /<think>([\s\S]*?)<\/think>/g;
|
|
||||||
|
|
||||||
return text.replace(thinkRegex, (match, content) => {
|
|
||||||
// We are replacing the opening tag with a look-alike character
|
|
||||||
// to avoid issues where thinking content includes dyad tags
|
|
||||||
// and are mishandled by:
|
|
||||||
// 1. FE markdown parser
|
|
||||||
// 2. Main process response processor
|
|
||||||
const processedContent = content
|
|
||||||
.replace(/<dyad/g, "<dyad")
|
|
||||||
.replace(/<\/dyad/g, "</dyad");
|
|
||||||
|
|
||||||
// Return the modified think tag with processed content
|
|
||||||
return `<think>${processedContent}</think>`;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function removeThinkingTags(text: string): string {
|
function removeThinkingTags(text: string): string {
|
||||||
const thinkRegex = /<think>([\s\S]*?)<\/think>/g;
|
const thinkRegex = /<think>([\s\S]*?)<\/think>/g;
|
||||||
return text.replace(thinkRegex, "").trim();
|
return text.replace(thinkRegex, "").trim();
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { safeSend } from "../utils/safe_sender";
|
import { safeSend } from "../utils/safe_sender";
|
||||||
|
import { cleanFullResponse } from "../utils/cleanFullResponse";
|
||||||
|
|
||||||
// e.g. [dyad-qa=add-dep]
|
// e.g. [dyad-qa=add-dep]
|
||||||
// Canned responses for test prompts
|
// Canned responses for test prompts
|
||||||
@@ -18,6 +19,12 @@ const TEST_RESPONSES: Record<string, string> = {
|
|||||||
<dyad-add-dependency packages="react-router-dom react-query"></dyad-add-dependency>
|
<dyad-add-dependency packages="react-router-dom react-query"></dyad-add-dependency>
|
||||||
|
|
||||||
EOM`,
|
EOM`,
|
||||||
|
"string-literal-leak": `BEFORE TAG
|
||||||
|
<dyad-write path="src/pages/locations/neighborhoods/louisville/Highlands.tsx" description="Updating Highlands neighborhood page to use <a> tags.">
|
||||||
|
import React from 'react';
|
||||||
|
</dyad-write>
|
||||||
|
AFTER TAG
|
||||||
|
`,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -64,6 +71,7 @@ export async function streamTestResponse(
|
|||||||
|
|
||||||
// Add the word plus a space
|
// Add the word plus a space
|
||||||
fullResponse += chunk + " ";
|
fullResponse += chunk + " ";
|
||||||
|
fullResponse = cleanFullResponse(fullResponse);
|
||||||
|
|
||||||
// Send the current accumulated response
|
// Send the current accumulated response
|
||||||
safeSend(event.sender, "chat:response:chunk", {
|
safeSend(event.sender, "chat:response:chunk", {
|
||||||
|
|||||||
15
src/ipc/utils/cleanFullResponse.ts
Normal file
15
src/ipc/utils/cleanFullResponse.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
export function cleanFullResponse(text: string): string {
|
||||||
|
// Replace < characters inside dyad-* attributes with fullwidth less-than sign <
|
||||||
|
// This prevents parsing issues when attributes contain HTML tags like <a> or <div>
|
||||||
|
return text.replace(/<dyad-[^<>]*(?:"[^"]*"[^<>]*)*>/g, (match: string) => {
|
||||||
|
// Find all attribute values (content within quotes) and replace < with < and > with >
|
||||||
|
const processedMatch = match.replace(
|
||||||
|
/="([^"]*)"/g,
|
||||||
|
(attrMatch: string, attrValue: string) => {
|
||||||
|
const cleanedValue = attrValue.replace(/</g, "<").replace(/>/g, ">");
|
||||||
|
return `="${cleanedValue}"`;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return processedMatch;
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user