aigorithm commited on
Commit
6ef077a
·
verified ·
1 Parent(s): fc11a91

Upload 2 files

Browse files
Files changed (2) hide show
  1. index.html +84 -39
  2. main.js +67 -164
index.html CHANGED
@@ -2,70 +2,115 @@
2
  <!DOCTYPE html>
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 Final</title>
8
  <style>
9
- html, body {
10
  margin: 0;
11
  background: #000;
12
- height: 100%;
 
 
 
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: 640px;
22
- margin: auto;
 
 
 
 
 
 
23
  padding: 10px;
24
- font-family: sans-serif;
25
- box-sizing: border-box;
 
26
  }
27
- .character, .message {
 
 
28
  display: flex;
29
- align-items: center;
30
- gap: 8px;
31
- margin-bottom: 10px;
32
  }
33
- .character img, .message img {
34
- width: 30px;
35
- height: 30px;
36
- border-radius: 50%;
37
  }
38
  .bubble {
39
  padding: 10px 14px;
40
- border-radius: 20px;
41
  max-width: 70%;
42
- background: #fff;
 
 
43
  opacity: 0;
44
- animation: fadeIn 1s forwards;
45
  }
46
- .controls, .chat {
47
- width: 100%;
48
- margin-top: 10px;
 
 
49
  }
50
- input, select, button {
51
- padding: 6px;
52
- margin: 4px 0;
53
- width: 100%;
54
  }
55
- @keyframes fadeIn {
56
- to {
57
- opacity: 1;
58
- }
59
  }
60
- .typing {
61
- font-style: italic;
62
- opacity: 0.6;
63
- animation: fadeIn 0.8s forwards;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  }
65
  </style>
66
  </head>
67
  <body>
68
- <div id="root"></div>
 
 
 
 
 
 
 
 
 
 
 
69
  <script type="module" src="main.js"></script>
70
  </body>
71
  </html>
 
2
  <!DOCTYPE html>
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>AiCut Pro Replica</title>
8
  <style>
9
+ body {
10
  margin: 0;
11
  background: #000;
12
+ font-family: -apple-system, BlinkMacSystemFont, sans-serif;
13
+ display: flex;
14
+ flex-direction: column;
15
+ align-items: center;
16
  }
17
+ .container {
18
  display: flex;
19
  flex-direction: column;
20
  align-items: center;
21
+ padding: 16px;
22
+ color: white;
23
+ }
24
+ .phone {
25
+ width: 360px;
26
+ height: 720px;
27
  background: #f0f2f5;
28
+ border-radius: 40px;
29
+ overflow: hidden;
30
+ box-shadow: 0 0 12px rgba(0,0,0,0.25);
31
+ display: flex;
32
+ flex-direction: column;
33
+ margin-top: 10px;
34
+ }
35
+ .topbar {
36
+ background: #fff;
37
+ text-align: center;
38
  padding: 10px;
39
+ font-weight: bold;
40
+ font-size: 16px;
41
+ border-bottom: 1px solid #ddd;
42
  }
43
+ .chat {
44
+ flex: 1;
45
+ padding: 10px;
46
  display: flex;
47
+ flex-direction: column;
48
+ gap: 10px;
49
+ overflow-y: auto;
50
  }
51
+ .message {
52
+ display: flex;
53
+ align-items: flex-end;
54
+ gap: 8px;
55
  }
56
  .bubble {
57
  padding: 10px 14px;
58
+ border-radius: 18px;
59
  max-width: 70%;
60
+ font-size: 14px;
61
+ line-height: 1.4;
62
+ animation: fadeSlideUp 0.4s ease forwards;
63
  opacity: 0;
 
64
  }
65
+ .left {
66
+ flex-direction: row;
67
+ }
68
+ .right {
69
+ flex-direction: row-reverse;
70
  }
71
+ .left .bubble {
72
+ background: #e5e5ea;
73
+ color: black;
 
74
  }
75
+ .right .bubble {
76
+ background: #007aff;
77
+ color: white;
 
78
  }
79
+ .avatar {
80
+ width: 28px;
81
+ height: 28px;
82
+ border-radius: 50%;
83
+ }
84
+ @keyframes fadeSlideUp {
85
+ from { opacity: 0; transform: translateY(20px); }
86
+ to { opacity: 1; transform: translateY(0); }
87
+ }
88
+ textarea {
89
+ width: 90%;
90
+ height: 100px;
91
+ margin-top: 10px;
92
+ font-size: 14px;
93
+ font-family: monospace;
94
+ }
95
+ button {
96
+ padding: 8px 16px;
97
+ margin-top: 10px;
98
  }
99
  </style>
100
  </head>
101
  <body>
102
+ <div class="container">
103
+ <h2>AiCut Pro Chat Generator</h2>
104
+ <textarea id="scriptInput">[
105
+ { "side": "left", "text": "Hey, are you there?" },
106
+ { "side": "right", "text": "Yeah, just got here." }
107
+ ]</textarea>
108
+ <button onclick="startPlayback()">🎬 Export Video with Voice</button>
109
+ <div class="phone">
110
+ <div class="topbar">Messages</div>
111
+ <div id="chat" class="chat"></div>
112
+ </div>
113
+ </div>
114
  <script type="module" src="main.js"></script>
115
  </body>
116
  </html>
main.js CHANGED
@@ -1,175 +1,78 @@
1
 
2
- import { createElement as h, useState, useRef } from "https://esm.sh/react";
3
- import { createRoot } from "https://esm.sh/react-dom/client";
4
-
5
- const voices = {
6
- Rachel: "21m00Tcm4TlvDq8ikWAM",
7
- Adam: "pNInz6obpgDQGcFmaJgB",
8
- Bella: "EXAVITQu4vr4xnSDxMaL"
9
- };
10
- const defaultVoice = "Rachel";
11
  const apiKey = "sk_4e67c39c0e9cbc87462cd2643e1f4d1d9959d7d81203adc2";
12
 
13
- const App = () => {
14
- const [characters, setCharacters] = useState([
15
- { id: 0, name: "User", avatar: "https://i.pravatar.cc/30?img=3", voice: "Rachel" },
16
- { id: 1, name: "Bot", avatar: "https://i.pravatar.cc/30?img=7", voice: "Adam" }
17
- ]);
18
- const [selectedChar, setSelectedChar] = useState(0);
19
- const [messages, setMessages] = useState([]);
20
- const [input, setInput] = useState("");
21
- const [recording, setRecording] = useState(false);
22
- const chatRef = useRef(null);
23
-
24
- const addCharacter = () => {
25
- const id = characters.length;
26
- setCharacters([...characters, { id, name: "New", avatar: "", voice: defaultVoice }]);
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 chat = document.getElementById("chatArea");
67
- const clone = chat.cloneNode(false);
68
- chat.parentNode.appendChild(clone);
69
- const stream = clone.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_typing.mp4";
80
- a.click();
81
- clone.remove();
82
- setRecording(false);
83
- };
84
 
85
- recorder.start();
 
 
 
86
 
87
- for (const msg of messages) {
88
- const char = characters.find(c => c.id === msg.charId);
89
-
90
- // Typing animation
91
- const typingDiv = document.createElement("div");
92
- typingDiv.className = "message typing";
93
- typingDiv.textContent = `${char.name} is typing...`;
94
- clone.appendChild(typingDiv);
95
- await new Promise(r => setTimeout(r, 1200));
96
- typingDiv.remove();
97
-
98
- // Real message
99
- const msgDiv = document.createElement("div");
100
- msgDiv.className = "message";
101
- const avatar = document.createElement("img");
102
- avatar.src = char.avatar;
103
- const text = document.createElement("div");
104
- text.className = "bubble";
105
- text.textContent = msg.text;
106
- msgDiv.appendChild(avatar);
107
- msgDiv.appendChild(text);
108
- clone.appendChild(msgDiv);
109
-
110
- // Voice playback
111
- const voiceBlob = await getVoiceBlob(msg.text, voices[char.voice]);
112
- const audio = new Audio(URL.createObjectURL(voiceBlob));
113
- await new Promise(res => {
114
- audio.onended = res;
115
- audio.play();
116
- });
117
- }
118
-
119
- recorder.stop();
120
- };
121
-
122
- return h("div", { className: "app" }, [
123
- h("h3", null, "Characters"),
124
- ...characters.map(char =>
125
- h("div", { className: "character", key: char.id }, [
126
- h("img", { src: char.avatar || "https://via.placeholder.com/30" }),
127
- h("input", {
128
- value: char.name,
129
- onChange: (e) => handleNameChange(char.id, e.target.value)
130
- }),
131
- h("select", {
132
- value: char.voice,
133
- onChange: (e) => handleVoiceChange(char.id, e.target.value)
134
- }, Object.keys(voices).map(v =>
135
- h("option", { value: v, key: v }, v)
136
- )),
137
- h("input", {
138
- type: "file",
139
- accept: "image/*",
140
- onChange: (e) => handleAvatar(char.id, e.target.files[0])
141
- })
142
- ])
143
- ),
144
- h("button", { onClick: addCharacter }, "+ Add Character"),
145
-
146
- h("div", { className: "controls" }, [
147
- h("select", {
148
- value: selectedChar,
149
- onChange: (e) => setSelectedChar(parseInt(e.target.value))
150
- }, characters.map(c => h("option", { value: c.id, key: c.id }, c.name))),
151
- h("input", {
152
- value: input,
153
- onChange: (e) => setInput(e.target.value),
154
- placeholder: "Type message..."
155
- }),
156
- h("button", { onClick: addMessage }, "Send Message")
157
- ]),
158
-
159
- h("div", { id: "chatArea", className: "chat", ref: chatRef }, messages.map((msg, i) => {
160
- const char = characters.find(c => c.id === msg.charId);
161
- return h("div", { className: "message", key: i }, [
162
- h("img", { src: char?.avatar }),
163
- h("div", { className: "bubble" }, msg.text)
164
- ]);
165
- })),
166
-
167
- h("button", {
168
- onClick: playbackAndRecord,
169
- disabled: recording,
170
- style: { marginTop: 20 }
171
- }, recording ? "Recording..." : "📱 Export TikTok-Style MP4")
172
- ]);
173
- };
174
 
175
- createRoot(document.getElementById("root")).render(h(App));
 
 
1
 
2
+ const chat = document.getElementById("chat");
3
+ const scriptInput = document.getElementById("scriptInput");
4
+ const voiceId = "21m00Tcm4TlvDq8ikWAM"; // Rachel
 
 
 
 
 
 
5
  const apiKey = "sk_4e67c39c0e9cbc87462cd2643e1f4d1d9959d7d81203adc2";
6
 
7
+ async function getVoiceBlob(text) {
8
+ const res = await fetch("https://api.elevenlabs.io/v1/text-to-speech/" + voiceId, {
9
+ method: "POST",
10
+ headers: {
11
+ "Content-Type": "application/json",
12
+ "xi-api-key": apiKey
13
+ },
14
+ body: JSON.stringify({
15
+ text,
16
+ model_id: "eleven_monolingual_v1",
17
+ voice_settings: { stability: 0.5, similarity_boost: 0.75 }
18
+ })
19
+ });
20
+ return await res.blob();
21
+ }
22
+
23
+ async function startPlayback() {
24
+ chat.innerHTML = "";
25
+ let messages;
26
+ try {
27
+ messages = JSON.parse(scriptInput.value);
28
+ } catch (e) {
29
+ alert("Invalid script format");
30
+ return;
31
+ }
32
+
33
+ const phone = document.querySelector(".phone");
34
+ const stream = phone.captureStream(30);
35
+ const recorder = new MediaRecorder(stream);
36
+ const chunks = [];
37
+
38
+ recorder.ondataavailable = e => chunks.push(e.data);
39
+ recorder.onstop = () => {
40
+ const blob = new Blob(chunks, { type: "video/webm" });
41
+ const url = URL.createObjectURL(blob);
42
+ const a = document.createElement("a");
43
+ a.href = url;
44
+ a.download = "chat_aicutpro_ui.mp4";
45
+ a.click();
46
  };
47
 
48
+ recorder.start();
 
 
49
 
50
+ for (const msg of messages) {
51
+ const wrapper = document.createElement("div");
52
+ wrapper.className = "message " + (msg.side || "left");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
 
54
+ const avatar = document.createElement("img");
55
+ avatar.src = msg.side === "right"
56
+ ? "https://i.pravatar.cc/30?img=3"
57
+ : "https://i.pravatar.cc/30?img=7";
58
+ avatar.className = "avatar";
 
 
 
59
 
60
+ const bubble = document.createElement("div");
61
+ bubble.className = "bubble";
62
+ bubble.textContent = msg.text;
 
 
 
 
 
 
 
 
63
 
64
+ wrapper.appendChild(avatar);
65
+ wrapper.appendChild(bubble);
66
+ chat.appendChild(wrapper);
67
+ chat.scrollTop = chat.scrollHeight;
68
 
69
+ const voice = await getVoiceBlob(msg.text);
70
+ const audio = new Audio(URL.createObjectURL(voice));
71
+ await new Promise(res => {
72
+ audio.onended = res;
73
+ audio.play();
74
+ });
75
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
 
77
+ recorder.stop();
78
+ }