From 95e2e9d263603805a75138817f4a128da9188545 Mon Sep 17 00:00:00 2001 From: ci-bot Date: Tue, 23 Sep 2025 23:28:02 +0200 Subject: [PATCH] gen website AI --- apps/quick-dapp/src/App.tsx | 7 +- apps/quick-dapp/src/actions/index.ts | 101 ++++---- .../src/components/ChatBox/ChatBox.css | 122 ++++++++++ .../src/components/ChatBox/index.tsx | 102 ++++++++ .../src/components/CreateInstance/index.tsx | 102 +++++--- .../src/components/DeployPanel/index.tsx | 13 +- .../src/components/EditHtmlTemplate/index.tsx | 213 ++++++++++++++++ .../src/components/ImageUpload/index.tsx | 2 +- apps/quick-dapp/src/reducers/state.ts | 1 + apps/quick-dapp/src/remix-client.ts | 3 +- apps/remix-ide/src/app.ts | 13 +- .../src/app/plugins/ai-dapp-generator.ts | 229 ++++++++++++++++++ .../src/app/plugins/iframe-content.tsx | 57 +++++ apps/remix-ide/src/remixAppManager.ts | 3 +- apps/remix-ide/src/remixEngine.js | 2 +- .../src/lib/components/universalDappUI.tsx | 74 +++++- libs/remix-ui/run-tab/src/lib/run-tab.tsx | 13 +- libs/remix-ui/run-tab/src/lib/types/index.ts | 4 +- package.json | 3 +- 19 files changed, 964 insertions(+), 100 deletions(-) create mode 100644 apps/quick-dapp/src/components/ChatBox/ChatBox.css create mode 100644 apps/quick-dapp/src/components/ChatBox/index.tsx create mode 100644 apps/quick-dapp/src/components/EditHtmlTemplate/index.tsx create mode 100644 apps/remix-ide/src/app/plugins/ai-dapp-generator.ts create mode 100644 apps/remix-ide/src/app/plugins/iframe-content.tsx diff --git a/apps/quick-dapp/src/App.tsx b/apps/quick-dapp/src/App.tsx index c01f92bb7dd..ac9a6bd31dc 100644 --- a/apps/quick-dapp/src/App.tsx +++ b/apps/quick-dapp/src/App.tsx @@ -2,6 +2,7 @@ import React, { useEffect, useReducer, useState } from 'react'; import { IntlProvider } from 'react-intl' import CreateInstance from './components/CreateInstance'; import EditInstance from './components/EditInstance'; +import EditHtmlTemplate from './components/EditHtmlTemplate'; import DeployPanel from './components/DeployPanel'; import LoadingScreen from './components/LoadingScreen'; import { appInitialState, appReducer } from './reducers/state'; @@ -52,7 +53,11 @@ function App(): JSX.Element { }} > - {Object.keys(appState.instance.abi).length > 0 ? ( + {appState.instance.htmlTemplate ? ( +
+ +
+ ) : Object.keys(appState.instance.abi).length > 0 ? (
diff --git a/apps/quick-dapp/src/actions/index.ts b/apps/quick-dapp/src/actions/index.ts index 217d14c2e7a..e2180c123b6 100644 --- a/apps/quick-dapp/src/actions/index.ts +++ b/apps/quick-dapp/src/actions/index.ts @@ -158,15 +158,7 @@ export const deploy = async (payload: any, callback: any) => { } } - const { data } = await axios.get( - // It's the json file contains all the static files paths of dapp-template. - // It's generated through the build process automatically. - `${window.origin}/plugins/remix-dapp/manifest.json` - ); - - const paths = Object.keys(data); - - const { logo, ...instance } = state.instance; + const { logo, htmlTemplate, ...instance } = state.instance; const instanceJson = JSON.stringify({ ...instance, @@ -179,29 +171,35 @@ export const deploy = async (payload: any, callback: any) => { 'dir/assets/instance.json': instanceJson, }; - // console.log( - // JSON.stringify({ - // ...instance, - // shareTo: payload.shareTo, - // }) - // ); - - for (let index = 0; index < paths.length; index++) { - const path = paths[index]; - // download all the static files from the dapp-template domain. - // here is the codebase of dapp-template: https://github.com/drafish/remix-dapp - const resp = await axios.get(`${window.origin}/plugins/remix-dapp/${path}`); - files[`dir/${path}`] = resp.data; + // Use the HTML template provided by the user instead of downloading dapp-template + if (htmlTemplate) { + files['dir/index.html'] = htmlTemplate; + } else { + // Fallback to the old method if no HTML template is provided + const { data } = await axios.get( + `${window.origin}/plugins/remix-dapp/manifest.json` + ); + + const paths = Object.keys(data); + + for (let index = 0; index < paths.length; index++) { + const path = paths[index]; + const resp = await axios.get(`${window.origin}/plugins/remix-dapp/${path}`); + files[`dir/${path}`] = resp.data; + } + + if (files['dir/index.html']) { + files['dir/index.html'] = files['dir/index.html'].replace( + 'assets/css/themes/remix-dark_tvx1s2.css', + themeMap[instance.theme].url + ); + } } if (logo) { files['dir/assets/logo.png'] = logo } files['dir/CORS'] = '*' - files['dir/index.html'] = files['dir/index.html'].replace( - 'assets/css/themes/remix-dark_tvx1s2.css', - themeMap[instance.theme].url - ); try { await surgeClient.publish({ @@ -287,8 +285,27 @@ export const initInstance = async ({ methodIdentifiers, devdoc, solcVersion, + htmlTemplate, ...payload }: any) => { + // If HTML template is provided, use simplified initialization + if (htmlTemplate) { + await dispatch({ + type: 'SET_INSTANCE', + payload: { + ...payload, + htmlTemplate, + abi: {}, + items: {}, + containers: [], + natSpec: { checked: false, methods: {} }, + solcVersion: solcVersion ? getVersion(solcVersion) : { version: '0.8.25', canReceive: true }, + }, + }); + return; + } + + // Original ABI-based initialization (kept for backward compatibility) const functionHashes: any = {}; const natSpec: any = { checked: false, methods: {} }; if (methodIdentifiers && devdoc) { @@ -324,18 +341,20 @@ export const initInstance = async ({ const abi: any = {}; const lowLevel: any = {} - payload.abi.forEach((item: any) => { - if (item.type === 'function') { - item.id = encodeFunctionId(item); - abi[item.id] = item; - } - if (item.type === 'fallback') { - lowLevel.fallback = item; - } - if (item.type === 'receive') { - lowLevel.receive = item; - } - }); + if (payload.abi) { + payload.abi.forEach((item: any) => { + if (item.type === 'function') { + item.id = encodeFunctionId(item); + abi[item.id] = item; + } + if (item.type === 'fallback') { + lowLevel.fallback = item; + } + if (item.type === 'receive') { + lowLevel.receive = item; + } + }); + } const ids = Object.keys(abi); const items = ids.length > 2 @@ -345,8 +364,6 @@ export const initInstance = async ({ } : { A: ids }; - // const logo = await axios.get('https://dev.remix-dapp.pages.dev/logo.png', { responseType: 'arraybuffer' }) - await dispatch({ type: 'SET_INSTANCE', payload: { @@ -355,9 +372,8 @@ export const initInstance = async ({ items, containers: Object.keys(items), natSpec, - solcVersion: getVersion(solcVersion), + solcVersion: solcVersion ? getVersion(solcVersion) : { version: '0.8.25', canReceive: true }, ...lowLevel, - // logo: logo.data, }, }); }; @@ -394,6 +410,7 @@ export const emptyInstance = async () => { name: '', address: '', network: '', + htmlTemplate: '', abi: {}, items: {}, containers: [], diff --git a/apps/quick-dapp/src/components/ChatBox/ChatBox.css b/apps/quick-dapp/src/components/ChatBox/ChatBox.css new file mode 100644 index 00000000000..3305ef621dc --- /dev/null +++ b/apps/quick-dapp/src/components/ChatBox/ChatBox.css @@ -0,0 +1,122 @@ +.chat-box-container { + display: flex; + flex-direction: column; + border: 1px solid var(--border); + background: var(--body-bg); +} + +.chat-box-header { + background: var(--secondary); + color: var(--text); + padding: 12px 16px; + border-bottom: 1px solid var(--border); +} + +.chat-box-body { + flex: 1; + overflow-y: auto; + padding: 16px; + background: var(--body-bg); +} + +.messages-container { + display: flex; + flex-direction: column; + gap: 12px; +} + +.message { + display: flex; + flex-direction: column; + gap: 4px; + animation: fadeIn 0.3s ease-in; +} + +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(10px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.user-message { + align-items: flex-end; +} + +.assistant-message { + align-items: flex-start; +} + +.message-role { + font-size: 12px; + color: var(--text-muted); + font-weight: 500; + text-transform: uppercase; +} + +.message-content { + max-width: 70%; + padding: 10px 14px; + border-radius: 8px; + word-wrap: break-word; +} + +.user-message .message-content { + background: var(--primary); + color: white; + border-bottom-right-radius: 4px; +} + +.assistant-message .message-content { + background: var(--light); + color: var(--text); + border-bottom-left-radius: 4px; +} + +.typing-indicator { + display: inline-block; + animation: typing 1.4s infinite; +} + +@keyframes typing { + 0%, 60%, 100% { + opacity: 0.3; + } + 30% { + opacity: 1; + } +} + +.chat-box-footer { + padding: 12px; + border-top: 1px solid var(--border); + background: var(--body-bg); +} + +.chat-input-group { + display: flex; + gap: 8px; +} + +.chat-input { + flex: 1; + resize: none; + border: 1px solid var(--border); + background: var(--body-bg); + color: var(--text); +} + +.chat-input:focus { + border-color: var(--primary); + background: var(--body-bg); + color: var(--text); +} + +.send-button { + align-self: flex-end; + min-width: 80px; +} \ No newline at end of file diff --git a/apps/quick-dapp/src/components/ChatBox/index.tsx b/apps/quick-dapp/src/components/ChatBox/index.tsx new file mode 100644 index 00000000000..12684b977da --- /dev/null +++ b/apps/quick-dapp/src/components/ChatBox/index.tsx @@ -0,0 +1,102 @@ +import React, { useState, useRef, useEffect } from 'react'; +import { Form, Button, Card } from 'react-bootstrap'; +import { FormattedMessage, useIntl } from 'react-intl'; +import './ChatBox.css'; + +interface Message { + id: string; + role: 'user' | 'assistant'; + content: string; + timestamp: Date; +} + +interface ChatBoxProps { + onSendMessage?: (message: string) => void; + onUpdateCode?: (code: string) => void; +} + +const ChatBox: React.FC = ({ onSendMessage, onUpdateCode }) => { + const intl = useIntl(); + const [messages, setMessages] = useState([]); + const [inputMessage, setInputMessage] = useState(''); + const [isLoading, setIsLoading] = useState(false); + const messagesEndRef = useRef(null); + + const scrollToBottom = () => { + messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); + }; + + useEffect(() => { + scrollToBottom(); + }, [messages]); + + const handleSendMessage = async () => { + if (!inputMessage.trim()) return; + + const newMessage: Message = { + id: Date.now().toString(), + role: 'user', + content: inputMessage, + timestamp: new Date() + }; + + setMessages(prev => [...prev, newMessage]); + setInputMessage(''); + setIsLoading(true); + + if (onSendMessage) { + onSendMessage(inputMessage); + } + + // Simulate assistant response (this will be replaced with actual LLM integration) + setTimeout(() => { + const assistantMessage: Message = { + id: (Date.now() + 1).toString(), + role: 'assistant', + content: 'I understand you want to update the frontend. I can help you modify the HTML template. What specific changes would you like to make?', + timestamp: new Date() + }; + setMessages(prev => [...prev, assistantMessage]); + setIsLoading(false); + }, 1000); + }; + + const handleKeyDown = (e: React.KeyboardEvent) => { + if (e.key === 'Enter' && !e.shiftKey) { + e.preventDefault(); + handleSendMessage(); + } + }; + + return ( + + +
+ setInputMessage(e.target.value)} + onKeyDown={handleKeyDown} + placeholder={intl.formatMessage({ + id: 'quickDapp.chatPlaceholder', + defaultMessage: 'Ask the assistant to help modify your dApp...' + })} + disabled={isLoading} + className="chat-input" + /> + +
+
+
+ ); +}; + +export default ChatBox; \ No newline at end of file diff --git a/apps/quick-dapp/src/components/CreateInstance/index.tsx b/apps/quick-dapp/src/components/CreateInstance/index.tsx index 9f4f763a20a..46a04653733 100644 --- a/apps/quick-dapp/src/components/CreateInstance/index.tsx +++ b/apps/quick-dapp/src/components/CreateInstance/index.tsx @@ -1,5 +1,5 @@ -import React, { useState } from 'react'; -import { Alert, Button, Form } from 'react-bootstrap'; +import React, { useState, useRef, useEffect } from 'react'; +import { Alert, Button, Form, Card, Row, Col } from 'react-bootstrap'; import { FormattedMessage, useIntl } from 'react-intl'; import { initInstance } from '../../actions'; @@ -7,11 +7,25 @@ const CreateInstance: React.FC = () => { const intl = useIntl() const [formVal, setFormVal] = useState({ address: '', - abi: [], + htmlTemplate: '', name: '', network: '', }); const [error, setError] = useState('') + const [showPreview, setShowPreview] = useState(false) + const iframeRef = useRef(null) + + useEffect(() => { + if (iframeRef.current && formVal.htmlTemplate && showPreview) { + const iframe = iframeRef.current; + const doc = iframe.contentDocument || iframe.contentWindow?.document; + if (doc) { + doc.open(); + doc.write(formVal.htmlTemplate); + doc.close(); + } + } + }, [formVal.htmlTemplate, showPreview]) return (
{ /> - - abi - { - setError('') - let abi = []; - if (e.target.value !== '') { - try { - abi = JSON.parse(e.target.value); - } catch (error) { - setError(error.toString()) - } - } - setFormVal({ ...formVal, abi }); - }} - /> - {error && - {error} - } + +
+ HTML Template + {formVal.htmlTemplate && ( + + )} +
+ + + { + setError('') + const template = e.target.value; + if (template && !template.includes(' or tag'); + } + setFormVal({ ...formVal, htmlTemplate: template }); + }} + /> + {error && + {error} + } + + {showPreview && ( + + + + Preview + + +