aigorithm commited on
Commit
3b2add9
·
verified ·
1 Parent(s): 7d41d24

Upload 2 files

Browse files
Files changed (2) hide show
  1. index.html +7 -8
  2. main.js +103 -59
index.html CHANGED
@@ -4,16 +4,15 @@
4
  <head>
5
  <meta charset="UTF-8" />
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
7
- <title>Chat Animator</title>
8
  <style>
9
  body { font-family: sans-serif; margin: 0; background: #f0f2f5; }
10
- .app { padding: 20px; max-width: 400px; margin: auto; }
11
- .bubble { border-radius: 20px; padding: 10px; margin: 8px 0; display: inline-block; max-width: 70%; }
12
- .user { background: #dcf8c6; align-self: flex-end; }
13
- .bot { background: #fff; align-self: flex-start; }
14
- .message { display: flex; align-items: center; gap: 8px; }
15
- .avatar { border-radius: 50%; width: 30px; height: 30px; }
16
- select, input, button { margin: 6px 0; padding: 8px; }
17
  </style>
18
  </head>
19
  <body>
 
4
  <head>
5
  <meta charset="UTF-8" />
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
7
+ <title>Chat Animator + Video Export</title>
8
  <style>
9
  body { font-family: sans-serif; margin: 0; background: #f0f2f5; }
10
+ .app { padding: 20px; max-width: 420px; margin: auto; }
11
+ .character, .message { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; }
12
+ .character img, .message img { width: 30px; height: 30px; border-radius: 50%; }
13
+ .bubble { padding: 10px 14px; border-radius: 20px; max-width: 70%; background: #fff; }
14
+ input, select, button { padding: 6px; margin: 4px 0; width: 100%; }
15
+ .controls, .chat { margin-top: 16px; }
 
16
  </style>
17
  </head>
18
  <body>
main.js CHANGED
@@ -1,83 +1,127 @@
1
 
2
- import { createElement as h, useState } from "https://esm.sh/react";
3
  import { createRoot } from "https://esm.sh/react-dom/client";
4
  import html2canvas from "https://esm.sh/html2canvas";
5
 
6
  const App = () => {
 
 
 
 
 
7
  const [messages, setMessages] = useState([]);
8
  const [input, setInput] = useState("");
9
- const [style, setStyle] = useState("whatsapp");
10
- const [voice, setVoice] = useState("Rachel");
 
 
 
 
 
 
11
 
12
- const voiceIdMap = {
13
- Rachel: "21m00Tcm4TlvDq8ikWAM",
14
- Adam: "pNInz6obpgDQGcFmaJgB",
15
- Bella: "EXAVITQu4vr4xnSDxMaL"
16
  };
17
- const apiKey = "sk_4e67c39c0e9cbc87462cd2643e1f4d1d9959d7d81203adc2";
18
-
19
- const generateVoice = async (text) => {
20
- const response = await fetch("https://api.elevenlabs.io/v1/text-to-speech/" + voiceIdMap[voice], {
21
- method: "POST",
22
- headers: { "Content-Type": "application/json", "xi-api-key": apiKey },
23
- body: JSON.stringify({
24
- text,
25
- model_id: "eleven_monolingual_v1",
26
- voice_settings: { stability: 0.5, similarity_boost: 0.75 }
27
- })
28
- });
29
- const blob = await response.blob();
30
- new Audio(URL.createObjectURL(blob)).play();
31
  };
32
 
33
- const addMessage = (sender) => {
34
  if (!input.trim()) return;
35
- const msg = { sender, text: input };
36
- setMessages([...messages, msg]);
37
  setInput("");
38
- generateVoice(input);
39
  };
40
 
41
- const exportImage = () => {
42
- html2canvas(document.getElementById("chat")).then((canvas) => {
 
 
 
 
 
 
 
 
 
 
 
 
43
  const link = document.createElement("a");
44
- link.href = canvas.toDataURL();
45
- link.download = "chat_export.png";
46
  link.click();
47
- });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  };
49
 
50
  return h("div", { className: "app" }, [
51
- h("select", { value: style, onChange: (e) => setStyle(e.target.value) }, [
52
- h("option", { value: "whatsapp" }, "WhatsApp"),
53
- h("option", { value: "imessage" }, "iMessage"),
54
- h("option", { value: "instagram" }, "Instagram")
55
- ]),
56
- h("select", { value: voice, onChange: (e) => setVoice(e.target.value) }, [
57
- h("option", { value: "Rachel" }, "Rachel"),
58
- h("option", { value: "Adam" }, "Adam"),
59
- h("option", { value: "Bella" }, "Bella")
60
- ]),
61
- h("div", { id: "chat", style: { display: "flex", flexDirection: "column", gap: "8px", marginTop: "10px" } },
62
- messages.map((msg, i) =>
63
- h("div", { key: i, className: "message", style: { justifyContent: msg.sender === "user" ? "flex-end" : "flex-start" } }, [
64
- msg.sender === "bot" && h("img", { src: "https://i.pravatar.cc/30?img=7", className: "avatar" }),
65
- h("div", { className: `bubble ${msg.sender}` }, msg.text),
66
- msg.sender === "user" && h("img", { src: "https://i.pravatar.cc/30?img=3", className: "avatar" })
67
- ])
68
- )
69
  ),
70
- h("input", {
71
- value: input,
72
- onChange: (e) => setInput(e.target.value),
73
- placeholder: "Type message...",
74
- style: { width: "100%", marginTop: "10px" }
75
- }),
76
- h("div", { style: { display: "flex", gap: "8px" } }, [
77
- h("button", { onClick: () => addMessage("user") }, "User"),
78
- h("button", { onClick: () => addMessage("bot") }, "Bot"),
79
- h("button", { onClick: exportImage }, "Export Image")
80
- ])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
  ]);
82
  };
83
 
 
1
 
2
+ import { createElement as h, useState, useRef } from "https://esm.sh/react";
3
  import { createRoot } from "https://esm.sh/react-dom/client";
4
  import html2canvas from "https://esm.sh/html2canvas";
5
 
6
  const App = () => {
7
+ const [characters, setCharacters] = useState([
8
+ { id: 0, name: "User", avatar: "https://i.pravatar.cc/30?img=3" },
9
+ { id: 1, name: "Bot", avatar: "https://i.pravatar.cc/30?img=7" }
10
+ ]);
11
+ const [selectedChar, setSelectedChar] = useState(0);
12
  const [messages, setMessages] = useState([]);
13
  const [input, setInput] = useState("");
14
+ const [playing, setPlaying] = useState(false);
15
+ const chatRef = useRef(null);
16
+ const streamRef = useRef(null);
17
+
18
+ const addCharacter = () => {
19
+ const id = characters.length;
20
+ setCharacters([...characters, { id, name: "New", avatar: "" }]);
21
+ };
22
 
23
+ const handleAvatar = (id, file) => {
24
+ const url = URL.createObjectURL(file);
25
+ setCharacters(characters.map(c => c.id === id ? { ...c, avatar: url } : c));
 
26
  };
27
+
28
+ const handleNameChange = (id, name) => {
29
+ setCharacters(characters.map(c => c.id === id ? { ...c, name } : c));
 
 
 
 
 
 
 
 
 
 
 
30
  };
31
 
32
+ const addMessage = () => {
33
  if (!input.trim()) return;
34
+ setMessages([...messages, { charId: selectedChar, text: input }]);
 
35
  setInput("");
 
36
  };
37
 
38
+ const playbackAndRecord = async () => {
39
+ setPlaying(true);
40
+ const container = chatRef.current;
41
+ const allMsgs = [...messages];
42
+ const tempContainer = container.cloneNode(false);
43
+ container.parentNode.appendChild(tempContainer);
44
+ const stream = tempContainer.captureStream(30);
45
+ streamRef.current = stream;
46
+ const recorder = new MediaRecorder(stream);
47
+ let chunks = [];
48
+
49
+ recorder.ondataavailable = (e) => chunks.push(e.data);
50
+ recorder.onstop = () => {
51
+ const blob = new Blob(chunks, { type: "video/webm" });
52
  const link = document.createElement("a");
53
+ link.href = URL.createObjectURL(blob);
54
+ link.download = "chat.mp4";
55
  link.click();
56
+ tempContainer.remove();
57
+ setPlaying(false);
58
+ };
59
+
60
+ recorder.start();
61
+
62
+ for (let i = 0; i < allMsgs.length; i++) {
63
+ const msg = allMsgs[i];
64
+ const char = characters.find(c => c.id === msg.charId);
65
+ const msgDiv = document.createElement("div");
66
+ msgDiv.className = "message";
67
+ const avatar = document.createElement("img");
68
+ avatar.src = char.avatar;
69
+ const text = document.createElement("div");
70
+ text.className = "bubble";
71
+ text.textContent = msg.text;
72
+ msgDiv.appendChild(avatar);
73
+ msgDiv.appendChild(text);
74
+ tempContainer.appendChild(msgDiv);
75
+ await new Promise(r => setTimeout(r, 1500));
76
+ }
77
+
78
+ recorder.stop();
79
  };
80
 
81
  return h("div", { className: "app" }, [
82
+ h("h3", null, "Characters"),
83
+ ...characters.map(char =>
84
+ h("div", { className: "character", key: char.id }, [
85
+ h("img", { src: char.avatar || "https://via.placeholder.com/30" }),
86
+ h("input", {
87
+ value: char.name,
88
+ onChange: (e) => handleNameChange(char.id, e.target.value)
89
+ }),
90
+ h("input", {
91
+ type: "file",
92
+ accept: "image/*",
93
+ onChange: (e) => handleAvatar(char.id, e.target.files[0])
94
+ })
95
+ ])
 
 
 
 
96
  ),
97
+ h("button", { onClick: addCharacter }, "+ Add Character"),
98
+
99
+ h("div", { className: "controls" }, [
100
+ h("select", {
101
+ value: selectedChar,
102
+ onChange: (e) => setSelectedChar(parseInt(e.target.value))
103
+ }, characters.map(c => h("option", { value: c.id, key: c.id }, c.name))),
104
+ h("input", {
105
+ value: input,
106
+ onChange: (e) => setInput(e.target.value),
107
+ placeholder: "Type message..."
108
+ }),
109
+ h("button", { onClick: addMessage }, "Send Message")
110
+ ]),
111
+
112
+ h("div", { className: "chat", ref: chatRef }, messages.map((msg, i) => {
113
+ const char = characters.find(c => c.id === msg.charId);
114
+ return h("div", { className: "message", key: i }, [
115
+ h("img", { src: char?.avatar }),
116
+ h("div", { className: "bubble" }, msg.text)
117
+ ]);
118
+ })),
119
+
120
+ h("button", {
121
+ onClick: playbackAndRecord,
122
+ disabled: playing,
123
+ style: { marginTop: 20 }
124
+ }, playing ? "Recording..." : "🎬 Export Video")
125
  ]);
126
  };
127