Spaces:
Running
Running
Upload 2 files
Browse files- index.html +73 -64
- main.js +71 -162
index.html
CHANGED
@@ -3,98 +3,107 @@
|
|
3 |
<html lang="en">
|
4 |
<head>
|
5 |
<meta charset="UTF-8" />
|
|
|
6 |
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
7 |
-
<title>Chat Animator Enhanced</title>
|
8 |
<style>
|
9 |
-
|
10 |
margin: 0;
|
11 |
background: #000;
|
12 |
-
|
13 |
-
|
14 |
-
.app {
|
15 |
display: flex;
|
16 |
flex-direction: column;
|
17 |
align-items: center;
|
18 |
-
background: #f0f2f5;
|
19 |
-
width: 100%;
|
20 |
-
max-width: 360px;
|
21 |
-
height: 720px;
|
22 |
-
margin: auto;
|
23 |
-
padding: 10px;
|
24 |
-
font-family: sans-serif;
|
25 |
-
box-sizing: border-box;
|
26 |
-
position: relative;
|
27 |
}
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
z-index: 0;
|
34 |
-
}
|
35 |
-
.chat {
|
36 |
-
position: relative;
|
37 |
-
z-index: 2;
|
38 |
-
flex: 1;
|
39 |
overflow-y: auto;
|
40 |
-
|
41 |
-
margin
|
|
|
42 |
}
|
43 |
-
.
|
44 |
display: flex;
|
45 |
-
align-items: center;
|
46 |
-
gap: 8px;
|
47 |
margin-bottom: 10px;
|
|
|
48 |
}
|
49 |
-
.
|
50 |
-
|
51 |
-
height: 30px;
|
52 |
-
border-radius: 50%;
|
53 |
-
}
|
54 |
.bubble {
|
55 |
padding: 10px 14px;
|
56 |
-
border-radius:
|
57 |
max-width: 70%;
|
58 |
font-size: 14px;
|
59 |
-
|
60 |
-
|
61 |
}
|
62 |
-
.left .bubble { background: #
|
63 |
-
.right .bubble { background: #007aff; color:
|
64 |
-
.
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
to { opacity: 1; }
|
70 |
}
|
71 |
-
.
|
72 |
-
position: relative;
|
73 |
-
z-index: 3;
|
74 |
width: 100%;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
75 |
}
|
76 |
-
|
|
|
77 |
padding: 6px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
78 |
width: 100%;
|
79 |
-
|
80 |
}
|
81 |
</style>
|
82 |
</head>
|
83 |
<body>
|
84 |
-
<
|
85 |
-
|
86 |
-
<
|
87 |
-
<
|
88 |
-
<
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
</select>
|
94 |
-
<button id="recordBtn">🎬 Export MP4</button>
|
95 |
-
</div>
|
96 |
-
<div id="chat" class="chat whatsapp"></div>
|
97 |
</div>
|
|
|
|
|
|
|
98 |
<script type="module" src="main.js"></script>
|
99 |
</body>
|
100 |
</html>
|
|
|
3 |
<html lang="en">
|
4 |
<head>
|
5 |
<meta charset="UTF-8" />
|
6 |
+
<title>AiCut Pro Style Chat Builder</title>
|
7 |
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
|
|
8 |
<style>
|
9 |
+
body {
|
10 |
margin: 0;
|
11 |
background: #000;
|
12 |
+
color: white;
|
13 |
+
font-family: sans-serif;
|
|
|
14 |
display: flex;
|
15 |
flex-direction: column;
|
16 |
align-items: center;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
17 |
}
|
18 |
+
.phone {
|
19 |
+
width: 360px;
|
20 |
+
height: 640px;
|
21 |
+
background: #f0f2f5;
|
22 |
+
border-radius: 32px;
|
|
|
|
|
|
|
|
|
|
|
|
|
23 |
overflow-y: auto;
|
24 |
+
box-shadow: 0 0 12px rgba(0,0,0,0.5);
|
25 |
+
margin: 20px 0;
|
26 |
+
padding: 10px;
|
27 |
}
|
28 |
+
.message {
|
29 |
display: flex;
|
|
|
|
|
30 |
margin-bottom: 10px;
|
31 |
+
align-items: flex-end;
|
32 |
}
|
33 |
+
.message.left { flex-direction: row; }
|
34 |
+
.message.right { flex-direction: row-reverse; }
|
|
|
|
|
|
|
35 |
.bubble {
|
36 |
padding: 10px 14px;
|
37 |
+
border-radius: 18px;
|
38 |
max-width: 70%;
|
39 |
font-size: 14px;
|
40 |
+
line-height: 1.4;
|
41 |
+
opacity: 1;
|
42 |
}
|
43 |
+
.left .bubble { background: #e5e5ea; color: black; }
|
44 |
+
.right .bubble { background: #007aff; color: white; }
|
45 |
+
.avatar {
|
46 |
+
width: 28px;
|
47 |
+
height: 28px;
|
48 |
+
border-radius: 50%;
|
49 |
+
margin: 0 6px;
|
|
|
50 |
}
|
51 |
+
.timeline {
|
|
|
|
|
52 |
width: 100%;
|
53 |
+
max-width: 420px;
|
54 |
+
background: #111;
|
55 |
+
padding: 10px;
|
56 |
+
border-radius: 8px;
|
57 |
+
box-shadow: 0 0 8px rgba(255,255,255,0.1);
|
58 |
+
}
|
59 |
+
.msg-row {
|
60 |
+
background: #1e1e1e;
|
61 |
+
margin: 6px 0;
|
62 |
+
padding: 8px;
|
63 |
+
border-radius: 8px;
|
64 |
+
display: flex;
|
65 |
+
flex-direction: column;
|
66 |
+
gap: 4px;
|
67 |
}
|
68 |
+
select, input[type="text"] {
|
69 |
+
width: 100%;
|
70 |
padding: 6px;
|
71 |
+
border-radius: 4px;
|
72 |
+
border: none;
|
73 |
+
background: #222;
|
74 |
+
color: white;
|
75 |
+
}
|
76 |
+
button {
|
77 |
+
margin-top: 10px;
|
78 |
+
padding: 8px 14px;
|
79 |
+
background: #007aff;
|
80 |
+
border: none;
|
81 |
+
border-radius: 6px;
|
82 |
+
color: white;
|
83 |
+
cursor: pointer;
|
84 |
+
}
|
85 |
+
.toolbar {
|
86 |
+
display: flex;
|
87 |
+
justify-content: space-between;
|
88 |
width: 100%;
|
89 |
+
max-width: 420px;
|
90 |
}
|
91 |
</style>
|
92 |
</head>
|
93 |
<body>
|
94 |
+
<h2>📱 Fake Chat Builder</h2>
|
95 |
+
<div class="toolbar">
|
96 |
+
<select id="styleSelect" onchange="updatePreview()">
|
97 |
+
<option value="imessage">iMessage</option>
|
98 |
+
<option value="whatsapp">WhatsApp</option>
|
99 |
+
<option value="instagram">Instagram</option>
|
100 |
+
<option value="reddit">Reddit</option>
|
101 |
+
</select>
|
102 |
+
<button onclick="addMessage()">+ Add Message</button>
|
|
|
|
|
|
|
|
|
103 |
</div>
|
104 |
+
<div class="phone" id="chatPreview"></div>
|
105 |
+
<div class="timeline" id="timeline"></div>
|
106 |
+
<button onclick="exportJSON()">📤 Export JSON</button>
|
107 |
<script type="module" src="main.js"></script>
|
108 |
</body>
|
109 |
</html>
|
main.js
CHANGED
@@ -1,165 +1,74 @@
|
|
1 |
|
2 |
-
|
3 |
-
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
{ id: 0, name: "User", avatar: "https://i.pravatar.cc/30?img=3", voice: "Rachel" },
|
15 |
-
{ id: 1, name: "Bot", avatar: "https://i.pravatar.cc/30?img=7", voice: "Adam" }
|
16 |
-
]);
|
17 |
-
const [selectedChar, setSelectedChar] = useState(0);
|
18 |
-
const [messages, setMessages] = useState([]);
|
19 |
-
const [input, setInput] = useState("");
|
20 |
-
const [recording, setRecording] = useState(false);
|
21 |
-
const chatRef = useRef(null);
|
22 |
-
const recordRef = useRef(null);
|
23 |
-
|
24 |
-
const addCharacter = () => {
|
25 |
-
const id = characters.length;
|
26 |
-
setCharacters([...characters, { id, name: "New", avatar: "", voice: "Rachel" }]);
|
27 |
-
};
|
28 |
-
|
29 |
-
const handleAvatar = (id, file) => {
|
30 |
-
const url = URL.createObjectURL(file);
|
31 |
-
setCharacters(characters.map(c => c.id === id ? { ...c, avatar: url } : c));
|
32 |
-
};
|
33 |
-
|
34 |
-
const handleNameChange = (id, name) => {
|
35 |
-
setCharacters(characters.map(c => c.id === id ? { ...c, name } : c));
|
36 |
-
};
|
37 |
-
|
38 |
-
const handleVoiceChange = (id, voice) => {
|
39 |
-
setCharacters(characters.map(c => c.id === id ? { ...c, voice } : c));
|
40 |
-
};
|
41 |
-
|
42 |
-
const addMessage = () => {
|
43 |
-
if (!input.trim()) return;
|
44 |
-
setMessages([...messages, { charId: selectedChar, text: input }]);
|
45 |
-
setInput("");
|
46 |
-
};
|
47 |
-
|
48 |
-
const getVoiceBlob = async (text, voiceId) => {
|
49 |
-
const res = await fetch(`https://api.elevenlabs.io/v1/text-to-speech/${voiceId}`, {
|
50 |
-
method: "POST",
|
51 |
-
headers: {
|
52 |
-
"Content-Type": "application/json",
|
53 |
-
"xi-api-key": apiKey
|
54 |
-
},
|
55 |
-
body: JSON.stringify({
|
56 |
-
text,
|
57 |
-
model_id: "eleven_monolingual_v1",
|
58 |
-
voice_settings: { stability: 0.4, similarity_boost: 0.75 }
|
59 |
-
})
|
60 |
-
});
|
61 |
-
return await res.blob();
|
62 |
-
};
|
63 |
-
|
64 |
-
const playbackAndRecord = async () => {
|
65 |
-
setRecording(true);
|
66 |
-
const container = recordRef.current;
|
67 |
-
container.innerHTML = "";
|
68 |
-
|
69 |
-
const stream = container.captureStream(30);
|
70 |
-
const recorder = new MediaRecorder(stream);
|
71 |
-
const chunks = [];
|
72 |
-
|
73 |
-
recorder.ondataavailable = e => chunks.push(e.data);
|
74 |
-
recorder.onstop = () => {
|
75 |
-
const blob = new Blob(chunks, { type: "video/webm" });
|
76 |
-
const url = URL.createObjectURL(blob);
|
77 |
-
const a = document.createElement("a");
|
78 |
-
a.href = url;
|
79 |
-
a.download = "chat_voice_fixed.mp4";
|
80 |
-
a.click();
|
81 |
-
setRecording(false);
|
82 |
-
};
|
83 |
-
|
84 |
-
recorder.start();
|
85 |
-
|
86 |
-
for (const msg of messages) {
|
87 |
-
const char = characters.find(c => c.id === msg.charId);
|
88 |
-
|
89 |
-
const typing = document.createElement("div");
|
90 |
-
typing.className = "message typing";
|
91 |
-
typing.textContent = `${char.name} is typing...`;
|
92 |
-
container.appendChild(typing);
|
93 |
-
await new Promise(r => setTimeout(r, 1200));
|
94 |
-
container.removeChild(typing);
|
95 |
-
|
96 |
-
const msgDiv = document.createElement("div");
|
97 |
-
msgDiv.className = "message";
|
98 |
-
const avatar = document.createElement("img");
|
99 |
-
avatar.src = char.avatar;
|
100 |
-
const text = document.createElement("div");
|
101 |
-
text.className = "bubble";
|
102 |
-
text.textContent = msg.text;
|
103 |
-
msgDiv.appendChild(avatar);
|
104 |
-
msgDiv.appendChild(text);
|
105 |
-
container.appendChild(msgDiv);
|
106 |
-
|
107 |
-
const blob = await getVoiceBlob(msg.text, voices[char.voice]);
|
108 |
-
const audio = new Audio(URL.createObjectURL(blob));
|
109 |
-
await new Promise(res => {
|
110 |
-
audio.onended = res;
|
111 |
-
audio.play();
|
112 |
-
});
|
113 |
-
}
|
114 |
-
|
115 |
-
recorder.stop();
|
116 |
-
};
|
117 |
-
|
118 |
-
return h("div", { className: "app" }, [
|
119 |
-
h("h3", null, "Characters"),
|
120 |
-
...characters.map(char =>
|
121 |
-
h("div", { className: "character", key: char.id }, [
|
122 |
-
h("img", { src: char.avatar || "https://via.placeholder.com/30" }),
|
123 |
-
h("input", {
|
124 |
-
value: char.name,
|
125 |
-
onChange: (e) => handleNameChange(char.id, e.target.value)
|
126 |
-
}),
|
127 |
-
h("select", {
|
128 |
-
value: char.voice,
|
129 |
-
onChange: (e) => handleVoiceChange(char.id, e.target.value)
|
130 |
-
}, Object.keys(voices).map(v =>
|
131 |
-
h("option", { value: v, key: v }, v)
|
132 |
-
)),
|
133 |
-
h("input", {
|
134 |
-
type: "file",
|
135 |
-
accept: "image/*",
|
136 |
-
onChange: (e) => handleAvatar(char.id, e.target.files[0])
|
137 |
-
})
|
138 |
-
])
|
139 |
-
),
|
140 |
-
h("button", { onClick: addCharacter }, "+ Add Character"),
|
141 |
-
|
142 |
-
h("div", { className: "controls" }, [
|
143 |
-
h("select", {
|
144 |
-
value: selectedChar,
|
145 |
-
onChange: (e) => setSelectedChar(parseInt(e.target.value))
|
146 |
-
}, characters.map(c => h("option", { value: c.id, key: c.id }, c.name))),
|
147 |
-
h("input", {
|
148 |
-
value: input,
|
149 |
-
onChange: (e) => setInput(e.target.value),
|
150 |
-
placeholder: "Type message..."
|
151 |
-
}),
|
152 |
-
h("button", { onClick: addMessage }, "Send Message")
|
153 |
-
]),
|
154 |
-
|
155 |
-
h("div", { id: "recordArea", ref: recordRef }),
|
156 |
-
|
157 |
-
h("button", {
|
158 |
-
onClick: playbackAndRecord,
|
159 |
-
disabled: recording,
|
160 |
-
style: { marginTop: 20 }
|
161 |
-
}, recording ? "Recording..." : "📽 Export MP4 with Voice")
|
162 |
-
]);
|
163 |
};
|
164 |
|
165 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
|
2 |
+
let messages = [
|
3 |
+
{ name: "Suren", side: "left", avatar: "https://i.pravatar.cc/30?img=1", voice: "Rachel", text: "Hello!" },
|
4 |
+
{ name: "You", side: "right", avatar: "https://i.pravatar.cc/30?img=3", voice: "Adam", text: "Hey there!" }
|
5 |
+
];
|
6 |
+
|
7 |
+
const preview = document.getElementById("chatPreview");
|
8 |
+
const timeline = document.getElementById("timeline");
|
9 |
+
const styles = {
|
10 |
+
imessage: { left: "#e5e5ea", right: "#007aff" },
|
11 |
+
whatsapp: { left: "#dcf8c6", right: "#34b7f1" },
|
12 |
+
instagram: { left: "#fff0f5", right: "#ffb6c1" },
|
13 |
+
reddit: { left: "#f6f7f8", right: "#ccc" }
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
14 |
};
|
15 |
|
16 |
+
function updatePreview() {
|
17 |
+
const style = document.getElementById("styleSelect").value;
|
18 |
+
preview.innerHTML = "";
|
19 |
+
messages.forEach(msg => {
|
20 |
+
const wrap = document.createElement("div");
|
21 |
+
wrap.className = "message " + msg.side;
|
22 |
+
const avatar = document.createElement("img");
|
23 |
+
avatar.src = msg.avatar;
|
24 |
+
avatar.className = "avatar";
|
25 |
+
const bubble = document.createElement("div");
|
26 |
+
bubble.className = "bubble";
|
27 |
+
bubble.style.background = styles[style][msg.side];
|
28 |
+
bubble.textContent = msg.text;
|
29 |
+
wrap.appendChild(avatar);
|
30 |
+
wrap.appendChild(bubble);
|
31 |
+
preview.appendChild(wrap);
|
32 |
+
});
|
33 |
+
}
|
34 |
+
|
35 |
+
function renderTimeline() {
|
36 |
+
timeline.innerHTML = "";
|
37 |
+
messages.forEach((msg, i) => {
|
38 |
+
const row = document.createElement("div");
|
39 |
+
row.className = "msg-row";
|
40 |
+
row.innerHTML = `
|
41 |
+
<input type="text" value="${msg.name}" placeholder="Name" onchange="messages[${i}].name = this.value" />
|
42 |
+
<input type="text" value="${msg.avatar}" placeholder="Avatar URL" onchange="messages[${i}].avatar = this.value" />
|
43 |
+
<select onchange="messages[${i}].voice = this.value">
|
44 |
+
<option ${msg.voice==="Rachel"?"selected":""}>Rachel</option>
|
45 |
+
<option ${msg.voice==="Adam"?"selected":""}>Adam</option>
|
46 |
+
<option ${msg.voice==="Bella"?"selected":""}>Bella</option>
|
47 |
+
</select>
|
48 |
+
<select onchange="messages[${i}].side = this.value">
|
49 |
+
<option value="left" ${msg.side==="left"?"selected":""}>Left</option>
|
50 |
+
<option value="right" ${msg.side==="right"?"selected":""}>Right</option>
|
51 |
+
</select>
|
52 |
+
<input type="text" value="${msg.text}" placeholder="Message" onchange="messages[${i}].text = this.value" />
|
53 |
+
`;
|
54 |
+
timeline.appendChild(row);
|
55 |
+
});
|
56 |
+
updatePreview();
|
57 |
+
}
|
58 |
+
|
59 |
+
function addMessage() {
|
60 |
+
messages.push({ name: "New", side: "left", avatar: "https://i.pravatar.cc/30", voice: "Rachel", text: "..." });
|
61 |
+
renderTimeline();
|
62 |
+
}
|
63 |
+
|
64 |
+
function exportJSON() {
|
65 |
+
const output = JSON.stringify(messages, null, 2);
|
66 |
+
const blob = new Blob([output], { type: "application/json" });
|
67 |
+
const url = URL.createObjectURL(blob);
|
68 |
+
const a = document.createElement("a");
|
69 |
+
a.href = url;
|
70 |
+
a.download = "chat_script.json";
|
71 |
+
a.click();
|
72 |
+
}
|
73 |
+
|
74 |
+
renderTimeline();
|