Skip to content

Commit 96c6fc9

Browse files
committed
Release v0.0.5: Enhanced command execution and UI improvements
- Add executeComplexCommand() for handling multi-line backend commands with shell operators (&&, ||, source, etc.) - Fix terminal command execution by updating runShellCommand.ts to use executeComplexCommand for complex commands - Update response_processor.ts to use workingDir parameter instead of prepending cd commands - Add description/title support to search_replace tags and display in UI - Fix proxy server 'Cannot write headers after they are sent' error by adding response tracking - Ensure 'AliFullStack' title is always centered in title bar regardless of other elements - Update DyadSearchReplace component heading to match Thinking component style - Implement backend error autofix for common startup failures - Fix TypeScript type errors for Buffer parameters - Fix workerPath initialization error in start_proxy_server.ts
1 parent bbf2ae1 commit 96c6fc9

File tree

8 files changed

+430
-41
lines changed

8 files changed

+430
-41
lines changed

src/app/TitleBar.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -77,15 +77,15 @@ export const TitleBar = () => {
7777

7878
return (
7979
<>
80-
<div className="@container z-11 w-full h-11 bg-(--sidebar) absolute top-0 left-0 app-region-drag flex items-center justify-between">
81-
<div className="flex items-center">
80+
<div className="@container z-11 w-full h-11 bg-(--sidebar) absolute top-0 left-0 app-region-drag flex items-center">
81+
<div className="flex items-center flex-shrink-0">
8282
<div className={`${showWindowControls ? "pl-2" : "pl-18"}`}></div>
8383
<img src={logo} alt="AliFullStack Logo" className="w-12 h-8" />
8484
<Button
8585
data-testid="title-bar-app-name-button"
8686
variant="outline"
8787
size="sm"
88-
className={`hidden @2xl:block no-app-region-drag text-xs max-w-38 truncate font-medium ${
88+
className={`hidden @2xl:block no-app-region-drag text-xs max-w-38 truncate font-medium ml-2 ${
8989
selectedApp ? "cursor-pointer" : ""
9090
}`}
9191
onClick={handleAppClick}
@@ -94,11 +94,11 @@ export const TitleBar = () => {
9494
</Button>
9595
</div>
9696

97-
<div className="flex-1 flex justify-center items-center">
98-
<span className="text-lg font-semibold no-app-region-drag">AliFullStack</span>
97+
<div className="flex-1 flex justify-center items-center absolute inset-0 pointer-events-none">
98+
<span className="text-lg font-semibold no-app-region-drag pointer-events-auto">AliFullStack</span>
9999
</div>
100100

101-
<div className="flex items-center">
101+
<div className="flex items-center flex-shrink-0 ml-auto">
102102
{isDyadPro && <DyadProButton isAliFullStackProEnabled={isAliFullStackProEnabled} />}
103103

104104
{/* Preview Header */}

src/components/chat/DyadMarkdownParser.tsx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { DyadAddIntegration } from "./DyadAddIntegration";
1010
import { DyadEdit } from "./DyadEdit";
1111
import { DyadCodebaseContext } from "./DyadCodebaseContext";
1212
import { DyadThink } from "./DyadThink";
13+
import { DyadSearchReplace } from "./DyadSearchReplace";
1314
import { CodeHighlight } from "./CodeHighlight";
1415
import { useAtomValue } from "jotai";
1516
import { isStreamingAtom } from "@/atoms/chatAtoms";
@@ -124,6 +125,7 @@ function preprocessUnclosedTags(content: string): {
124125
"dyad-codebase-context",
125126
"think",
126127
"dyad-command",
128+
"search_replace",
127129
];
128130

129131
let processedContent = content;
@@ -192,6 +194,7 @@ function parseCustomTags(content: string): ContentPiece[] {
192194
"think",
193195
"dyad-command",
194196
"run_terminal_cmd",
197+
"search_replace",
195198
];
196199

197200
const tagPattern = new RegExp(
@@ -429,6 +432,19 @@ function renderCustomTag(
429432
// Don't render anything for run_terminal_cmd tags in chat stream
430433
return null;
431434

435+
case "search_replace":
436+
return (
437+
<DyadSearchReplace
438+
node={{
439+
properties: {
440+
state: getState({ isStreaming, inProgress }),
441+
},
442+
}}
443+
>
444+
{content}
445+
</DyadSearchReplace>
446+
);
447+
432448
default:
433449
return null;
434450
}
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
import React, { useState, useEffect } from "react";
2+
import { Search, ChevronDown, ChevronUp, Loader, FileText } from "lucide-react";
3+
import { VanillaMarkdownParser } from "./DyadMarkdownParser";
4+
import { CustomTagState } from "./stateTypes";
5+
6+
interface DyadSearchReplaceProps {
7+
node?: any;
8+
children?: React.ReactNode;
9+
}
10+
11+
export const DyadSearchReplace: React.FC<DyadSearchReplaceProps> = ({ children, node }) => {
12+
const state = node?.properties?.state as CustomTagState;
13+
const inProgress = state === "pending";
14+
const [isExpanded, setIsExpanded] = useState(inProgress);
15+
16+
// Parse the search_replace content
17+
const parseSearchReplaceContent = (content: string) => {
18+
const fileMatch = content.match(/<file path="([^"]+)"[^>]*\/>/);
19+
const searchMatch = content.match(/<search>([\s\S]*?)<\/search>/);
20+
const replaceMatch = content.match(/<replace>([\s\S]*?)<\/replace>/);
21+
22+
return {
23+
filePath: fileMatch ? fileMatch[1] : null,
24+
searchContent: searchMatch ? searchMatch[1] : content, // fallback to full content
25+
replaceContent: replaceMatch ? replaceMatch[1] : null,
26+
};
27+
};
28+
29+
// Get description from node properties if available
30+
const description = node?.properties?.description;
31+
32+
const { filePath, searchContent, replaceContent } = React.useMemo(() => {
33+
if (typeof children === "string") {
34+
return parseSearchReplaceContent(children);
35+
}
36+
return { filePath: null, searchContent: children, replaceContent: null };
37+
}, [children]);
38+
39+
// Collapse when transitioning from in-progress to not-in-progress
40+
useEffect(() => {
41+
if (!inProgress && isExpanded) {
42+
setIsExpanded(false);
43+
}
44+
}, [inProgress]);
45+
46+
return (
47+
<div
48+
className={`relative bg-(--background-lightest) dark:bg-zinc-900 hover:bg-(--background-lighter) rounded-lg px-4 py-2 border my-2 cursor-pointer ${
49+
inProgress ? "border-purple-500" : "border-border"
50+
}`}
51+
onClick={() => setIsExpanded(!isExpanded)}
52+
role="button"
53+
aria-expanded={isExpanded}
54+
tabIndex={0}
55+
onKeyDown={(e) => {
56+
if (e.key === "Enter" || e.key === " ") {
57+
e.preventDefault();
58+
setIsExpanded(!isExpanded);
59+
}
60+
}}
61+
>
62+
{/* Top-left label badge - styled like Thinking component */}
63+
<div
64+
className="absolute top-2 left-2 flex items-center gap-1 px-2 py-0.5 rounded text-xs font-semibold text-purple-500 bg-white dark:bg-zinc-900 border border-purple-200 dark:border-purple-800"
65+
style={{ zIndex: 1 }}
66+
>
67+
<Search size={16} className="text-purple-500" />
68+
<span>{description || "Search & Replace"}</span>
69+
{inProgress && (
70+
<Loader size={14} className="ml-1 text-purple-500 animate-spin" />
71+
)}
72+
</div>
73+
74+
{/* Indicator icon */}
75+
<div className="absolute top-2 right-2 p-1 text-gray-500">
76+
{isExpanded ? <ChevronUp size={16} /> : <ChevronDown size={16} />}
77+
</div>
78+
79+
{/* Main content with smooth transition */}
80+
<div
81+
className="pt-6 overflow-hidden transition-all duration-300 ease-in-out"
82+
style={{
83+
maxHeight: isExpanded ? "none" : "0px",
84+
opacity: isExpanded ? 1 : 0,
85+
marginBottom: isExpanded ? "0" : "-6px", // Compensate for padding
86+
}}
87+
>
88+
<div className="px-0 space-y-3">
89+
{/* File path section */}
90+
{filePath && (
91+
<div className="flex items-center gap-2 p-2 bg-gray-50 dark:bg-gray-800 rounded text-sm">
92+
<FileText size={14} className="text-gray-500 flex-shrink-0" />
93+
<span className="font-mono text-xs text-gray-600 dark:text-gray-300 truncate">
94+
{filePath}
95+
</span>
96+
</div>
97+
)}
98+
99+
{/* Search content */}
100+
{searchContent && (
101+
<div className="space-y-1">
102+
<div className="text-xs font-semibold text-red-600 dark:text-red-400 uppercase tracking-wide">
103+
Search
104+
</div>
105+
<div className="border border-red-200 dark:border-red-800 rounded p-2 bg-red-50 dark:bg-red-950/20">
106+
<pre className="text-xs font-mono text-gray-800 dark:text-gray-200 whitespace-pre-wrap overflow-x-auto">
107+
{typeof searchContent === "string" ? searchContent : String(searchContent)}
108+
</pre>
109+
</div>
110+
</div>
111+
)}
112+
113+
{/* Replace content */}
114+
{replaceContent && (
115+
<div className="space-y-1">
116+
<div className="text-xs font-semibold text-green-600 dark:text-green-400 uppercase tracking-wide">
117+
Replace
118+
</div>
119+
<div className="border border-green-200 dark:border-green-800 rounded p-2 bg-green-50 dark:bg-green-950/20">
120+
<pre className="text-xs font-mono text-gray-800 dark:text-gray-200 whitespace-pre-wrap overflow-x-auto">
121+
{replaceContent}
122+
</pre>
123+
</div>
124+
</div>
125+
)}
126+
127+
{/* Fallback for unparsed content */}
128+
{!filePath && !searchContent && !replaceContent && (
129+
<div className="text-sm text-gray-600 dark:text-gray-300">
130+
{typeof children === "string" ? (
131+
<VanillaMarkdownParser content={children} />
132+
) : (
133+
children
134+
)}
135+
</div>
136+
)}
137+
</div>
138+
</div>
139+
</div>
140+
);
141+
};

0 commit comments

Comments
 (0)