radames commited on
Commit
a6e7e8f
1 Parent(s): 32561d8

prompt submssion

Browse files
frontend/package.json CHANGED
@@ -22,6 +22,7 @@
22
  "@typescript-eslint/eslint-plugin": "^5.38.0",
23
  "@typescript-eslint/parser": "^5.38.0",
24
  "autoprefixer": "^10.4.12",
 
25
  "eslint": "^8.24.0",
26
  "eslint-config-prettier": "^8.3.0",
27
  "eslint-plugin-svelte3": "^4.0.0",
 
22
  "@typescript-eslint/eslint-plugin": "^5.38.0",
23
  "@typescript-eslint/parser": "^5.38.0",
24
  "autoprefixer": "^10.4.12",
25
+ "d3-scale": "^4.0.2",
26
  "eslint": "^8.24.0",
27
  "eslint-config-prettier": "^8.3.0",
28
  "eslint-plugin-svelte3": "^4.0.0",
frontend/src/lib/App.svelte CHANGED
@@ -3,16 +3,103 @@
3
  import Frame from '$lib/Frame.svelte';
4
  import Canvas from '$lib/Canvas.svelte';
5
  import Menu from '$lib/Menu.svelte';
 
6
  import type { Room } from '@liveblocks/client';
7
  import { COLORS, EMOJIS } from '$lib/constants';
8
- import { currZoomTransform, myPresence, others } from '$lib/store';
9
-
 
 
 
 
 
 
 
 
 
10
  /**
11
  * The main Liveblocks code for the example.
12
  * Check in src/routes/index.svelte to see the setup code.
13
  */
14
 
15
  export let room: Room;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  </script>
17
 
18
  <!-- Show the current user's cursor location -->
@@ -20,11 +107,19 @@
20
  {$myPresence?.cursor
21
  ? `${$myPresence.cursor.x} × ${$myPresence.cursor.y}`
22
  : 'Move your cursor to broadcast its position to other people in the room.'}
 
 
23
  </div>
 
 
 
24
  <div class="fixed left-0 z-0 w-screen h-screen cursor-none">
25
  <Canvas />
26
 
27
  <main class="z-10 relative">
 
 
 
28
  {#if $myPresence?.cursor}
29
  <Frame color={COLORS[0]} position={$myPresence.cursor} transform={$currZoomTransform} />
30
  <Cursor
@@ -56,7 +151,7 @@
56
  {/if}
57
  </main>
58
  </div>
59
- <div class="fixed bottom-0 left-0 right-0 z-50 my-2">
60
  <Menu />
61
  </div>
62
 
 
3
  import Frame from '$lib/Frame.svelte';
4
  import Canvas from '$lib/Canvas.svelte';
5
  import Menu from '$lib/Menu.svelte';
6
+ import PromptModal from '$lib/PromptModal.svelte';
7
  import type { Room } from '@liveblocks/client';
8
  import { COLORS, EMOJIS } from '$lib/constants';
9
+ import { PUBLIC_WS_ENDPOINT } from '$env/static/public';
10
+ import {
11
+ isLoading,
12
+ loadingState,
13
+ currZoomTransform,
14
+ myPresence,
15
+ others,
16
+ isPrompting,
17
+ clickedPosition
18
+ } from '$lib/store';
19
+ import { base64ToBlob, uploadImage } from '$lib/utils';
20
  /**
21
  * The main Liveblocks code for the example.
22
  * Check in src/routes/index.svelte to see the setup code.
23
  */
24
 
25
  export let room: Room;
26
+
27
+ async function onClose(e: CustomEvent) {
28
+ $isPrompting = false;
29
+ }
30
+ async function onPrompt(e: CustomEvent) {
31
+ const prompt = e.detail.prompt;
32
+ const imgURLs = await generateImage(prompt);
33
+ $isPrompting = false;
34
+ console.log('prompt', prompt, imgURLs);
35
+ }
36
+ async function generateImage(_prompt: string) {
37
+ if (!_prompt || $isLoading == true) return;
38
+ $loadingState = 'Pending';
39
+ $isLoading = true;
40
+ const sessionHash = crypto.randomUUID();
41
+
42
+ const payload = {
43
+ fn_index: 2,
44
+ data: [_prompt],
45
+ session_hash: sessionHash
46
+ };
47
+ const websocket = new WebSocket(PUBLIC_WS_ENDPOINT);
48
+ // websocket.onopen = async function (event) {
49
+ // websocket.send(JSON.stringify({ hash: sessionHash }));
50
+ // };
51
+ websocket.onclose = (evt) => {
52
+ if (!evt.wasClean) {
53
+ $loadingState = 'Error';
54
+ $isLoading = false;
55
+ }
56
+ };
57
+ websocket.onmessage = async function (event) {
58
+ try {
59
+ const data = JSON.parse(event.data);
60
+ $loadingState = '';
61
+ switch (data.msg) {
62
+ case 'send_data':
63
+ $loadingState = 'Sending Data';
64
+ websocket.send(JSON.stringify(payload));
65
+ break;
66
+ case 'queue_full':
67
+ $loadingState = 'Queue full';
68
+ websocket.close();
69
+ $isLoading = false;
70
+ return;
71
+ case 'estimation':
72
+ const { msg, rank, queue_size } = data;
73
+ $loadingState = `On queue ${rank}/${queue_size}`;
74
+ break;
75
+ case 'process_generating':
76
+ $loadingState = data.success ? 'Generating' : 'Error';
77
+ break;
78
+ case 'process_completed':
79
+ try {
80
+ const imgsBase64 = data.output.data[0] as string[];
81
+ const imgBlobs = await Promise.all(imgsBase64.map((base64) => base64ToBlob(base64)));
82
+ const imgURLs = await Promise.all(imgBlobs.map((blob) => uploadImage(blob, _prompt)));
83
+ console.log(imgURLs);
84
+ $loadingState = data.success ? 'Complete' : 'Error';
85
+ } catch (e) {
86
+ $loadingState = e.message;
87
+ }
88
+ websocket.close();
89
+ $isLoading = false;
90
+ return;
91
+ case 'process_starts':
92
+ $loadingState = 'Processing';
93
+ break;
94
+ }
95
+ } catch (e) {
96
+ console.error(e);
97
+ $isLoading = false;
98
+ $loadingState = 'Error';
99
+ }
100
+ };
101
+ }
102
+ let modal = false;
103
  </script>
104
 
105
  <!-- Show the current user's cursor location -->
 
107
  {$myPresence?.cursor
108
  ? `${$myPresence.cursor.x} × ${$myPresence.cursor.y}`
109
  : 'Move your cursor to broadcast its position to other people in the room.'}
110
+ {$loadingState}
111
+ {$isLoading}
112
  </div>
113
+ {#if $isPrompting}
114
+ <PromptModal on:prompt={onPrompt} on:close={onClose} />
115
+ {/if}
116
  <div class="fixed left-0 z-0 w-screen h-screen cursor-none">
117
  <Canvas />
118
 
119
  <main class="z-10 relative">
120
+ {#if $clickedPosition}
121
+ <Frame color={COLORS[0]} position={$clickedPosition} transform={$currZoomTransform} />
122
+ {/if}
123
  {#if $myPresence?.cursor}
124
  <Frame color={COLORS[0]} position={$myPresence.cursor} transform={$currZoomTransform} />
125
  <Cursor
 
151
  {/if}
152
  </main>
153
  </div>
154
+ <div class="fixed bottom-0 left-0 right-0 z-10 my-2">
155
  <Menu />
156
  </div>
157
 
frontend/src/lib/Canvas.svelte CHANGED
@@ -3,7 +3,7 @@
3
  import { select } from 'd3-selection';
4
  import { scaleLinear } from 'd3-scale';
5
  import { onMount } from 'svelte';
6
- import { currZoomTransform, myPresence, others } from '$lib/store';
7
 
8
  const height = 512 * 5;
9
  const width = 512 * 5;
@@ -26,7 +26,7 @@
26
 
27
  const scale = width / containerEl.clientWidth;
28
  const zoomHandler = zoom()
29
- .scaleExtent([1/scale,1])
30
  // .translateExtent([
31
  // [0, 0],
32
  // [width, height]
@@ -39,7 +39,13 @@
39
  .call(zoomHandler as any)
40
  // .call(zoomHandler.scaleTo as any, 1 / scale)
41
  .on('pointermove', handlePointerMove)
42
- .on('pointerleave', handlePointerLeave);
 
 
 
 
 
 
43
 
44
  canvasCtx = canvasEl.getContext('2d') as CanvasRenderingContext2D;
45
  canvasCtx.fillStyle = 'red';
 
3
  import { select } from 'd3-selection';
4
  import { scaleLinear } from 'd3-scale';
5
  import { onMount } from 'svelte';
6
+ import { currZoomTransform, myPresence, isPrompting, clickedPosition } from '$lib/store';
7
 
8
  const height = 512 * 5;
9
  const width = 512 * 5;
 
26
 
27
  const scale = width / containerEl.clientWidth;
28
  const zoomHandler = zoom()
29
+ .scaleExtent([1 / scale, 1])
30
  // .translateExtent([
31
  // [0, 0],
32
  // [width, height]
 
39
  .call(zoomHandler as any)
40
  // .call(zoomHandler.scaleTo as any, 1 / scale)
41
  .on('pointermove', handlePointerMove)
42
+ .on('pointerleave', handlePointerLeave)
43
+ .on('dblclick.zoom', null)
44
+ .on('click', () => {
45
+ $isPrompting = true;
46
+ $clickedPosition = $myPresence.cursor;
47
+ console.log($clickedPosition);
48
+ });
49
 
50
  canvasCtx = canvasEl.getContext('2d') as CanvasRenderingContext2D;
51
  canvasCtx.fillStyle = 'red';
frontend/src/lib/PromptModal.svelte ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ import { createEventDispatcher, onMount } from 'svelte';
3
+ const dispatch = createEventDispatcher();
4
+ let prompt: string;
5
+
6
+ const onKeyup = (e: KeyboardEvent) => {
7
+ if (e.key === 'Escape') {
8
+ dispatch('close');
9
+ }
10
+ };
11
+ onMount(() => {
12
+ window.addEventListener('keyup', onKeyup);
13
+ return () => {
14
+ window.removeEventListener('keyup', onKeyup);
15
+ };
16
+ });
17
+ </script>
18
+
19
+ <form
20
+ class="fixed w-screen h-screen top-0 left-0 z-50 flex items-center justify-center bg-black bg-opacity-80 px-3"
21
+ on:submit|preventDefault={() => dispatch('prompt', { prompt })}
22
+ on:click={() => dispatch('close')}
23
+ >
24
+ <input
25
+ on:click|stopPropagation
26
+ class="input"
27
+ placeholder="Type a prompt..."
28
+ title="Input prompt to generate image and obtain palette"
29
+ type="text"
30
+ name="prompt"
31
+ bind:value={prompt}
32
+ />
33
+ </form>
34
+
35
+ <style lang="postcss" scoped>
36
+ .link {
37
+ @apply text-xs underline font-bold hover:no-underline hover:text-gray-500 visited:text-gray-500;
38
+ }
39
+ .input {
40
+ @apply w-full max-w-sm text-sm disabled:opacity-50 italic placeholder:text-white text-white placeholder:text-opacity-50 bg-slate-900 border-2 border-white rounded-2xl px-2 shadow-sm focus:outline-none focus:border-gray-400 focus:ring-1;
41
+ }
42
+ </style>
frontend/src/lib/store.ts CHANGED
@@ -5,6 +5,8 @@ import { type ZoomTransform, zoomIdentity } from 'd3-zoom';
5
 
6
  export const loadingState = writable<string>('');
7
  export const isLoading = writable<boolean>(false);
 
 
8
 
9
  export const currZoomTransform = writable<ZoomTransform>(zoomIdentity);
10
 
 
5
 
6
  export const loadingState = writable<string>('');
7
  export const isLoading = writable<boolean>(false);
8
+ export const isPrompting = writable<boolean>(false);
9
+ export const clickedPosition = writable<{ x: number; y: number }>();
10
 
11
  export const currZoomTransform = writable<ZoomTransform>(zoomIdentity);
12
 
frontend/src/lib/utils.ts CHANGED
@@ -3,6 +3,27 @@ import { dev } from '$app/environment';
3
  export function randomSeed() {
4
  return BigInt(13248873089935215612 & (((1 << 63) - 1) * Math.random()));
5
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  export async function uploadImage(imagBlob: Blob, prompt: string): string {
7
  // simple regex slugify string for file name
8
  const promptSlug = slugify(prompt);
 
3
  export function randomSeed() {
4
  return BigInt(13248873089935215612 & (((1 << 63) - 1) * Math.random()));
5
  }
6
+
7
+ export function base64ToBlob(base64image: string): Promise<Blob> {
8
+ return new Promise((resolve) => {
9
+ const img = new Image();
10
+ img.onload = async () => {
11
+ const w = img.width;
12
+ const h = img.height;
13
+ const canvas = document.createElement('canvas');
14
+ canvas.width = w;
15
+ canvas.height = h;
16
+ const ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
17
+ ctx.drawImage(img, 0, 0, w, h);
18
+
19
+ const imgBlob: Blob = await new Promise((_resolve) =>
20
+ canvas.toBlob(_resolve, 'image/jpeg', 0.95)
21
+ );
22
+ resolve(imgBlob);
23
+ };
24
+ img.src = base64image;
25
+ });
26
+ }
27
  export async function uploadImage(imagBlob: Blob, prompt: string): string {
28
  // simple regex slugify string for file name
29
  const promptSlug = slugify(prompt);
frontend/src/routes/+page.svelte CHANGED
@@ -46,40 +46,12 @@
46
  </script>
47
 
48
  <div class="max-w-screen-md mx-auto px-3 py-8 relative">
49
- <div class="relative z-10">
50
  <h1 class="text-3xl font-bold leading-normal">Stable Diffussion Outpainting Multiplayer</h1>
51
- <p class="text-sm" />
52
- <div class="relative bg-white dark:bg-black py-3">
53
- <form class="grid grid-cols-6">
54
- <input
55
- class="input"
56
- placeholder="A photo of a beautiful sunset in San Francisco"
57
- title="Input prompt to generate image and obtain palette"
58
- type="text"
59
- name="prompt"
60
- disabled={$isLoading}
61
- />
62
- <button class="button" disabled={$isLoading} title="Generate Palette">
63
- Create Palette
64
- </button>
65
- </form>
66
- </div>
67
  </div>
68
- <div class="relative z-0">
69
  {#if room}
70
  <App {room} />
71
  {/if}
72
  </div>
73
  </div>
74
-
75
- <style lang="postcss" scoped>
76
- .link {
77
- @apply text-xs underline font-bold hover:no-underline hover:text-gray-500 visited:text-gray-500;
78
- }
79
- .input {
80
- @apply text-sm disabled:opacity-50 col-span-4 md:col-span-5 italic dark:placeholder:text-black placeholder:text-white text-white dark:text-black placeholder:text-opacity-30 dark:placeholder:text-opacity-10 dark:bg-white bg-slate-900 border-2 border-black rounded-2xl px-2 shadow-sm focus:outline-none focus:border-gray-400 focus:ring-1;
81
- }
82
- .button {
83
- @apply disabled:opacity-50 col-span-2 md:col-span-1 dark:bg-white dark:text-black border-2 border-black rounded-2xl ml-2 px-2 py-2 text-xs shadow-sm font-bold focus:outline-none focus:border-gray-400;
84
- }
85
- </style>
 
46
  </script>
47
 
48
  <div class="max-w-screen-md mx-auto px-3 py-8 relative">
49
+ <div class="relative">
50
  <h1 class="text-3xl font-bold leading-normal">Stable Diffussion Outpainting Multiplayer</h1>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
  </div>
52
+ <div class="relative">
53
  {#if room}
54
  <App {room} />
55
  {/if}
56
  </div>
57
  </div>