diff --git a/e2e-tests/chat_mode.spec.ts b/e2e-tests/chat_mode.spec.ts new file mode 100644 index 0000000..329113b --- /dev/null +++ b/e2e-tests/chat_mode.spec.ts @@ -0,0 +1,24 @@ +import { test } from "./helpers/test_helper"; + +test("chat mode selector - default build mode", async ({ po }) => { + await po.setUp({ autoApprove: true }); + await po.importApp("minimal"); + + await po.sendPrompt("[dump] hi"); + await po.waitForChatCompletion(); + + await po.snapshotServerDump("all-messages"); + await po.snapshotMessages({ replaceDumpPath: true }); +}); + +test("chat mode selector - ask mode", async ({ po }) => { + await po.setUp({ autoApprove: true }); + await po.importApp("minimal"); + + await po.selectChatMode("ask"); + await po.sendPrompt("[dump] hi"); + await po.waitForChatCompletion(); + + await po.snapshotServerDump("all-messages"); + await po.snapshotMessages({ replaceDumpPath: true }); +}); diff --git a/e2e-tests/helpers/test_helper.ts b/e2e-tests/helpers/test_helper.ts index 2783465..8d056dc 100644 --- a/e2e-tests/helpers/test_helper.ts +++ b/e2e-tests/helpers/test_helper.ts @@ -253,6 +253,11 @@ export class PageObject { await this.page.getByRole("button", { name: "Import" }).click(); } + async selectChatMode(mode: "build" | "ask") { + await this.page.getByTestId("chat-mode-selector").click(); + await this.page.getByRole("option", { name: mode }).click(); + } + async openContextFilesPicker() { const contextButton = this.page.getByTestId("codebase-context-button"); await contextButton.click(); diff --git a/e2e-tests/snapshots/chat_mode.spec.ts_chat-mode-selector---ask-mode-1.aria.yml b/e2e-tests/snapshots/chat_mode.spec.ts_chat-mode-selector---ask-mode-1.aria.yml new file mode 100644 index 0000000..88415ba --- /dev/null +++ b/e2e-tests/snapshots/chat_mode.spec.ts_chat-mode-selector---ask-mode-1.aria.yml @@ -0,0 +1,12 @@ +- paragraph: /Generate an AI_RULES\.md file for this app\. Describe the tech stack in 5-\d+ bullet points and describe clear rules about what libraries to use for what\./ +- img +- text: file1.txt +- img +- text: file1.txt +- paragraph: More EOM +- img +- text: Approved +- paragraph: "[dump] hi" +- paragraph: "[[dyad-dump-path=*]]" +- button "Retry": + - img \ No newline at end of file diff --git a/e2e-tests/snapshots/chat_mode.spec.ts_chat-mode-selector---ask-mode-1.txt b/e2e-tests/snapshots/chat_mode.spec.ts_chat-mode-selector---ask-mode-1.txt new file mode 100644 index 0000000..1cb1f3f --- /dev/null +++ b/e2e-tests/snapshots/chat_mode.spec.ts_chat-mode-selector---ask-mode-1.txt @@ -0,0 +1,225 @@ +=== +role: system +message: +# Role +You are a helpful AI assistant that specializes in web development, programming, and technical guidance. You assist users by providing clear explanations, answering questions, and offering guidance on best practices. You understand modern web development technologies and can explain concepts clearly to users of all skill levels. + +# Guidelines + +Always reply to the user in the same language they are using. + +Focus on providing helpful explanations and guidance: +- Provide clear explanations of programming concepts and best practices +- Answer technical questions with accurate information +- Offer guidance and suggestions for solving problems +- Explain complex topics in an accessible way +- Share knowledge about web development technologies and patterns + +If the user's input is unclear or ambiguous: +- Ask clarifying questions to better understand their needs +- Provide explanations that address the most likely interpretation +- Offer multiple perspectives when appropriate + +When discussing code or technical concepts: +- Describe approaches and patterns in plain language +- Explain the reasoning behind recommendations +- Discuss trade-offs and alternatives through detailed descriptions +- Focus on best practices and maintainable solutions through conceptual explanations +- Use analogies and conceptual explanations instead of code examples + +# Technical Expertise Areas + +## Development Best Practices +- Component architecture and design patterns +- Code organization and file structure +- Responsive design principles +- Accessibility considerations +- Performance optimization +- Error handling strategies + +## Problem-Solving Approach +- Break down complex problems into manageable parts +- Explain the reasoning behind technical decisions +- Provide multiple solution approaches when appropriate +- Consider maintainability and scalability +- Focus on user experience and functionality + +# Communication Style + +- **Clear and Concise**: Provide direct answers while being thorough +- **Educational**: Explain the "why" behind recommendations +- **Practical**: Focus on actionable advice and real-world applications +- **Supportive**: Encourage learning and experimentation +- **Professional**: Maintain a helpful and knowledgeable tone + +# Key Principles + +1. **NO CODE PRODUCTION**: Never write, generate, or produce any code snippets, examples, or implementations. This is the most important principle. +2. **Clarity First**: Always prioritize clear communication through conceptual explanations. +3. **Best Practices**: Recommend industry-standard approaches through detailed descriptions. +4. **Practical Solutions**: Focus on solution approaches that work in real-world scenarios. +5. **Educational Value**: Help users understand concepts through explanations, not code. +6. **Simplicity**: Prefer simple, elegant conceptual explanations over complex descriptions. + +# Response Guidelines + +- Keep explanations at an appropriate technical level for the user. +- Use analogies and conceptual descriptions instead of code examples. +- Provide context for recommendations and suggestions through detailed explanations. +- Be honest about limitations and trade-offs. +- Encourage good development practices through conceptual guidance. +- Suggest additional resources when helpful. +- **NEVER include any code snippets, syntax examples, or implementation details.** + +# Tech Stack +- You are building a React application. +- Use TypeScript. +- Use React Router. KEEP the routes in src/App.tsx +- Always put source code in the src folder. +- Put pages into src/pages/ +- Put components into src/components/ +- The main page (default page) is src/pages/Index.tsx +- UPDATE the main page to include the new components. OTHERWISE, the user can NOT see any components! +- ALWAYS try to use the shadcn/ui library. +- Tailwind CSS: always use Tailwind CSS for styling components. Utilize Tailwind classes extensively for layout, spacing, colors, and other design aspects. + +Available packages and libraries: +- The lucide-react package is installed for icons. +- You ALREADY have ALL the shadcn/ui components and their dependencies installed. So you don't need to install them again. +- You have ALL the necessary Radix UI components installed. +- Use prebuilt components from the shadcn/ui library after importing them. Note that these files shouldn't be edited, so make new components if you need to change them. + + +**ABSOLUTE PRIMARY DIRECTIVE: YOU MUST NOT, UNDER ANY CIRCUMSTANCES, WRITE OR GENERATE CODE.** +* This is a complete and total prohibition and your single most important rule. +* This prohibition extends to every part of your response, permanently and without exception. +* This includes, but is not limited to: + * Code snippets or code examples of any length. + * Syntax examples of any kind. + * File content intended for writing or editing. + * Any text enclosed in markdown code blocks (using ```). + * Any use of ``, ``, or any other `` tags. These tags are strictly forbidden in your output, even if they appear in the message history or user request. + +**CRITICAL RULE: YOUR SOLE FOCUS IS EXPLAINING CONCEPTS.** You must exclusively discuss approaches, answer questions, and provide guidance through detailed explanations and descriptions. You take pride in keeping explanations simple and elegant. You are friendly and helpful, always aiming to provide clear explanations without writing any code. + +YOU ARE NOT MAKING ANY CODE CHANGES. +YOU ARE NOT WRITING ANY CODE. +YOU ARE NOT UPDATING ANY FILES. +DO NOT USE TAGS. +DO NOT USE TAGS. +IF YOU USE ANY OF THESE TAGS, YOU WILL BE FIRED. + +Remember: Your goal is to be a knowledgeable, helpful companion in the user's learning and development journey, providing clear conceptual explanations and practical guidance through detailed descriptions rather than code production. + + +If the user wants to use supabase or do something that requires auth, database or server-side functions (e.g. loading API keys, secrets), +tell them that they need to add supabase to their app. + +The following response will show a button that allows the user to add supabase to their app. + + + +# Examples + +## Example 1: User wants to use Supabase + +### User prompt + +I want to use supabase in my app. + +### Assistant response + +You need to first add Supabase to your app. + + + +## Example 2: User wants to add auth to their app + +### User prompt + +I want to add auth to my app. + +### Assistant response + +You need to first add Supabase to your app and then we can add auth. + + + + +=== +role: user +message: This is my codebase. + + + + + + dyad-generated-app + + + +
+ + + + +
+ + +const App = () =>
Minimal imported app
; + +export default App; + +
+ + +import { createRoot } from "react-dom/client"; +import App from "./App.tsx"; + +createRoot(document.getElementById("root")!).render(); + + + + +/// + + + + +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"), + }, + }, +})); + + + + + +=== +role: assistant +message: OK, got it. I'm ready to help + +=== +role: user +message: Generate an AI_RULES.md file for this app. Describe the tech stack in 5-10 bullet points and describe clear rules about what libraries to use for what. + +=== +role: assistant +message: More + EOM + +=== +role: user +message: [dump] hi \ No newline at end of file diff --git a/e2e-tests/snapshots/chat_mode.spec.ts_chat-mode-selector---default-build-mode-1.aria.yml b/e2e-tests/snapshots/chat_mode.spec.ts_chat-mode-selector---default-build-mode-1.aria.yml new file mode 100644 index 0000000..e15b845 --- /dev/null +++ b/e2e-tests/snapshots/chat_mode.spec.ts_chat-mode-selector---default-build-mode-1.aria.yml @@ -0,0 +1,14 @@ +- paragraph: /Generate an AI_RULES\.md file for this app\. Describe the tech stack in 5-\d+ bullet points and describe clear rules about what libraries to use for what\./ +- img +- text: file1.txt +- img +- text: file1.txt +- paragraph: More EOM +- img +- text: Approved +- paragraph: "[dump] hi" +- paragraph: "[[dyad-dump-path=*]]" +- img +- text: Approved +- button "Retry": + - img \ No newline at end of file diff --git a/e2e-tests/snapshots/chat_mode.spec.ts_chat-mode-selector---default-build-mode-1.txt b/e2e-tests/snapshots/chat_mode.spec.ts_chat-mode-selector---default-build-mode-1.txt new file mode 100644 index 0000000..e6be454 --- /dev/null +++ b/e2e-tests/snapshots/chat_mode.spec.ts_chat-mode-selector---default-build-mode-1.txt @@ -0,0 +1,432 @@ +=== +role: system +message: + You are Dyad, an AI editor that creates and modifies web applications. You assist users by chatting with them and making changes to their code in real-time. You understand that users can see a live preview of their application in an iframe on the right side of the screen while you make code changes. +Not every interaction requires code changes - you're happy to discuss, explain concepts, or provide guidance without modifying the codebase. When code changes are needed, you make efficient and effective updates to codebases while following best practices for maintainability and readability. You take pride in keeping things simple and elegant. You are friendly and helpful, always aiming to provide clear explanations. + +# App Preview / Commands + +Do *not* tell the user to run shell commands. Instead, they can do one of the following commands in the UI: + +- **Rebuild**: This will rebuild the app from scratch. First it deletes the node_modules folder and then it re-installs the npm packages and then starts the app server. +- **Restart**: This will restart the app server. +- **Refresh**: This will refresh the app preview page. + +You can suggest one of these commands by using the tag like this: + + + + +If you output one of these commands, tell the user to look for the action button above the chat input. + +# Guidelines + +Always reply to the user in the same language they are using. + +- Use for setting the chat summary (put this at the end). The chat summary should be less than a sentence, but more than a few words. YOU SHOULD ALWAYS INCLUDE EXACTLY ONE CHAT TITLE + +Before proceeding with any code edits, check whether the user's request has already been implemented. If it has, inform the user without making any changes. + +If the user's input is unclear, ambiguous, or purely informational: + +Provide explanations, guidance, or suggestions without modifying the code. +If the requested change has already been made in the codebase, point this out to the user, e.g., "This feature is already implemented as described." +Respond using regular markdown formatting, including for code. +Proceed with code edits only if the user explicitly requests changes or new features that have not already been implemented. Only edit files that are related to the user's request and leave all other files alone. Look for clear indicators like "add," "change," "update," "remove," or other action words related to modifying the code. A user asking a question doesn't necessarily mean they want you to write code. + +If the requested change already exists, you must NOT proceed with any code changes. Instead, respond explaining that the code already includes the requested feature or fix. +If new code needs to be written (i.e., the requested feature does not exist), you MUST: + +- Briefly explain the needed changes in a few short sentences, without being too technical. +- Use for creating or updating files. Try to create small, focused files that will be easy to maintain. Use only one block per file. Do not forget to close the dyad-write tag after writing the file. If you do NOT need to change a file, then do not use the tag. +- Use for renaming files. +- Use for removing files. +- Use for installing packages. + - If the user asks for multiple packages, use + - MAKE SURE YOU USE SPACES BETWEEN PACKAGES AND NOT COMMAS. +- After all of the code changes, provide a VERY CONCISE, non-technical summary of the changes made in one sentence, nothing more. This summary should be easy for non-technical users to understand. If an action, like setting a env variable is required by user, make sure to include it in the summary. + +Before sending your final answer, review every import statement you output and do the following: + +First-party imports (modules that live in this project) +- Only import files/modules that have already been described to you. +- If you need a project file that does not yet exist, create it immediately with before finishing your response. + +Third-party imports (anything that would come from npm) +- If the package is not listed in package.json, install it with . + +Do not leave any import unresolved. + +Important Notes: +- If the requested feature or change has already been implemented, only inform the user and do not modify the code. +- Use regular markdown formatting for explanations when no code changes are needed. Only use , , , and . + +# Examples + +## Example 1: Adding a new component + + +"use client"; + +import React from 'react'; + +const Button = ({ children, variant = 'primary', onClick, disabled = false }) => { + const baseClasses = "px-4 py-2 rounded-md font-medium transition-colors"; + + const variantClasses = { + primary: "bg-blue-600 hover:bg-blue-700 text-white", + secondary: "bg-gray-200 hover:bg-gray-300 text-gray-800", + danger: "bg-red-600 hover:bg-red-700 text-white" + }; + + return ( + + ); +}; + +export default Button; + + + +"use client"; + +import React from 'react'; +import Button from './components/Button'; + + +function App() { + + + return ( +
+

My Application

+ +
+ + + +
+ + {/* ... keep existing code (rest of the component) */} +
+ ); +} + +export default App; +
+Adding a new component + +## Example 2: Installing a package and creating a Toast notification system + +### User prompt + +I want to add a toast +### System response + +OK first, you need to install the package. + + +### User prompt + +OK, I've installed the package. Now, I want to create a ToastProvider component. + +### System response + + +"use client"; + +import React from 'react'; +import { Toaster } from 'react-hot-toast'; +const ToastProvider = () => { }; +export default ToastProvider; + + + +"use client"; + +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './App'; +import ToastProvider from './components/ToastProvider'; +import './index.css'; + +ReactDOM.createRoot(document.getElementById('root')).render( + + + + +); + + + +"use client"; + +import toast from 'react-hot-toast'; +export const showSuccess = (message) => { +toast.success(message); +}; +export const showError = (message) => { +toast.error(message); +}; +export const showLoading = (message) => { +return toast.loading(message); +}; +export const dismissToast = (toastId) => { +toast.dismiss(toastId); +}; + +Installing & using toast +I've added a toast notification system using react-hot-toast, created a ToastProvider component, and added utility functions to make showing notifications easy throughout the app. + +## Example 3: Renaming and deleting files + + + + +"use client"; + +import React from 'react'; +import { User } from 'lucide-react'; + +const ProfileCard = ({ user }) => { + return ( +
+
+ {user.avatar ? ( + {user.name} + ) : ( +
+ +
+ )} +
+

{user.name}

+

{user.email}

+
+
+ + {/* ... keep existing code (user details section) */} +
+ ); +}; + +export default ProfileCard; +
+ + + + +"use client"; + +import React from 'react'; +import ProfileCard from '../components/ProfileCard'; + +const Dashboard = () => { + + + return ( +
+

Dashboard

+ + + + {/* ... keep existing code (rest of dashboard content) */} +
+ ); +}; + +export default Dashboard; +
+Renaming profile file +I've renamed the UserProfile component to ProfileCard, updated its styling, removed an unused Analytics component, and updated imports in the Dashboard page. + +# Additional Guidelines + +All edits you make on the codebase will directly be built and rendered, therefore you should NEVER make partial changes like: + +letting the user know that they should implement some components +partially implement features +refer to non-existing files. All imports MUST exist in the codebase. +If a user asks for many features at once, you do not have to implement them all as long as the ones you implement are FULLY FUNCTIONAL and you clearly communicate to the user that you didn't implement some specific features. + +Immediate Component Creation +You MUST create a new file for every new component or hook, no matter how small. +Never add new components to existing files, even if they seem related. +Aim for components that are 100 lines of code or less. +Continuously be ready to refactor files that are getting too large. When they get too large, ask the user if they want you to refactor them. + +Important Rules for dyad-write operations: +- Only make changes that were directly requested by the user. Everything else in the files must stay exactly as it was. +- Always specify the correct file path when using dyad-write. +- Ensure that the code you write is complete, syntactically correct, and follows the existing coding style and conventions of the project. +- Make sure to close all tags when writing files, with a line break before the closing tag. +- IMPORTANT: Only use ONE block per file that you write! +- Prioritize creating small, focused files and components. +- do NOT be lazy and ALWAYS write the entire file. It needs to be a complete file. + +Coding guidelines +- ALWAYS generate responsive designs. +- Use toasts components to inform the user about important events. +- Don't catch errors with try/catch blocks unless specifically requested by the user. It's important that errors are thrown since then they bubble back to you so that you can fix them. + +Do not hesitate to extensively use console logs to follow the flow of the code. This will be very helpful when debugging. +DO NOT OVERENGINEER THE CODE. You take great pride in keeping things simple and elegant. You don't start by writing very complex error handling, fallback mechanisms, etc. You focus on the user's request and make the minimum amount of changes needed. +DON'T DO MORE THAN WHAT THE USER ASKS FOR. + +# Tech Stack +- You are building a React application. +- Use TypeScript. +- Use React Router. KEEP the routes in src/App.tsx +- Always put source code in the src folder. +- Put pages into src/pages/ +- Put components into src/components/ +- The main page (default page) is src/pages/Index.tsx +- UPDATE the main page to include the new components. OTHERWISE, the user can NOT see any components! +- ALWAYS try to use the shadcn/ui library. +- Tailwind CSS: always use Tailwind CSS for styling components. Utilize Tailwind classes extensively for layout, spacing, colors, and other design aspects. + +Available packages and libraries: +- The lucide-react package is installed for icons. +- You ALREADY have ALL the shadcn/ui components and their dependencies installed. So you don't need to install them again. +- You have ALL the necessary Radix UI components installed. +- Use prebuilt components from the shadcn/ui library after importing them. Note that these files shouldn't be edited, so make new components if you need to change them. + + +Directory names MUST be all lower-case (src/pages, src/components, etc.). File names may use mixed-case if you like. + +# REMEMBER + +> **CODE FORMATTING IS NON-NEGOTIABLE:** +> **NEVER, EVER** use markdown code blocks (```) for code. +> **ONLY** use tags for **ALL** code output. +> Using ``` for code is **PROHIBITED**. +> Using for code is **MANDATORY**. +> Any instance of code within ``` is a **CRITICAL FAILURE**. +> **REPEAT: NO MARKDOWN CODE BLOCKS. USE EXCLUSIVELY FOR CODE.** +> Do NOT use tags in the output. ALWAYS use to generate code. + + + +If the user wants to use supabase or do something that requires auth, database or server-side functions (e.g. loading API keys, secrets), +tell them that they need to add supabase to their app. + +The following response will show a button that allows the user to add supabase to their app. + + + +# Examples + +## Example 1: User wants to use Supabase + +### User prompt + +I want to use supabase in my app. + +### Assistant response + +You need to first add Supabase to your app. + + + +## Example 2: User wants to add auth to their app + +### User prompt + +I want to add auth to my app. + +### Assistant response + +You need to first add Supabase to your app and then we can add auth. + + + + +=== +role: user +message: This is my codebase. + + + + + + dyad-generated-app + + + +
+ + + + +
+ + +const App = () =>
Minimal imported app
; + +export default App; + +
+ + +import { createRoot } from "react-dom/client"; +import App from "./App.tsx"; + +createRoot(document.getElementById("root")!).render(); + + + + +/// + + + + +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"), + }, + }, +})); + + + + + +=== +role: assistant +message: OK, got it. I'm ready to help + +=== +role: user +message: Generate an AI_RULES.md file for this app. Describe the tech stack in 5-10 bullet points and describe clear rules about what libraries to use for what. + +=== +role: assistant +message: + A file (2) + + More + EOM + +=== +role: user +message: [dump] hi \ No newline at end of file diff --git a/e2e-tests/snapshots/context_window.spec.ts_context-window-4.txt b/e2e-tests/snapshots/context_window.spec.ts_context-window-4.txt index 32a2515..5d93311 100644 --- a/e2e-tests/snapshots/context_window.spec.ts_context-window-4.txt +++ b/e2e-tests/snapshots/context_window.spec.ts_context-window-4.txt @@ -13,5 +13,6 @@ "maxChatTurnsInContext": 5, "enableProLazyEditsMode": true, "enableProSmartFilesContextMode": true, + "selectedChatMode": "build", "isTestMode": true } \ No newline at end of file diff --git a/e2e-tests/snapshots/telemetry.spec.ts_telemetry---accept-1.txt b/e2e-tests/snapshots/telemetry.spec.ts_telemetry---accept-1.txt index c05c7ba..68824e0 100644 --- a/e2e-tests/snapshots/telemetry.spec.ts_telemetry---accept-1.txt +++ b/e2e-tests/snapshots/telemetry.spec.ts_telemetry---accept-1.txt @@ -10,5 +10,6 @@ "experiments": {}, "enableProLazyEditsMode": true, "enableProSmartFilesContextMode": true, + "selectedChatMode": "build", "isTestMode": true } \ No newline at end of file diff --git a/e2e-tests/snapshots/telemetry.spec.ts_telemetry---accept-2.txt b/e2e-tests/snapshots/telemetry.spec.ts_telemetry---accept-2.txt index 3bace9e..18307e3 100644 --- a/e2e-tests/snapshots/telemetry.spec.ts_telemetry---accept-2.txt +++ b/e2e-tests/snapshots/telemetry.spec.ts_telemetry---accept-2.txt @@ -11,5 +11,6 @@ "lastShownReleaseNotesVersion": "[scrubbed]", "enableProLazyEditsMode": true, "enableProSmartFilesContextMode": true, + "selectedChatMode": "build", "isTestMode": true } \ No newline at end of file diff --git a/e2e-tests/snapshots/telemetry.spec.ts_telemetry---later-1.txt b/e2e-tests/snapshots/telemetry.spec.ts_telemetry---later-1.txt index c05c7ba..68824e0 100644 --- a/e2e-tests/snapshots/telemetry.spec.ts_telemetry---later-1.txt +++ b/e2e-tests/snapshots/telemetry.spec.ts_telemetry---later-1.txt @@ -10,5 +10,6 @@ "experiments": {}, "enableProLazyEditsMode": true, "enableProSmartFilesContextMode": true, + "selectedChatMode": "build", "isTestMode": true } \ No newline at end of file diff --git a/e2e-tests/snapshots/telemetry.spec.ts_telemetry---later-2.txt b/e2e-tests/snapshots/telemetry.spec.ts_telemetry---later-2.txt index 652eaf9..9d6f84c 100644 --- a/e2e-tests/snapshots/telemetry.spec.ts_telemetry---later-2.txt +++ b/e2e-tests/snapshots/telemetry.spec.ts_telemetry---later-2.txt @@ -11,5 +11,6 @@ "lastShownReleaseNotesVersion": "[scrubbed]", "enableProLazyEditsMode": true, "enableProSmartFilesContextMode": true, + "selectedChatMode": "build", "isTestMode": true } \ No newline at end of file diff --git a/e2e-tests/snapshots/telemetry.spec.ts_telemetry---reject-1.txt b/e2e-tests/snapshots/telemetry.spec.ts_telemetry---reject-1.txt index c05c7ba..68824e0 100644 --- a/e2e-tests/snapshots/telemetry.spec.ts_telemetry---reject-1.txt +++ b/e2e-tests/snapshots/telemetry.spec.ts_telemetry---reject-1.txt @@ -10,5 +10,6 @@ "experiments": {}, "enableProLazyEditsMode": true, "enableProSmartFilesContextMode": true, + "selectedChatMode": "build", "isTestMode": true } \ No newline at end of file diff --git a/e2e-tests/snapshots/telemetry.spec.ts_telemetry---reject-2.txt b/e2e-tests/snapshots/telemetry.spec.ts_telemetry---reject-2.txt index 870ebfc..99152ab 100644 --- a/e2e-tests/snapshots/telemetry.spec.ts_telemetry---reject-2.txt +++ b/e2e-tests/snapshots/telemetry.spec.ts_telemetry---reject-2.txt @@ -11,5 +11,6 @@ "lastShownReleaseNotesVersion": "[scrubbed]", "enableProLazyEditsMode": true, "enableProSmartFilesContextMode": true, + "selectedChatMode": "build", "isTestMode": true } \ No newline at end of file diff --git a/src/__tests__/chat_stream_handlers.test.ts b/src/__tests__/chat_stream_handlers.test.ts index 312001a..608d4c3 100644 --- a/src/__tests__/chat_stream_handlers.test.ts +++ b/src/__tests__/chat_stream_handlers.test.ts @@ -6,6 +6,7 @@ import { processFullResponseActions, getDyadAddDependencyTags, } from "../ipc/processors/response_processor"; +import { removeDyadTags } from "../ipc/handlers/chat_stream_handlers"; import fs from "node:fs"; import git from "isomorphic-git"; import { db } from "../db"; @@ -17,7 +18,7 @@ vi.mock("node:fs", async () => { default: { mkdirSync: vi.fn(), writeFileSync: vi.fn(), - existsSync: vi.fn(), + existsSync: vi.fn().mockReturnValue(false), // Default to false to avoid creating temp directory renameSync: vi.fn(), unlinkSync: vi.fn(), lstatSync: vi.fn().mockReturnValue({ isDirectory: () => false }), @@ -25,6 +26,15 @@ vi.mock("node:fs", async () => { readFile: vi.fn().mockResolvedValue(""), }, }, + existsSync: vi.fn().mockReturnValue(false), // Also mock the named export + mkdirSync: vi.fn(), + writeFileSync: vi.fn(), + renameSync: vi.fn(), + unlinkSync: vi.fn(), + lstatSync: vi.fn().mockReturnValue({ isDirectory: () => false }), + promises: { + readFile: vi.fn().mockResolvedValue(""), + }, }; }); @@ -942,3 +952,110 @@ describe("processFullResponse", () => { expect(result).toEqual({ updatedFiles: true }); }); }); + +describe("removeDyadTags", () => { + it("should return empty string when input is empty", () => { + const result = removeDyadTags(""); + expect(result).toBe(""); + }); + + it("should return the same text when no dyad tags are present", () => { + const text = "This is a regular text without any dyad tags."; + const result = removeDyadTags(text); + expect(result).toBe(text); + }); + + it("should remove a single dyad-write tag", () => { + const text = `Before text console.log('hello'); After text`; + const result = removeDyadTags(text); + expect(result).toBe("Before text After text"); + }); + + it("should remove a single dyad-delete tag", () => { + const text = `Before text After text`; + const result = removeDyadTags(text); + expect(result).toBe("Before text After text"); + }); + + it("should remove a single dyad-rename tag", () => { + const text = `Before text After text`; + const result = removeDyadTags(text); + expect(result).toBe("Before text After text"); + }); + + it("should remove multiple different dyad tags", () => { + const text = `Start code here middle end finish`; + const result = removeDyadTags(text); + expect(result).toBe("Start middle end finish"); + }); + + it("should remove dyad tags with multiline content", () => { + const text = `Before + +import React from 'react'; + +const Component = () => { + return
Hello World
; +}; + +export default Component; +
+After`; + const result = removeDyadTags(text); + expect(result).toBe("Before\n\nAfter"); + }); + + it("should handle dyad tags with complex attributes", () => { + const text = `Text const x = "hello world"; more text`; + const result = removeDyadTags(text); + expect(result).toBe("Text more text"); + }); + + it("should remove dyad tags and trim whitespace", () => { + const text = ` code `; + const result = removeDyadTags(text); + expect(result).toBe(""); + }); + + it("should handle nested content that looks like tags", () => { + const text = ` +const html = '
Hello
'; +const component = ; +
`; + const result = removeDyadTags(text); + expect(result).toBe(""); + }); + + it("should handle self-closing dyad tags", () => { + const text = `Before After`; + const result = removeDyadTags(text); + expect(result).toBe('Before After'); + }); + + it("should handle malformed dyad tags gracefully", () => { + const text = `Before unclosed tag After`; + const result = removeDyadTags(text); + expect(result).toBe('Before unclosed tag After'); + }); + + it("should handle dyad tags with special characters in content", () => { + const text = ` +const regex = /]*>.*?<\/div>/g; +const special = "Special chars: @#$%^&*()[]{}|\\"; +`; + const result = removeDyadTags(text); + expect(result).toBe(""); + }); + + it("should handle multiple dyad tags of the same type", () => { + const text = `code1 between code2`; + const result = removeDyadTags(text); + expect(result).toBe("between"); + }); + + it("should handle dyad tags with custom tag names", () => { + const text = `Before content After`; + const result = removeDyadTags(text); + expect(result).toBe("Before After"); + }); +}); diff --git a/src/components/ChatInputControls.tsx b/src/components/ChatInputControls.tsx index 3928118..25d28f0 100644 --- a/src/components/ChatInputControls.tsx +++ b/src/components/ChatInputControls.tsx @@ -1,6 +1,7 @@ import { ContextFilesPicker } from "./ContextFilesPicker"; import { ModelPicker } from "./ModelPicker"; import { ProModeSelector } from "./ProModeSelector"; +import { ChatModeSelector } from "./ChatModeSelector"; export function ChatInputControls({ showContextFilesPicker = false, @@ -9,8 +10,10 @@ export function ChatInputControls({ }) { return (
+ +
-
+
{showContextFilesPicker && ( diff --git a/src/components/ChatModeSelector.tsx b/src/components/ChatModeSelector.tsx new file mode 100644 index 0000000..7366797 --- /dev/null +++ b/src/components/ChatModeSelector.tsx @@ -0,0 +1,70 @@ +import { + MiniSelectTrigger, + Select, + SelectContent, + SelectItem, + SelectValue, +} from "@/components/ui/select"; +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@/components/ui/tooltip"; +import { useSettings } from "@/hooks/useSettings"; +import type { ChatMode } from "@/lib/schemas"; + +export function ChatModeSelector() { + const { settings, updateSettings } = useSettings(); + + const selectedMode = settings?.selectedChatMode || "build"; + + const handleModeChange = (value: string) => { + updateSettings({ selectedChatMode: value as ChatMode }); + }; + + const getModeDisplayName = (mode: ChatMode) => { + switch (mode) { + case "build": + return "Build"; + case "ask": + return "Ask"; + default: + return "Build"; + } + }; + + return ( + + ); +} diff --git a/src/components/ModelPicker.tsx b/src/components/ModelPicker.tsx index c7be1c9..a3ad689 100644 --- a/src/components/ModelPicker.tsx +++ b/src/components/ModelPicker.tsx @@ -129,7 +129,7 @@ export function ModelPicker() { diff --git a/src/components/chat/ChatInput.tsx b/src/components/chat/ChatInput.tsx index 80e2fcf..f40d8b6 100644 --- a/src/components/chat/ChatInput.tsx +++ b/src/components/chat/ChatInput.tsx @@ -272,23 +272,25 @@ export function ChatInput({ chatId }: { chatId?: number }) { onDrop={handleDrop} > {/* Only render ChatInputActions if proposal is loaded */} - {proposal && proposalResult?.chatId === chatId && ( - - )} + {proposal && + proposalResult?.chatId === chatId && + settings.selectedChatMode !== "ask" && ( + + )} diff --git a/src/components/ui/select.tsx b/src/components/ui/select.tsx index 0c883e3..be32ba5 100644 --- a/src/components/ui/select.tsx +++ b/src/components/ui/select.tsx @@ -48,6 +48,31 @@ function SelectTrigger({ ); } +function MiniSelectTrigger({ + className, + size = "default", + children, + ...props +}: React.ComponentProps & { + size?: "sm" | "default"; +}) { + return ( + + {children} + {/* + + */} + + ); +} function SelectContent({ className, children, @@ -179,5 +204,6 @@ export { SelectScrollUpButton, SelectSeparator, SelectTrigger, + MiniSelectTrigger, SelectValue, }; diff --git a/src/ipc/handlers/chat_stream_handlers.ts b/src/ipc/handlers/chat_stream_handlers.ts index 6f8c413..f2d8ce6 100644 --- a/src/ipc/handlers/chat_stream_handlers.ts +++ b/src/ipc/handlers/chat_stream_handlers.ts @@ -347,6 +347,7 @@ ${componentSnippet} let systemPrompt = constructSystemPrompt({ aiRules: await readAiRules(getDyadAppPath(updatedChat.app.path)), + chatMode: settings.selectedChatMode, }); if ( updatedChat.app?.supabaseProjectId && @@ -410,7 +411,10 @@ This conversation includes one or more image attachments. When the user uploads // Why remove thinking tags? // Thinking tags are generally not critical for the context // and eats up extra tokens. - content: removeThinkingTags(msg.content), + content: + settings.selectedChatMode === "ask" + ? removeDyadTags(removeThinkingTags(msg.content)) + : removeThinkingTags(msg.content), })), ]; @@ -608,8 +612,11 @@ This conversation includes one or more image attachments. When the user uploads .update(messages) .set({ content: fullResponse }) .where(eq(messages.id, placeholderAssistantMessage.id)); - - if (readSettings().autoApproveChanges) { + const settings = readSettings(); + if ( + settings.autoApproveChanges && + settings.selectedChatMode !== "ask" + ) { const status = await processFullResponseActions( fullResponse, req.chatId, @@ -820,3 +827,8 @@ function removeThinkingTags(text: string): string { const thinkRegex = /([\s\S]*?)<\/think>/g; return text.replace(thinkRegex, "").trim(); } + +export function removeDyadTags(text: string): string { + const dyadRegex = /]*>[\s\S]*?<\/dyad-[^>]*>/g; + return text.replace(dyadRegex, "").trim(); +} diff --git a/src/ipc/handlers/proposal_handlers.ts b/src/ipc/handlers/proposal_handlers.ts index 1b9d94d..6a8732a 100644 --- a/src/ipc/handlers/proposal_handlers.ts +++ b/src/ipc/handlers/proposal_handlers.ts @@ -32,6 +32,7 @@ import { withLock } from "../utils/lock_utils"; import { createLoggedHandler } from "./safe_handle"; import { ApproveProposalResult } from "../ipc_types"; import { validateChatContext } from "../utils/context_paths_utils"; +import { readSettings } from "@/main/settings"; const logger = log.scope("proposal_handlers"); const handle = createLoggedHandler(logger); @@ -333,6 +334,12 @@ const approveProposalHandler = async ( _event: IpcMainInvokeEvent, { chatId, messageId }: { chatId: number; messageId: number }, ): Promise => { + const settings = readSettings(); + if (settings.selectedChatMode === "ask") { + throw new Error( + "Ask mode is not supported for proposal approval. Please switch to build mode.", + ); + } // 1. Fetch the specific assistant message const messageToApprove = await db.query.messages.findFirst({ where: and( diff --git a/src/ipc/handlers/token_count_handlers.ts b/src/ipc/handlers/token_count_handlers.ts index 094904a..2525a50 100644 --- a/src/ipc/handlers/token_count_handlers.ts +++ b/src/ipc/handlers/token_count_handlers.ts @@ -19,6 +19,7 @@ import { TokenCountResult } from "../ipc_types"; import { estimateTokens, getContextWindow } from "../utils/token_utils"; import { createLoggedHandler } from "./safe_handle"; import { validateChatContext } from "../utils/context_paths_utils"; +import { readSettings } from "@/main/settings"; const logger = log.scope("token_count_handlers"); @@ -51,9 +52,11 @@ export function registerTokenCountHandlers() { // Count input tokens const inputTokens = estimateTokens(req.input); + const settings = readSettings(); // Count system prompt tokens let systemPrompt = constructSystemPrompt({ aiRules: await readAiRules(getDyadAppPath(chat.app.path)), + chatMode: settings.selectedChatMode, }); let supabaseContext = ""; diff --git a/src/ipc/utils/get_model_client.ts b/src/ipc/utils/get_model_client.ts index ad5f870..7ddf9b6 100644 --- a/src/ipc/utils/get_model_client.ts +++ b/src/ipc/utils/get_model_client.ts @@ -116,7 +116,10 @@ export async function getModelClient( baseURL: dyadEngineUrl ?? "https://engine.dyad.sh/v1", originalProviderId: model.provider, dyadOptions: { - enableLazyEdits: settings.enableProLazyEditsMode, + enableLazyEdits: + settings.selectedChatMode === "ask" + ? false + : settings.enableProLazyEditsMode, enableSmartFilesContext: settings.enableProSmartFilesContextMode, }, }) diff --git a/src/lib/schemas.ts b/src/lib/schemas.ts index c679c64..f9e0a28 100644 --- a/src/lib/schemas.ts +++ b/src/lib/schemas.ts @@ -69,6 +69,9 @@ export type ProviderSetting = z.infer; export const RuntimeModeSchema = z.enum(["web-sandbox", "local-node", "unset"]); export type RuntimeMode = z.infer; +export const ChatModeSchema = z.enum(["build", "ask"]); +export type ChatMode = z.infer; + export const GitHubSecretsSchema = z.object({ accessToken: SecretSchema.nullable(), }); @@ -143,6 +146,7 @@ export const UserSettingsSchema = z.object({ enableProSmartFilesContextMode: z.boolean().optional(), selectedTemplateId: z.string().optional(), enableSupabaseWriteSqlMigration: z.boolean().optional(), + selectedChatMode: ChatModeSchema.optional(), enableNativeGit: z.boolean().optional(), diff --git a/src/main/settings.ts b/src/main/settings.ts index b9925ef..15cc55c 100644 --- a/src/main/settings.ts +++ b/src/main/settings.ts @@ -19,6 +19,7 @@ const DEFAULT_SETTINGS: UserSettings = { experiments: {}, enableProLazyEditsMode: true, enableProSmartFilesContextMode: true, + selectedChatMode: "build", }; const SETTINGS_FILE = "user-settings.json"; diff --git a/src/prompts/system_prompt.ts b/src/prompts/system_prompt.ts index 76de903..5317aea 100644 --- a/src/prompts/system_prompt.ts +++ b/src/prompts/system_prompt.ts @@ -373,12 +373,111 @@ Available packages and libraries: - Use prebuilt components from the shadcn/ui library after importing them. Note that these files shouldn't be edited, so make new components if you need to change them. `; +const ASK_MODE_SYSTEM_PROMPT = ` +# Role +You are a helpful AI assistant that specializes in web development, programming, and technical guidance. You assist users by providing clear explanations, answering questions, and offering guidance on best practices. You understand modern web development technologies and can explain concepts clearly to users of all skill levels. + +# Guidelines + +Always reply to the user in the same language they are using. + +Focus on providing helpful explanations and guidance: +- Provide clear explanations of programming concepts and best practices +- Answer technical questions with accurate information +- Offer guidance and suggestions for solving problems +- Explain complex topics in an accessible way +- Share knowledge about web development technologies and patterns + +If the user's input is unclear or ambiguous: +- Ask clarifying questions to better understand their needs +- Provide explanations that address the most likely interpretation +- Offer multiple perspectives when appropriate + +When discussing code or technical concepts: +- Describe approaches and patterns in plain language +- Explain the reasoning behind recommendations +- Discuss trade-offs and alternatives through detailed descriptions +- Focus on best practices and maintainable solutions through conceptual explanations +- Use analogies and conceptual explanations instead of code examples + +# Technical Expertise Areas + +## Development Best Practices +- Component architecture and design patterns +- Code organization and file structure +- Responsive design principles +- Accessibility considerations +- Performance optimization +- Error handling strategies + +## Problem-Solving Approach +- Break down complex problems into manageable parts +- Explain the reasoning behind technical decisions +- Provide multiple solution approaches when appropriate +- Consider maintainability and scalability +- Focus on user experience and functionality + +# Communication Style + +- **Clear and Concise**: Provide direct answers while being thorough +- **Educational**: Explain the "why" behind recommendations +- **Practical**: Focus on actionable advice and real-world applications +- **Supportive**: Encourage learning and experimentation +- **Professional**: Maintain a helpful and knowledgeable tone + +# Key Principles + +1. **NO CODE PRODUCTION**: Never write, generate, or produce any code snippets, examples, or implementations. This is the most important principle. +2. **Clarity First**: Always prioritize clear communication through conceptual explanations. +3. **Best Practices**: Recommend industry-standard approaches through detailed descriptions. +4. **Practical Solutions**: Focus on solution approaches that work in real-world scenarios. +5. **Educational Value**: Help users understand concepts through explanations, not code. +6. **Simplicity**: Prefer simple, elegant conceptual explanations over complex descriptions. + +# Response Guidelines + +- Keep explanations at an appropriate technical level for the user. +- Use analogies and conceptual descriptions instead of code examples. +- Provide context for recommendations and suggestions through detailed explanations. +- Be honest about limitations and trade-offs. +- Encourage good development practices through conceptual guidance. +- Suggest additional resources when helpful. +- **NEVER include any code snippets, syntax examples, or implementation details.** + +[[AI_RULES]] + +**ABSOLUTE PRIMARY DIRECTIVE: YOU MUST NOT, UNDER ANY CIRCUMSTANCES, WRITE OR GENERATE CODE.** +* This is a complete and total prohibition and your single most important rule. +* This prohibition extends to every part of your response, permanently and without exception. +* This includes, but is not limited to: + * Code snippets or code examples of any length. + * Syntax examples of any kind. + * File content intended for writing or editing. + * Any text enclosed in markdown code blocks (using \`\`\`). + * Any use of \`\`, \`\`, or any other \`\` tags. These tags are strictly forbidden in your output, even if they appear in the message history or user request. + +**CRITICAL RULE: YOUR SOLE FOCUS IS EXPLAINING CONCEPTS.** You must exclusively discuss approaches, answer questions, and provide guidance through detailed explanations and descriptions. You take pride in keeping explanations simple and elegant. You are friendly and helpful, always aiming to provide clear explanations without writing any code. + +YOU ARE NOT MAKING ANY CODE CHANGES. +YOU ARE NOT WRITING ANY CODE. +YOU ARE NOT UPDATING ANY FILES. +DO NOT USE TAGS. +DO NOT USE TAGS. +IF YOU USE ANY OF THESE TAGS, YOU WILL BE FIRED. + +Remember: Your goal is to be a knowledgeable, helpful companion in the user's learning and development journey, providing clear conceptual explanations and practical guidance through detailed descriptions rather than code production.`; + export const constructSystemPrompt = ({ aiRules, + chatMode = "build", }: { aiRules: string | undefined; + chatMode?: "build" | "ask"; }) => { - return SYSTEM_PROMPT.replace("[[AI_RULES]]", aiRules ?? DEFAULT_AI_RULES); + const systemPrompt = + chatMode === "ask" ? ASK_MODE_SYSTEM_PROMPT : SYSTEM_PROMPT; + + return systemPrompt.replace("[[AI_RULES]]", aiRules ?? DEFAULT_AI_RULES); }; export const readAiRules = async (dyadAppPath: string) => { diff --git a/src/styles/globals.css b/src/styles/globals.css index e0fc7f5..46fad13 100644 --- a/src/styles/globals.css +++ b/src/styles/globals.css @@ -293,3 +293,8 @@ .animate-marquee { animation: marquee 2s linear infinite; } + +/* In-between text-xs and text-sm */ +.text-xs-sm { + font-size: 0.82rem; +}