Skip to content

Commit 1c4700a

Browse files
committed
refactor
1 parent d184d3b commit 1c4700a

File tree

8 files changed

+754
-744
lines changed

8 files changed

+754
-744
lines changed

apps/remix-ide/src/app/plugins/remix-ai-assistant.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React, { useEffect, useRef, createRef } from 'react'
22
import { ViewPlugin } from '@remixproject/engine-web'
33
import * as packageJson from '../../../../../package.json'
44
import { PluginViewWrapper } from '@remix-ui/helper'
5-
import { RemixUiRemixAiAssistant, RemixUiRemixAiAssistantHandle, ChatMessage } from '@remix-ui/remix-ai-assistant'
5+
import { ChatMessage, RemixUiRemixAiAssistant, RemixUiRemixAiAssistantHandle } from '@remix-ui/remix-ai-assistant'
66
import { EventEmitter } from 'events'
77

88
const profile = {

libs/remix-ai-core/src/agents/workspaceAgent.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export class workspaceAgent {
3939

4040
async writeGenerationResults(payload) {
4141
try {
42-
let modifiedFilesMarkdown = '## Modified Files\n'
42+
let modifiedFilesMarkdown = 'Modified Files\n'
4343
for (const file of payload.files) {
4444
if (!Object.values(SupportedFileExtensions).some(ext => file.fileName.endsWith(ext))) continue;
4545
await this.plugin.call('fileManager', 'writeFile', file.fileName, file.content);
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
import ReactMarkdown from "react-markdown"
2+
import remarkGfm from "remark-gfm"
3+
import copy from "copy-to-clipboard"
4+
import { ChatMessage, assistantAvatar } from "../lib/types"
5+
import React from 'react'
6+
7+
const DEFAULT_SUGGESTIONS = [
8+
'Explain what a modifier is',
9+
'Explain what a UniSwap hook is',
10+
'What is a ZKP?'
11+
]
12+
13+
// ChatHistory component
14+
export interface ChatHistoryComponentProps {
15+
messages: ChatMessage[]
16+
isStreaming: boolean
17+
sendPrompt: (prompt: string) => void
18+
recordFeedback: (msgId: string, next: 'like' | 'dislike' | 'none') => void
19+
historyRef: React.RefObject<HTMLDivElement>
20+
}
21+
22+
export const ChatHistoryComponent: React.FC<ChatHistoryComponentProps> = ({
23+
messages,
24+
isStreaming,
25+
sendPrompt,
26+
recordFeedback,
27+
historyRef
28+
}) => {
29+
return (
30+
<div
31+
ref={historyRef}
32+
className="ai-chat-history"
33+
style={{ flexGrow: 1, overflowY: 'auto' }}
34+
>
35+
{messages.length === 0 ? (
36+
<div className="assistant-landing d-flex flex-column align-items-center justify-content-center text-center px-3 h-100">
37+
<img src={assistantAvatar} alt="RemixAI logo" style={{ width: '120px' }} className="mb-3" />
38+
<h5 className="mb-2">RemixAI</h5>
39+
<p className="mb-4" style={{ maxWidth: '220px' }}>
40+
RemixAI provides you personalized guidance as you build. It can break down concepts,
41+
answer questions about blockchain technology and assist you with your smart contracts.
42+
</p>
43+
{DEFAULT_SUGGESTIONS.map(s => (
44+
<button
45+
key={s}
46+
className="btn btn-secondary mb-2 w-100"
47+
onClick={() => sendPrompt(s)}
48+
>
49+
<i className="fa fa-user-robot-xmarks mr-2"></i>{s}
50+
</button>
51+
))}
52+
</div>
53+
) : (
54+
messages.map(msg => {
55+
const bubbleClass =
56+
msg.role === 'user' ? 'bubble-user bg-light' : 'bubble-assistant bg-light'
57+
58+
return (
59+
<div key={msg.id} className="chat-row d-flex mb-2">
60+
{/* Avatar for assistant */}
61+
{msg.role === 'assistant' && (
62+
<img
63+
src={assistantAvatar}
64+
alt="AI"
65+
className="assistant-avatar me-2 flex-shrink-0 mr-1"
66+
/>
67+
)}
68+
69+
{/* Bubble */}
70+
<div className="flex-grow-1">
71+
<div className={`chat-bubble p-2 rounded ${bubbleClass}`}>
72+
{msg.role === 'user' && (
73+
<small className="text-uppercase fw-bold text-secondary d-block mb-1">
74+
You
75+
</small>
76+
)}
77+
78+
{msg.role === 'assistant' ? (
79+
<ReactMarkdown
80+
remarkPlugins={[remarkGfm]}
81+
linkTarget="_blank"
82+
components={{
83+
code({ node, inline, className, children, ...props }) {
84+
const text = String(children).replace(/\n$/, '')
85+
86+
if (inline) {
87+
return (
88+
<code className={className} {...props}>
89+
{text}
90+
</code>
91+
)
92+
}
93+
94+
return (
95+
<div
96+
className="code-block position-relative"
97+
>
98+
<button
99+
type="button"
100+
className="btn btn-sm btn-light position-absolute copy-btn"
101+
style={{ top: '0.25rem', right: '0.25rem' }}
102+
onClick={() =>
103+
copy(text)
104+
}
105+
>
106+
<i className="fa-regular fa-copy"></i>
107+
</button>
108+
<pre className={className} {...props}>
109+
<code>{text}</code>
110+
</pre>
111+
</div>
112+
)
113+
}
114+
}}
115+
>
116+
{msg.content}
117+
</ReactMarkdown>
118+
) : (
119+
msg.content
120+
)}
121+
</div>
122+
123+
{/* Feedback buttons */}
124+
{msg.role === 'assistant' && (
125+
<div className="feedback text-end mt-2 me-1">
126+
<span
127+
role="button"
128+
aria-label="thumbs up"
129+
className={`feedback-btn me-3 ${msg.sentiment === 'like' ? 'fas fa-thumbs-up' : 'far fa-thumbs-up'
130+
}`}
131+
onClick={() =>
132+
recordFeedback(
133+
msg.id,
134+
msg.sentiment === 'like' ? 'none' : 'like'
135+
)
136+
}
137+
></span>
138+
<span
139+
role="button"
140+
aria-label="thumbs down"
141+
className={`feedback-btn ml-2 ${msg.sentiment === 'dislike'
142+
? 'fas fa-thumbs-down'
143+
: 'far fa-thumbs-down'
144+
}`}
145+
onClick={() =>
146+
recordFeedback(
147+
msg.id,
148+
msg.sentiment === 'dislike' ? 'none' : 'dislike'
149+
)
150+
}
151+
></span>
152+
</div>
153+
)}
154+
</div>
155+
</div>
156+
)
157+
})
158+
)}
159+
{isStreaming && (
160+
<div className="text-center my-2">
161+
<i className="fa fa-spinner fa-spin fa-lg text-muted"></i>
162+
</div>
163+
)}
164+
</div>
165+
)
166+
}
167+

0 commit comments

Comments
 (0)