Skip to content

Commit a7c2560

Browse files
committed
feat: add run_terminal_cmd support to DyadCodebaseContext
- Add DyadRunTerminalCmd component for displaying terminal commands in UI - Update DyadMarkdownParser to render run_terminal_cmd tags as interactive components - Enable run_terminal_cmd tags to participate in codebase context flow - Ensure terminal commands are visible and tracked in chat interface This ensures that commands with <run_terminal_cmd> tags are part of the DyadCodebaseContext and run in their appropriate terminals, with fullstack mode support for file writing and command execution.
1 parent 487a748 commit a7c2560

File tree

3 files changed

+125
-1
lines changed

3 files changed

+125
-1
lines changed

dyad/src/components/chat/DyadMarkdownParser.tsx

Lines changed: 18 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 { DyadRunTerminalCmd } from "./DyadRunTerminalCmd";
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+
"run_terminal_cmd",
127129
];
128130

129131
let processedContent = content;
@@ -191,6 +193,7 @@ function parseCustomTags(content: string): ContentPiece[] {
191193
"dyad-codebase-context",
192194
"think",
193195
"dyad-command",
196+
"run_terminal_cmd",
194197
];
195198

196199
const tagPattern = new RegExp(
@@ -424,6 +427,21 @@ function renderCustomTag(
424427
// Don't render anything for dyad-command
425428
return null;
426429

430+
case "run_terminal_cmd":
431+
return (
432+
<DyadRunTerminalCmd
433+
node={{
434+
properties: {
435+
description: attributes.description || "",
436+
cwd: attributes.cwd || "",
437+
state: getState({ isStreaming, inProgress }),
438+
},
439+
}}
440+
>
441+
{content}
442+
</DyadRunTerminalCmd>
443+
);
444+
427445
default:
428446
return null;
429447
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import React, { useState } from "react";
2+
import { Terminal, ChevronUp, ChevronDown, CheckCircle, XCircle, Loader2 } from "lucide-react";
3+
import { CustomTagState } from "./stateTypes";
4+
5+
interface DyadRunTerminalCmdProps {
6+
children: React.ReactNode;
7+
node?: {
8+
properties?: {
9+
description?: string;
10+
cwd?: string;
11+
state?: CustomTagState;
12+
};
13+
};
14+
}
15+
16+
export const DyadRunTerminalCmd: React.FC<DyadRunTerminalCmdProps> = ({
17+
children,
18+
node,
19+
}) => {
20+
const [isExpanded, setIsExpanded] = useState(false);
21+
const state = node?.properties?.state;
22+
const description = node?.properties?.description;
23+
const cwd = node?.properties?.cwd;
24+
25+
const getStateIcon = () => {
26+
switch (state) {
27+
case "pending":
28+
return <Loader2 size={16} className="text-blue-500 animate-spin" />;
29+
case "finished":
30+
return <CheckCircle size={16} className="text-green-500" />;
31+
case "aborted":
32+
return <XCircle size={16} className="text-red-500" />;
33+
default:
34+
return <Terminal size={16} className="text-gray-500" />;
35+
}
36+
};
37+
38+
const getBorderColor = () => {
39+
switch (state) {
40+
case "pending":
41+
return "border-blue-500";
42+
case "finished":
43+
return "border-green-500";
44+
case "aborted":
45+
return "border-red-500";
46+
default:
47+
return "border-gray-500";
48+
}
49+
};
50+
51+
return (
52+
<div
53+
className={`relative bg-gray-50 dark:bg-gray-900 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-lg px-4 py-3 border border-l-4 my-2 cursor-pointer transition-colors ${getBorderColor()}`}
54+
onClick={() => setIsExpanded(!isExpanded)}
55+
role="button"
56+
aria-expanded={isExpanded}
57+
tabIndex={0}
58+
onKeyDown={(e) => {
59+
if (e.key === "Enter" || e.key === " ") {
60+
e.preventDefault();
61+
setIsExpanded(!isExpanded);
62+
}
63+
}}
64+
>
65+
{/* Header */}
66+
<div className="flex items-center justify-between">
67+
<div className="flex items-center gap-2">
68+
{getStateIcon()}
69+
<span className="text-sm font-medium text-gray-800 dark:text-gray-200">
70+
Terminal Command
71+
</span>
72+
{description && (
73+
<span className="text-xs text-gray-600 dark:text-gray-400">
74+
{description}
75+
</span>
76+
)}
77+
</div>
78+
<div className="flex items-center gap-2">
79+
{cwd && (
80+
<span className="text-xs text-gray-500 dark:text-gray-500 bg-gray-200 dark:bg-gray-800 px-2 py-1 rounded">
81+
cwd: {cwd}
82+
</span>
83+
)}
84+
{isExpanded ? (
85+
<ChevronUp size={16} className="text-gray-500" />
86+
) : (
87+
<ChevronDown size={16} className="text-gray-500" />
88+
)}
89+
</div>
90+
</div>
91+
92+
{/* Expandable content */}
93+
<div
94+
className={`overflow-hidden transition-all duration-300 ease-in-out ${
95+
isExpanded ? "max-h-96 opacity-100 mt-3" : "max-h-0 opacity-0"
96+
}`}
97+
>
98+
<div className="bg-gray-900 dark:bg-black rounded-md p-3 font-mono text-sm text-green-400 dark:text-green-300">
99+
<pre className="whitespace-pre-wrap break-words">{children}</pre>
100+
</div>
101+
</div>
102+
</div>
103+
);
104+
};

src/ipc/ipc_host.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { ipcMain } from "electron";
12
import { registerAppHandlers } from "./handlers/app_handlers";
23
import { registerChatHandlers } from "./handlers/chat_handlers";
34
import { registerChatStreamHandlers } from "./handlers/chat_stream_handlers";
@@ -30,7 +31,8 @@ import { registerTemplateHandlers } from "./handlers/template_handlers";
3031
import { registerPortalHandlers } from "./handlers/portal_handlers";
3132
import { registerPromptHandlers } from "./handlers/prompt_handlers";
3233
import { registerHelpBotHandlers } from "./handlers/help_bot_handlers";
33-
import { registerTerminalHandlers } from "./handlers/terminal_handlers";
34+
import { registerTerminalHandlers, addTerminalOutput } from "./handlers/terminal_handlers";
35+
import { AppOutput } from "./ipc_types";
3436

3537
export function registerIpcHandlers() {
3638
// Register all IPC handlers by category

0 commit comments

Comments
 (0)