Spaces:
Running
on
A100
Running
on
A100
format
Browse files- frontend/src/lib/lcmLive.ts +82 -84
- frontend/src/lib/mediaStream.ts +102 -104
- frontend/src/lib/store.ts +12 -13
- frontend/src/lib/types.ts +28 -29
- frontend/src/lib/utils.ts +42 -36
- frontend/src/routes/+page.ts +1 -1
- frontend/vite.config.ts +2 -2
frontend/src/lib/lcmLive.ts
CHANGED
@@ -1,12 +1,11 @@
|
|
1 |
import { writable } from 'svelte/store';
|
2 |
|
3 |
-
|
4 |
export enum LCMLiveStatus {
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
}
|
11 |
|
12 |
const initStatus: LCMLiveStatus = LCMLiveStatus.DISCONNECTED;
|
@@ -16,84 +15,83 @@ export const streamId = writable<string | null>(null);
|
|
16 |
|
17 |
let websocket: WebSocket | null = null;
|
18 |
export const lcmLiveActions = {
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
websocket = new WebSocket(websocketURL);
|
28 |
-
websocket.onopen = () => {
|
29 |
-
console.log("Connected to websocket");
|
30 |
-
};
|
31 |
-
websocket.onclose = () => {
|
32 |
-
lcmLiveStatus.set(LCMLiveStatus.DISCONNECTED);
|
33 |
-
console.log("Disconnected from websocket");
|
34 |
-
};
|
35 |
-
websocket.onerror = (err) => {
|
36 |
-
console.error(err);
|
37 |
-
};
|
38 |
-
websocket.onmessage = (event) => {
|
39 |
-
const data = JSON.parse(event.data);
|
40 |
-
switch (data.status) {
|
41 |
-
case "connected":
|
42 |
-
lcmLiveStatus.set(LCMLiveStatus.CONNECTED);
|
43 |
-
streamId.set(userId);
|
44 |
-
resolve({ status: "connected", userId });
|
45 |
-
break;
|
46 |
-
case "send_frame":
|
47 |
-
lcmLiveStatus.set(LCMLiveStatus.SEND_FRAME);
|
48 |
-
const streamData = getSreamdata();
|
49 |
-
websocket?.send(JSON.stringify({ status: "next_frame" }));
|
50 |
-
for (const d of streamData) {
|
51 |
-
this.send(d);
|
52 |
-
}
|
53 |
-
break;
|
54 |
-
case "wait":
|
55 |
-
lcmLiveStatus.set(LCMLiveStatus.WAIT);
|
56 |
-
break;
|
57 |
-
case "timeout":
|
58 |
-
console.log("timeout");
|
59 |
-
lcmLiveStatus.set(LCMLiveStatus.TIMEOUT);
|
60 |
-
streamId.set(null);
|
61 |
-
reject(new Error("timeout"));
|
62 |
-
break;
|
63 |
-
case "error":
|
64 |
-
console.log(data.message);
|
65 |
-
lcmLiveStatus.set(LCMLiveStatus.DISCONNECTED);
|
66 |
-
streamId.set(null);
|
67 |
-
reject(new Error(data.message));
|
68 |
-
break;
|
69 |
-
}
|
70 |
-
};
|
71 |
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
92 |
lcmLiveStatus.set(LCMLiveStatus.DISCONNECTED);
|
93 |
-
if (websocket) {
|
94 |
-
websocket.close();
|
95 |
-
}
|
96 |
-
websocket = null;
|
97 |
streamId.set(null);
|
98 |
-
|
99 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
import { writable } from 'svelte/store';
|
2 |
|
|
|
3 |
export enum LCMLiveStatus {
|
4 |
+
CONNECTED = 'connected',
|
5 |
+
DISCONNECTED = 'disconnected',
|
6 |
+
WAIT = 'wait',
|
7 |
+
SEND_FRAME = 'send_frame',
|
8 |
+
TIMEOUT = 'timeout'
|
9 |
}
|
10 |
|
11 |
const initStatus: LCMLiveStatus = LCMLiveStatus.DISCONNECTED;
|
|
|
15 |
|
16 |
let websocket: WebSocket | null = null;
|
17 |
export const lcmLiveActions = {
|
18 |
+
async start(getSreamdata: () => any[]) {
|
19 |
+
return new Promise((resolve, reject) => {
|
20 |
+
try {
|
21 |
+
const userId = crypto.randomUUID();
|
22 |
+
const websocketURL = `${
|
23 |
+
window.location.protocol === 'https:' ? 'wss' : 'ws'
|
24 |
+
}:${window.location.host}/api/ws/${userId}`;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
25 |
|
26 |
+
websocket = new WebSocket(websocketURL);
|
27 |
+
websocket.onopen = () => {
|
28 |
+
console.log('Connected to websocket');
|
29 |
+
};
|
30 |
+
websocket.onclose = () => {
|
31 |
+
lcmLiveStatus.set(LCMLiveStatus.DISCONNECTED);
|
32 |
+
console.log('Disconnected from websocket');
|
33 |
+
};
|
34 |
+
websocket.onerror = (err) => {
|
35 |
+
console.error(err);
|
36 |
+
};
|
37 |
+
websocket.onmessage = (event) => {
|
38 |
+
const data = JSON.parse(event.data);
|
39 |
+
switch (data.status) {
|
40 |
+
case 'connected':
|
41 |
+
lcmLiveStatus.set(LCMLiveStatus.CONNECTED);
|
42 |
+
streamId.set(userId);
|
43 |
+
resolve({ status: 'connected', userId });
|
44 |
+
break;
|
45 |
+
case 'send_frame':
|
46 |
+
lcmLiveStatus.set(LCMLiveStatus.SEND_FRAME);
|
47 |
+
const streamData = getSreamdata();
|
48 |
+
websocket?.send(JSON.stringify({ status: 'next_frame' }));
|
49 |
+
for (const d of streamData) {
|
50 |
+
this.send(d);
|
51 |
+
}
|
52 |
+
break;
|
53 |
+
case 'wait':
|
54 |
+
lcmLiveStatus.set(LCMLiveStatus.WAIT);
|
55 |
+
break;
|
56 |
+
case 'timeout':
|
57 |
+
console.log('timeout');
|
58 |
+
lcmLiveStatus.set(LCMLiveStatus.TIMEOUT);
|
59 |
+
streamId.set(null);
|
60 |
+
reject(new Error('timeout'));
|
61 |
+
break;
|
62 |
+
case 'error':
|
63 |
+
console.log(data.message);
|
64 |
+
lcmLiveStatus.set(LCMLiveStatus.DISCONNECTED);
|
65 |
+
streamId.set(null);
|
66 |
+
reject(new Error(data.message));
|
67 |
+
break;
|
68 |
+
}
|
69 |
+
};
|
70 |
+
} catch (err) {
|
71 |
+
console.error(err);
|
72 |
lcmLiveStatus.set(LCMLiveStatus.DISCONNECTED);
|
|
|
|
|
|
|
|
|
73 |
streamId.set(null);
|
74 |
+
reject(err);
|
75 |
+
}
|
76 |
+
});
|
77 |
+
},
|
78 |
+
send(data: Blob | { [key: string]: any }) {
|
79 |
+
if (websocket && websocket.readyState === WebSocket.OPEN) {
|
80 |
+
if (data instanceof Blob) {
|
81 |
+
websocket.send(data);
|
82 |
+
} else {
|
83 |
+
websocket.send(JSON.stringify(data));
|
84 |
+
}
|
85 |
+
} else {
|
86 |
+
console.log('WebSocket not connected');
|
87 |
+
}
|
88 |
+
},
|
89 |
+
async stop() {
|
90 |
+
lcmLiveStatus.set(LCMLiveStatus.DISCONNECTED);
|
91 |
+
if (websocket) {
|
92 |
+
websocket.close();
|
93 |
+
}
|
94 |
+
websocket = null;
|
95 |
+
streamId.set(null);
|
96 |
+
}
|
97 |
+
};
|
frontend/src/lib/mediaStream.ts
CHANGED
@@ -1,10 +1,10 @@
|
|
1 |
-
import { writable, type Writable
|
2 |
|
3 |
const BASE_HEIGHT = 720;
|
4 |
export enum MediaStreamStatusEnum {
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
}
|
9 |
export const onFrameChangeStore: Writable<{ blob: Blob }> = writable({ blob: new Blob() });
|
10 |
|
@@ -13,108 +13,106 @@ export const mediaStreamStatus = writable(MediaStreamStatusEnum.INIT);
|
|
13 |
export const mediaStream = writable<MediaStream | null>(null);
|
14 |
|
15 |
export const mediaStreamActions = {
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
|
|
40 |
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
61 |
-
};
|
62 |
-
|
63 |
-
|
64 |
-
let captureStream = null;
|
65 |
|
66 |
-
|
67 |
-
captureStream = await navigator.mediaDevices.getDisplayMedia(displayMediaOptions);
|
68 |
-
const videoTrack = captureStream.getVideoTracks()[0];
|
69 |
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
console.log(JSON.stringify(videoTrack.getConstraints(), null, 2));
|
74 |
-
mediaStreamStatus.set(MediaStreamStatusEnum.CONNECTED);
|
75 |
-
mediaStream.set(captureStream)
|
76 |
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
}
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
});
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { get, writable, type Writable } from 'svelte/store';
|
2 |
|
3 |
const BASE_HEIGHT = 720;
|
4 |
export enum MediaStreamStatusEnum {
|
5 |
+
INIT = 'init',
|
6 |
+
CONNECTED = 'connected',
|
7 |
+
DISCONNECTED = 'disconnected'
|
8 |
}
|
9 |
export const onFrameChangeStore: Writable<{ blob: Blob }> = writable({ blob: new Blob() });
|
10 |
|
|
|
13 |
export const mediaStream = writable<MediaStream | null>(null);
|
14 |
|
15 |
export const mediaStreamActions = {
|
16 |
+
async enumerateDevices() {
|
17 |
+
// console.log("Enumerating devices");
|
18 |
+
await navigator.mediaDevices
|
19 |
+
.enumerateDevices()
|
20 |
+
.then((devices) => {
|
21 |
+
const cameras = devices.filter((device) => device.kind === 'videoinput');
|
22 |
+
mediaDevices.set(cameras);
|
23 |
+
})
|
24 |
+
.catch((err) => {
|
25 |
+
console.error(err);
|
26 |
+
});
|
27 |
+
},
|
28 |
+
async start(mediaDevicedID?: string, aspectRatio: number = 1) {
|
29 |
+
const constraints = {
|
30 |
+
audio: false,
|
31 |
+
video: {
|
32 |
+
width: {
|
33 |
+
ideal: BASE_HEIGHT * aspectRatio
|
34 |
+
},
|
35 |
+
height: {
|
36 |
+
ideal: BASE_HEIGHT
|
37 |
+
},
|
38 |
+
deviceId: mediaDevicedID
|
39 |
+
}
|
40 |
+
};
|
41 |
|
42 |
+
await navigator.mediaDevices
|
43 |
+
.getUserMedia(constraints)
|
44 |
+
.then((stream) => {
|
45 |
+
mediaStreamStatus.set(MediaStreamStatusEnum.CONNECTED);
|
46 |
+
mediaStream.set(stream);
|
47 |
+
})
|
48 |
+
.catch((err) => {
|
49 |
+
console.error(`${err.name}: ${err.message}`);
|
50 |
+
mediaStreamStatus.set(MediaStreamStatusEnum.DISCONNECTED);
|
51 |
+
mediaStream.set(null);
|
52 |
+
});
|
53 |
+
},
|
54 |
+
async startScreenCapture() {
|
55 |
+
const displayMediaOptions = {
|
56 |
+
video: {
|
57 |
+
displaySurface: 'window'
|
58 |
+
},
|
59 |
+
audio: false,
|
60 |
+
surfaceSwitching: 'include'
|
61 |
+
};
|
|
|
|
|
|
|
|
|
62 |
|
63 |
+
let captureStream = null;
|
|
|
|
|
64 |
|
65 |
+
try {
|
66 |
+
captureStream = await navigator.mediaDevices.getDisplayMedia(displayMediaOptions);
|
67 |
+
const videoTrack = captureStream.getVideoTracks()[0];
|
|
|
|
|
|
|
68 |
|
69 |
+
console.log('Track settings:');
|
70 |
+
console.log(JSON.stringify(videoTrack.getSettings(), null, 2));
|
71 |
+
console.log('Track constraints:');
|
72 |
+
console.log(JSON.stringify(videoTrack.getConstraints(), null, 2));
|
73 |
+
mediaStreamStatus.set(MediaStreamStatusEnum.CONNECTED);
|
74 |
+
mediaStream.set(captureStream);
|
75 |
|
76 |
+
const capabilities = videoTrack.getCapabilities();
|
77 |
+
const aspectRatio = capabilities.aspectRatio;
|
78 |
+
console.log('Aspect Ratio Constraints:', aspectRatio);
|
79 |
+
} catch (err) {
|
80 |
+
console.error(err);
|
81 |
+
}
|
82 |
+
},
|
83 |
+
async switchCamera(mediaDevicedID: string, aspectRatio: number) {
|
84 |
+
console.log('Switching camera');
|
85 |
+
if (get(mediaStreamStatus) !== MediaStreamStatusEnum.CONNECTED) {
|
86 |
+
return;
|
87 |
+
}
|
88 |
+
const constraints = {
|
89 |
+
audio: false,
|
90 |
+
video: {
|
91 |
+
width: {
|
92 |
+
ideal: BASE_HEIGHT * aspectRatio
|
93 |
+
},
|
94 |
+
height: {
|
95 |
+
ideal: BASE_HEIGHT
|
96 |
+
},
|
97 |
+
deviceId: mediaDevicedID
|
98 |
+
}
|
99 |
+
};
|
100 |
+
console.log('Switching camera', constraints);
|
101 |
+
await navigator.mediaDevices
|
102 |
+
.getUserMedia(constraints)
|
103 |
+
.then((stream) => {
|
104 |
+
mediaStreamStatus.set(MediaStreamStatusEnum.CONNECTED);
|
105 |
+
mediaStream.set(stream);
|
106 |
+
})
|
107 |
+
.catch((err) => {
|
108 |
+
console.error(`${err.name}: ${err.message}`);
|
109 |
+
});
|
110 |
+
},
|
111 |
+
async stop() {
|
112 |
+
navigator.mediaDevices.getUserMedia({ video: true }).then((stream) => {
|
113 |
+
stream.getTracks().forEach((track) => track.stop());
|
114 |
+
});
|
115 |
+
mediaStreamStatus.set(MediaStreamStatusEnum.DISCONNECTED);
|
116 |
+
mediaStream.set(null);
|
117 |
+
}
|
118 |
+
};
|
frontend/src/lib/store.ts
CHANGED
@@ -1,15 +1,14 @@
|
|
1 |
-
|
2 |
-
import { derived, writable, get, type Writable, type Readable } from 'svelte/store';
|
3 |
|
4 |
export const pipelineValues: Writable<Record<string, any>> = writable({});
|
5 |
-
export const deboucedPipelineValues: Readable<Record<string, any>>
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
export const getPipelineValues = () => get(pipelineValues);
|
|
|
1 |
+
import { derived, get, writable, type Readable, type Writable } from 'svelte/store';
|
|
|
2 |
|
3 |
export const pipelineValues: Writable<Record<string, any>> = writable({});
|
4 |
+
export const deboucedPipelineValues: Readable<Record<string, any>> = derived(
|
5 |
+
pipelineValues,
|
6 |
+
($pipelineValues, set) => {
|
7 |
+
const debounced = setTimeout(() => {
|
8 |
+
set($pipelineValues);
|
9 |
+
}, 100);
|
10 |
+
return () => clearTimeout(debounced);
|
11 |
+
}
|
12 |
+
);
|
13 |
+
|
14 |
+
export const getPipelineValues = () => get(pipelineValues);
|
frontend/src/lib/types.ts
CHANGED
@@ -1,40 +1,39 @@
|
|
1 |
export const enum FieldType {
|
2 |
-
|
3 |
-
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
}
|
8 |
export const enum PipelineMode {
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
}
|
13 |
|
14 |
-
|
15 |
export interface Fields {
|
16 |
-
|
17 |
}
|
18 |
|
19 |
export interface FieldProps {
|
20 |
-
|
21 |
-
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
}
|
31 |
export interface PipelineInfo {
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
}
|
|
|
1 |
export const enum FieldType {
|
2 |
+
RANGE = 'range',
|
3 |
+
SEED = 'seed',
|
4 |
+
TEXTAREA = 'textarea',
|
5 |
+
CHECKBOX = 'checkbox',
|
6 |
+
SELECT = 'select'
|
7 |
}
|
8 |
export const enum PipelineMode {
|
9 |
+
IMAGE = 'image',
|
10 |
+
VIDEO = 'video',
|
11 |
+
TEXT = 'text'
|
12 |
}
|
13 |
|
|
|
14 |
export interface Fields {
|
15 |
+
[key: string]: FieldProps;
|
16 |
}
|
17 |
|
18 |
export interface FieldProps {
|
19 |
+
default: number | string;
|
20 |
+
max?: number;
|
21 |
+
min?: number;
|
22 |
+
title: string;
|
23 |
+
field: FieldType;
|
24 |
+
step?: number;
|
25 |
+
disabled?: boolean;
|
26 |
+
hide?: boolean;
|
27 |
+
id: string;
|
28 |
+
values?: string[];
|
29 |
}
|
30 |
export interface PipelineInfo {
|
31 |
+
title: {
|
32 |
+
default: string;
|
33 |
+
};
|
34 |
+
name: string;
|
35 |
+
description: string;
|
36 |
+
input_mode: {
|
37 |
+
default: PipelineMode;
|
38 |
+
};
|
39 |
+
}
|
frontend/src/lib/utils.ts
CHANGED
@@ -1,44 +1,46 @@
|
|
1 |
-
import * as piexif from
|
2 |
|
3 |
interface IImageInfo {
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
}
|
9 |
|
10 |
export function snapImage(imageEl: HTMLImageElement, info: IImageInfo) {
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
|
|
|
|
19 |
|
20 |
-
|
21 |
-
|
22 |
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
}
|
39 |
|
40 |
-
export function expandWindow(
|
41 |
-
|
42 |
<html>
|
43 |
<head>
|
44 |
<title>Real-Time Latent Consistency Model</title>
|
@@ -71,11 +73,15 @@ export function expandWindow(steramURL: string): Window {
|
|
71 |
}
|
72 |
</script>
|
73 |
|
74 |
-
<img src="${
|
75 |
</body>
|
76 |
</html>
|
77 |
`;
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
|
|
|
|
|
|
|
|
|
1 |
+
import * as piexif from 'piexifjs';
|
2 |
|
3 |
interface IImageInfo {
|
4 |
+
prompt?: string;
|
5 |
+
negative_prompt?: string;
|
6 |
+
seed?: number;
|
7 |
+
guidance_scale?: number;
|
8 |
}
|
9 |
|
10 |
export function snapImage(imageEl: HTMLImageElement, info: IImageInfo) {
|
11 |
+
try {
|
12 |
+
const zeroth: { [key: string]: any } = {};
|
13 |
+
const exif: { [key: string]: any } = {};
|
14 |
+
const gps: { [key: string]: any } = {};
|
15 |
+
zeroth[piexif.ImageIFD.Make] = 'LCM Image-to-Image ControNet';
|
16 |
+
zeroth[piexif.ImageIFD.ImageDescription] =
|
17 |
+
`prompt: ${info?.prompt} | negative_prompt: ${info?.negative_prompt} | seed: ${info?.seed} | guidance_scale: ${info?.guidance_scale}`;
|
18 |
+
zeroth[piexif.ImageIFD.Software] =
|
19 |
+
'https://github.com/radames/Real-Time-Latent-Consistency-Model';
|
20 |
+
exif[piexif.ExifIFD.DateTimeOriginal] = new Date().toISOString();
|
21 |
|
22 |
+
const exifObj = { '0th': zeroth, Exif: exif, GPS: gps };
|
23 |
+
const exifBytes = piexif.dump(exifObj);
|
24 |
|
25 |
+
const canvas = document.createElement('canvas');
|
26 |
+
canvas.width = imageEl.naturalWidth;
|
27 |
+
canvas.height = imageEl.naturalHeight;
|
28 |
+
const ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
|
29 |
+
ctx.drawImage(imageEl, 0, 0);
|
30 |
+
const dataURL = canvas.toDataURL('image/jpeg');
|
31 |
+
const withExif = piexif.insert(exifBytes, dataURL);
|
32 |
|
33 |
+
const a = document.createElement('a');
|
34 |
+
a.href = withExif;
|
35 |
+
a.download = `lcm_txt_2_img${Date.now()}.png`;
|
36 |
+
a.click();
|
37 |
+
} catch (err) {
|
38 |
+
console.log(err);
|
39 |
+
}
|
40 |
}
|
41 |
|
42 |
+
export function expandWindow(streamURL: string): Window {
|
43 |
+
const html = `
|
44 |
<html>
|
45 |
<head>
|
46 |
<title>Real-Time Latent Consistency Model</title>
|
|
|
73 |
}
|
74 |
</script>
|
75 |
|
76 |
+
<img src="${streamURL}" style="width: 100%; height: 100%; object-fit: contain;" />
|
77 |
</body>
|
78 |
</html>
|
79 |
`;
|
80 |
+
const newWindow = window.open(
|
81 |
+
'',
|
82 |
+
'_blank',
|
83 |
+
'width=1024,height=1024,scrollbars=0,resizable=1,toolbar=0,menubar=0,location=0,directories=0,status=0'
|
84 |
+
) as Window;
|
85 |
+
newWindow.document.write(html);
|
86 |
+
return newWindow;
|
87 |
+
}
|
frontend/src/routes/+page.ts
CHANGED
@@ -1 +1 @@
|
|
1 |
-
export const prerender = true
|
|
|
1 |
+
export const prerender = true;
|
frontend/vite.config.ts
CHANGED
@@ -10,6 +10,6 @@ export default defineConfig({
|
|
10 |
target: 'ws://localhost:7860',
|
11 |
ws: true
|
12 |
}
|
13 |
-
}
|
14 |
}
|
15 |
-
});
|
|
|
10 |
target: 'ws://localhost:7860',
|
11 |
ws: true
|
12 |
}
|
13 |
+
}
|
14 |
}
|
15 |
+
});
|