Support turbo edits (pro) (#166)

This commit is contained in:
Will Chen
2025-05-14 23:35:50 -07:00
committed by GitHub
parent d545babb63
commit 35b459d82d
12 changed files with 400 additions and 26 deletions

View File

@@ -20,6 +20,13 @@ export function ProModeSelector() {
const toggleSaverMode = () => {
updateSettings({ enableProSaverMode: !settings?.enableProSaverMode });
};
const toggleLazyEdits = () => {
updateSettings({
enableProLazyEditsMode: !settings?.enableProLazyEditsMode,
});
};
if (!settings?.enableDyadPro) {
return null;
}
@@ -75,6 +82,29 @@ export function ProModeSelector() {
onCheckedChange={toggleSaverMode}
/>
</div>
<div className="flex items-center justify-between">
<div className="space-y-2">
<Label htmlFor="lazy-edits">Turbo Edits</Label>
<div className="flex items-center gap-1">
<Tooltip>
<TooltipTrigger asChild>
<Info className="h-4 w-4 text-muted-foreground cursor-help" />
</TooltipTrigger>
<TooltipContent side="right" className="max-w-72">
Edits files faster.
</TooltipContent>
</Tooltip>
<p className="text-xs text-muted-foreground max-w-55">
Makes editing files faster and cheaper.
</p>
</div>
</div>
<Switch
id="lazy-edits"
checked={Boolean(settings?.enableProLazyEditsMode)}
onCheckedChange={toggleLazyEdits}
/>
</div>
</div>
</PopoverContent>
</Popover>

View File

@@ -0,0 +1,110 @@
import type React from "react";
import type { ReactNode } from "react";
import { useState } from "react";
import {
ChevronsDownUp,
ChevronsUpDown,
Loader,
CircleX,
Rabbit,
} from "lucide-react";
import { CodeHighlight } from "./CodeHighlight";
import { CustomTagState } from "./stateTypes";
interface DyadEditProps {
children?: ReactNode;
node?: any;
path?: string;
description?: string;
}
export const DyadEdit: React.FC<DyadEditProps> = ({
children,
node,
path: pathProp,
description: descriptionProp,
}) => {
const [isContentVisible, setIsContentVisible] = useState(false);
// Use props directly if provided, otherwise extract from node
const path = pathProp || node?.properties?.path || "";
const description = descriptionProp || node?.properties?.description || "";
const state = node?.properties?.state as CustomTagState;
const inProgress = state === "pending";
const aborted = state === "aborted";
// Extract filename from path
const fileName = path ? path.split("/").pop() : "";
return (
<div
className={`bg-(--background-lightest) hover:bg-(--background-lighter) rounded-lg px-4 py-2 border my-2 cursor-pointer ${
inProgress
? "border-amber-500"
: aborted
? "border-red-500"
: "border-border"
}`}
onClick={() => setIsContentVisible(!isContentVisible)}
>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<div className="flex items-center">
<Rabbit size={16} />
<span className="bg-blue-500 text-white text-xs px-1.5 py-0.5 rounded ml-1 font-medium">
Turbo Edit
</span>
</div>
{fileName && (
<span className="text-gray-700 dark:text-gray-300 font-medium text-sm">
{fileName}
</span>
)}
{inProgress && (
<div className="flex items-center text-amber-600 text-xs">
<Loader size={14} className="mr-1 animate-spin" />
<span>Editing...</span>
</div>
)}
{aborted && (
<div className="flex items-center text-red-600 text-xs">
<CircleX size={14} className="mr-1" />
<span>Did not finish</span>
</div>
)}
</div>
<div className="flex items-center">
{isContentVisible ? (
<ChevronsDownUp
size={20}
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
/>
) : (
<ChevronsUpDown
size={20}
className="text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
/>
)}
</div>
</div>
{path && (
<div className="text-xs text-gray-500 dark:text-gray-400 font-medium mb-1">
{path}
</div>
)}
{description && (
<div className="text-sm text-gray-600 dark:text-gray-300">
<span className="font-medium">Summary: </span>
{description}
</div>
)}
{isContentVisible && (
<div className="text-xs">
<CodeHighlight className="language-typescript">
{children}
</CodeHighlight>
</div>
)}
</div>
);
};

View File

@@ -7,6 +7,7 @@ import { DyadDelete } from "./DyadDelete";
import { DyadAddDependency } from "./DyadAddDependency";
import { DyadExecuteSql } from "./DyadExecuteSql";
import { DyadAddIntegration } from "./DyadAddIntegration";
import { DyadEdit } from "./DyadEdit";
import { CodeHighlight } from "./CodeHighlight";
import { useAtomValue } from "jotai";
import { isStreamingAtom } from "@/atoms/chatAtoms";
@@ -115,6 +116,7 @@ function preprocessUnclosedTags(content: string): {
"dyad-add-integration",
"dyad-output",
"dyad-chat-summary",
"dyad-edit",
];
let processedContent = content;
@@ -177,6 +179,7 @@ function parseCustomTags(content: string): ContentPiece[] {
"dyad-add-integration",
"dyad-output",
"dyad-chat-summary",
"dyad-edit",
];
const tagPattern = new RegExp(
@@ -344,6 +347,21 @@ function renderCustomTag(
</DyadAddIntegration>
);
case "dyad-edit":
return (
<DyadEdit
node={{
properties: {
path: attributes.path || "",
description: attributes.description || "",
state: getState({ isStreaming, inProgress }),
},
}}
>
{content}
</DyadEdit>
);
case "dyad-output":
return (
<DyadOutput