Skip to content

Commit 2ed2ba2

Browse files
added thinking artifacts
1 parent f3bb7e6 commit 2ed2ba2

11 files changed

Lines changed: 982 additions & 39 deletions

File tree

app/components/chat/Markdown.tsx

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type { BundledLanguage } from 'shiki';
44
import { createScopedLogger } from '~/utils/logger';
55
import { rehypePlugins, remarkPlugins, allowedHTMLElements } from '~/utils/markdown';
66
import { Artifact } from './Artifact';
7+
import { ThinkingArtifact } from './ThinkingArtifact';
78
import { CodeBlock } from './CodeBlock';
89
import { ThinkingProcess } from './ThinkingProcess';
910

@@ -34,6 +35,16 @@ export const Markdown = memo(({ children, html = false, limitedMarkdown = false
3435
return <Artifact messageId={messageId} />;
3536
}
3637

38+
if (className?.includes('__thinkingArtifact__')) {
39+
const messageId = node?.properties.dataMessageId as string;
40+
41+
if (!messageId) {
42+
logger.error(`Invalid message id ${messageId}`);
43+
}
44+
45+
return <ThinkingArtifact messageId={messageId} />;
46+
}
47+
3748
if (className?.includes('__codinitThought__')) {
3849
return <ThoughtBox title="Thought process">{children}</ThoughtBox>;
3950
}
@@ -83,33 +94,22 @@ export const Markdown = memo(({ children, html = false, limitedMarkdown = false
8394
);
8495
});
8596

86-
/**
87-
* Removes code fence markers (```) surrounding an artifact element while preserving the artifact content.
88-
* This is necessary because artifacts should not be wrapped in code blocks when rendered for rendering action list.
89-
*
90-
* @param content - The markdown content to process
91-
* @returns The processed content with code fence markers removed around artifacts
92-
*
93-
* @example
94-
* // Removes code fences around artifact
95-
* const input = "```xml\n<div class='__exampleArtifact__'></div>\n```";
96-
* stripCodeFenceFromArtifact(input);
97-
* // Returns: "\n<div class='__exampleArtifact__'></div>\n"
98-
*
99-
* @remarks
100-
* - Only removes code fences that directly wrap an artifact (marked with __exampleArtifact__ class)
101-
* - Handles code fences with optional language specifications (e.g. ```xml, ```typescript)
102-
* - Preserves original content if no artifact is found
103-
* - Safely handles edge cases like empty input or artifacts at start/end of content
104-
*/
10597
export const stripCodeFenceFromArtifact = (content: string) => {
106-
if (!content || (!content.includes('__exampleArtifact__') && !content.includes('__codinitThinking__'))) {
98+
if (
99+
!content ||
100+
(!content.includes('__exampleArtifact__') &&
101+
!content.includes('__codinitThinking__') &&
102+
!content.includes('__thinkingArtifact__'))
103+
) {
107104
return content;
108105
}
109106

110107
const lines = content.split('\n');
111108
const artifactLineIndex = lines.findIndex(
112-
(line) => line.includes('__exampleArtifact__') || line.includes('__codinitThinking__'),
109+
(line) =>
110+
line.includes('__exampleArtifact__') ||
111+
line.includes('__codinitThinking__') ||
112+
line.includes('__thinkingArtifact__'),
113113
);
114114

115115
if (artifactLineIndex === -1) {
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import { useStore } from '@nanostores/react';
2+
import { AnimatePresence, motion } from 'framer-motion';
3+
import { memo, useState } from 'react';
4+
import { workbenchStore } from '~/lib/stores/workbench';
5+
import { cubicEasingFn } from '~/utils/easings';
6+
7+
interface ThinkingArtifactProps {
8+
messageId: string;
9+
}
10+
11+
export const ThinkingArtifact = memo(({ messageId }: ThinkingArtifactProps) => {
12+
const [showSteps, setShowSteps] = useState(true);
13+
14+
const thinkingArtifacts = useStore(workbenchStore.thinkingArtifacts);
15+
const thinkingArtifact = thinkingArtifacts[messageId];
16+
17+
if (!thinkingArtifact) {
18+
return null;
19+
}
20+
21+
const toggleSteps = () => {
22+
setShowSteps(!showSteps);
23+
};
24+
25+
return (
26+
<div className="thinking-artifact border border-codinit-elements-borderColor flex flex-col overflow-hidden rounded-lg w-full transition-border duration-150 thinking-glow">
27+
<div className="flex">
28+
<div className="flex items-stretch bg-codinit-elements-artifacts-background hover:bg-codinit-elements-artifacts-backgroundHover w-full overflow-hidden">
29+
<div className="px-5 p-3.5 w-full text-left">
30+
<div className="w-full thinking-glow-text font-medium leading-5 text-sm">{thinkingArtifact.title}</div>
31+
<div className="w-full w-full text-codinit-elements-textSecondary text-xs mt-0.5">
32+
Chain of thought reasoning
33+
</div>
34+
</div>
35+
</div>
36+
<AnimatePresence>
37+
<motion.button
38+
initial={{ width: 0 }}
39+
animate={{ width: 'auto' }}
40+
exit={{ width: 0 }}
41+
transition={{ duration: 0.15, ease: cubicEasingFn }}
42+
className="bg-codinit-elements-artifacts-background hover:bg-codinit-elements-artifacts-backgroundHover"
43+
onClick={toggleSteps}
44+
>
45+
<div className="p-4">
46+
<div className={showSteps ? 'i-ph:caret-up-bold' : 'i-ph:caret-down-bold'}></div>
47+
</div>
48+
</motion.button>
49+
</AnimatePresence>
50+
</div>
51+
<AnimatePresence>
52+
{showSteps && thinkingArtifact.steps.length > 0 && (
53+
<motion.div
54+
className="steps"
55+
initial={{ height: 0 }}
56+
animate={{ height: 'auto' }}
57+
exit={{ height: '0px' }}
58+
transition={{ duration: 0.15 }}
59+
>
60+
<div className="bg-codinit-elements-artifacts-borderColor h-[1px]" />
61+
<div className="p-5 text-left bg-codinit-elements-actions-background">
62+
<ThinkingSteps steps={thinkingArtifact.steps} />
63+
</div>
64+
</motion.div>
65+
)}
66+
</AnimatePresence>
67+
</div>
68+
);
69+
});
70+
71+
interface ThinkingStepsProps {
72+
steps: string[];
73+
}
74+
75+
const stepVariants = {
76+
hidden: { opacity: 0, y: 20 },
77+
visible: { opacity: 1, y: 0 },
78+
};
79+
80+
function ThinkingSteps({ steps }: ThinkingStepsProps) {
81+
return (
82+
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} transition={{ duration: 0.15 }}>
83+
<ul className="list-none space-y-2.5">
84+
{steps.map((step, index) => (
85+
<motion.li
86+
key={index}
87+
variants={stepVariants}
88+
initial="hidden"
89+
animate="visible"
90+
transition={{
91+
duration: 0.2,
92+
ease: cubicEasingFn,
93+
}}
94+
>
95+
<div className="flex items-start gap-3">
96+
<div className="flex-shrink-0 w-6 h-6 rounded-full building-glow text-white text-xs font-bold flex items-center justify-center mt-0.5 building-glow-text">
97+
{index + 1}
98+
</div>
99+
<div className="flex-1 text-sm text-codinit-elements-textPrimary leading-relaxed pt-0.5">{step}</div>
100+
</div>
101+
</motion.li>
102+
))}
103+
</ul>
104+
</motion.div>
105+
);
106+
}

app/lib/common/prompts/optimized.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -224,20 +224,20 @@ You are CodinIT, an expert AI assistant and exceptional senior software develope
224224
</message_formatting_info>
225225
226226
<chain_of_thought_instructions>
227-
CRITICAL: For EVERY response, you MUST show your reasoning process using the thinking tag format.
227+
CRITICAL: For EVERY response, you MUST show your reasoning process using the thinking artifact format.
228228
229-
Before providing any solution or artifact, wrap your planning and reasoning steps in <codinitThinking> tags.
229+
Before providing any solution or artifact, wrap your planning and reasoning steps in <thinkingArtifact> tags.
230230
231231
Format:
232-
<codinitThinking>
232+
<thinkingArtifact title="Reasoning Process" id="reasoning-{unique-id}">
233233
1. [First step or consideration]
234234
2. [Second step or consideration]
235235
3. [Third step or consideration]
236236
...
237-
</codinitThinking>
237+
</thinkingArtifact>
238238
239239
Rules:
240-
- ALWAYS use <codinitThinking> tags at the start of EVERY response
240+
- ALWAYS use <thinkingArtifact> tags at the start of EVERY response
241241
- List 2-6 concrete steps you'll take
242242
- Be specific about what you'll implement or check
243243
- Keep each step concise (one line)

app/lib/common/prompts/prompts.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -278,16 +278,17 @@ You are CodinIT, an expert AI assistant and exceptional senior software develope
278278
</message_formatting_info>
279279
280280
<chain_of_thought_instructions>
281-
CRITICAL: For EVERY response, you MUST show your reasoning process using the thinking tag format.
281+
CRITICAL: For EVERY response, you MUST show your reasoning process using the thinking artifact format.
282282
283-
Before providing any solution or artifact, wrap your planning and reasoning steps in <codinitThinking> tags. This helps ensure systematic thinking and clear communication.
283+
Before providing any solution or artifact, wrap your planning and reasoning steps in <thinkingArtifact> tags. This helps ensure systematic thinking and clear communication.
284284
285285
Format:
286-
<codinitThinking>
286+
<thinkingArtifact title="Reasoning Process" id="reasoning-{unique-id}">
287287
1. [First step or consideration]
288288
2. [Second step or consideration]
289289
3. [Third step or consideration]
290290
...
291+
</thinkingArtifact>
291292
</codinitThinking>
292293
293294
Rules:

app/lib/hooks/useMessageParser.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,16 @@ const messageParser = new StreamingMessageParser({
4040
logger.trace('onActionStream', data.action);
4141
workbenchStore.runAction(data, true);
4242
},
43+
onThinkingArtifactOpen: (data) => {
44+
logger.trace('onThinkingArtifactOpen', data);
45+
46+
workbenchStore.addThinkingArtifact(data);
47+
},
48+
onThinkingArtifactClose: (data) => {
49+
logger.trace('onThinkingArtifactClose', data);
50+
51+
workbenchStore.updateThinkingArtifact(data, { closed: true });
52+
},
4353
},
4454
});
4555
const extractTextContent = (message: Message) =>

0 commit comments

Comments
 (0)