Explicit thinking (#183)

This commit is contained in:
Will Chen
2025-05-16 22:35:08 -07:00
committed by GitHub
parent f9f33596bd
commit 63e41454c7
3 changed files with 144 additions and 0 deletions

View File

@@ -9,6 +9,7 @@ import { DyadExecuteSql } from "./DyadExecuteSql";
import { DyadAddIntegration } from "./DyadAddIntegration";
import { DyadEdit } from "./DyadEdit";
import { DyadCodebaseContext } from "./DyadCodebaseContext";
import { DyadThink } from "./DyadThink";
import { CodeHighlight } from "./CodeHighlight";
import { useAtomValue } from "jotai";
import { isStreamingAtom } from "@/atoms/chatAtoms";
@@ -119,6 +120,7 @@ function preprocessUnclosedTags(content: string): {
"dyad-chat-summary",
"dyad-edit",
"dyad-codebase-context",
"think",
];
let processedContent = content;
@@ -183,6 +185,7 @@ function parseCustomTags(content: string): ContentPiece[] {
"dyad-chat-summary",
"dyad-edit",
"dyad-codebase-context",
"think",
];
const tagPattern = new RegExp(
@@ -268,6 +271,18 @@ function renderCustomTag(
const { tag, attributes, content, inProgress } = tagInfo;
switch (tag) {
case "think":
return (
<DyadThink
node={{
properties: {
state: getState({ isStreaming, inProgress }),
},
}}
>
{content}
</DyadThink>
);
case "dyad-write":
return (
<DyadWrite

View File

@@ -0,0 +1,75 @@
import React, { useState, useEffect } from "react";
import { Brain, ChevronDown, ChevronUp, Loader } from "lucide-react";
import { VanillaMarkdownParser } from "./DyadMarkdownParser";
import { CustomTagState } from "./stateTypes";
interface DyadThinkProps {
node?: any;
children?: React.ReactNode;
}
export const DyadThink: React.FC<DyadThinkProps> = ({ children, node }) => {
const state = node?.properties?.state as CustomTagState;
const inProgress = state === "pending";
const [isExpanded, setIsExpanded] = useState(inProgress);
// Collapse when transitioning from in-progress to not-in-progress
useEffect(() => {
if (!inProgress && isExpanded) {
setIsExpanded(false);
}
}, [inProgress]);
return (
<div
className={`relative bg-(--background-lightest) dark:bg-zinc-900 hover:bg-(--background-lighter) rounded-lg px-4 py-2 border my-2 cursor-pointer ${
inProgress ? "border-purple-500" : "border-border"
}`}
onClick={() => setIsExpanded(!isExpanded)}
role="button"
aria-expanded={isExpanded}
tabIndex={0}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
setIsExpanded(!isExpanded);
}
}}
>
{/* Top-left label badge */}
<div
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"
style={{ zIndex: 1 }}
>
<Brain size={16} className="text-purple-500" />
<span>Thinking</span>
{inProgress && (
<Loader size={14} className="ml-1 text-purple-500 animate-spin" />
)}
</div>
{/* Indicator icon */}
<div className="absolute top-2 right-2 p-1 text-gray-500">
{isExpanded ? <ChevronUp size={16} /> : <ChevronDown size={16} />}
</div>
{/* Main content with smooth transition */}
<div
className="pt-6 overflow-hidden transition-all duration-300 ease-in-out"
style={{
maxHeight: isExpanded ? "1000px" : "0px",
opacity: isExpanded ? 1 : 0,
marginBottom: isExpanded ? "0" : "-6px", // Compensate for padding
}}
>
<div className="px-0 text-sm text-gray-600 dark:text-gray-300">
{typeof children === "string" ? (
<VanillaMarkdownParser content={children} />
) : (
children
)}
</div>
</div>
</div>
);
};