aigorithm commited on
Commit
557b9b9
·
verified ·
1 Parent(s): 8a46dd4

Upload 2 files

Browse files
Files changed (2) hide show
  1. index.html +84 -84
  2. main.js +53 -78
index.html CHANGED
@@ -3,121 +3,121 @@
3
  <html lang="en">
4
  <head>
5
  <meta charset="UTF-8" />
6
- <title>Fake Chat Studio - AiCut Pro</title>
7
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
8
  <style>
9
- body {
10
  margin: 0;
 
 
 
11
  font-family: -apple-system, BlinkMacSystemFont, sans-serif;
12
- background: #f9f9f9;
13
- color: #111;
14
- display: flex;
15
- flex-direction: column;
16
- align-items: center;
17
  }
18
- h2 {
19
- margin-top: 16px;
20
- }
21
- .chat-preview {
22
  width: 360px;
23
- height: 600px;
24
- background: #f0f2f5;
25
- border-radius: 24px;
26
- box-shadow: 0 0 10px rgba(0,0,0,0.1);
27
- overflow-y: auto;
28
- padding: 10px;
29
- margin: 12px 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  }
31
  .message {
32
  display: flex;
33
- margin-bottom: 10px;
34
  align-items: flex-end;
 
 
 
 
 
 
 
 
 
 
 
 
35
  }
36
- .message.left { flex-direction: row; }
37
- .message.right { flex-direction: row-reverse; }
38
  .bubble {
39
- padding: 10px 14px;
40
- border-radius: 18px;
41
  max-width: 70%;
 
42
  font-size: 14px;
 
 
43
  }
44
- .left .bubble {
 
 
 
45
  background: #e5e5ea;
46
- color: black;
47
- }
48
- .right .bubble {
49
- background: #007aff;
50
- color: white;
 
 
51
  }
52
- .avatar {
53
- width: 28px;
54
- height: 28px;
 
55
  border-radius: 50%;
56
- margin: 0 6px;
57
  }
58
- .editor {
59
- width: 100%;
60
- max-width: 420px;
61
- display: flex;
62
- flex-direction: column;
63
- gap: 10px;
64
- padding: 10px;
65
  }
66
- .msg-editor {
67
- background: #fff;
68
- border: 1px solid #ddd;
69
- border-radius: 12px;
70
- padding: 12px;
71
- display: flex;
72
- flex-direction: column;
73
- gap: 6px;
74
- box-shadow: 0 2px 6px rgba(0,0,0,0.05);
75
  }
76
- .msg-editor input, .msg-editor select {
77
- padding: 8px;
78
- font-size: 14px;
79
- border-radius: 6px;
80
- border: 1px solid #ccc;
81
  }
82
  button {
83
- background: #007aff;
 
84
  border: none;
85
- border-radius: 6px;
86
  color: white;
87
- padding: 10px;
88
- font-size: 14px;
89
  cursor: pointer;
90
  }
91
- .controls {
92
- display: flex;
93
- flex-wrap: wrap;
94
- gap: 10px;
95
- width: 100%;
96
- max-width: 420px;
97
- justify-content: space-between;
98
- padding: 0 10px;
99
- }
100
- .controls input[type="text"] {
101
- flex: 1;
102
- }
103
  </style>
104
  </head>
105
  <body>
106
- <h2>📱 Fake Chat Studio</h2>
107
- <div class="controls">
108
- <select id="styleSelect" onchange="renderPreview()">
109
- <option value="imessage">iMessage</option>
110
- <option value="whatsapp">WhatsApp</option>
111
- <option value="instagram">Instagram</option>
112
- <option value="reddit">Reddit</option>
113
- </select>
114
- <input type="text" id="prompt" placeholder="Generate chat: funny convo about aliens">
115
- <button onclick="generateAI()">🤖 Generate</button>
116
- <button onclick="addMessage()">+ Add</button>
117
- <button onclick="renderAndExport()">🎬 Export Video</button>
118
  </div>
119
- <div class="chat-preview" id="chatPreview"></div>
120
- <div class="editor" id="editor"></div>
121
  <script type="module" src="main.js"></script>
122
  </body>
123
  </html>
 
3
  <html lang="en">
4
  <head>
5
  <meta charset="UTF-8" />
6
+ <title>Chat Animator TikTok Export</title>
7
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
8
  <style>
9
+ html, body {
10
  margin: 0;
11
+ padding: 0;
12
+ background: #000;
13
+ height: 100%;
14
  font-family: -apple-system, BlinkMacSystemFont, sans-serif;
 
 
 
 
 
15
  }
16
+ .phone-frame {
 
 
 
17
  width: 360px;
18
+ height: 640px;
19
+ margin: 0 auto;
20
+ position: relative;
21
+ overflow: hidden;
22
+ border-radius: 32px;
23
+ box-shadow: 0 0 12px rgba(0,0,0,0.6);
24
+ }
25
+ video#bg {
26
+ position: absolute;
27
+ width: 100%;
28
+ height: 100%;
29
+ object-fit: cover;
30
+ z-index: 0;
31
+ }
32
+ #chat {
33
+ position: absolute;
34
+ z-index: 1;
35
+ width: 100%;
36
+ height: 100%;
37
+ padding: 12px;
38
+ display: flex;
39
+ flex-direction: column;
40
+ justify-content: flex-end;
41
+ overflow-y: hidden;
42
  }
43
  .message {
44
  display: flex;
 
45
  align-items: flex-end;
46
+ margin-bottom: 10px;
47
+ opacity: 0;
48
+ transform: translateY(20px);
49
+ animation: fadeIn 0.6s ease forwards;
50
+ }
51
+ .left { flex-direction: row; }
52
+ .right { flex-direction: row-reverse; }
53
+ .avatar {
54
+ width: 32px;
55
+ height: 32px;
56
+ border-radius: 50%;
57
+ margin: 0 6px;
58
  }
 
 
59
  .bubble {
 
 
60
  max-width: 70%;
61
+ padding: 10px 14px;
62
  font-size: 14px;
63
+ border-radius: 18px;
64
+ background: white;
65
  }
66
+ .right .bubble { background: #007aff; color: white; }
67
+ .typing {
68
+ width: 48px;
69
+ height: 20px;
70
  background: #e5e5ea;
71
+ border-radius: 12px;
72
+ display: flex;
73
+ align-items: center;
74
+ justify-content: center;
75
+ gap: 3px;
76
+ margin-bottom: 10px;
77
+ animation: fadeIn 0.3s ease;
78
  }
79
+ .typing span {
80
+ width: 6px;
81
+ height: 6px;
82
+ background: #999;
83
  border-radius: 50%;
84
+ animation: blink 1.4s infinite;
85
  }
86
+ .typing span:nth-child(2) { animation-delay: 0.2s; }
87
+ .typing span:nth-child(3) { animation-delay: 0.4s; }
88
+
89
+ @keyframes fadeIn {
90
+ to { opacity: 1; transform: translateY(0); }
 
 
91
  }
92
+ @keyframes blink {
93
+ 0%, 80%, 100% { opacity: 0.2; }
94
+ 40% { opacity: 1; }
 
 
 
 
 
 
95
  }
96
+ #controls {
97
+ text-align: center;
98
+ margin: 10px auto;
99
+ color: white;
 
100
  }
101
  button {
102
+ padding: 10px 20px;
103
+ background: #25d366;
104
  border: none;
105
+ border-radius: 8px;
106
  color: white;
107
+ font-size: 16px;
 
108
  cursor: pointer;
109
  }
 
 
 
 
 
 
 
 
 
 
 
 
110
  </style>
111
  </head>
112
  <body>
113
+ <div class="phone-frame">
114
+ <video id="bg" muted loop></video>
115
+ <div id="chat"></div>
116
+ </div>
117
+ <div id="controls">
118
+ <input type="file" accept="video/mp4" onchange="loadBackground(event)">
119
+ <button onclick="start()">🎬 Export MP4 with Voice</button>
 
 
 
 
 
120
  </div>
 
 
121
  <script type="module" src="main.js"></script>
122
  </body>
123
  </html>
main.js CHANGED
@@ -1,112 +1,87 @@
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 editor = document.getElementById("editor");
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
  const apiKey = "sk_4e67c39c0e9cbc87462cd2643e1f4d1d9959d7d81203adc2";
16
- const voiceIdMap = { Rachel: "21m00Tcm4TlvDq8ikWAM", Adam: "pNInz6obpgDQGcFmaJgB", Bella: "EXAVITQu4vr4xnSDxMaL" };
17
-
18
- function renderPreview() {
19
- const style = document.getElementById("styleSelect").value;
20
- preview.innerHTML = "";
21
- messages.forEach(msg => {
22
- const wrap = document.createElement("div");
23
- wrap.className = "message " + msg.side;
24
- const avatar = document.createElement("img");
25
- avatar.src = msg.avatar;
26
- avatar.className = "avatar";
27
- const bubble = document.createElement("div");
28
- bubble.className = "bubble";
29
- bubble.style.background = styles[style][msg.side];
30
- bubble.textContent = msg.text;
31
- wrap.appendChild(avatar);
32
- wrap.appendChild(bubble);
33
- preview.appendChild(wrap);
34
- });
35
- }
36
 
37
- function renderEditor() {
38
- editor.innerHTML = "";
39
- messages.forEach((msg, i) => {
40
- const row = document.createElement("div");
41
- row.className = "msg-editor";
42
- row.innerHTML = `
43
- <input type="text" value="${msg.name}" placeholder="Name" onchange="messages[${i}].name=this.value">
44
- <input type="text" value="${msg.avatar}" placeholder="Avatar URL" onchange="messages[${i}].avatar=this.value">
45
- <select onchange="messages[${i}].voice=this.value">
46
- <option ${msg.voice==="Rachel"?"selected":""}>Rachel</option>
47
- <option ${msg.voice==="Adam"?"selected":""}>Adam</option>
48
- <option ${msg.voice==="Bella"?"selected":""}>Bella</option>
49
- </select>
50
- <select onchange="messages[${i}].side=this.value">
51
- <option value="left" ${msg.side==="left"?"selected":""}>Left</option>
52
- <option value="right" ${msg.side==="right"?"selected":""}>Right</option>
53
- </select>
54
- <input type="text" value="${msg.text}" placeholder="Message" onchange="messages[${i}].text=this.value">
55
- `;
56
- editor.appendChild(row);
57
- });
58
- renderPreview();
59
- }
60
-
61
- function addMessage() {
62
- messages.push({ name: "New", side: "left", avatar: "https://i.pravatar.cc/30", voice: "Rachel", text: "..." });
63
- renderEditor();
64
  }
65
 
66
  async function getVoiceBlob(text, voice) {
67
- const voiceId = voiceIdMap[voice] || voiceIdMap.Rachel;
68
  const res = await fetch("https://api.elevenlabs.io/v1/text-to-speech/" + voiceId, {
69
  method: "POST",
70
  headers: {
71
  "Content-Type": "application/json",
72
  "xi-api-key": apiKey
73
  },
74
- body: JSON.stringify({ text, model_id: "eleven_monolingual_v1", voice_settings: { stability: 0.5, similarity_boost: 0.75 } })
75
  });
76
  return await res.blob();
77
  }
78
 
79
- async function renderAndExport() {
80
- renderPreview();
81
- const stream = preview.captureStream(30);
 
82
  const recorder = new MediaRecorder(stream);
83
  const chunks = [];
 
84
  recorder.ondataavailable = e => chunks.push(e.data);
85
  recorder.onstop = () => {
86
  const blob = new Blob(chunks, { type: "video/webm" });
87
  const a = document.createElement("a");
88
  a.href = URL.createObjectURL(blob);
89
- a.download = "chat_ai_video.mp4";
90
  a.click();
91
  };
 
92
  recorder.start();
 
 
93
  for (const msg of messages) {
94
- const voice = await getVoiceBlob(msg.text, msg.voice);
95
- const audio = new Audio(URL.createObjectURL(voice));
96
- await new Promise(res => {
97
- audio.onended = res;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
  audio.play();
99
  });
100
  }
101
- recorder.stop();
102
- }
103
 
104
- async function generateAI() {
105
- const prompt = document.getElementById("prompt").value || "funny conversation between Suren and Elon Musk";
106
- const mock = [
107
- { name: "Suren", side: "left", avatar: "https://i.pravatar.cc/30?img=4", voice: "Rachel", text: "Did you launch that rocket again?" },
108
- { name: "Elon", side: "right", avatar: "https://i.pravatar.cc/30?img=8", voice: "Adam", text: "Yes... but it came back with fries." }
109
- ];
110
- messages = mock;
111
- renderEditor();
112
  }
 
1
 
2
+ const chat = document.getElementById("chat");
3
+ const bg = document.getElementById("bg");
4
+ const messages = [
5
+ { name: "Suren", side: "left", avatar: "https://i.pravatar.cc/30?img=5", voice: "Rachel", text: "Are you ready to launch?" },
6
+ { name: "Elon", side: "right", avatar: "https://i.pravatar.cc/30?img=9", voice: "Adam", text: "Always ready. 3...2...1..." },
7
+ { name: "Suren", side: "left", avatar: "https://i.pravatar.cc/30?img=5", voice: "Rachel", text: "We’re live on TikTok now!" }
8
  ];
9
+ const voiceMap = {
10
+ Rachel: "21m00Tcm4TlvDq8ikWAM",
11
+ Adam: "pNInz6obpgDQGcFmaJgB",
12
+ Bella: "EXAVITQu4vr4xnSDxMaL"
 
 
 
 
13
  };
14
  const apiKey = "sk_4e67c39c0e9cbc87462cd2643e1f4d1d9959d7d81203adc2";
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
+ function loadBackground(e) {
17
+ const file = e.target.files[0];
18
+ const url = URL.createObjectURL(file);
19
+ bg.src = url;
20
+ bg.play();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  }
22
 
23
  async function getVoiceBlob(text, voice) {
24
+ const voiceId = voiceMap[voice] || voiceMap.Rachel;
25
  const res = await fetch("https://api.elevenlabs.io/v1/text-to-speech/" + voiceId, {
26
  method: "POST",
27
  headers: {
28
  "Content-Type": "application/json",
29
  "xi-api-key": apiKey
30
  },
31
+ body: JSON.stringify({ text, model_id: "eleven_monolingual_v1", voice_settings: { stability: 0.4, similarity_boost: 0.75 } })
32
  });
33
  return await res.blob();
34
  }
35
 
36
+ async function start() {
37
+ chat.innerHTML = "";
38
+ bg.currentTime = 0;
39
+ const stream = document.querySelector(".phone-frame").captureStream(30);
40
  const recorder = new MediaRecorder(stream);
41
  const chunks = [];
42
+
43
  recorder.ondataavailable = e => chunks.push(e.data);
44
  recorder.onstop = () => {
45
  const blob = new Blob(chunks, { type: "video/webm" });
46
  const a = document.createElement("a");
47
  a.href = URL.createObjectURL(blob);
48
+ a.download = "chat_tiktok_ready.mp4";
49
  a.click();
50
  };
51
+
52
  recorder.start();
53
+ bg.play();
54
+
55
  for (const msg of messages) {
56
+ const typing = document.createElement("div");
57
+ typing.className = "typing";
58
+ typing.innerHTML = "<span></span><span></span><span></span>";
59
+ chat.appendChild(typing);
60
+ chat.scrollTop = chat.scrollHeight;
61
+ await new Promise(r => setTimeout(r, 1200));
62
+ chat.removeChild(typing);
63
+
64
+ const wrap = document.createElement("div");
65
+ wrap.className = "message " + msg.side;
66
+ const avatar = document.createElement("img");
67
+ avatar.src = msg.avatar;
68
+ avatar.className = "avatar";
69
+ const bubble = document.createElement("div");
70
+ bubble.className = "bubble";
71
+ bubble.textContent = msg.text;
72
+
73
+ wrap.appendChild(avatar);
74
+ wrap.appendChild(bubble);
75
+ chat.appendChild(wrap);
76
+ chat.scrollTop = chat.scrollHeight;
77
+
78
+ const blob = await getVoiceBlob(msg.text, msg.voice);
79
+ const audio = new Audio(URL.createObjectURL(blob));
80
+ await new Promise(r => {
81
+ audio.onended = r;
82
  audio.play();
83
  });
84
  }
 
 
85
 
86
+ recorder.stop();
 
 
 
 
 
 
 
87
  }