Spaces:
Running
Running
| import { createElement as h, useState, useRef } from "https://esm.sh/react"; | |
| import { createRoot } from "https://esm.sh/react-dom/client"; | |
| import html2canvas from "https://esm.sh/html2canvas"; | |
| const App = () => { | |
| const [characters, setCharacters] = useState([ | |
| { id: 0, name: "User", avatar: "https://i.pravatar.cc/30?img=3" }, | |
| { id: 1, name: "Bot", avatar: "https://i.pravatar.cc/30?img=7" } | |
| ]); | |
| const [selectedChar, setSelectedChar] = useState(0); | |
| const [messages, setMessages] = useState([]); | |
| const [input, setInput] = useState(""); | |
| const [playing, setPlaying] = useState(false); | |
| const chatRef = useRef(null); | |
| const streamRef = useRef(null); | |
| const addCharacter = () => { | |
| const id = characters.length; | |
| setCharacters([...characters, { id, name: "New", avatar: "" }]); | |
| }; | |
| const handleAvatar = (id, file) => { | |
| const url = URL.createObjectURL(file); | |
| setCharacters(characters.map(c => c.id === id ? { ...c, avatar: url } : c)); | |
| }; | |
| const handleNameChange = (id, name) => { | |
| setCharacters(characters.map(c => c.id === id ? { ...c, name } : c)); | |
| }; | |
| const addMessage = () => { | |
| if (!input.trim()) return; | |
| setMessages([...messages, { charId: selectedChar, text: input }]); | |
| setInput(""); | |
| }; | |
| const playbackAndRecord = async () => { | |
| setPlaying(true); | |
| const container = chatRef.current; | |
| const allMsgs = [...messages]; | |
| const tempContainer = container.cloneNode(false); | |
| container.parentNode.appendChild(tempContainer); | |
| const stream = tempContainer.captureStream(30); | |
| streamRef.current = stream; | |
| const recorder = new MediaRecorder(stream); | |
| let chunks = []; | |
| recorder.ondataavailable = (e) => chunks.push(e.data); | |
| recorder.onstop = () => { | |
| const blob = new Blob(chunks, { type: "video/webm" }); | |
| const link = document.createElement("a"); | |
| link.href = URL.createObjectURL(blob); | |
| link.download = "chat.mp4"; | |
| link.click(); | |
| tempContainer.remove(); | |
| setPlaying(false); | |
| }; | |
| recorder.start(); | |
| for (let i = 0; i < allMsgs.length; i++) { | |
| const msg = allMsgs[i]; | |
| const char = characters.find(c => c.id === msg.charId); | |
| const msgDiv = document.createElement("div"); | |
| msgDiv.className = "message"; | |
| const avatar = document.createElement("img"); | |
| avatar.src = char.avatar; | |
| const text = document.createElement("div"); | |
| text.className = "bubble"; | |
| text.textContent = msg.text; | |
| msgDiv.appendChild(avatar); | |
| msgDiv.appendChild(text); | |
| tempContainer.appendChild(msgDiv); | |
| await new Promise(r => setTimeout(r, 1500)); | |
| } | |
| recorder.stop(); | |
| }; | |
| return h("div", { className: "app" }, [ | |
| h("h3", null, "Characters"), | |
| ...characters.map(char => | |
| h("div", { className: "character", key: char.id }, [ | |
| h("img", { src: char.avatar || "https://via.placeholder.com/30" }), | |
| h("input", { | |
| value: char.name, | |
| onChange: (e) => handleNameChange(char.id, e.target.value) | |
| }), | |
| h("input", { | |
| type: "file", | |
| accept: "image/*", | |
| onChange: (e) => handleAvatar(char.id, e.target.files[0]) | |
| }) | |
| ]) | |
| ), | |
| h("button", { onClick: addCharacter }, "+ Add Character"), | |
| h("div", { className: "controls" }, [ | |
| h("select", { | |
| value: selectedChar, | |
| onChange: (e) => setSelectedChar(parseInt(e.target.value)) | |
| }, characters.map(c => h("option", { value: c.id, key: c.id }, c.name))), | |
| h("input", { | |
| value: input, | |
| onChange: (e) => setInput(e.target.value), | |
| placeholder: "Type message..." | |
| }), | |
| h("button", { onClick: addMessage }, "Send Message") | |
| ]), | |
| h("div", { className: "chat", ref: chatRef }, messages.map((msg, i) => { | |
| const char = characters.find(c => c.id === msg.charId); | |
| return h("div", { className: "message", key: i }, [ | |
| h("img", { src: char?.avatar }), | |
| h("div", { className: "bubble" }, msg.text) | |
| ]); | |
| })), | |
| h("button", { | |
| onClick: playbackAndRecord, | |
| disabled: playing, | |
| style: { marginTop: 20 } | |
| }, playing ? "Recording..." : "🎬 Export Video") | |
| ]); | |
| }; | |
| createRoot(document.getElementById("root")).render(h(App)); | |