multimodalart HF Staff commited on
Commit
335c836
·
verified ·
1 Parent(s): e886e32

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +251 -293
index.html CHANGED
@@ -709,19 +709,17 @@
709
  const app = {
710
  mode: 'text',
711
  isGenerating: false,
712
- isConnected: false,
713
  frameCount: 0,
714
  maxFrames: 234,
715
  ws: null,
716
  frameBuffer: [],
717
  playbackInterval: null,
718
  frameExtractionInterval: null,
719
- mediaRecorder: null,
720
- recordedChunks: [],
721
- sessionStarted: false,
722
  webcamStream: null,
723
- videoMetadata: null,
724
  currentVideoFile: null,
 
 
 
725
  promptUpdateTimer: null,
726
  pendingPromptUpdate: null,
727
 
@@ -735,6 +733,9 @@
735
  document.getElementById('videoModeBtn').addEventListener('click', () => this.setMode('video'));
736
  document.getElementById('webcamModeBtn').addEventListener('click', () => this.setMode('webcam'));
737
 
 
 
 
738
  document.getElementById('playbackFps').addEventListener('input', (e) => {
739
  document.getElementById('playbackFpsValue').textContent = e.target.value + ' fps';
740
  if (this.playbackInterval) {
@@ -761,16 +762,13 @@
761
  this.queuePromptUpdate();
762
  }
763
  });
764
-
765
- // Prompt changes - queue updates every 2 seconds
766
  document.getElementById('prompt').addEventListener('input', () => {
767
  if (this.isGenerating && (this.mode === 'text' || this.mode === 'webcam')) {
768
  this.queuePromptUpdate();
769
  }
770
  });
771
-
772
- // Video file upload
773
- document.getElementById('videoFile').addEventListener('change', (e) => this.handleVideoUpload(e));
774
  },
775
 
776
  setMode(mode) {
@@ -829,29 +827,216 @@
829
  document.getElementById('maxFrames').textContent = estimatedFrames;
830
  },
831
 
832
- queuePromptUpdate() {
833
- // Store the pending update
834
- this.pendingPromptUpdate = {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
835
  prompt: document.getElementById('prompt').value,
836
- num_blocks: parseInt(document.getElementById('numBlocks').value)
 
 
 
 
837
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
838
 
839
- // Clear existing timer
840
- if (this.promptUpdateTimer) {
841
- clearTimeout(this.promptUpdateTimer);
 
 
 
 
 
 
 
 
842
  }
 
 
 
 
 
843
 
844
- // Set new timer to send update after 2 seconds
845
- this.promptUpdateTimer = setTimeout(() => {
846
- if (this.pendingPromptUpdate && this.ws && this.ws.readyState === WebSocket.OPEN) {
847
- const encoded = createMsgpackEncoder(this.pendingPromptUpdate);
848
- this.ws.send(encoded);
849
- this.maxFrames = (12 * this.pendingPromptUpdate.num_blocks) - 6;
850
- document.getElementById('maxFrames').textContent = this.maxFrames;
851
- console.log('Sent prompt update:', this.pendingPromptUpdate);
852
- this.pendingPromptUpdate = null;
853
- }
854
- }, 2000);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
855
  },
856
 
857
  async handleVideoUpload(event) {
@@ -893,10 +1078,7 @@
893
  const video = document.getElementById('hiddenVideo');
894
  const canvas = document.getElementById('extractionCanvas');
895
 
896
- if (!video || !canvas) {
897
- console.error('Video or canvas element not found');
898
- return null;
899
- }
900
 
901
  const ctx = canvas.getContext('2d');
902
  canvas.width = 832;
@@ -915,7 +1097,6 @@
915
  return new Promise((resolve) => {
916
  canvas.toBlob(async (blob) => {
917
  if (!blob) {
918
- console.error('Failed to create blob from canvas');
919
  resolve(null);
920
  return;
921
  }
@@ -924,7 +1105,7 @@
924
  const uint8Array = new Uint8Array(arrayBuffer);
925
  resolve(uint8Array);
926
  } catch (error) {
927
- console.error('Failed to convert blob to Uint8Array:', error);
928
  resolve(null);
929
  }
930
  }, 'image/jpeg', 0.9);
@@ -935,50 +1116,38 @@
935
  const video = document.getElementById('webcamVideo');
936
  const canvas = document.getElementById('extractionCanvas');
937
 
938
- if (!video || !canvas || !video.videoWidth) {
939
- console.error('Webcam not ready');
940
- return null;
941
- }
942
 
943
  const ctx = canvas.getContext('2d');
944
-
945
- // Set fixed dimensions
946
  const targetWidth = 832;
947
  const targetHeight = 480;
948
  canvas.width = targetWidth;
949
  canvas.height = targetHeight;
950
 
951
- // Calculate source rectangle to crop center 832x480 from webcam
952
  const videoWidth = video.videoWidth;
953
  const videoHeight = video.videoHeight;
954
-
955
- // Calculate crop dimensions maintaining aspect ratio
956
  const sourceAspect = videoWidth / videoHeight;
957
  const targetAspect = targetWidth / targetHeight;
958
 
959
  let sx, sy, sWidth, sHeight;
960
 
961
  if (sourceAspect > targetAspect) {
962
- // Video is wider, crop sides
963
  sHeight = videoHeight;
964
  sWidth = videoHeight * targetAspect;
965
  sx = (videoWidth - sWidth) / 2;
966
  sy = 0;
967
  } else {
968
- // Video is taller, crop top/bottom
969
  sWidth = videoWidth;
970
  sHeight = videoWidth / targetAspect;
971
  sx = 0;
972
  sy = (videoHeight - sHeight) / 2;
973
  }
974
 
975
- // Draw cropped and scaled image
976
  ctx.drawImage(video, sx, sy, sWidth, sHeight, 0, 0, targetWidth, targetHeight);
977
 
978
  return new Promise((resolve) => {
979
  canvas.toBlob(async (blob) => {
980
  if (!blob) {
981
- console.error('Failed to create blob from canvas');
982
  resolve(null);
983
  return;
984
  }
@@ -987,7 +1156,7 @@
987
  const uint8Array = new Uint8Array(arrayBuffer);
988
  resolve(uint8Array);
989
  } catch (error) {
990
- console.error('Failed to convert blob to Uint8Array:', error);
991
  resolve(null);
992
  }
993
  }, 'image/jpeg', 0.9);
@@ -1026,28 +1195,18 @@
1026
  }
1027
 
1028
  const strengthValue = parseFloat(document.getElementById('strength').value);
1029
- if (isNaN(strengthValue)) {
1030
- console.error('Invalid strength value');
1031
- return;
1032
- }
1033
-
1034
  const message = {
1035
  image: frameBytes,
1036
  strength: strengthValue,
1037
  timestamp: Date.now()
1038
  };
1039
 
1040
- // For webcam mode, include prompt and num_blocks continually
1041
  if (this.mode === 'webcam') {
1042
  message.prompt = document.getElementById('prompt').value;
1043
  message.num_blocks = parseInt(document.getElementById('numBlocks').value);
1044
  }
1045
 
1046
- if (!(message.image instanceof Uint8Array)) {
1047
- console.error('Frame bytes is not a Uint8Array');
1048
- return;
1049
- }
1050
-
1051
  const encoded = createMsgpackEncoder(message);
1052
  this.ws.send(encoded);
1053
  }, intervalMs);
@@ -1064,189 +1223,25 @@
1064
  video.currentTime = 0;
1065
  },
1066
 
1067
- randomizeSeed() {
1068
- document.getElementById('seed').value = Math.floor(Math.random() * (1 << 24));
1069
- },
1070
-
1071
- async toggleGeneration() {
1072
- if (this.isGenerating) {
1073
- this.disconnect();
1074
- } else {
1075
- // Record session start
1076
- if (!this.sessionStarted) {
1077
- try {
1078
- const response = await fetch('/api/start-session', {method: 'POST'});
1079
- if (!response.ok) {
1080
- const data = await response.json();
1081
- this.showError(data.detail || 'Failed to start session');
1082
- return;
1083
- }
1084
- this.sessionStarted = true;
1085
- } catch (error) {
1086
- this.showError('Failed to start session');
1087
- return;
1088
- }
1089
- }
1090
-
1091
- const prompt = document.getElementById('prompt').value.trim();
1092
- if (!prompt) {
1093
- this.showError('Please enter a prompt');
1094
- return;
1095
- }
1096
-
1097
- this.isGenerating = true;
1098
- this.updateUI();
1099
-
1100
- // Connect to backend WebSocket proxy (keeps API key secure)
1101
- const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
1102
- const wsUrl = `${protocol}//${window.location.host}/ws/video-gen`;
1103
-
1104
- try {
1105
- this.ws = new WebSocket(wsUrl);
1106
- this.ws.binaryType = 'arraybuffer';
1107
-
1108
- this.ws.onopen = () => {
1109
- this.showInfo('Connected! Waiting for ready signal...');
1110
- };
1111
-
1112
- this.ws.onmessage = async (event) => {
1113
- if (typeof event.data === 'string') {
1114
- try {
1115
- const data = JSON.parse(event.data);
1116
- if (data.status === 'ready') {
1117
- this.showInfo('Ready - sending parameters...');
1118
- await this.sendInitialParams();
1119
- } else if (data.error) {
1120
- this.showError(`Server error: ${JSON.stringify(data.error)}`);
1121
- this.disconnect();
1122
- }
1123
- } catch (e) {
1124
- console.log('Text message:', event.data);
1125
- }
1126
- } else if (event.data instanceof ArrayBuffer) {
1127
- await this.displayFrame(event.data);
1128
- }
1129
- };
1130
-
1131
- this.ws.onerror = (error) => {
1132
- this.showError('WebSocket connection error');
1133
- console.error('WebSocket error:', error);
1134
- };
1135
-
1136
- this.ws.onclose = (event) => {
1137
- this.showInfo(`Disconnected: ${event.reason || 'Connection closed'}`);
1138
- this.isGenerating = false;
1139
- this.updateUI();
1140
- };
1141
-
1142
- } catch (error) {
1143
- this.showError('Failed to connect: ' + error.message);
1144
- this.isGenerating = false;
1145
- this.updateUI();
1146
- }
1147
- }
1148
- },
1149
-
1150
- async sendInitialParams() {
1151
- const payload = {
1152
  prompt: document.getElementById('prompt').value,
1153
- num_blocks: parseInt(document.getElementById('numBlocks').value),
1154
- num_denoising_steps: 4,
1155
- strength: parseFloat(document.getElementById('strength').value) || 0.45,
1156
- width: 832,
1157
- height: 480
1158
  };
1159
 
1160
- // Handle start frame for video and webcam modes
1161
- if (this.mode === 'video' && this.currentVideoFile) {
1162
- try {
1163
- const video = document.getElementById('hiddenVideo');
1164
- if (!video) {
1165
- throw new Error('Video element not found');
1166
- }
1167
-
1168
- video.currentTime = 0;
1169
-
1170
- await new Promise((resolve, reject) => {
1171
- const timeout = setTimeout(() => {
1172
- reject(new Error('Video seek timeout'));
1173
- }, 5000);
1174
-
1175
- video.onseeked = () => {
1176
- clearTimeout(timeout);
1177
- resolve();
1178
- };
1179
- });
1180
-
1181
- const startFrame = await this.extractVideoFrameBytes();
1182
- if (!startFrame || !(startFrame instanceof Uint8Array)) {
1183
- throw new Error('Failed to extract valid start frame');
1184
- }
1185
-
1186
- payload.start_frame = new Uint8Array(startFrame);
1187
- console.log('Start frame extracted:', payload.start_frame.length, 'bytes');
1188
- } catch (error) {
1189
- this.showError(`Failed to extract start frame: ${error.message}`);
1190
- return false;
1191
- }
1192
- } else if (this.mode === 'webcam') {
1193
- try {
1194
- const startFrame = await this.extractWebcamBytes();
1195
- if (!startFrame || !(startFrame instanceof Uint8Array)) {
1196
- throw new Error('Failed to extract valid start frame from webcam');
1197
- }
1198
- payload.start_frame = new Uint8Array(startFrame);
1199
- console.log('Start frame extracted from webcam:', payload.start_frame.length, 'bytes');
1200
- } catch (error) {
1201
- this.showError(`Failed to extract start frame from webcam: ${error.message}`);
1202
- return false;
1203
- }
1204
- }
1205
-
1206
- const seedValue = document.getElementById('seed').value;
1207
- if (seedValue && seedValue.trim() !== '') {
1208
- payload.seed = parseInt(seedValue);
1209
- } else {
1210
- payload.seed = Math.floor(Math.random() * (1 << 24));
1211
  }
1212
 
1213
- // Clear canvas
1214
- const canvas = document.getElementById('outputCanvas');
1215
- if (canvas) {
1216
- const ctx = canvas.getContext('2d');
1217
- ctx.clearRect(0, 0, canvas.width, canvas.height);
1218
- }
1219
-
1220
- this.frameBuffer = [];
1221
- this.frameCount = 0;
1222
- document.getElementById('frameCount').textContent = '0';
1223
- this.updateProgress();
1224
-
1225
- this.startRecording();
1226
-
1227
- try {
1228
- console.log('Sending payload:', {
1229
- ...payload,
1230
- start_frame: payload.start_frame ? `[${payload.start_frame.length} bytes]` : undefined
1231
- });
1232
-
1233
- const encoded = createMsgpackEncoder(payload);
1234
- this.ws.send(encoded);
1235
- this.showInfo('Generation started!');
1236
- this.isConnected = true;
1237
- document.getElementById('statusPill').className = 'status-pill status-connected';
1238
- document.getElementById('statusPill').textContent = 'Connected';
1239
-
1240
- // Start frame extraction for video/webcam modes
1241
- if (this.mode === 'video' || this.mode === 'webcam') {
1242
- setTimeout(() => this.startFrameExtraction(), 500);
1243
  }
1244
-
1245
- return true;
1246
- } catch (error) {
1247
- this.showError('Failed to send parameters: ' + error.message);
1248
- return false;
1249
- }
1250
  },
1251
 
1252
  startRecording() {
@@ -1276,62 +1271,6 @@
1276
  }
1277
  },
1278
 
1279
- updateProgress() {
1280
- const progress = Math.min(100, (this.frameCount / this.maxFrames) * 100);
1281
- document.getElementById('progressFill').style.width = progress + '%';
1282
-
1283
- // Auto-disconnect when reaching max frames
1284
- if (this.frameCount >= this.maxFrames && this.isGenerating) {
1285
- this.showInfo(`Reached maximum frames (${this.maxFrames}). Disconnecting...`);
1286
- setTimeout(() => this.disconnect(), 1000);
1287
- }
1288
- },
1289
-
1290
- async displayFrame(imageData) {
1291
- const blob = new Blob([imageData], { type: 'image/jpeg' });
1292
- const bitmap = await createImageBitmap(blob);
1293
-
1294
- this.frameBuffer.push(bitmap);
1295
- this.frameCount++;
1296
- document.getElementById('frameCount').textContent = this.frameCount;
1297
-
1298
- if (this.frameCount === 1) {
1299
- document.getElementById('placeholder').style.display = 'none';
1300
- this.startPlaybackLoop();
1301
- }
1302
-
1303
- // Update progress
1304
- const progress = Math.min(100, (this.frameCount / this.maxFrames) * 100);
1305
- document.getElementById('progressFill').style.width = progress + '%';
1306
- },
1307
-
1308
- drawNextFrame() {
1309
- if (this.frameBuffer.length === 0) return;
1310
-
1311
- const canvas = document.getElementById('outputCanvas');
1312
- const bitmap = this.frameBuffer.shift();
1313
-
1314
- canvas.width = bitmap.width;
1315
- canvas.height = bitmap.height;
1316
-
1317
- const ctx = canvas.getContext('2d');
1318
- ctx.drawImage(bitmap, 0, 0);
1319
-
1320
- if (typeof bitmap.close === 'function') {
1321
- bitmap.close();
1322
- }
1323
- },
1324
-
1325
- startPlaybackLoop() {
1326
- if (this.playbackInterval) {
1327
- clearInterval(this.playbackInterval);
1328
- }
1329
-
1330
- const fps = parseInt(document.getElementById('playbackFps').value);
1331
- const intervalMs = Math.max(10, Math.floor(1000 / fps));
1332
- this.playbackInterval = setInterval(() => this.drawNextFrame(), intervalMs);
1333
- },
1334
-
1335
  disconnect() {
1336
  if (this.ws) {
1337
  this.ws.close();
@@ -1341,13 +1280,32 @@
1341
  clearInterval(this.playbackInterval);
1342
  this.playbackInterval = null;
1343
  }
 
 
 
 
 
 
1344
  this.stopWebcam();
 
1345
  this.isGenerating = false;
1346
  this.updateUI();
1347
  },
1348
 
1349
  downloadVideo() {
1350
- this.showInfo('Download functionality coming soon');
 
 
 
 
 
 
 
 
 
 
 
 
1351
  },
1352
 
1353
  updateUI() {
 
709
  const app = {
710
  mode: 'text',
711
  isGenerating: false,
 
712
  frameCount: 0,
713
  maxFrames: 234,
714
  ws: null,
715
  frameBuffer: [],
716
  playbackInterval: null,
717
  frameExtractionInterval: null,
 
 
 
718
  webcamStream: null,
 
719
  currentVideoFile: null,
720
+ videoMetadata: null,
721
+ mediaRecorder: null,
722
+ recordedChunks: [],
723
  promptUpdateTimer: null,
724
  pendingPromptUpdate: null,
725
 
 
733
  document.getElementById('videoModeBtn').addEventListener('click', () => this.setMode('video'));
734
  document.getElementById('webcamModeBtn').addEventListener('click', () => this.setMode('webcam'));
735
 
736
+ // Video file upload
737
+ document.getElementById('videoFile').addEventListener('change', (e) => this.handleVideoUpload(e));
738
+
739
  document.getElementById('playbackFps').addEventListener('input', (e) => {
740
  document.getElementById('playbackFpsValue').textContent = e.target.value + ' fps';
741
  if (this.playbackInterval) {
 
762
  this.queuePromptUpdate();
763
  }
764
  });
765
+
766
+ // Prompt changes for live updates
767
  document.getElementById('prompt').addEventListener('input', () => {
768
  if (this.isGenerating && (this.mode === 'text' || this.mode === 'webcam')) {
769
  this.queuePromptUpdate();
770
  }
771
  });
 
 
 
772
  },
773
 
774
  setMode(mode) {
 
827
  document.getElementById('maxFrames').textContent = estimatedFrames;
828
  },
829
 
830
+ randomizeSeed() {
831
+ document.getElementById('seed').value = Math.floor(Math.random() * (1 << 24));
832
+ },
833
+
834
+ async toggleGeneration() {
835
+ if (this.isGenerating) {
836
+ this.disconnect();
837
+ } else {
838
+ // ✅ Record session EVERY time user clicks "Start Generation"
839
+ try {
840
+ const response = await fetch('/api/start-session', {method: 'POST'});
841
+ if (!response.ok) {
842
+ const data = await response.json();
843
+ this.showError(data.detail || 'Failed to start session');
844
+ return;
845
+ }
846
+ // Update session count in UI
847
+ const sessionData = await response.json();
848
+ const usedSpan = document.querySelector('.usage-info');
849
+ if (usedSpan) {
850
+ usedSpan.textContent = `Sessions: ${sessionData.sessions_used}/${sessionData.sessions_limit} today`;
851
+ }
852
+ } catch (error) {
853
+ this.showError('Failed to start session');
854
+ return;
855
+ }
856
+
857
+ const prompt = document.getElementById('prompt').value.trim();
858
+ if (!prompt) {
859
+ this.showError('Please enter a prompt');
860
+ return;
861
+ }
862
+
863
+ if (this.mode === 'video' && !this.currentVideoFile) {
864
+ this.showError('Please upload a video file');
865
+ return;
866
+ }
867
+
868
+ if (this.mode === 'webcam' && !this.webcamStream) {
869
+ this.showError('Webcam not started');
870
+ return;
871
+ }
872
+
873
+ this.isGenerating = true;
874
+ this.frameCount = 0;
875
+ document.getElementById('frameCount').textContent = '0';
876
+ this.frameBuffer = [];
877
+ this.updateUI();
878
+
879
+ // Start recording
880
+ this.startRecording();
881
+
882
+ // Connect to backend WebSocket proxy
883
+ const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
884
+ const wsUrl = `${protocol}//${window.location.host}/ws/video-gen`;
885
+
886
+ try {
887
+ this.ws = new WebSocket(wsUrl);
888
+ this.ws.binaryType = 'arraybuffer';
889
+
890
+ this.ws.onopen = () => {
891
+ this.showInfo('Connected! Waiting for ready signal...');
892
+ };
893
+
894
+ this.ws.onmessage = async (event) => {
895
+ if (typeof event.data === 'string') {
896
+ try {
897
+ const data = JSON.parse(event.data);
898
+ if (data.status === 'ready') {
899
+ this.showInfo('Ready - sending parameters...');
900
+ await this.sendInitialParams();
901
+ } else if (data.error) {
902
+ this.showError(`Server error: ${JSON.stringify(data.error)}`);
903
+ this.disconnect();
904
+ }
905
+ } catch (e) {
906
+ console.log('Text message:', event.data);
907
+ }
908
+ } else if (event.data instanceof ArrayBuffer) {
909
+ await this.displayFrame(event.data);
910
+ }
911
+ };
912
+
913
+ this.ws.onerror = (error) => {
914
+ this.showError('WebSocket connection error');
915
+ console.error('WebSocket error:', error);
916
+ };
917
+
918
+ this.ws.onclose = (event) => {
919
+ this.showInfo(`Disconnected: ${event.reason || 'Connection closed'}`);
920
+ this.isGenerating = false;
921
+ this.stopFrameExtraction();
922
+ this.stopRecording();
923
+ this.updateUI();
924
+ };
925
+
926
+ } catch (error) {
927
+ this.showError('Failed to connect: ' + error.message);
928
+ this.isGenerating = false;
929
+ this.updateUI();
930
+ }
931
+ }
932
+ },
933
+
934
+ async sendInitialParams() {
935
+ const payload = {
936
  prompt: document.getElementById('prompt').value,
937
+ num_blocks: parseInt(document.getElementById('numBlocks').value),
938
+ num_denoising_steps: 4,
939
+ strength: parseFloat(document.getElementById('strength').value) || 0.45,
940
+ width: 832,
941
+ height: 480
942
  };
943
+
944
+ // Add start_frame for video and webcam modes
945
+ if (this.mode === 'video' && this.currentVideoFile) {
946
+ try {
947
+ const video = document.getElementById('hiddenVideo');
948
+ video.currentTime = 0;
949
+ await new Promise((resolve, reject) => {
950
+ const timeout = setTimeout(() => reject(new Error('Video seek timeout')), 5000);
951
+ video.onseeked = () => {
952
+ clearTimeout(timeout);
953
+ resolve();
954
+ };
955
+ });
956
+ const startFrame = await this.extractVideoFrameBytes();
957
+ if (!startFrame) throw new Error('Failed to extract start frame');
958
+ payload.start_frame = startFrame;
959
+ } catch (error) {
960
+ this.showError(`Failed to extract start frame: ${error.message}`);
961
+ this.disconnect();
962
+ return;
963
+ }
964
+ } else if (this.mode === 'webcam') {
965
+ try {
966
+ const startFrame = await this.extractWebcamBytes();
967
+ if (!startFrame) throw new Error('Failed to extract webcam frame');
968
+ payload.start_frame = startFrame;
969
+ } catch (error) {
970
+ this.showError(`Failed to extract webcam frame: ${error.message}`);
971
+ this.disconnect();
972
+ return;
973
+ }
974
+ }
975
+
976
+ const seedValue = document.getElementById('seed').value;
977
+ if (seedValue && seedValue.trim() !== '') {
978
+ payload.seed = parseInt(seedValue);
979
+ } else {
980
+ payload.seed = Math.floor(Math.random() * (1 << 24));
981
+ }
982
+
983
+ try {
984
+ const encoded = createMsgpackEncoder(payload);
985
+ this.ws.send(encoded);
986
+ this.showInfo('Generation started!');
987
+
988
+ // Start frame extraction for v2v and webcam modes
989
+ if (this.mode === 'video' || this.mode === 'webcam') {
990
+ setTimeout(() => this.startFrameExtraction(), 500);
991
+ }
992
+ } catch (error) {
993
+ this.showError('Failed to send parameters: ' + error.message);
994
+ }
995
+ },
996
 
997
+ async displayFrame(imageData) {
998
+ const blob = new Blob([imageData], { type: 'image/jpeg' });
999
+ const bitmap = await createImageBitmap(blob);
1000
+
1001
+ this.frameBuffer.push(bitmap);
1002
+ this.frameCount++;
1003
+ document.getElementById('frameCount').textContent = this.frameCount;
1004
+
1005
+ if (this.frameCount === 1) {
1006
+ document.getElementById('placeholder').style.display = 'none';
1007
+ this.startPlaybackLoop();
1008
  }
1009
+
1010
+ // Update progress
1011
+ const progress = Math.min(100, (this.frameCount / this.maxFrames) * 100);
1012
+ document.getElementById('progressFill').style.width = progress + '%';
1013
+ },
1014
 
1015
+ drawNextFrame() {
1016
+ if (this.frameBuffer.length === 0) return;
1017
+
1018
+ const canvas = document.getElementById('outputCanvas');
1019
+ const bitmap = this.frameBuffer.shift();
1020
+
1021
+ canvas.width = bitmap.width;
1022
+ canvas.height = bitmap.height;
1023
+
1024
+ const ctx = canvas.getContext('2d');
1025
+ ctx.drawImage(bitmap, 0, 0);
1026
+
1027
+ if (typeof bitmap.close === 'function') {
1028
+ bitmap.close();
1029
+ }
1030
+ },
1031
+
1032
+ startPlaybackLoop() {
1033
+ if (this.playbackInterval) {
1034
+ clearInterval(this.playbackInterval);
1035
+ }
1036
+
1037
+ const fps = parseInt(document.getElementById('playbackFps').value);
1038
+ const intervalMs = Math.max(10, Math.floor(1000 / fps));
1039
+ this.playbackInterval = setInterval(() => this.drawNextFrame(), intervalMs);
1040
  },
1041
 
1042
  async handleVideoUpload(event) {
 
1078
  const video = document.getElementById('hiddenVideo');
1079
  const canvas = document.getElementById('extractionCanvas');
1080
 
1081
+ if (!video || !canvas) return null;
 
 
 
1082
 
1083
  const ctx = canvas.getContext('2d');
1084
  canvas.width = 832;
 
1097
  return new Promise((resolve) => {
1098
  canvas.toBlob(async (blob) => {
1099
  if (!blob) {
 
1100
  resolve(null);
1101
  return;
1102
  }
 
1105
  const uint8Array = new Uint8Array(arrayBuffer);
1106
  resolve(uint8Array);
1107
  } catch (error) {
1108
+ console.error('Failed to convert blob:', error);
1109
  resolve(null);
1110
  }
1111
  }, 'image/jpeg', 0.9);
 
1116
  const video = document.getElementById('webcamVideo');
1117
  const canvas = document.getElementById('extractionCanvas');
1118
 
1119
+ if (!video || !canvas || !video.videoWidth) return null;
 
 
 
1120
 
1121
  const ctx = canvas.getContext('2d');
 
 
1122
  const targetWidth = 832;
1123
  const targetHeight = 480;
1124
  canvas.width = targetWidth;
1125
  canvas.height = targetHeight;
1126
 
 
1127
  const videoWidth = video.videoWidth;
1128
  const videoHeight = video.videoHeight;
 
 
1129
  const sourceAspect = videoWidth / videoHeight;
1130
  const targetAspect = targetWidth / targetHeight;
1131
 
1132
  let sx, sy, sWidth, sHeight;
1133
 
1134
  if (sourceAspect > targetAspect) {
 
1135
  sHeight = videoHeight;
1136
  sWidth = videoHeight * targetAspect;
1137
  sx = (videoWidth - sWidth) / 2;
1138
  sy = 0;
1139
  } else {
 
1140
  sWidth = videoWidth;
1141
  sHeight = videoWidth / targetAspect;
1142
  sx = 0;
1143
  sy = (videoHeight - sHeight) / 2;
1144
  }
1145
 
 
1146
  ctx.drawImage(video, sx, sy, sWidth, sHeight, 0, 0, targetWidth, targetHeight);
1147
 
1148
  return new Promise((resolve) => {
1149
  canvas.toBlob(async (blob) => {
1150
  if (!blob) {
 
1151
  resolve(null);
1152
  return;
1153
  }
 
1156
  const uint8Array = new Uint8Array(arrayBuffer);
1157
  resolve(uint8Array);
1158
  } catch (error) {
1159
+ console.error('Failed to convert blob:', error);
1160
  resolve(null);
1161
  }
1162
  }, 'image/jpeg', 0.9);
 
1195
  }
1196
 
1197
  const strengthValue = parseFloat(document.getElementById('strength').value);
 
 
 
 
 
1198
  const message = {
1199
  image: frameBytes,
1200
  strength: strengthValue,
1201
  timestamp: Date.now()
1202
  };
1203
 
1204
+ // For webcam mode, include prompt and num_blocks
1205
  if (this.mode === 'webcam') {
1206
  message.prompt = document.getElementById('prompt').value;
1207
  message.num_blocks = parseInt(document.getElementById('numBlocks').value);
1208
  }
1209
 
 
 
 
 
 
1210
  const encoded = createMsgpackEncoder(message);
1211
  this.ws.send(encoded);
1212
  }, intervalMs);
 
1223
  video.currentTime = 0;
1224
  },
1225
 
1226
+ queuePromptUpdate() {
1227
+ this.pendingPromptUpdate = {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1228
  prompt: document.getElementById('prompt').value,
1229
+ num_blocks: parseInt(document.getElementById('numBlocks').value)
 
 
 
 
1230
  };
1231
 
1232
+ if (this.promptUpdateTimer) {
1233
+ clearTimeout(this.promptUpdateTimer);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1234
  }
1235
 
1236
+ this.promptUpdateTimer = setTimeout(() => {
1237
+ if (this.pendingPromptUpdate && this.ws?.readyState === WebSocket.OPEN) {
1238
+ const encoded = createMsgpackEncoder(this.pendingPromptUpdate);
1239
+ this.ws.send(encoded);
1240
+ this.maxFrames = (12 * this.pendingPromptUpdate.num_blocks) - 6;
1241
+ document.getElementById('maxFrames').textContent = this.maxFrames;
1242
+ this.pendingPromptUpdate = null;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1243
  }
1244
+ }, 2000);
 
 
 
 
 
1245
  },
1246
 
1247
  startRecording() {
 
1271
  }
1272
  },
1273
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1274
  disconnect() {
1275
  if (this.ws) {
1276
  this.ws.close();
 
1280
  clearInterval(this.playbackInterval);
1281
  this.playbackInterval = null;
1282
  }
1283
+ if (this.promptUpdateTimer) {
1284
+ clearTimeout(this.promptUpdateTimer);
1285
+ this.promptUpdateTimer = null;
1286
+ }
1287
+ this.pendingPromptUpdate = null;
1288
+ this.stopFrameExtraction();
1289
  this.stopWebcam();
1290
+ this.stopRecording();
1291
  this.isGenerating = false;
1292
  this.updateUI();
1293
  },
1294
 
1295
  downloadVideo() {
1296
+ if (this.recordedChunks.length === 0) {
1297
+ this.showError('No video data to download');
1298
+ return;
1299
+ }
1300
+
1301
+ const blob = new Blob(this.recordedChunks, { type: 'video/webm' });
1302
+ const url = URL.createObjectURL(blob);
1303
+ const a = document.createElement('a');
1304
+ a.href = url;
1305
+ a.download = `generated-video-${Date.now()}.webm`;
1306
+ a.click();
1307
+ URL.revokeObjectURL(url);
1308
+ this.showInfo('Video downloaded successfully');
1309
  },
1310
 
1311
  updateUI() {