Skip to content

Commit 1eff1e4

Browse files
committed
update prompt and updatedapp logic
1 parent f413074 commit 1eff1e4

File tree

4 files changed

+150
-97
lines changed

4 files changed

+150
-97
lines changed

apps/quick-dapp/src/InBrowserVite.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,13 +210,11 @@ export class InBrowserVite {
210210
}
211211

212212
const cdnPath = `https://esm.sh/${args.path}`;
213-
console.log(`[InBrowserVite] Resolved '${args.path}' to external CDN: ${cdnPath}`);
214213

215214
return { path: cdnPath, external: true };
216215
});
217216

218217
build.onLoad({ filter: /\.css$/, namespace: 'local' }, async (args: any) => {
219-
console.log(`[InBrowserVite-LOG] CSS 파일 "${args.path}"를 'css-in-js'로 변환합니다.`);
220218

221219
const pathsToTry = [
222220
args.path,

apps/quick-dapp/src/components/EditHtmlTemplate/index.tsx

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -153,13 +153,66 @@ function EditHtmlTemplate(): JSX.Element {
153153

154154
const handleChatMessage = async (message: string) => {
155155
try {
156-
await remixClient.call('ai-dapp-generator' as any, 'updateDapp', appState.instance.address, message)
156+
const currentFiles = new Map<string, string>();
157+
await readDappFiles('dapp', currentFiles);
157158

158-
runBuild();
159+
const currentFilesObject: Pages = Object.fromEntries(currentFiles);
160+
161+
const pages: Record<string, string> = await remixClient.call(
162+
'ai-dapp-generator' as any,
163+
'updateDapp',
164+
appState.instance.address,
165+
message,
166+
currentFilesObject
167+
);
168+
169+
try {
170+
await remixClient.call('fileManager', 'remove', 'dapp');
171+
} catch (e) {}
172+
173+
await remixClient.call('fileManager', 'mkdir', 'dapp');
174+
175+
const writePromises = [];
176+
const createdFolders = new Set<string>(['dapp']);
177+
178+
for (const [rawFilename, content] of Object.entries(pages)) {
179+
const safeParts = rawFilename.replace(/\\/g, '/')
180+
.split('/')
181+
.filter(part => part !== '..' && part !== '.' && part !== '');
182+
183+
if (safeParts.length === 0) {
184+
continue;
185+
}
186+
const safeFilename = safeParts.join('/');
187+
const fullPath = 'dapp/' + safeFilename;
188+
189+
writePromises.push(
190+
(async () => {
191+
if (safeParts.length > 1) {
192+
const subFolders = safeParts.slice(0, -1);
193+
let currentPath = 'dapp';
194+
for (const folder of subFolders) {
195+
currentPath = `${currentPath}/${folder}`;
196+
if (!createdFolders.has(currentPath)) {
197+
try {
198+
await remixClient.call('fileManager', 'mkdir', currentPath);
199+
createdFolders.add(currentPath);
200+
} catch (e) {}
201+
}
202+
}
203+
}
204+
await remixClient.call('fileManager', 'writeFile', fullPath, content);
205+
})()
206+
);
207+
}
208+
209+
await Promise.all(writePromises);
210+
runBuild();
159211

160212
} catch (error) {
161-
console.error('Error updating DApp via chat:', error)
162-
setIframeError('Failed to update DApp via AI: ' + error.message);
213+
const errorMsg = (error instanceof Error) ? error.message : String(error);
214+
console.error('[DEBUG-LOG E] (ERROR) handleChatMessage:', errorMsg);
215+
setIframeError('Failed to update DApp via AI: ' + errorMsg);
163216
}
164217
};
165218

apps/remix-ide/src/app/plugins/ai-dapp-generator.ts

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,12 @@ export class AIDappGenerator extends Plugin {
5151
{ role: 'user', content: message }
5252
]
5353

54-
const htmlContent = await this.callLLMAPI(messagesToSend)
54+
const htmlContent = await this.callLLMAPI(messagesToSend, INITIAL_SYSTEM_PROMPT)
5555

5656
const pages = parsePages(htmlContent)
57-
5857
context.messages = [
5958
{ role: 'user', content: message },
60-
{ role: 'assistant', content: htmlContent }
59+
{ role: 'assistant', content: htmlContent }
6160
]
6261
this.saveContext(options.address, context)
6362

@@ -80,21 +79,24 @@ export class AIDappGenerator extends Plugin {
8079
/**
8180
* Update an existing DApp with new description
8281
*/
83-
async updateDapp(address: string, description: string): Promise<Pages> {
84-
const context = this.getOrCreateContext(address)
82+
async updateDapp(address: string, description: string, currentFiles: Pages): Promise<Pages> {
83+
const context = this.getOrCreateContext(address)
8584

8685
if (context.messages.length === 0) {
8786
throw new Error('No existing DApp found for this address. Please generate one first.')
8887
}
8988

90-
const message = this.createUpdateMessage(description)
89+
const message = this.createUpdateMessage(description, currentFiles)
9190
context.messages.push({ role: 'user', content: message })
9291

9392
try {
94-
const htmlContent = await this.callLLMAPI(context.messages)
93+
const htmlContent = await this.callLLMAPI(context.messages, FOLLOW_UP_SYSTEM_PROMPT)
9594

96-
const currentPages: Pages = await this.getLastGeneratedDapp(address) || {}
97-
const pages = parsePagesFollowUp(htmlContent, currentPages)
95+
const pages = parsePages(htmlContent)
96+
97+
if (Object.keys(pages).length === 0) {
98+
throw new Error("AI failed to return valid file structure. Check logs.");
99+
}
98100

99101
context.messages.push({ role: 'assistant', content: htmlContent })
100102
this.saveContext(address, context)
@@ -155,11 +157,10 @@ export class AIDappGenerator extends Plugin {
155157
if (context) {
156158
for (const message of context.messages) {
157159
if (message.role === 'assistant') {
158-
if ((message.content.indexOf(UPDATE_PAGE_START) === -1)) {
159-
currentPages = parsePages(message.content)
160-
} else {
161-
currentPages = parsePagesFollowUp(message.content, currentPages)
162-
}
160+
const newPages = parsePages(message.content)
161+
if (Object.keys(newPages).length > 0) {
162+
currentPages = newPages
163+
}
163164
}
164165
}
165166
}
@@ -175,12 +176,12 @@ export class AIDappGenerator extends Plugin {
175176

176177
private createInitialMessage(options: GenerateDappOptions): string {
177178
const providerCode = this.getProviderCode()
178-
return INITIAL_SYSTEM_PROMPT + `. \n The website should interact the smart contract deployed at this address: ${options.address} on ${options.chainId} network. The ABI of the contract is: ${JSON.stringify(options.abi)}. Put the following code in the header to be able to connect to the blockchain:${providerCode}. Follow the design and features proposed in this description: ${options.description}`
179+
return `. \n The website should interact the smart contract deployed at this address: ${options.address} on ${options.chainId} network. The ABI of the contract is: ${JSON.stringify(options.abi)}. Put the following code in the header to be able to connect to the blockchain:${providerCode}. Follow the design and features proposed in this description: ${options.description}`
179180
}
180181

181-
private createUpdateMessage(description: string): string {
182-
const providerCode = this.getProviderCode()
183-
return FOLLOW_UP_SYSTEM_PROMPT + `. \n Update the website to satisfy the following description: ${description}.`
182+
private createUpdateMessage(description: string, currentFiles: Pages): string {
183+
const filesString = JSON.stringify(currentFiles, null, 2);
184+
return `Here is the full code of my current project:\n\n${filesString}\n\nNow, please apply the following update based on this current code: ${description}. Remember to return ALL project files in the specified START_TITLE format.`
184185
}
185186

186187
private getProviderCode(): string {
@@ -194,7 +195,7 @@ export class AIDappGenerator extends Plugin {
194195
</script>`
195196
}
196197

197-
private async callLLMAPI(messages: any[]): Promise<string> {
198+
private async callLLMAPI(messages: any[], systemPrompt: string): Promise<string> {
198199
const param = new QueryParams()
199200
const apikey = param.get()['fireworksapikey']
200201

@@ -217,7 +218,10 @@ export class AIDappGenerator extends Plugin {
217218
presence_penalty: 0,
218219
frequency_penalty: 0,
219220
temperature: 0.6,
220-
messages: messages
221+
messages: [
222+
{ role: 'system', content: systemPrompt },
223+
...messages
224+
]
221225
})
222226
})
223227

apps/remix-ide/src/app/plugins/prompt.ts

Lines changed: 69 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -95,82 +95,80 @@ export default App;
9595
IMPORTANT: The first file should be always named index.html.
9696
You MUST generate all files: index.html, src/main.jsx, src/App.jsx.`;
9797

98-
export const FOLLOW_UP_SYSTEM_PROMPT = `You are an expert UI/UX and Front-End Developer modifying an existing HTML files.
99-
The user wants to apply changes and probably add new features/pages to the website, based on their request.
100-
You MUST output ONLY the changes required using the following UPDATE_PAGE_START and SEARCH/REPLACE format. Do NOT output the entire file.
101-
If it's a new page, you MUST applied the following NEW_PAGE_START and UPDATE_PAGE_END format.
98+
export const FOLLOW_UP_SYSTEM_PROMPT = `You are an expert Front-End Developer specializing in React, Vite, and ethers.js.
99+
Your task is to generate a multi-file DApp project structure.
100+
You MUST generate separate files for HTML, CSS, and JavaScript (JSX).
101+
You MUST use React with JSX syntax (not "text/babel" scripts).
102+
You MUST use ethers.js (v6) for all blockchain interactions.
103+
The user's contract address, ABI, and network info will be provided in the main prompt.
104+
105+
Return EACH file using the specified "TITLE_PAGE_START" format.
106+
The file structure MUST be:
107+
1. \`index.html\`: The HTML root file. It MUST link to \`/src/main.jsx\` as a module.
108+
2. \`src/main.jsx\`: The React entry point. It MUST import \`App.jsx\` and use \`ReactDOM.createRoot\`.
109+
3. \`src/App.jsx\`: The main React component containing all DApp logic (wallet connection, ABI calls).
110+
4. \`src/index.css\`: (Optional) Basic CSS file, imported by \`src/main.jsx\`.
111+
102112
${PROMPT_FOR_IMAGE_GENERATION}
103-
Do NOT explain the changes or what you did, just return the expected results.
104-
Update Format Rules:
105-
1. Start with ${UPDATE_PAGE_START}
106-
2. Provide the name of the page you are modifying.
107-
3. Close the start tag with the ${UPDATE_PAGE_END}.
108-
4. Start with ${SEARCH_START}
109-
5. Provide the exact lines from the current code that need to be replaced.
110-
6. Use ${DIVIDER} to separate the search block from the replacement.
111-
7. Provide the new lines that should replace the original lines.
112-
8. End with ${REPLACE_END}
113-
9. You can use multiple SEARCH/REPLACE blocks if changes are needed in different parts of the file.
114-
10. To insert code, use an empty SEARCH block (only ${SEARCH_START} and ${DIVIDER} on their lines) if inserting at the very beginning, otherwise provide the line *before* the insertion point in the SEARCH block and include that line plus the new lines in the REPLACE block.
115-
11. To delete code, provide the lines to delete in the SEARCH block and leave the REPLACE block empty (only ${DIVIDER} and ${REPLACE_END} on their lines).
116-
12. IMPORTANT: The SEARCH block must *exactly* match the current code, including indentation and whitespace.
117-
Example Modifying Code:
118-
\`\`\`
119-
Some explanation...
120-
${UPDATE_PAGE_START}index.html${UPDATE_PAGE_END}
121-
${SEARCH_START}
122-
<h1>Old Title</h1>
123-
${DIVIDER}
124-
<h1>New Title</h1>
125-
${REPLACE_END}
126-
${SEARCH_START}
127-
</body>
128-
${DIVIDER}
129-
<script>console.log("Added script");</script>
130-
</body>
131-
${REPLACE_END}
132-
\`\`\`
133-
Example Deleting Code:
134-
\`\`\`
135-
Removing the paragraph...
113+
No need to explain what you did. Just return the code for each file.
114+
115+
Example Format:
136116
${TITLE_PAGE_START}index.html${TITLE_PAGE_END}
137-
${SEARCH_START}
138-
<p>This paragraph will be deleted.</p>
139-
${DIVIDER}
140-
${REPLACE_END}
141-
\`\`\`
142-
The user can also ask to add a new page, in this case you should return the new page in the following format:
143-
1. Start with ${NEW_PAGE_START}.
144-
2. Add the name of the page without special character, such as spaces or punctuation, using the .html format only, right after the start tag.
145-
3. Close the start tag with the ${NEW_PAGE_END}.
146-
4. Start the HTML response with the triple backticks, like \`\`\`html.
147-
5. Insert the following html there.
148-
6. Close with the triple backticks, like \`\`\`.
149-
7. Retry if another pages.
150-
Example Code:
151-
${NEW_PAGE_START}index.html${NEW_PAGE_END}
152117
\`\`\`html
153118
<!DOCTYPE html>
154119
<html lang="en">
155-
<head>
156-
<meta charset="UTF-8">
157-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
158-
<title>Index</title>
159-
<link rel="icon" type="image/x-icon" href="/static/favicon.ico">
120+
<head>
121+
<meta charset="UTF-8" />
122+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
123+
<title>DApp</title>
160124
<script src="https://cdn.tailwindcss.com"></script>
161-
<link href="https://unpkg.com/aos@2.3.1/dist/aos.css" rel="stylesheet">
162-
<script src="https://unpkg.com/aos@2.3.1/dist/aos.js"></script>
163-
<script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
164-
<script src="https://cdn.jsdelivr.net/npm/animejs/lib/anime.iife.min.js"></script>
165-
<script src="https://unpkg.com/feather-icons"></script>
166-
</head>
167-
<body>
168-
<h1>Hello World</h1>
169-
<script>AOS.init();</script>
170-
<script>const { animate } = anime;</script>
171-
<script>feather.replace();</script>
172-
</body>
125+
<link rel="stylesheet" href="/src/index.css">
126+
</head>
127+
<body>
128+
<div id="root"></div>
129+
<script type="module" src="/src/main.jsx"></script>
130+
</body>
173131
</html>
174132
\`\`\`
175-
IMPORTANT: While creating a new page, UPDATE ALL THE OTHERS (using the UPDATE_PAGE_START and SEARCH/REPLACE format) pages to add or replace the link to the new page, otherwise the user will not be able to navigate to the new page. (Dont use onclick to navigate, only href)
176-
No need to explain what you did. Just return the expected result.`
133+
${TITLE_PAGE_START}src/index.css${TITLE_PAGE_END}
134+
\`\`\`css
135+
/* AI will generate Tailwind base styles or custom CSS here */
136+
body {
137+
font-family: sans-serif;
138+
}
139+
\`\`\`
140+
${TITLE_PAGE_START}src/main.jsx${TITLE_PAGE_END}
141+
\`\`\`javascript
142+
import React from 'react';
143+
import ReactDOM from 'react-dom/client';
144+
import App from './App.jsx';
145+
import './index.css';
146+
147+
ReactDOM.createRoot(document.getElementById('root')).render(
148+
<React.StrictMode>
149+
<App />
150+
</React.StrictMode>
151+
);
152+
\`\`\`
153+
${TITLE_PAGE_START}src/App.jsx${TITLE_PAGE_END}
154+
\`\`\`javascript
155+
import React, { useState, useEffect } from 'react';
156+
import { ethers } from 'ethers';
157+
158+
// AI will generate the React component logic here...
159+
// ... including ABI, address, wallet connection, etc.
160+
161+
const App = () => {
162+
// AI-generated React logic will go here
163+
return (
164+
<div>
165+
<h1>My DApp</h1>
166+
{/* AI-generated UI will go here */}
167+
</div>
168+
);
169+
};
170+
171+
export default App;
172+
\`\`\`
173+
IMPORTANT: The first file should be always named index.html.
174+
You MUST generate all files: index.html, src/main.jsx, src/App.jsx.`;

0 commit comments

Comments
 (0)