Allow manual context management (#376)
This commit is contained in:
1
drizzle/0006_mushy_squirrel_girl.sql
Normal file
1
drizzle/0006_mushy_squirrel_girl.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE `apps` ADD `chat_context` text;
|
||||
377
drizzle/meta/0006_snapshot.json
Normal file
377
drizzle/meta/0006_snapshot.json
Normal file
@@ -0,0 +1,377 @@
|
||||
{
|
||||
"version": "6",
|
||||
"dialect": "sqlite",
|
||||
"id": "164b6b9d-8df1-41f0-b3d2-5fe479312bdc",
|
||||
"prevId": "0a47ec41-9477-4457-b3e8-e5ecb3e3a855",
|
||||
"tables": {
|
||||
"apps": {
|
||||
"name": "apps",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"path": {
|
||||
"name": "path",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "(unixepoch())"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "(unixepoch())"
|
||||
},
|
||||
"github_org": {
|
||||
"name": "github_org",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"github_repo": {
|
||||
"name": "github_repo",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"supabase_project_id": {
|
||||
"name": "supabase_project_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"chat_context": {
|
||||
"name": "chat_context",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"chats": {
|
||||
"name": "chats",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"app_id": {
|
||||
"name": "app_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"title": {
|
||||
"name": "title",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"initial_commit_hash": {
|
||||
"name": "initial_commit_hash",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "(unixepoch())"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"chats_app_id_apps_id_fk": {
|
||||
"name": "chats_app_id_apps_id_fk",
|
||||
"tableFrom": "chats",
|
||||
"tableTo": "apps",
|
||||
"columnsFrom": [
|
||||
"app_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"language_model_providers": {
|
||||
"name": "language_model_providers",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"api_base_url": {
|
||||
"name": "api_base_url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"env_var_name": {
|
||||
"name": "env_var_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "(unixepoch())"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "(unixepoch())"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"language_models": {
|
||||
"name": "language_models",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"display_name": {
|
||||
"name": "display_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"api_name": {
|
||||
"name": "api_name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"builtin_provider_id": {
|
||||
"name": "builtin_provider_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"custom_provider_id": {
|
||||
"name": "custom_provider_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"description": {
|
||||
"name": "description",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"max_output_tokens": {
|
||||
"name": "max_output_tokens",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"context_window": {
|
||||
"name": "context_window",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "(unixepoch())"
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "(unixepoch())"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"language_models_custom_provider_id_language_model_providers_id_fk": {
|
||||
"name": "language_models_custom_provider_id_language_model_providers_id_fk",
|
||||
"tableFrom": "language_models",
|
||||
"tableTo": "language_model_providers",
|
||||
"columnsFrom": [
|
||||
"custom_provider_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"messages": {
|
||||
"name": "messages",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "integer",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": true
|
||||
},
|
||||
"chat_id": {
|
||||
"name": "chat_id",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"role": {
|
||||
"name": "role",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"content": {
|
||||
"name": "content",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"approval_state": {
|
||||
"name": "approval_state",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"commit_hash": {
|
||||
"name": "commit_hash",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "(unixepoch())"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"messages_chat_id_chats_id_fk": {
|
||||
"name": "messages_chat_id_chats_id_fk",
|
||||
"tableFrom": "messages",
|
||||
"tableTo": "chats",
|
||||
"columnsFrom": [
|
||||
"chat_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
}
|
||||
},
|
||||
"views": {},
|
||||
"enums": {},
|
||||
"_meta": {
|
||||
"schemas": {},
|
||||
"tables": {},
|
||||
"columns": {}
|
||||
},
|
||||
"internal": {
|
||||
"indexes": {}
|
||||
}
|
||||
}
|
||||
@@ -43,6 +43,13 @@
|
||||
"when": 1747095436506,
|
||||
"tag": "0005_clumsy_namor",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 6,
|
||||
"version": "6",
|
||||
"when": 1749515724373,
|
||||
"tag": "0006_mushy_squirrel_girl",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
76
e2e-tests/context_manage.spec.ts
Normal file
76
e2e-tests/context_manage.spec.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { test } from "./helpers/test_helper";
|
||||
|
||||
test("manage context - default", async ({ po }) => {
|
||||
await po.setUp();
|
||||
await po.importApp("context-manage");
|
||||
|
||||
const dialog = await po.openContextFilesPicker();
|
||||
await po.snapshotDialog();
|
||||
await dialog.addManualContextFile("DELETETHIS");
|
||||
await dialog.removeManualContextFile();
|
||||
await dialog.addManualContextFile("src/**/*.ts");
|
||||
await dialog.addManualContextFile("src/sub/**");
|
||||
await po.snapshotDialog();
|
||||
await dialog.close();
|
||||
|
||||
await po.sendPrompt("[dump]");
|
||||
|
||||
await po.snapshotServerDump("all-messages");
|
||||
});
|
||||
|
||||
test("manage context - smart context", async ({ po }) => {
|
||||
await po.setUpDyadPro();
|
||||
await po.selectModel({ provider: "Google", model: "Gemini 2.5 Pro" });
|
||||
await po.importApp("context-manage");
|
||||
|
||||
let dialog = await po.openContextFilesPicker();
|
||||
await po.snapshotDialog();
|
||||
|
||||
await dialog.addManualContextFile("src/**/*.ts");
|
||||
await dialog.addManualContextFile("src/sub/**");
|
||||
await dialog.addAutoIncludeContextFile("a.ts");
|
||||
await dialog.addAutoIncludeContextFile("manual/**");
|
||||
await po.snapshotDialog();
|
||||
await dialog.close();
|
||||
|
||||
await po.sendPrompt("[dump]");
|
||||
|
||||
await po.snapshotServerDump("request");
|
||||
await po.snapshotServerDump("all-messages");
|
||||
|
||||
// Disabling smart context will automatically disable
|
||||
// the auto-includes.
|
||||
const proModesDialog = await po.openProModesDialog();
|
||||
await proModesDialog.toggleSmartContext();
|
||||
await proModesDialog.close();
|
||||
|
||||
await po.sendPrompt("[dump]");
|
||||
await po.snapshotServerDump("request");
|
||||
|
||||
// Removing manual context files will result in all files being included.
|
||||
dialog = await po.openContextFilesPicker();
|
||||
await dialog.removeManualContextFile();
|
||||
await dialog.removeManualContextFile();
|
||||
await dialog.close();
|
||||
|
||||
await po.sendPrompt("[dump]");
|
||||
await po.snapshotServerDump("request");
|
||||
});
|
||||
|
||||
test("manage context - smart context - auto-includes only", async ({ po }) => {
|
||||
await po.setUpDyadPro();
|
||||
await po.selectModel({ provider: "Google", model: "Gemini 2.5 Pro" });
|
||||
await po.importApp("context-manage");
|
||||
|
||||
const dialog = await po.openContextFilesPicker();
|
||||
await po.snapshotDialog();
|
||||
|
||||
await dialog.addAutoIncludeContextFile("a.ts");
|
||||
await dialog.addAutoIncludeContextFile("manual/**");
|
||||
await po.snapshotDialog();
|
||||
await dialog.close();
|
||||
|
||||
await po.sendPrompt("[dump]");
|
||||
|
||||
await po.snapshotServerDump("request");
|
||||
});
|
||||
1
e2e-tests/fixtures/import-app/context-manage/.env.foobar
Normal file
1
e2e-tests/fixtures/import-app/context-manage/.env.foobar
Normal file
@@ -0,0 +1 @@
|
||||
# THIS FILE SHOULD NOT BE SENT IN THE CONTEXT
|
||||
1
e2e-tests/fixtures/import-app/context-manage/AI_RULES.md
Normal file
1
e2e-tests/fixtures/import-app/context-manage/AI_RULES.md
Normal file
@@ -0,0 +1 @@
|
||||
# AI_RULES.md
|
||||
1
e2e-tests/fixtures/import-app/context-manage/a.ts
Normal file
1
e2e-tests/fixtures/import-app/context-manage/a.ts
Normal file
@@ -0,0 +1 @@
|
||||
// a.ts
|
||||
@@ -0,0 +1 @@
|
||||
// exclude.ts: this file is not in any of the globs
|
||||
@@ -0,0 +1 @@
|
||||
// exclude.tsx: this file is not in any of the globs
|
||||
@@ -0,0 +1 @@
|
||||
["should not be included b/c it's json"]
|
||||
@@ -0,0 +1 @@
|
||||
// button.tsx
|
||||
@@ -0,0 +1 @@
|
||||
// helper.ts
|
||||
@@ -0,0 +1 @@
|
||||
/* some.css */
|
||||
1
e2e-tests/fixtures/import-app/context-manage/src/foo.ts
Normal file
1
e2e-tests/fixtures/import-app/context-manage/src/foo.ts
Normal file
@@ -0,0 +1 @@
|
||||
// foo.ts
|
||||
@@ -0,0 +1 @@
|
||||
// sub/sub1.ts
|
||||
@@ -0,0 +1 @@
|
||||
// sub/sub2.tsx
|
||||
@@ -0,0 +1,92 @@
|
||||
// very-large-file.ts
|
||||
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
@@ -1,5 +1,5 @@
|
||||
import { test as base, Page, expect } from "@playwright/test";
|
||||
import { findLatestBuild, parseElectronApp } from "electron-playwright-helpers";
|
||||
import * as eph from "electron-playwright-helpers";
|
||||
import { ElectronApplication, _electron as electron } from "playwright";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
@@ -16,6 +16,54 @@ export const Timeout = {
|
||||
MEDIUM: process.env.CI ? 30_000 : 15_000,
|
||||
};
|
||||
|
||||
export class ContextFilesPickerDialog {
|
||||
constructor(
|
||||
public page: Page,
|
||||
public close: () => Promise<void>,
|
||||
) {}
|
||||
|
||||
async addManualContextFile(path: string) {
|
||||
await this.page.getByTestId("manual-context-files-input").fill(path);
|
||||
await this.page.getByTestId("manual-context-files-add-button").click();
|
||||
}
|
||||
|
||||
async addAutoIncludeContextFile(path: string) {
|
||||
await this.page.getByTestId("auto-include-context-files-input").fill(path);
|
||||
await this.page
|
||||
.getByTestId("auto-include-context-files-add-button")
|
||||
.click();
|
||||
}
|
||||
|
||||
async removeManualContextFile() {
|
||||
await this.page
|
||||
.getByTestId("manual-context-files-remove-button")
|
||||
.first()
|
||||
.click();
|
||||
}
|
||||
|
||||
async removeAutoIncludeContextFile() {
|
||||
await this.page
|
||||
.getByTestId("auto-include-context-files-remove-button")
|
||||
.first()
|
||||
.click();
|
||||
}
|
||||
}
|
||||
|
||||
class ProModesDialog {
|
||||
constructor(
|
||||
public page: Page,
|
||||
public close: () => Promise<void>,
|
||||
) {}
|
||||
|
||||
async toggleSmartContext() {
|
||||
await this.page.getByRole("switch", { name: "Smart Context" }).click();
|
||||
}
|
||||
|
||||
async toggleTurboEdits() {
|
||||
await this.page.getByRole("switch", { name: "Turbo Edits" }).click();
|
||||
}
|
||||
}
|
||||
|
||||
export class PageObject {
|
||||
private userDataDir: string;
|
||||
|
||||
@@ -45,6 +93,15 @@ export class PageObject {
|
||||
await this.selectTestModel();
|
||||
}
|
||||
|
||||
async importApp(appDir: string) {
|
||||
await this.page.getByRole("button", { name: "Import App" }).click();
|
||||
await eph.stubDialog(this.electronApp, "showOpenDialog", {
|
||||
filePaths: [path.join(__dirname, "..", "fixtures", "import-app", appDir)],
|
||||
});
|
||||
await this.page.getByRole("button", { name: "Select Folder" }).click();
|
||||
await this.page.getByRole("button", { name: "Import" }).click();
|
||||
}
|
||||
|
||||
async setUpDyadPro({ autoApprove = false }: { autoApprove?: boolean } = {}) {
|
||||
await this.goToSettingsTab();
|
||||
if (autoApprove) {
|
||||
@@ -74,6 +131,32 @@ export class PageObject {
|
||||
// await page.getByRole('button', { name: 'Select Folder' }).press('Escape');
|
||||
}
|
||||
|
||||
async openContextFilesPicker() {
|
||||
const contextButton = this.page.getByRole("button", {
|
||||
name: "Context",
|
||||
exact: true,
|
||||
});
|
||||
await contextButton.click();
|
||||
return new ContextFilesPickerDialog(this.page, async () => {
|
||||
await contextButton.click();
|
||||
});
|
||||
}
|
||||
|
||||
async openProModesDialog(): Promise<ProModesDialog> {
|
||||
const proButton = this.page
|
||||
// Assumes you're on the chat page.
|
||||
.getByTestId("chat-input-container")
|
||||
.getByRole("button", { name: "Pro", exact: true });
|
||||
await proButton.click();
|
||||
return new ProModesDialog(this.page, async () => {
|
||||
await proButton.click();
|
||||
});
|
||||
}
|
||||
|
||||
async snapshotDialog() {
|
||||
await expect(this.page.getByRole("dialog")).toMatchAriaSnapshot();
|
||||
}
|
||||
|
||||
async snapshotAppFiles({ name }: { name?: string } = {}) {
|
||||
const appPath = await this.getCurrentAppPath();
|
||||
if (!appPath || !fs.existsSync(appPath)) {
|
||||
@@ -214,7 +297,17 @@ export class PageObject {
|
||||
// Perform snapshot comparison
|
||||
const parsedDump = JSON.parse(dumpContent);
|
||||
if (type === "request") {
|
||||
expect(dumpContent).toMatchSnapshot(name);
|
||||
parsedDump["body"]["messages"] = parsedDump["body"]["messages"].map(
|
||||
(message: any) => {
|
||||
if (message.role === "system") {
|
||||
message.content = "[[SYSTEM_MESSAGE]]";
|
||||
}
|
||||
return message;
|
||||
},
|
||||
);
|
||||
expect(
|
||||
JSON.stringify(parsedDump, null, 2).replace(/\\r\\n/g, "\\n"),
|
||||
).toMatchSnapshot(name);
|
||||
return;
|
||||
}
|
||||
expect(
|
||||
@@ -555,9 +648,9 @@ export const test = base.extend<{
|
||||
electronApp: [
|
||||
async ({}, use) => {
|
||||
// find the latest build in the out directory
|
||||
const latestBuild = findLatestBuild();
|
||||
const latestBuild = eph.findLatestBuild();
|
||||
// parse the directory and find paths and other info
|
||||
const appInfo = parseElectronApp(latestBuild);
|
||||
const appInfo = eph.parseElectronApp(latestBuild);
|
||||
process.env.OLLAMA_HOST = "http://localhost:3500/ollama";
|
||||
process.env.LM_STUDIO_BASE_URL_FOR_TESTING =
|
||||
"http://localhost:3500/lmstudio";
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
- dialog:
|
||||
- heading "Codebase Context" [level=3]
|
||||
- paragraph:
|
||||
- text: Select the files to use as context.
|
||||
- img
|
||||
- textbox "src/**/*.tsx"
|
||||
- button "Add"
|
||||
- paragraph: Dyad will use the entire codebase as context.
|
||||
@@ -0,0 +1,514 @@
|
||||
===
|
||||
role: system
|
||||
message:
|
||||
<role> 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. </role>
|
||||
|
||||
# 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 <dyad-command> tag like this:
|
||||
<dyad-command type="rebuild"></dyad-command>
|
||||
<dyad-command type="restart"></dyad-command>
|
||||
<dyad-command type="refresh"></dyad-command>
|
||||
|
||||
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 <dyad-chat-summary> 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 <dyad-write> for creating or updating files. Try to create small, focused files that will be easy to maintain. Use only one <dyad-write> 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 <dyad-write> tag.
|
||||
- Use <dyad-rename> for renaming files.
|
||||
- Use <dyad-delete> for removing files.
|
||||
- Use <dyad-add-dependency> for installing packages.
|
||||
- If the user asks for multiple packages, use <dyad-add-dependency packages="package1 package2 package3"></dyad-add-dependency>
|
||||
- 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 <dyad-write> 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 <dyad-add-dependency>.
|
||||
|
||||
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 <dyad-write>, <dyad-rename>, <dyad-delete>, and <dyad-add-dependency>.
|
||||
|
||||
# Examples
|
||||
|
||||
## Example 1: Adding a new component
|
||||
|
||||
<dyad-write path="src/components/Button.jsx" description="Creating a new Button component with Tailwind styling">
|
||||
"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 (
|
||||
<button
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default Button;
|
||||
</dyad-write>
|
||||
|
||||
<dyad-write path="src/App.jsx" description="Updating the App.jsx file to use the new Button component.">
|
||||
"use client";
|
||||
|
||||
import React from 'react';
|
||||
import Button from './components/Button';
|
||||
|
||||
|
||||
function App() {
|
||||
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
<h1 className="text-2xl font-bold mb-4">My Application</h1>
|
||||
|
||||
<div className="space-x-2">
|
||||
<Button onClick={() => console.log('Primary clicked')}>Primary Button</Button>
|
||||
<Button variant="secondary" onClick={() => console.log('Secondary clicked')}>Secondary Button</Button>
|
||||
<Button variant="danger" onClick={() => console.log('Danger clicked')}>Danger Button</Button>
|
||||
</div>
|
||||
|
||||
{/* ... keep existing code (rest of the component) */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
</dyad-write>
|
||||
<dyad-chat-summary>Adding a new component</dyad-chat-summary>
|
||||
|
||||
## 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.
|
||||
<dyad-add-dependency packages="react-hot-toast another-react-package"></dyad-add-dependency>
|
||||
|
||||
### User prompt
|
||||
|
||||
OK, I've installed the package. Now, I want to create a ToastProvider component.
|
||||
|
||||
### System response
|
||||
|
||||
<dyad-write path="src/components/ToastProvider.jsx" description="Creating a ToastProvider component.">
|
||||
"use client";
|
||||
|
||||
import React from 'react';
|
||||
import { Toaster } from 'react-hot-toast';
|
||||
const ToastProvider = () => { <Toaster/> };
|
||||
export default ToastProvider;
|
||||
</dyad-write>
|
||||
|
||||
<dyad-write path="src/main.jsx" description="Updating the main file to include the 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(
|
||||
<React.StrictMode>
|
||||
<ToastProvider />
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
);
|
||||
</dyad-write>
|
||||
|
||||
<dyad-write path="src/utils/toast.js" description="Creating a utility file to make using toasts easier.">
|
||||
"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);
|
||||
};
|
||||
</dyad-write>
|
||||
<dyad-chat-summary>Installing & using toast</dyad-chat-summary>
|
||||
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
|
||||
|
||||
<dyad-rename from="src/components/UserProfile.jsx" to="src/components/ProfileCard.jsx"></dyad-rename>
|
||||
|
||||
<dyad-write path="src/components/ProfileCard.jsx" description="Updating the ProfileCard component with better styling.">
|
||||
"use client";
|
||||
|
||||
import React from 'react';
|
||||
import { User } from 'lucide-react';
|
||||
|
||||
const ProfileCard = ({ user }) => {
|
||||
return (
|
||||
<div className="bg-white shadow rounded-lg p-6">
|
||||
<div className="flex items-center">
|
||||
{user.avatar ? (
|
||||
<img
|
||||
src={user.avatar}
|
||||
alt={user.name}
|
||||
className="w-12 h-12 rounded-full mr-4"
|
||||
/>
|
||||
) : (
|
||||
<div className="w-12 h-12 bg-gray-200 rounded-full flex items-center justify-center mr-4">
|
||||
<User className="text-gray-500" size={24} />
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<h3 className="font-medium text-lg">{user.name}</h3>
|
||||
<p className="text-gray-500">{user.email}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ... keep existing code (user details section) */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProfileCard;
|
||||
</dyad-write>
|
||||
|
||||
<dyad-delete path="src/components/Analytics.jsx"></dyad-delete>
|
||||
|
||||
<dyad-write path="src/pages/Dashboard.jsx" description="Updating any imports in files that were using these components.">
|
||||
"use client";
|
||||
|
||||
import React from 'react';
|
||||
import ProfileCard from '../components/ProfileCard';
|
||||
|
||||
const Dashboard = () => {
|
||||
|
||||
|
||||
return (
|
||||
<div className="container mx-auto p-4">
|
||||
<h1 className="text-2xl font-bold mb-6">Dashboard</h1>
|
||||
|
||||
<ProfileCard user={currentUser} />
|
||||
|
||||
{/* ... keep existing code (rest of dashboard content) */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Dashboard;
|
||||
</dyad-write>
|
||||
<dyad-chat-summary>Renaming profile file</dyad-chat-summary>
|
||||
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 <dyad-write> 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.
|
||||
|
||||
|
||||
# Thinking Process
|
||||
|
||||
Before responding to user requests, ALWAYS use <think></think> tags to carefully plan your approach. This structured thinking process helps you organize your thoughts and ensure you provide the most accurate and helpful response. Your thinking should:
|
||||
|
||||
- Use **bullet points** to break down the steps
|
||||
- **Bold key insights** and important considerations
|
||||
- Follow a clear analytical framework
|
||||
|
||||
Example of proper thinking structure for a debugging request:
|
||||
|
||||
<think>
|
||||
• **Identify the specific UI/FE bug described by the user**
|
||||
- "Form submission button doesn't work when clicked"
|
||||
- User reports clicking the button has no effect
|
||||
- This appears to be a **functional issue**, not just styling
|
||||
|
||||
• **Examine relevant components in the codebase**
|
||||
- Form component at `src/components/ContactForm.jsx`
|
||||
- Button component at `src/components/Button.jsx`
|
||||
- Form submission logic in `src/utils/formHandlers.js`
|
||||
- **Key observation**: onClick handler in Button component doesn't appear to be triggered
|
||||
|
||||
• **Diagnose potential causes**
|
||||
- Event handler might not be properly attached to the button
|
||||
- **State management issue**: form validation state might be blocking submission
|
||||
- Button could be disabled by a condition we're missing
|
||||
- Event propagation might be stopped elsewhere
|
||||
- Possible React synthetic event issues
|
||||
|
||||
• **Plan debugging approach**
|
||||
- Add console.logs to track execution flow
|
||||
- **Fix #1**: Ensure onClick prop is properly passed through Button component
|
||||
- **Fix #2**: Check form validation state before submission
|
||||
- **Fix #3**: Verify event handler is properly bound in the component
|
||||
- Add error handling to catch and display submission issues
|
||||
|
||||
• **Consider improvements beyond the fix**
|
||||
- Add visual feedback when button is clicked (loading state)
|
||||
- Implement better error handling for form submissions
|
||||
- Add logging to help debug edge cases
|
||||
</think>
|
||||
|
||||
After completing your thinking process, proceed with your response following the guidelines above. Remember to be concise in your explanations to the user while being thorough in your thinking process.
|
||||
|
||||
This structured thinking ensures you:
|
||||
1. Don't miss important aspects of the request
|
||||
2. Consider all relevant factors before making changes
|
||||
3. Deliver more accurate and helpful responses
|
||||
4. Maintain a consistent approach to problem-solving
|
||||
|
||||
|
||||
# AI_RULES.md
|
||||
|
||||
|
||||
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 <dyad-write> tags for **ALL** code output.
|
||||
> Using ``` for code is **PROHIBITED**.
|
||||
> Using <dyad-write> for code is **MANDATORY**.
|
||||
> Any instance of code within ``` is a **CRITICAL FAILURE**.
|
||||
> **REPEAT: NO MARKDOWN CODE BLOCKS. USE <dyad-write> EXCLUSIVELY FOR CODE.**
|
||||
> Do NOT use <dyad-file> tags in the output. ALWAYS use <dyad-write> 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.
|
||||
|
||||
<dyad-add-integration provider="supabase"></dyad-add-integration>
|
||||
|
||||
# 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.
|
||||
|
||||
<dyad-add-integration provider="supabase"></dyad-add-integration>
|
||||
|
||||
## 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.
|
||||
|
||||
<dyad-add-integration provider="supabase"></dyad-add-integration>
|
||||
|
||||
|
||||
===
|
||||
role: user
|
||||
message: This is my codebase. <dyad-file path="src/components/ui/helper.ts">
|
||||
// Contents omitted for brevity
|
||||
</dyad-file>
|
||||
|
||||
<dyad-file path="src/foo.ts">
|
||||
// foo.ts
|
||||
|
||||
</dyad-file>
|
||||
|
||||
<dyad-file path="src/sub/sub1.ts">
|
||||
// sub/sub1.ts
|
||||
|
||||
</dyad-file>
|
||||
|
||||
<dyad-file path="src/sub/sub2.tsx">
|
||||
// sub/sub2.tsx
|
||||
|
||||
</dyad-file>
|
||||
|
||||
<dyad-file path="src/very-large-file.ts">
|
||||
// very-large-file.ts
|
||||
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
// 1234567890
|
||||
|
||||
</dyad-file>
|
||||
|
||||
|
||||
|
||||
===
|
||||
role: assistant
|
||||
message: OK, got it. I'm ready to help
|
||||
|
||||
===
|
||||
role: user
|
||||
message: [dump]
|
||||
@@ -0,0 +1,13 @@
|
||||
- dialog:
|
||||
- heading "Codebase Context" [level=3]
|
||||
- paragraph:
|
||||
- text: Select the files to use as context.
|
||||
- img
|
||||
- textbox "src/**/*.tsx"
|
||||
- button "Add"
|
||||
- text: /src\/\*\*\/\*\.ts 4 files, ~\d+ tokens/
|
||||
- button:
|
||||
- img
|
||||
- text: /src\/sub\/\*\* 2 files, ~\d+ tokens/
|
||||
- button:
|
||||
- img
|
||||
@@ -0,0 +1,14 @@
|
||||
- dialog:
|
||||
- heading "Codebase Context" [level=3]
|
||||
- paragraph:
|
||||
- text: Select the files to use as context.
|
||||
- img
|
||||
- textbox "src/**/*.tsx"
|
||||
- button "Add"
|
||||
- paragraph: Dyad will use Smart Context to automatically find the most relevant files to use as context.
|
||||
- heading "Smart Context Auto-includes" [level=3]
|
||||
- paragraph:
|
||||
- text: These files will always be included in the context.
|
||||
- img
|
||||
- textbox "src/**/*.config.ts"
|
||||
- button "Add"
|
||||
@@ -0,0 +1,92 @@
|
||||
{
|
||||
"body": {
|
||||
"model": "gemini/gemini-2.5-pro-preview-05-06",
|
||||
"max_tokens": 65535,
|
||||
"temperature": 0,
|
||||
"messages": [
|
||||
{
|
||||
"role": "system",
|
||||
"content": "[[SYSTEM_MESSAGE]]"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "[dump]"
|
||||
}
|
||||
],
|
||||
"stream": true,
|
||||
"dyad_options": {
|
||||
"files": [
|
||||
{
|
||||
"path": "a.ts",
|
||||
"content": "// a.ts\n",
|
||||
"force": true
|
||||
},
|
||||
{
|
||||
"path": "AI_RULES.md",
|
||||
"content": "# AI_RULES.md\n",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
"path": "exclude/exclude.ts",
|
||||
"content": "// exclude.ts: this file is not in any of the globs\n",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
"path": "exclude/exclude.tsx",
|
||||
"content": "// exclude.tsx: this file is not in any of the globs\n",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
"path": "manual/file.ts",
|
||||
"content": "",
|
||||
"force": true
|
||||
},
|
||||
{
|
||||
"path": "manual/sub-manual/sub-manual.js",
|
||||
"content": "",
|
||||
"force": true
|
||||
},
|
||||
{
|
||||
"path": "src/components/ui/button.tsx",
|
||||
"content": "// Contents omitted for brevity",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
"path": "src/components/ui/helper.ts",
|
||||
"content": "// Contents omitted for brevity",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
"path": "src/dir/some.css",
|
||||
"content": "/* some.css */\n",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
"path": "src/foo.ts",
|
||||
"content": "// foo.ts\n",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
"path": "src/sub/sub1.ts",
|
||||
"content": "// sub/sub1.ts\n",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
"path": "src/sub/sub2.tsx",
|
||||
"content": "// sub/sub2.tsx\n",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
"path": "src/very-large-file.ts",
|
||||
"content": "// very-large-file.ts\n\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n",
|
||||
"force": false
|
||||
}
|
||||
],
|
||||
"enable_lazy_edits": true,
|
||||
"enable_smart_files_context": true
|
||||
}
|
||||
},
|
||||
"headers": {
|
||||
"authorization": "Bearer testdyadkey"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
- dialog:
|
||||
- heading "Codebase Context" [level=3]
|
||||
- paragraph:
|
||||
- text: Select the files to use as context.
|
||||
- img
|
||||
- textbox "src/**/*.tsx"
|
||||
- button "Add"
|
||||
- paragraph: Dyad will use Smart Context to automatically find the most relevant files to use as context.
|
||||
- heading "Smart Context Auto-includes" [level=3]
|
||||
- paragraph:
|
||||
- text: These files will always be included in the context.
|
||||
- img
|
||||
- textbox "src/**/*.config.ts"
|
||||
- button "Add"
|
||||
- text: /a\.ts 1 files, ~\d+ tokens/
|
||||
- button:
|
||||
- img
|
||||
- text: /manual\/\*\* 2 files, ~\d+ tokens/
|
||||
- button:
|
||||
- img
|
||||
@@ -0,0 +1,14 @@
|
||||
- dialog:
|
||||
- heading "Codebase Context" [level=3]
|
||||
- paragraph:
|
||||
- text: Select the files to use as context.
|
||||
- img
|
||||
- textbox "src/**/*.tsx"
|
||||
- button "Add"
|
||||
- paragraph: Dyad will use Smart Context to automatically find the most relevant files to use as context.
|
||||
- heading "Smart Context Auto-includes" [level=3]
|
||||
- paragraph:
|
||||
- text: These files will always be included in the context.
|
||||
- img
|
||||
- textbox "src/**/*.config.ts"
|
||||
- button "Add"
|
||||
@@ -0,0 +1,67 @@
|
||||
{
|
||||
"body": {
|
||||
"model": "gemini/gemini-2.5-pro-preview-05-06",
|
||||
"max_tokens": 65535,
|
||||
"temperature": 0,
|
||||
"messages": [
|
||||
{
|
||||
"role": "system",
|
||||
"content": "[[SYSTEM_MESSAGE]]"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "[dump]"
|
||||
}
|
||||
],
|
||||
"stream": true,
|
||||
"dyad_options": {
|
||||
"files": [
|
||||
{
|
||||
"path": "a.ts",
|
||||
"content": "// a.ts\n",
|
||||
"force": true
|
||||
},
|
||||
{
|
||||
"path": "manual/file.ts",
|
||||
"content": "",
|
||||
"force": true
|
||||
},
|
||||
{
|
||||
"path": "manual/sub-manual/sub-manual.js",
|
||||
"content": "",
|
||||
"force": true
|
||||
},
|
||||
{
|
||||
"path": "src/components/ui/helper.ts",
|
||||
"content": "// Contents omitted for brevity",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
"path": "src/foo.ts",
|
||||
"content": "// foo.ts\n",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
"path": "src/sub/sub1.ts",
|
||||
"content": "// sub/sub1.ts\n",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
"path": "src/sub/sub2.tsx",
|
||||
"content": "// sub/sub2.tsx\n",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
"path": "src/very-large-file.ts",
|
||||
"content": "// very-large-file.ts\n\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n",
|
||||
"force": false
|
||||
}
|
||||
],
|
||||
"enable_lazy_edits": true,
|
||||
"enable_smart_files_context": true
|
||||
}
|
||||
},
|
||||
"headers": {
|
||||
"authorization": "Bearer testdyadkey"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
- dialog:
|
||||
- heading "Codebase Context" [level=3]
|
||||
- paragraph:
|
||||
- text: Select the files to use as context.
|
||||
- img
|
||||
- textbox "src/**/*.tsx"
|
||||
- button "Add"
|
||||
- text: /src\/\*\*\/\*\.ts 4 files, ~\d+ tokens/
|
||||
- button:
|
||||
- img
|
||||
- text: /src\/sub\/\*\* 2 files, ~\d+ tokens/
|
||||
- button:
|
||||
- img
|
||||
- heading "Smart Context Auto-includes" [level=3]
|
||||
- paragraph:
|
||||
- text: These files will always be included in the context.
|
||||
- img
|
||||
- textbox "src/**/*.config.ts"
|
||||
- button "Add"
|
||||
- text: /a\.ts 1 files, ~\d+ tokens/
|
||||
- button:
|
||||
- img
|
||||
- text: /manual\/\*\* 2 files, ~\d+ tokens/
|
||||
- button:
|
||||
- img
|
||||
@@ -0,0 +1,391 @@
|
||||
===
|
||||
role: system
|
||||
message:
|
||||
<role> 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. </role>
|
||||
|
||||
# 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 <dyad-command> tag like this:
|
||||
<dyad-command type="rebuild"></dyad-command>
|
||||
<dyad-command type="restart"></dyad-command>
|
||||
<dyad-command type="refresh"></dyad-command>
|
||||
|
||||
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 <dyad-chat-summary> 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 <dyad-write> for creating or updating files. Try to create small, focused files that will be easy to maintain. Use only one <dyad-write> 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 <dyad-write> tag.
|
||||
- Use <dyad-rename> for renaming files.
|
||||
- Use <dyad-delete> for removing files.
|
||||
- Use <dyad-add-dependency> for installing packages.
|
||||
- If the user asks for multiple packages, use <dyad-add-dependency packages="package1 package2 package3"></dyad-add-dependency>
|
||||
- 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 <dyad-write> 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 <dyad-add-dependency>.
|
||||
|
||||
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 <dyad-write>, <dyad-rename>, <dyad-delete>, and <dyad-add-dependency>.
|
||||
|
||||
# Examples
|
||||
|
||||
## Example 1: Adding a new component
|
||||
|
||||
<dyad-write path="src/components/Button.jsx" description="Creating a new Button component with Tailwind styling">
|
||||
"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 (
|
||||
<button
|
||||
onClick={onClick}
|
||||
disabled={disabled}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default Button;
|
||||
</dyad-write>
|
||||
|
||||
<dyad-write path="src/App.jsx" description="Updating the App.jsx file to use the new Button component.">
|
||||
"use client";
|
||||
|
||||
import React from 'react';
|
||||
import Button from './components/Button';
|
||||
|
||||
|
||||
function App() {
|
||||
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
<h1 className="text-2xl font-bold mb-4">My Application</h1>
|
||||
|
||||
<div className="space-x-2">
|
||||
<Button onClick={() => console.log('Primary clicked')}>Primary Button</Button>
|
||||
<Button variant="secondary" onClick={() => console.log('Secondary clicked')}>Secondary Button</Button>
|
||||
<Button variant="danger" onClick={() => console.log('Danger clicked')}>Danger Button</Button>
|
||||
</div>
|
||||
|
||||
{/* ... keep existing code (rest of the component) */}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
</dyad-write>
|
||||
<dyad-chat-summary>Adding a new component</dyad-chat-summary>
|
||||
|
||||
## 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.
|
||||
<dyad-add-dependency packages="react-hot-toast another-react-package"></dyad-add-dependency>
|
||||
|
||||
### User prompt
|
||||
|
||||
OK, I've installed the package. Now, I want to create a ToastProvider component.
|
||||
|
||||
### System response
|
||||
|
||||
<dyad-write path="src/components/ToastProvider.jsx" description="Creating a ToastProvider component.">
|
||||
"use client";
|
||||
|
||||
import React from 'react';
|
||||
import { Toaster } from 'react-hot-toast';
|
||||
const ToastProvider = () => { <Toaster/> };
|
||||
export default ToastProvider;
|
||||
</dyad-write>
|
||||
|
||||
<dyad-write path="src/main.jsx" description="Updating the main file to include the 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(
|
||||
<React.StrictMode>
|
||||
<ToastProvider />
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
);
|
||||
</dyad-write>
|
||||
|
||||
<dyad-write path="src/utils/toast.js" description="Creating a utility file to make using toasts easier.">
|
||||
"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);
|
||||
};
|
||||
</dyad-write>
|
||||
<dyad-chat-summary>Installing & using toast</dyad-chat-summary>
|
||||
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
|
||||
|
||||
<dyad-rename from="src/components/UserProfile.jsx" to="src/components/ProfileCard.jsx"></dyad-rename>
|
||||
|
||||
<dyad-write path="src/components/ProfileCard.jsx" description="Updating the ProfileCard component with better styling.">
|
||||
"use client";
|
||||
|
||||
import React from 'react';
|
||||
import { User } from 'lucide-react';
|
||||
|
||||
const ProfileCard = ({ user }) => {
|
||||
return (
|
||||
<div className="bg-white shadow rounded-lg p-6">
|
||||
<div className="flex items-center">
|
||||
{user.avatar ? (
|
||||
<img
|
||||
src={user.avatar}
|
||||
alt={user.name}
|
||||
className="w-12 h-12 rounded-full mr-4"
|
||||
/>
|
||||
) : (
|
||||
<div className="w-12 h-12 bg-gray-200 rounded-full flex items-center justify-center mr-4">
|
||||
<User className="text-gray-500" size={24} />
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<h3 className="font-medium text-lg">{user.name}</h3>
|
||||
<p className="text-gray-500">{user.email}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* ... keep existing code (user details section) */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProfileCard;
|
||||
</dyad-write>
|
||||
|
||||
<dyad-delete path="src/components/Analytics.jsx"></dyad-delete>
|
||||
|
||||
<dyad-write path="src/pages/Dashboard.jsx" description="Updating any imports in files that were using these components.">
|
||||
"use client";
|
||||
|
||||
import React from 'react';
|
||||
import ProfileCard from '../components/ProfileCard';
|
||||
|
||||
const Dashboard = () => {
|
||||
|
||||
|
||||
return (
|
||||
<div className="container mx-auto p-4">
|
||||
<h1 className="text-2xl font-bold mb-6">Dashboard</h1>
|
||||
|
||||
<ProfileCard user={currentUser} />
|
||||
|
||||
{/* ... keep existing code (rest of dashboard content) */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Dashboard;
|
||||
</dyad-write>
|
||||
<dyad-chat-summary>Renaming profile file</dyad-chat-summary>
|
||||
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 <dyad-write> 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.
|
||||
|
||||
|
||||
# Thinking Process
|
||||
|
||||
Before responding to user requests, ALWAYS use <think></think> tags to carefully plan your approach. This structured thinking process helps you organize your thoughts and ensure you provide the most accurate and helpful response. Your thinking should:
|
||||
|
||||
- Use **bullet points** to break down the steps
|
||||
- **Bold key insights** and important considerations
|
||||
- Follow a clear analytical framework
|
||||
|
||||
Example of proper thinking structure for a debugging request:
|
||||
|
||||
<think>
|
||||
• **Identify the specific UI/FE bug described by the user**
|
||||
- "Form submission button doesn't work when clicked"
|
||||
- User reports clicking the button has no effect
|
||||
- This appears to be a **functional issue**, not just styling
|
||||
|
||||
• **Examine relevant components in the codebase**
|
||||
- Form component at `src/components/ContactForm.jsx`
|
||||
- Button component at `src/components/Button.jsx`
|
||||
- Form submission logic in `src/utils/formHandlers.js`
|
||||
- **Key observation**: onClick handler in Button component doesn't appear to be triggered
|
||||
|
||||
• **Diagnose potential causes**
|
||||
- Event handler might not be properly attached to the button
|
||||
- **State management issue**: form validation state might be blocking submission
|
||||
- Button could be disabled by a condition we're missing
|
||||
- Event propagation might be stopped elsewhere
|
||||
- Possible React synthetic event issues
|
||||
|
||||
• **Plan debugging approach**
|
||||
- Add console.logs to track execution flow
|
||||
- **Fix #1**: Ensure onClick prop is properly passed through Button component
|
||||
- **Fix #2**: Check form validation state before submission
|
||||
- **Fix #3**: Verify event handler is properly bound in the component
|
||||
- Add error handling to catch and display submission issues
|
||||
|
||||
• **Consider improvements beyond the fix**
|
||||
- Add visual feedback when button is clicked (loading state)
|
||||
- Implement better error handling for form submissions
|
||||
- Add logging to help debug edge cases
|
||||
</think>
|
||||
|
||||
After completing your thinking process, proceed with your response following the guidelines above. Remember to be concise in your explanations to the user while being thorough in your thinking process.
|
||||
|
||||
This structured thinking ensures you:
|
||||
1. Don't miss important aspects of the request
|
||||
2. Consider all relevant factors before making changes
|
||||
3. Deliver more accurate and helpful responses
|
||||
4. Maintain a consistent approach to problem-solving
|
||||
|
||||
|
||||
# AI_RULES.md
|
||||
|
||||
|
||||
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 <dyad-write> tags for **ALL** code output.
|
||||
> Using ``` for code is **PROHIBITED**.
|
||||
> Using <dyad-write> for code is **MANDATORY**.
|
||||
> Any instance of code within ``` is a **CRITICAL FAILURE**.
|
||||
> **REPEAT: NO MARKDOWN CODE BLOCKS. USE <dyad-write> EXCLUSIVELY FOR CODE.**
|
||||
> Do NOT use <dyad-file> tags in the output. ALWAYS use <dyad-write> 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.
|
||||
|
||||
<dyad-add-integration provider="supabase"></dyad-add-integration>
|
||||
|
||||
# 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.
|
||||
|
||||
<dyad-add-integration provider="supabase"></dyad-add-integration>
|
||||
|
||||
## 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.
|
||||
|
||||
<dyad-add-integration provider="supabase"></dyad-add-integration>
|
||||
|
||||
|
||||
===
|
||||
role: user
|
||||
message: [dump]
|
||||
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"body": {
|
||||
"model": "gemini/gemini-2.5-pro-preview-05-06",
|
||||
"max_tokens": 65535,
|
||||
"temperature": 0,
|
||||
"messages": [
|
||||
{
|
||||
"role": "system",
|
||||
"content": "[[SYSTEM_MESSAGE]]"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "[dump]"
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "[[dyad-dump-path=*]]"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "[dump]"
|
||||
}
|
||||
],
|
||||
"stream": true,
|
||||
"dyad_options": {
|
||||
"files": [
|
||||
{
|
||||
"path": "src/components/ui/helper.ts",
|
||||
"content": "// Contents omitted for brevity",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
"path": "src/foo.ts",
|
||||
"content": "// foo.ts\n",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
"path": "src/sub/sub1.ts",
|
||||
"content": "// sub/sub1.ts\n",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
"path": "src/sub/sub2.tsx",
|
||||
"content": "// sub/sub2.tsx\n",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
"path": "src/very-large-file.ts",
|
||||
"content": "// very-large-file.ts\n\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n",
|
||||
"force": false
|
||||
}
|
||||
],
|
||||
"enable_lazy_edits": true,
|
||||
"enable_smart_files_context": false
|
||||
}
|
||||
},
|
||||
"headers": {
|
||||
"authorization": "Bearer testdyadkey"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
{
|
||||
"body": {
|
||||
"model": "gemini/gemini-2.5-pro-preview-05-06",
|
||||
"max_tokens": 65535,
|
||||
"temperature": 0,
|
||||
"messages": [
|
||||
{
|
||||
"role": "system",
|
||||
"content": "[[SYSTEM_MESSAGE]]"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "[dump]"
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "[[dyad-dump-path=*]]"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "[dump]"
|
||||
},
|
||||
{
|
||||
"role": "assistant",
|
||||
"content": "[[dyad-dump-path=*]]"
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "[dump]"
|
||||
}
|
||||
],
|
||||
"stream": true,
|
||||
"dyad_options": {
|
||||
"files": [
|
||||
{
|
||||
"path": "a.ts",
|
||||
"content": "// a.ts\n",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
"path": "AI_RULES.md",
|
||||
"content": "# AI_RULES.md\n",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
"path": "exclude/exclude.ts",
|
||||
"content": "// exclude.ts: this file is not in any of the globs\n",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
"path": "exclude/exclude.tsx",
|
||||
"content": "// exclude.tsx: this file is not in any of the globs\n",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
"path": "manual/file.ts",
|
||||
"content": "",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
"path": "manual/sub-manual/sub-manual.js",
|
||||
"content": "",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
"path": "src/components/ui/button.tsx",
|
||||
"content": "// Contents omitted for brevity",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
"path": "src/components/ui/helper.ts",
|
||||
"content": "// Contents omitted for brevity",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
"path": "src/dir/some.css",
|
||||
"content": "/* some.css */\n",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
"path": "src/foo.ts",
|
||||
"content": "// foo.ts\n",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
"path": "src/sub/sub1.ts",
|
||||
"content": "// sub/sub1.ts\n",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
"path": "src/sub/sub2.tsx",
|
||||
"content": "// sub/sub2.tsx\n",
|
||||
"force": false
|
||||
},
|
||||
{
|
||||
"path": "src/very-large-file.ts",
|
||||
"content": "// very-large-file.ts\n\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n// 1234567890\n",
|
||||
"force": false
|
||||
}
|
||||
],
|
||||
"enable_lazy_edits": true,
|
||||
"enable_smart_files_context": false
|
||||
}
|
||||
},
|
||||
"headers": {
|
||||
"authorization": "Bearer testdyadkey"
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
470
package-lock.json
generated
470
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "dyad",
|
||||
"version": "0.8.0",
|
||||
"version": "0.7.5",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "dyad",
|
||||
"version": "0.8.0",
|
||||
"version": "0.7.5",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "^1.2.8",
|
||||
@@ -19,6 +19,7 @@
|
||||
"@openrouter/ai-sdk-provider": "^0.4.5",
|
||||
"@radix-ui/react-accordion": "^1.2.4",
|
||||
"@radix-ui/react-alert-dialog": "^1.1.13",
|
||||
"@radix-ui/react-checkbox": "^1.3.2",
|
||||
"@radix-ui/react-dialog": "^1.1.7",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.7",
|
||||
"@radix-ui/react-label": "^2.1.4",
|
||||
@@ -51,6 +52,7 @@
|
||||
"fix-path": "^4.0.0",
|
||||
"framer-motion": "^12.6.3",
|
||||
"geist": "^1.3.1",
|
||||
"glob": "^11.0.2",
|
||||
"isomorphic-git": "^1.30.1",
|
||||
"jotai": "^2.12.2",
|
||||
"kill-port": "^2.0.1",
|
||||
@@ -90,6 +92,7 @@
|
||||
"@playwright/test": "^1.52.0",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
"@types/better-sqlite3": "^7.6.13",
|
||||
"@types/glob": "^8.1.0",
|
||||
"@types/kill-port": "^2.0.3",
|
||||
"@types/node": "^22.14.0",
|
||||
"@types/react": "^19.0.10",
|
||||
@@ -1166,6 +1169,27 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/@electron/asar/node_modules/glob": {
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
|
||||
"deprecated": "Glob versions prior to v9 are no longer supported",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.1.1",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/@electron/fuses": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@electron/fuses/-/fuses-1.8.0.tgz",
|
||||
@@ -3005,6 +3029,50 @@
|
||||
"url": "https://opencollective.com/libvips"
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/cliui": {
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
||||
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"string-width": "^5.1.2",
|
||||
"string-width-cjs": "npm:string-width@^4.2.0",
|
||||
"strip-ansi": "^7.0.1",
|
||||
"strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
|
||||
"wrap-ansi": "^8.1.0",
|
||||
"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/cliui/node_modules/ansi-regex": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
|
||||
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/cliui/node_modules/strip-ansi": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
|
||||
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@jest/expect-utils": {
|
||||
"version": "29.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz",
|
||||
@@ -3868,6 +3936,77 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-checkbox": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.2.tgz",
|
||||
"integrity": "sha512-yd+dI56KZqawxKZrJ31eENUwqc1QSqg4OZ15rybGjF2ZNwMO+wCyHzAVLRp9qoYJf7kYy0YpZ2b0JCzJ42HZpA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/primitive": "1.1.2",
|
||||
"@radix-ui/react-compose-refs": "1.1.2",
|
||||
"@radix-ui/react-context": "1.1.2",
|
||||
"@radix-ui/react-presence": "1.1.4",
|
||||
"@radix-ui/react-primitive": "2.1.3",
|
||||
"@radix-ui/react-use-controllable-state": "1.2.2",
|
||||
"@radix-ui/react-use-previous": "1.1.1",
|
||||
"@radix-ui/react-use-size": "1.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-primitive": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
|
||||
"integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-slot": "1.2.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"@types/react-dom": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-slot": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
|
||||
"integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@radix-ui/react-compose-refs": "1.1.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "*",
|
||||
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@radix-ui/react-collapsible": {
|
||||
"version": "1.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.8.tgz",
|
||||
@@ -5908,6 +6047,17 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/glob": {
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-8.1.0.tgz",
|
||||
"integrity": "sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/minimatch": "^5.1.2",
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/hast": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz",
|
||||
@@ -6034,6 +6184,13 @@
|
||||
"@types/unist": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/minimatch": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz",
|
||||
"integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/ms": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
|
||||
@@ -6673,7 +6830,6 @@
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
@@ -8981,7 +9137,6 @@
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
||||
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/ee-first": {
|
||||
@@ -9079,6 +9234,29 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-installer-common/node_modules/glob": {
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
|
||||
"deprecated": "Glob versions prior to v9 are no longer supported",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.1.1",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-installer-debian": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/electron-installer-debian/-/electron-installer-debian-3.2.0.tgz",
|
||||
@@ -9591,7 +9769,6 @@
|
||||
"version": "9.2.2",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
|
||||
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/encodeurl": {
|
||||
@@ -10818,6 +10995,34 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/foreground-child": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
|
||||
"integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"cross-spawn": "^7.0.6",
|
||||
"signal-exit": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/foreground-child/node_modules/signal-exit": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
|
||||
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
|
||||
@@ -11281,21 +11486,23 @@
|
||||
}
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
|
||||
"deprecated": "Glob versions prior to v9 are no longer supported",
|
||||
"version": "11.0.2",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-11.0.2.tgz",
|
||||
"integrity": "sha512-YT7U7Vye+t5fZ/QMkBFrTJ7ZQxInIUjwyAjVj84CYXqgBdv30MFUPGnBR6sQaVq6Is15wYJUsnzTuWaGRBhBAQ==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.1.1",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
"foreground-child": "^3.1.0",
|
||||
"jackspeak": "^4.0.1",
|
||||
"minimatch": "^10.0.0",
|
||||
"minipass": "^7.1.2",
|
||||
"package-json-from-dist": "^1.0.0",
|
||||
"path-scurry": "^2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"glob": "dist/esm/bin.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
"node": "20 || >=22"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
@@ -11314,6 +11521,39 @@
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/glob/node_modules/brace-expansion": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
|
||||
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"balanced-match": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/glob/node_modules/minimatch": {
|
||||
"version": "10.0.1",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz",
|
||||
"integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "20 || >=22"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/glob/node_modules/minipass": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
|
||||
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/global-agent": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz",
|
||||
@@ -12542,6 +12782,21 @@
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/jackspeak": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz",
|
||||
"integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==",
|
||||
"license": "BlueOak-1.0.0",
|
||||
"dependencies": {
|
||||
"@isaacs/cliui": "^8.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "20 || >=22"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/jest-diff": {
|
||||
"version": "29.7.0",
|
||||
"resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz",
|
||||
@@ -15790,6 +16045,12 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/package-json-from-dist": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
|
||||
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
|
||||
"license": "BlueOak-1.0.0"
|
||||
},
|
||||
"node_modules/pako": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
|
||||
@@ -15917,6 +16178,40 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/path-scurry": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz",
|
||||
"integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==",
|
||||
"license": "BlueOak-1.0.0",
|
||||
"dependencies": {
|
||||
"lru-cache": "^11.0.0",
|
||||
"minipass": "^7.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": "20 || >=22"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/path-scurry/node_modules/lru-cache": {
|
||||
"version": "11.1.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.1.0.tgz",
|
||||
"integrity": "sha512-QIXZUBJUx+2zHUdQujWejBkcD9+cs94tLn0+YL8UrCh+D5sCXZ4c7LaEH48pNwRY3MLDgqUFyhlCyjJPf1WP0A==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": "20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/path-scurry/node_modules/minipass": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
|
||||
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=16 || 14 >=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/path-to-regexp": {
|
||||
"version": "0.1.12",
|
||||
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
|
||||
@@ -17073,6 +17368,28 @@
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/rimraf/node_modules/glob": {
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
|
||||
"deprecated": "Glob versions prior to v9 are no longer supported",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.1.1",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/roarr": {
|
||||
"version": "2.15.4",
|
||||
"resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz",
|
||||
@@ -18080,7 +18397,6 @@
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
|
||||
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"eastasianwidth": "^0.2.0",
|
||||
@@ -18094,11 +18410,40 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/string-width-cjs": {
|
||||
"name": "string-width",
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
"strip-ansi": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/string-width-cjs/node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/string-width-cjs/node_modules/is-fullwidth-code-point": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/string-width/node_modules/ansi-regex": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
|
||||
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
@@ -18111,7 +18456,6 @@
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
|
||||
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^6.0.1"
|
||||
@@ -18200,7 +18544,19 @@
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-ansi-cjs": {
|
||||
"name": "strip-ansi",
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
@@ -18484,6 +18840,29 @@
|
||||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/temp/node_modules/glob": {
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
|
||||
"deprecated": "Glob versions prior to v9 are no longer supported",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.1.1",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/temp/node_modules/mkdirp": {
|
||||
"version": "0.5.6",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
|
||||
@@ -20203,7 +20582,6 @@
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
|
||||
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^6.1.0",
|
||||
@@ -20217,11 +20595,57 @@
|
||||
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi-cjs": {
|
||||
"name": "wrap-ansi",
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.0.0",
|
||||
"string-width": "^4.1.0",
|
||||
"strip-ansi": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/wrap-ansi-cjs/node_modules/is-fullwidth-code-point": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi-cjs/node_modules/string-width": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
"strip-ansi": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi/node_modules/ansi-regex": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
|
||||
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
@@ -20234,7 +20658,6 @@
|
||||
"version": "6.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
|
||||
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
@@ -20247,7 +20670,6 @@
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
|
||||
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^6.0.1"
|
||||
|
||||
@@ -57,6 +57,7 @@
|
||||
"@playwright/test": "^1.52.0",
|
||||
"@testing-library/react": "^16.3.0",
|
||||
"@types/better-sqlite3": "^7.6.13",
|
||||
"@types/glob": "^8.1.0",
|
||||
"@types/kill-port": "^2.0.3",
|
||||
"@types/node": "^22.14.0",
|
||||
"@types/react": "^19.0.10",
|
||||
@@ -89,6 +90,7 @@
|
||||
"@openrouter/ai-sdk-provider": "^0.4.5",
|
||||
"@radix-ui/react-accordion": "^1.2.4",
|
||||
"@radix-ui/react-alert-dialog": "^1.1.13",
|
||||
"@radix-ui/react-checkbox": "^1.3.2",
|
||||
"@radix-ui/react-dialog": "^1.1.7",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.7",
|
||||
"@radix-ui/react-label": "^2.1.4",
|
||||
@@ -121,6 +123,7 @@
|
||||
"fix-path": "^4.0.0",
|
||||
"framer-motion": "^12.6.3",
|
||||
"geist": "^1.3.1",
|
||||
"glob": "^11.0.2",
|
||||
"isomorphic-git": "^1.30.1",
|
||||
"jotai": "^2.12.2",
|
||||
"kill-port": "^2.0.1",
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
import { ContextFilesPicker } from "./ContextFilesPicker";
|
||||
import { ModelPicker } from "./ModelPicker";
|
||||
import { ProModeSelector } from "./ProModeSelector";
|
||||
|
||||
export function ChatInputControls() {
|
||||
export function ChatInputControls({
|
||||
showContextFilesPicker = false,
|
||||
}: {
|
||||
showContextFilesPicker?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<div className="pb-2 flex gap-2">
|
||||
<ModelPicker />
|
||||
{showContextFilesPicker && <ContextFilesPicker />}
|
||||
<ProModeSelector />
|
||||
</div>
|
||||
);
|
||||
|
||||
281
src/components/ContextFilesPicker.tsx
Normal file
281
src/components/ContextFilesPicker.tsx
Normal file
@@ -0,0 +1,281 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from "@/components/ui/popover";
|
||||
|
||||
import { FileCode, InfoIcon, Trash2 } from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipProvider,
|
||||
TooltipTrigger,
|
||||
} from "./ui/tooltip";
|
||||
import { useSettings } from "@/hooks/useSettings";
|
||||
import { useContextPaths } from "@/hooks/useContextPaths";
|
||||
|
||||
export function ContextFilesPicker() {
|
||||
const { settings } = useSettings();
|
||||
const {
|
||||
contextPaths,
|
||||
smartContextAutoIncludes,
|
||||
updateContextPaths,
|
||||
updateSmartContextAutoIncludes,
|
||||
} = useContextPaths();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [newPath, setNewPath] = useState("");
|
||||
const [newAutoIncludePath, setNewAutoIncludePath] = useState("");
|
||||
|
||||
const addPath = () => {
|
||||
if (
|
||||
newPath.trim() === "" ||
|
||||
contextPaths.find((p) => p.globPath === newPath)
|
||||
) {
|
||||
setNewPath("");
|
||||
return;
|
||||
}
|
||||
const newPaths = [
|
||||
...contextPaths.map(({ globPath }) => ({ globPath })),
|
||||
{
|
||||
globPath: newPath,
|
||||
},
|
||||
];
|
||||
updateContextPaths(newPaths);
|
||||
setNewPath("");
|
||||
};
|
||||
|
||||
const removePath = (pathToRemove: string) => {
|
||||
const newPaths = contextPaths
|
||||
.filter((p) => p.globPath !== pathToRemove)
|
||||
.map(({ globPath }) => ({ globPath }));
|
||||
updateContextPaths(newPaths);
|
||||
};
|
||||
|
||||
const addAutoIncludePath = () => {
|
||||
if (
|
||||
newAutoIncludePath.trim() === "" ||
|
||||
smartContextAutoIncludes.find((p) => p.globPath === newAutoIncludePath)
|
||||
) {
|
||||
setNewAutoIncludePath("");
|
||||
return;
|
||||
}
|
||||
const newPaths = [
|
||||
...smartContextAutoIncludes.map(({ globPath }) => ({ globPath })),
|
||||
{
|
||||
globPath: newAutoIncludePath,
|
||||
},
|
||||
];
|
||||
updateSmartContextAutoIncludes(newPaths);
|
||||
setNewAutoIncludePath("");
|
||||
};
|
||||
|
||||
const removeAutoIncludePath = (pathToRemove: string) => {
|
||||
const newPaths = smartContextAutoIncludes
|
||||
.filter((p) => p.globPath !== pathToRemove)
|
||||
.map(({ globPath }) => ({ globPath }));
|
||||
updateSmartContextAutoIncludes(newPaths);
|
||||
};
|
||||
|
||||
const isSmartContextEnabled =
|
||||
settings?.enableDyadPro && settings?.enableProSmartFilesContextMode;
|
||||
|
||||
return (
|
||||
<Popover open={isOpen} onOpenChange={setIsOpen}>
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant="ghost" className="gap-2">
|
||||
<FileCode className="size-4" />
|
||||
<span>Context</span>
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-96" align="start">
|
||||
<div className="relative space-y-4">
|
||||
<div>
|
||||
<h3 className="font-medium">Codebase Context</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="flex items-center gap-1 cursor-help">
|
||||
Select the files to use as context.{" "}
|
||||
<InfoIcon className="size-4" />
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="max-w-[300px]">
|
||||
{isSmartContextEnabled ? (
|
||||
<p>
|
||||
With Smart Context, Dyad uses the most relevant files as
|
||||
context.
|
||||
</p>
|
||||
) : (
|
||||
<p>By default, Dyad uses your whole codebase.</p>
|
||||
)}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex w-full max-w-sm items-center space-x-2">
|
||||
<Input
|
||||
data-testid="manual-context-files-input"
|
||||
type="text"
|
||||
placeholder="src/**/*.tsx"
|
||||
value={newPath}
|
||||
onChange={(e) => setNewPath(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
addPath();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
type="submit"
|
||||
onClick={addPath}
|
||||
data-testid="manual-context-files-add-button"
|
||||
>
|
||||
Add
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<TooltipProvider>
|
||||
{contextPaths.length > 0 ? (
|
||||
<div className="space-y-2">
|
||||
{contextPaths.map((p) => (
|
||||
<div
|
||||
key={p.globPath}
|
||||
className="flex items-center justify-between gap-2 rounded-md border p-2"
|
||||
>
|
||||
<div className="flex flex-1 flex-col overflow-hidden">
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="truncate font-mono text-sm">
|
||||
{p.globPath}
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>{p.globPath}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{p.files} files, ~{p.tokens} tokens
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => removePath(p.globPath)}
|
||||
data-testid="manual-context-files-remove-button"
|
||||
>
|
||||
<Trash2 className="size-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="rounded-md border border-dashed p-4 text-center">
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{isSmartContextEnabled
|
||||
? "Dyad will use Smart Context to automatically find the most relevant files to use as context."
|
||||
: "Dyad will use the entire codebase as context."}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</TooltipProvider>
|
||||
|
||||
{isSmartContextEnabled && (
|
||||
<div className="pt-2">
|
||||
<div>
|
||||
<h3 className="font-medium">Smart Context Auto-includes</h3>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="flex items-center gap-1 cursor-help">
|
||||
These files will always be included in the context.{" "}
|
||||
<InfoIcon className="ml-2 size-4" />
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="max-w-[300px]">
|
||||
<p>
|
||||
Auto-include files are always included in the context
|
||||
in addition to the files selected as relevant by Smart
|
||||
Context.
|
||||
</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex w-full max-w-sm items-center space-x-2 mt-4">
|
||||
<Input
|
||||
data-testid="auto-include-context-files-input"
|
||||
type="text"
|
||||
placeholder="src/**/*.config.ts"
|
||||
value={newAutoIncludePath}
|
||||
onChange={(e) => setNewAutoIncludePath(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === "Enter") {
|
||||
addAutoIncludePath();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
type="submit"
|
||||
onClick={addAutoIncludePath}
|
||||
data-testid="auto-include-context-files-add-button"
|
||||
>
|
||||
Add
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<TooltipProvider>
|
||||
{smartContextAutoIncludes.length > 0 && (
|
||||
<div className="space-y-2 mt-4">
|
||||
{smartContextAutoIncludes.map((p) => (
|
||||
<div
|
||||
key={p.globPath}
|
||||
className="flex items-center justify-between gap-2 rounded-md border p-2"
|
||||
>
|
||||
<div className="flex flex-1 flex-col overflow-hidden">
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<span className="truncate font-mono text-sm">
|
||||
{p.globPath}
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>{p.globPath}</p>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
<span className="text-xs text-muted-foreground">
|
||||
{p.files} files, ~{p.tokens} tokens
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
onClick={() => removeAutoIncludePath(p.globPath)}
|
||||
data-testid="auto-include-context-files-remove-button"
|
||||
>
|
||||
<Trash2 className="size-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</TooltipProvider>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
@@ -341,7 +341,7 @@ export function ChatInput({ chatId }: { chatId?: number }) {
|
||||
)}
|
||||
</div>
|
||||
<div className="pl-2 pr-1 flex items-center justify-between">
|
||||
<ChatInputControls />
|
||||
<ChatInputControls showContextFilesPicker={true} />
|
||||
<button
|
||||
onClick={() => setShowTokenBar(!showTokenBar)}
|
||||
className="flex items-center px-2 py-1 text-xs text-muted-foreground hover:bg-muted rounded"
|
||||
|
||||
36
src/components/ui/badge.tsx
Normal file
36
src/components/ui/badge.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import * as React from "react";
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const badgeVariants = cva(
|
||||
"inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
|
||||
secondary:
|
||||
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
destructive:
|
||||
"border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
|
||||
outline: "text-foreground",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
export interface BadgeProps
|
||||
extends React.HTMLAttributes<HTMLDivElement>,
|
||||
VariantProps<typeof badgeVariants> {}
|
||||
|
||||
function Badge({ className, variant, ...props }: BadgeProps) {
|
||||
return (
|
||||
<div className={cn(badgeVariants({ variant }), className)} {...props} />
|
||||
);
|
||||
}
|
||||
|
||||
export { Badge, badgeVariants };
|
||||
30
src/components/ui/checkbox.tsx
Normal file
30
src/components/ui/checkbox.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import * as React from "react";
|
||||
import * as CheckboxPrimitive from "@radix-ui/react-checkbox";
|
||||
import { CheckIcon } from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
function Checkbox({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof CheckboxPrimitive.Root>) {
|
||||
return (
|
||||
<CheckboxPrimitive.Root
|
||||
data-slot="checkbox"
|
||||
className={cn(
|
||||
"peer border-input dark:bg-input/30 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground dark:data-[state=checked]:bg-primary data-[state=checked]:border-primary focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive size-4 shrink-0 rounded-[4px] border shadow-xs transition-shadow outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<CheckboxPrimitive.Indicator
|
||||
data-slot="checkbox-indicator"
|
||||
className="flex items-center justify-center text-current transition-none"
|
||||
>
|
||||
<CheckIcon className="size-3.5" />
|
||||
</CheckboxPrimitive.Indicator>
|
||||
</CheckboxPrimitive.Root>
|
||||
);
|
||||
}
|
||||
|
||||
export { Checkbox };
|
||||
@@ -15,6 +15,7 @@ export const apps = sqliteTable("apps", {
|
||||
githubOrg: text("github_org"),
|
||||
githubRepo: text("github_repo"),
|
||||
supabaseProjectId: text("supabase_project_id"),
|
||||
chatContext: text("chat_context", { mode: "json" }),
|
||||
});
|
||||
|
||||
export const chats = sqliteTable("chats", {
|
||||
|
||||
73
src/hooks/useContextPaths.ts
Normal file
73
src/hooks/useContextPaths.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { useAtomValue } from "jotai";
|
||||
import { selectedAppIdAtom } from "@/atoms/appAtoms";
|
||||
import { IpcClient } from "@/ipc/ipc_client";
|
||||
import { GlobPath, ContextPathResults } from "@/lib/schemas";
|
||||
|
||||
export function useContextPaths() {
|
||||
const queryClient = useQueryClient();
|
||||
const appId = useAtomValue(selectedAppIdAtom);
|
||||
|
||||
const {
|
||||
data: contextPathsData,
|
||||
isLoading,
|
||||
error,
|
||||
} = useQuery<ContextPathResults, Error>({
|
||||
queryKey: ["context-paths", appId],
|
||||
queryFn: async () => {
|
||||
if (!appId) return { contextPaths: [], smartContextAutoIncludes: [] };
|
||||
const ipcClient = IpcClient.getInstance();
|
||||
return ipcClient.getChatContextResults({ appId });
|
||||
},
|
||||
enabled: !!appId,
|
||||
});
|
||||
|
||||
const updateContextPathsMutation = useMutation<
|
||||
unknown,
|
||||
Error,
|
||||
{ contextPaths: GlobPath[]; smartContextAutoIncludes?: GlobPath[] }
|
||||
>({
|
||||
mutationFn: async ({ contextPaths, smartContextAutoIncludes }) => {
|
||||
if (!appId) throw new Error("No app selected");
|
||||
const ipcClient = IpcClient.getInstance();
|
||||
return ipcClient.setChatContext({
|
||||
appId,
|
||||
chatContext: {
|
||||
contextPaths,
|
||||
smartContextAutoIncludes: smartContextAutoIncludes || [],
|
||||
},
|
||||
});
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["context-paths", appId] });
|
||||
},
|
||||
});
|
||||
|
||||
const updateContextPaths = async (paths: GlobPath[]) => {
|
||||
const currentAutoIncludes =
|
||||
contextPathsData?.smartContextAutoIncludes || [];
|
||||
return updateContextPathsMutation.mutateAsync({
|
||||
contextPaths: paths,
|
||||
smartContextAutoIncludes: currentAutoIncludes.map(({ globPath }) => ({
|
||||
globPath,
|
||||
})),
|
||||
});
|
||||
};
|
||||
|
||||
const updateSmartContextAutoIncludes = async (paths: GlobPath[]) => {
|
||||
const currentContextPaths = contextPathsData?.contextPaths || [];
|
||||
return updateContextPathsMutation.mutateAsync({
|
||||
contextPaths: currentContextPaths.map(({ globPath }) => ({ globPath })),
|
||||
smartContextAutoIncludes: paths,
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
contextPaths: contextPathsData?.contextPaths || [],
|
||||
smartContextAutoIncludes: contextPathsData?.smartContextAutoIncludes || [],
|
||||
isLoading,
|
||||
error,
|
||||
updateContextPaths,
|
||||
updateSmartContextAutoIncludes,
|
||||
};
|
||||
}
|
||||
@@ -33,6 +33,7 @@ import { readFile, writeFile, unlink } from "fs/promises";
|
||||
import { getMaxTokens } from "../utils/token_utils";
|
||||
import { MAX_CHAT_TURNS_IN_CONTEXT } from "@/constants/settings_constants";
|
||||
import { streamTextWithBackup } from "../utils/stream_utils";
|
||||
import { validateChatContext } from "../utils/context_paths_utils";
|
||||
|
||||
const logger = log.scope("chat_stream_handlers");
|
||||
|
||||
@@ -226,7 +227,10 @@ export function registerChatStreamHandlers() {
|
||||
if (updatedChat.app) {
|
||||
const appPath = getDyadAppPath(updatedChat.app.path);
|
||||
try {
|
||||
const out = await extractCodebase(appPath);
|
||||
const out = await extractCodebase({
|
||||
appPath,
|
||||
chatContext: validateChatContext(updatedChat.app.chatContext),
|
||||
});
|
||||
codebaseInfo = out.formattedOutput;
|
||||
files = out.files;
|
||||
logger.log(`Extracted codebase information from ${appPath}`);
|
||||
|
||||
98
src/ipc/handlers/context_paths_handlers.ts
Normal file
98
src/ipc/handlers/context_paths_handlers.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import { db } from "@/db";
|
||||
import { apps } from "@/db/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import {
|
||||
AppChatContext,
|
||||
AppChatContextSchema,
|
||||
ContextPathResults,
|
||||
} from "@/lib/schemas";
|
||||
import { estimateTokens } from "../utils/token_utils";
|
||||
import { createLoggedHandler } from "./safe_handle";
|
||||
import log from "electron-log";
|
||||
import { getDyadAppPath } from "@/paths/paths";
|
||||
import { extractCodebase } from "@/utils/codebase";
|
||||
import { validateChatContext } from "../utils/context_paths_utils";
|
||||
|
||||
const logger = log.scope("context_paths_handlers");
|
||||
const handle = createLoggedHandler(logger);
|
||||
|
||||
export function registerContextPathsHandlers() {
|
||||
handle(
|
||||
"get-context-paths",
|
||||
async (_, { appId }: { appId: number }): Promise<ContextPathResults> => {
|
||||
z.object({ appId: z.number() }).parse({ appId });
|
||||
|
||||
const app = await db.query.apps.findFirst({
|
||||
where: eq(apps.id, appId),
|
||||
});
|
||||
|
||||
if (!app) {
|
||||
throw new Error("App not found");
|
||||
}
|
||||
|
||||
if (!app.path) {
|
||||
throw new Error("App path not set");
|
||||
}
|
||||
const appPath = getDyadAppPath(app.path);
|
||||
|
||||
const results: ContextPathResults = {
|
||||
contextPaths: [],
|
||||
smartContextAutoIncludes: [],
|
||||
};
|
||||
const { contextPaths, smartContextAutoIncludes } = validateChatContext(
|
||||
app.chatContext,
|
||||
);
|
||||
for (const contextPath of contextPaths) {
|
||||
const { formattedOutput, files } = await extractCodebase({
|
||||
appPath,
|
||||
chatContext: {
|
||||
contextPaths: [contextPath],
|
||||
smartContextAutoIncludes: [],
|
||||
},
|
||||
});
|
||||
const totalTokens = estimateTokens(formattedOutput);
|
||||
|
||||
results.contextPaths.push({
|
||||
...contextPath,
|
||||
files: files.length,
|
||||
tokens: totalTokens,
|
||||
});
|
||||
}
|
||||
|
||||
for (const contextPath of smartContextAutoIncludes) {
|
||||
const { formattedOutput, files } = await extractCodebase({
|
||||
appPath,
|
||||
chatContext: {
|
||||
contextPaths: [contextPath],
|
||||
smartContextAutoIncludes: [],
|
||||
},
|
||||
});
|
||||
const totalTokens = estimateTokens(formattedOutput);
|
||||
|
||||
results.smartContextAutoIncludes.push({
|
||||
...contextPath,
|
||||
files: files.length,
|
||||
tokens: totalTokens,
|
||||
});
|
||||
}
|
||||
return results;
|
||||
},
|
||||
);
|
||||
|
||||
handle(
|
||||
"set-context-paths",
|
||||
async (
|
||||
_,
|
||||
{ appId, chatContext }: { appId: number; chatContext: AppChatContext },
|
||||
) => {
|
||||
const schema = z.object({
|
||||
appId: z.number(),
|
||||
chatContext: AppChatContextSchema,
|
||||
});
|
||||
schema.parse({ appId, chatContext });
|
||||
|
||||
await db.update(apps).set({ chatContext }).where(eq(apps.id, appId));
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import { chats, apps } from "../../db/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { getDyadAppPath } from "../../paths/paths";
|
||||
import { LargeLanguageModel } from "@/lib/schemas";
|
||||
import { validateChatContext } from "../utils/context_paths_utils";
|
||||
|
||||
// Shared function to get system debug info
|
||||
async function getSystemDebugInfo({
|
||||
@@ -175,7 +176,12 @@ export function registerDebugHandlers() {
|
||||
|
||||
// Extract codebase
|
||||
const appPath = getDyadAppPath(app.path);
|
||||
const codebase = (await extractCodebase(appPath)).formattedOutput;
|
||||
const codebase = (
|
||||
await extractCodebase({
|
||||
appPath,
|
||||
chatContext: validateChatContext(app.chatContext),
|
||||
})
|
||||
).formattedOutput;
|
||||
|
||||
return {
|
||||
debugInfo,
|
||||
|
||||
@@ -31,6 +31,7 @@ import { getDyadAppPath } from "../../paths/paths";
|
||||
import { withLock } from "../utils/lock_utils";
|
||||
import { createLoggedHandler } from "./safe_handle";
|
||||
import { ApproveProposalResult } from "../ipc_types";
|
||||
import { validateChatContext } from "../utils/context_paths_utils";
|
||||
|
||||
const logger = log.scope("proposal_handlers");
|
||||
const handle = createLoggedHandler(logger);
|
||||
@@ -41,6 +42,7 @@ interface CodebaseTokenCache {
|
||||
messageContent: string;
|
||||
tokenCount: number;
|
||||
timestamp: number;
|
||||
chatContext: string;
|
||||
}
|
||||
|
||||
// Cache expiration time (5 minutes)
|
||||
@@ -74,6 +76,7 @@ async function getCodebaseTokenCount(
|
||||
messageId: number,
|
||||
messageContent: string,
|
||||
appPath: string,
|
||||
chatContext: unknown,
|
||||
): Promise<number> {
|
||||
// Clean up expired cache entries first
|
||||
cleanupExpiredCacheEntries();
|
||||
@@ -86,6 +89,7 @@ async function getCodebaseTokenCount(
|
||||
cacheEntry &&
|
||||
cacheEntry.messageId === messageId &&
|
||||
cacheEntry.messageContent === messageContent &&
|
||||
cacheEntry.chatContext === JSON.stringify(chatContext) &&
|
||||
now - cacheEntry.timestamp < CACHE_EXPIRATION_MS
|
||||
) {
|
||||
logger.log(`Using cached codebase token count for chatId: ${chatId}`);
|
||||
@@ -94,8 +98,12 @@ async function getCodebaseTokenCount(
|
||||
|
||||
// Calculate and cache the token count
|
||||
logger.log(`Calculating codebase token count for chatId: ${chatId}`);
|
||||
const codebase = (await extractCodebase(getDyadAppPath(appPath)))
|
||||
.formattedOutput;
|
||||
const codebase = (
|
||||
await extractCodebase({
|
||||
appPath: getDyadAppPath(appPath),
|
||||
chatContext: validateChatContext(chatContext),
|
||||
})
|
||||
).formattedOutput;
|
||||
const tokenCount = estimateTokens(codebase);
|
||||
|
||||
// Store in cache
|
||||
@@ -105,6 +113,7 @@ async function getCodebaseTokenCount(
|
||||
messageContent,
|
||||
tokenCount,
|
||||
timestamp: now,
|
||||
chatContext: JSON.stringify(chatContext),
|
||||
});
|
||||
|
||||
return tokenCount;
|
||||
@@ -277,6 +286,7 @@ const getProposalHandler = async (
|
||||
latestAssistantMessage.id,
|
||||
latestAssistantMessage.content || "",
|
||||
chat.app.path,
|
||||
chat.app.chatContext,
|
||||
);
|
||||
|
||||
const totalTokens = messagesTokenCount + codebaseTokenCount;
|
||||
|
||||
@@ -18,6 +18,7 @@ import { TokenCountParams } from "../ipc_types";
|
||||
import { TokenCountResult } from "../ipc_types";
|
||||
import { estimateTokens, getContextWindow } from "../utils/token_utils";
|
||||
import { createLoggedHandler } from "./safe_handle";
|
||||
import { validateChatContext } from "../utils/context_paths_utils";
|
||||
|
||||
const logger = log.scope("token_count_handlers");
|
||||
|
||||
@@ -73,7 +74,12 @@ export function registerTokenCountHandlers() {
|
||||
|
||||
if (chat.app) {
|
||||
const appPath = getDyadAppPath(chat.app.path);
|
||||
codebaseInfo = (await extractCodebase(appPath)).formattedOutput;
|
||||
codebaseInfo = (
|
||||
await extractCodebase({
|
||||
appPath,
|
||||
chatContext: validateChatContext(chat.app.chatContext),
|
||||
})
|
||||
).formattedOutput;
|
||||
codebaseTokens = estimateTokens(codebaseInfo);
|
||||
logger.log(
|
||||
`Extracted codebase information from ${appPath}, tokens: ${codebaseTokens}`,
|
||||
|
||||
@@ -3,9 +3,9 @@ import {
|
||||
type ChatSummary,
|
||||
ChatSummariesSchema,
|
||||
type UserSettings,
|
||||
type ContextPathResults,
|
||||
} from "../lib/schemas";
|
||||
import type {
|
||||
App,
|
||||
AppOutput,
|
||||
Chat,
|
||||
ChatResponseEnd,
|
||||
@@ -32,8 +32,9 @@ import type {
|
||||
RenameBranchParams,
|
||||
UserBudgetInfo,
|
||||
CopyAppParams,
|
||||
App,
|
||||
} from "./ipc_types";
|
||||
import type { ProposalResult } from "@/lib/schemas";
|
||||
import type { AppChatContext, ProposalResult } from "@/lib/schemas";
|
||||
import { showError } from "@/lib/toast";
|
||||
|
||||
export interface ChatStreamCallbacks {
|
||||
@@ -847,4 +848,17 @@ export class IpcClient {
|
||||
public async getUserBudget(): Promise<UserBudgetInfo | null> {
|
||||
return this.ipcRenderer.invoke("get-user-budget");
|
||||
}
|
||||
|
||||
public async getChatContextResults(params: {
|
||||
appId: number;
|
||||
}): Promise<ContextPathResults> {
|
||||
return this.ipcRenderer.invoke("get-context-paths", params);
|
||||
}
|
||||
|
||||
public async setChatContext(params: {
|
||||
appId: number;
|
||||
chatContext: AppChatContext;
|
||||
}): Promise<void> {
|
||||
return this.ipcRenderer.invoke("set-context-paths", params);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import { registerReleaseNoteHandlers } from "./handlers/release_note_handlers";
|
||||
import { registerImportHandlers } from "./handlers/import_handlers";
|
||||
import { registerSessionHandlers } from "./handlers/session_handlers";
|
||||
import { registerProHandlers } from "./handlers/pro_handlers";
|
||||
import { registerContextPathsHandlers } from "./handlers/context_paths_handlers";
|
||||
|
||||
export function registerIpcHandlers() {
|
||||
// Register all IPC handlers by category
|
||||
@@ -43,4 +44,5 @@ export function registerIpcHandlers() {
|
||||
registerImportHandlers();
|
||||
registerSessionHandlers();
|
||||
registerProHandlers();
|
||||
registerContextPathsHandlers();
|
||||
}
|
||||
|
||||
25
src/ipc/utils/context_paths_utils.ts
Normal file
25
src/ipc/utils/context_paths_utils.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { AppChatContext, AppChatContextSchema } from "@/lib/schemas";
|
||||
import log from "electron-log";
|
||||
|
||||
const logger = log.scope("context_paths_utils");
|
||||
|
||||
export function validateChatContext(chatContext: unknown): AppChatContext {
|
||||
if (!chatContext) {
|
||||
return {
|
||||
contextPaths: [],
|
||||
smartContextAutoIncludes: [],
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
// Validate that the contextPaths data matches the expected schema
|
||||
return AppChatContextSchema.parse(chatContext);
|
||||
} catch (error) {
|
||||
logger.warn("Invalid contextPaths data:", error);
|
||||
// Return empty array as fallback if validation fails
|
||||
return {
|
||||
contextPaths: [],
|
||||
smartContextAutoIncludes: [],
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -100,6 +100,28 @@ export const DyadProBudgetSchema = z.object({
|
||||
});
|
||||
export type DyadProBudget = z.infer<typeof DyadProBudgetSchema>;
|
||||
|
||||
export const GlobPathSchema = z.object({
|
||||
globPath: z.string(),
|
||||
});
|
||||
|
||||
export type GlobPath = z.infer<typeof GlobPathSchema>;
|
||||
|
||||
export const AppChatContextSchema = z.object({
|
||||
contextPaths: z.array(GlobPathSchema),
|
||||
smartContextAutoIncludes: z.array(GlobPathSchema),
|
||||
});
|
||||
export type AppChatContext = z.infer<typeof AppChatContextSchema>;
|
||||
|
||||
export type ContextPathResult = GlobPath & {
|
||||
files: number;
|
||||
tokens: number;
|
||||
};
|
||||
|
||||
export type ContextPathResults = {
|
||||
contextPaths: ContextPathResult[];
|
||||
smartContextAutoIncludes: ContextPathResult[];
|
||||
};
|
||||
|
||||
/**
|
||||
* Zod schema for user settings
|
||||
*/
|
||||
|
||||
@@ -77,6 +77,8 @@ const validInvokeChannels = [
|
||||
"rename-branch",
|
||||
"clear-session-data",
|
||||
"get-user-budget",
|
||||
"get-context-paths",
|
||||
"set-context-paths",
|
||||
// Test-only channels
|
||||
// These should ALWAYS be guarded with IS_TEST_BUILD in the main process.
|
||||
// We can't detect with IS_TEST_BUILD in the preload script because
|
||||
|
||||
@@ -4,6 +4,9 @@ import path from "node:path";
|
||||
import { isIgnored } from "isomorphic-git";
|
||||
import log from "electron-log";
|
||||
import { IS_TEST_BUILD } from "../ipc/utils/test_utils";
|
||||
import { glob } from "glob";
|
||||
import { AppChatContext } from "../lib/schemas";
|
||||
import { readSettings } from "@/main/settings";
|
||||
|
||||
const logger = log.scope("utils/codebase");
|
||||
|
||||
@@ -315,15 +318,31 @@ ${content}
|
||||
}
|
||||
}
|
||||
|
||||
export type CodebaseFile = {
|
||||
path: string;
|
||||
content: string;
|
||||
force?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Extract and format codebase files as a string to be included in prompts
|
||||
* @param appPath - Path to the codebase to extract
|
||||
* @returns Object containing formatted output and individual files
|
||||
*/
|
||||
export async function extractCodebase(appPath: string): Promise<{
|
||||
export async function extractCodebase({
|
||||
appPath,
|
||||
chatContext,
|
||||
}: {
|
||||
appPath: string;
|
||||
chatContext: AppChatContext;
|
||||
}): Promise<{
|
||||
formattedOutput: string;
|
||||
files: { path: string; content: string }[];
|
||||
files: CodebaseFile[];
|
||||
}> {
|
||||
const settings = readSettings();
|
||||
const isSmartContextEnabled =
|
||||
settings?.enableDyadPro && settings?.enableProSmartFilesContextMode;
|
||||
|
||||
try {
|
||||
await fsAsync.access(appPath);
|
||||
} catch {
|
||||
@@ -335,14 +354,67 @@ export async function extractCodebase(appPath: string): Promise<{
|
||||
const startTime = Date.now();
|
||||
|
||||
// Collect all relevant files
|
||||
const files = await collectFiles(appPath, appPath);
|
||||
let files = await collectFiles(appPath, appPath);
|
||||
|
||||
// Collect files from contextPaths and smartContextAutoIncludes
|
||||
const { contextPaths, smartContextAutoIncludes } = chatContext;
|
||||
const includedFiles = new Set<string>();
|
||||
const autoIncludedFiles = new Set<string>();
|
||||
|
||||
// Add files from contextPaths
|
||||
if (contextPaths && contextPaths.length > 0) {
|
||||
for (const p of contextPaths) {
|
||||
const pattern = createFullGlobPath({
|
||||
appPath,
|
||||
globPath: p.globPath,
|
||||
});
|
||||
const matches = await glob(pattern, {
|
||||
nodir: true,
|
||||
absolute: true,
|
||||
ignore: "**/node_modules/**",
|
||||
});
|
||||
matches.forEach((file) => {
|
||||
const normalizedFile = path.normalize(file);
|
||||
includedFiles.add(normalizedFile);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Add files from smartContextAutoIncludes
|
||||
if (
|
||||
isSmartContextEnabled &&
|
||||
smartContextAutoIncludes &&
|
||||
smartContextAutoIncludes.length > 0
|
||||
) {
|
||||
for (const p of smartContextAutoIncludes) {
|
||||
const pattern = createFullGlobPath({
|
||||
appPath,
|
||||
globPath: p.globPath,
|
||||
});
|
||||
const matches = await glob(pattern, {
|
||||
nodir: true,
|
||||
absolute: true,
|
||||
});
|
||||
matches.forEach((file) => {
|
||||
const normalizedFile = path.normalize(file);
|
||||
autoIncludedFiles.add(normalizedFile);
|
||||
includedFiles.add(normalizedFile); // Also add to included files
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Only filter files if contextPaths are provided
|
||||
// If only smartContextAutoIncludes are provided, keep all files and just mark auto-includes as forced
|
||||
if (contextPaths && contextPaths.length > 0) {
|
||||
files = files.filter((file) => includedFiles.has(path.normalize(file)));
|
||||
}
|
||||
|
||||
// Sort files by modification time (oldest first)
|
||||
// This is important for cache-ability.
|
||||
const sortedFiles = await sortFilesByModificationTime(files);
|
||||
const sortedFiles = await sortFilesByModificationTime([...new Set(files)]);
|
||||
|
||||
// Format files and collect individual file contents
|
||||
const filesArray: { path: string; content: string }[] = [];
|
||||
const filesArray: CodebaseFile[] = [];
|
||||
const formatPromises = sortedFiles.map(async (file) => {
|
||||
const formattedContent = await formatFile(file, appPath);
|
||||
|
||||
@@ -352,6 +424,9 @@ export async function extractCodebase(appPath: string): Promise<{
|
||||
// Why? Normalize Windows-style paths which causes lots of weird issues (e.g. Git commit)
|
||||
.split(path.sep)
|
||||
.join("/");
|
||||
|
||||
const isForced = autoIncludedFiles.has(path.normalize(file));
|
||||
|
||||
const fileContent = isOmittedFile(relativePath)
|
||||
? OMITTED_FILE_CONTENT
|
||||
: await readFileWithCache(file);
|
||||
@@ -359,6 +434,7 @@ export async function extractCodebase(appPath: string): Promise<{
|
||||
filesArray.push({
|
||||
path: relativePath,
|
||||
content: fileContent,
|
||||
force: isForced,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -413,3 +489,15 @@ async function sortFilesByModificationTime(files: string[]): Promise<string[]> {
|
||||
// Sort by modification time (oldest first)
|
||||
return fileStats.sort((a, b) => a.mtime - b.mtime).map((item) => item.file);
|
||||
}
|
||||
|
||||
function createFullGlobPath({
|
||||
appPath,
|
||||
globPath,
|
||||
}: {
|
||||
appPath: string;
|
||||
globPath: string;
|
||||
}): string {
|
||||
// By default the glob package treats "\" as an escape character.
|
||||
// We want the path to use forward slash for all platforms.
|
||||
return `${appPath.replace(/\\/g, "/")}/${globPath}`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user