Spaces:
Running
Running
| import gradio as gr | |
| import modelscope_studio.components.antd as antd | |
| import modelscope_studio.components.base as ms | |
| import modelscope_studio.components.pro as pro | |
| with gr.Blocks() as demo, ms.Application(), antd.ConfigProvider(): | |
| pro.WebSandbox(value={ | |
| "./index.tsx": | |
| """import Demo from './demo.tsx' | |
| import "@tailwindcss/browser" | |
| export default Demo | |
| """, | |
| "./demo.tsx": | |
| """import { useState, useEffect } from 'react'; | |
| export default function App() { | |
| const [messages, setMessages] = useState([ | |
| { id: 1, sender: 'ai', content: 'Hello! I am your AI assistant. How can I help you today?', timestamp: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) }, | |
| ]); | |
| const [inputValue, setInputValue] = useState(''); | |
| const [isTyping, setIsTyping] = useState(false); | |
| const [darkMode, setDarkMode] = useState(false); | |
| // Simulate AI typing effect | |
| const handleSendMessage = () => { | |
| if (inputValue.trim() === '') return; | |
| const userMessage = { | |
| id: messages.length + 1, | |
| sender: 'user', | |
| content: inputValue, | |
| timestamp: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }), | |
| }; | |
| setMessages((prev) => [...prev, userMessage]); | |
| setInputValue(''); | |
| setIsTyping(true); | |
| // Simulate AI response after delay | |
| setTimeout(() => { | |
| const aiResponse = { | |
| id: messages.length + 2, | |
| sender: 'ai', | |
| content: 'I am processing your request and will respond shortly.', | |
| timestamp: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }), | |
| }; | |
| setMessages((prev) => [...prev, aiResponse]); | |
| setIsTyping(false); | |
| }, 1500); | |
| }; | |
| // Handle Enter key press | |
| const handleKeyPress = (e) => { | |
| if (e.key === 'Enter') { | |
| handleSendMessage(); | |
| } | |
| }; | |
| // Toggle dark mode | |
| const toggleDarkMode = () => { | |
| setDarkMode(!darkMode); | |
| }; | |
| useEffect(() => { | |
| const app = document.documentElement; | |
| if (darkMode) { | |
| app.classList.add('dark'); | |
| } else { | |
| app.classList.remove('dark'); | |
| } | |
| }, [darkMode]); | |
| return ( | |
| <div className={"min-h-screen flex flex-col transition-colors duration-300 " + (darkMode ? 'bg-gray-900 text-white' : 'bg-gray-100 text-gray-900')}> | |
| {/* Header */} | |
| <header className={"px-6 py-4 shadow-md flex justify-between items-center " + (darkMode ? 'bg-gray-800' : 'bg-white')}> | |
| <div className="flex items-center space-x-2"> | |
| <svg width="32" height="32" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" className="text-blue-500"> | |
| <path d="M12 4L4 8V16L12 20L20 16V8L12 4Z" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/> | |
| <path d="M12 15L9 12H15L12 9V15Z" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/> | |
| </svg> | |
| <h1 className="text-xl font-bold">Chatbot</h1> | |
| </div> | |
| <button onClick={toggleDarkMode} className="p-2 rounded-full hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"> | |
| {darkMode ? ( | |
| <svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> | |
| <circle cx="12" cy="12" r="5" stroke="white" strokeWidth="2"/> | |
| <path d="M12 2V4M12 20V22M4 12H2M6.31 6.31L4.9 4.9M17.69 6.31L19.1 4.9M6.31 17.69L4.9 19.1M17.69 17.69L19.1 19.1" stroke="white" strokeWidth="2" strokeLinecap="round"/> | |
| </svg> | |
| ) : ( | |
| <svg width="20" height="20" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"> | |
| <path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"/> | |
| </svg> | |
| )} | |
| </button> | |
| </header> | |
| {/* Chat Area */} | |
| <div className="flex-1 overflow-y-auto p-4 space-y-4"> | |
| {messages.map((message) => ( | |
| <div | |
| key={message.id} | |
| className={"flex " + (message.sender === 'user' ? 'justify-end' : 'justify-start')} | |
| > | |
| <div | |
| className={"max-w-xs sm:max-w-md px-4 py-2 rounded-lg " + | |
| (message.sender === 'user' | |
| ? 'bg-blue-500 text-white rounded-br-none' | |
| : (darkMode ? 'bg-gray-700' : 'bg-white') + ' rounded-bl-none') | |
| } | |
| > | |
| <p>{message.content}</p> | |
| <span className={"text-xs mt-1 block " + (message.sender === 'user' ? 'text-blue-100' : 'text-gray-500')}> | |
| {message.timestamp} | |
| </span> | |
| </div> | |
| </div> | |
| ))} | |
| {isTyping && ( | |
| <div className="flex justify-start"> | |
| <div className={"px-4 py-2 rounded-lg " + (darkMode ? 'bg-gray-700' : 'bg-white') + ' rounded-bl-none'}> | |
| <div className="flex space-x-1"> | |
| <span className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: '0ms' }}></span> | |
| <span className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: '150ms' }}></span> | |
| <span className="w-2 h-2 bg-gray-400 rounded-full animate-bounce" style={{ animationDelay: '300ms' }}></span> | |
| </div> | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| {/* Input Area */} | |
| <div className={"p-4 border-t " + (darkMode ? 'bg-gray-800 border-gray-700' : 'bg-white border-gray-200')}> | |
| <div className="flex space-x-2"> | |
| <input | |
| type="text" | |
| value={inputValue} | |
| onChange={(e) => setInputValue(e.target.value)} | |
| onKeyPress={handleKeyPress} | |
| placeholder="Type a message..." | |
| className={"flex-1 px-4 py-2 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 " + | |
| (darkMode ? 'bg-gray-700 text-white placeholder-gray-400' : 'bg-gray-100 text-gray-900 placeholder-gray-500') | |
| } | |
| /> | |
| <button | |
| onClick={handleSendMessage} | |
| disabled={inputValue.trim() === ''} | |
| className={"px-4 py-2 rounded-lg font-medium text-white bg-blue-500 hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-colors " + | |
| (inputValue.trim() === '' ? 'opacity-50 cursor-not-allowed' : '') | |
| } | |
| > | |
| Send | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| }""" | |
| }, | |
| height=600, | |
| template="react", | |
| imports={ | |
| "@tailwindcss/browser": | |
| "https://esm.sh/@tailwindcss/browser", | |
| }) | |
| if __name__ == "__main__": | |
| demo.queue().launch() | |