radames commited on
Commit
18cd77b
·
1 Parent(s): 6724b65
frontend/src/app.d.ts CHANGED
@@ -6,7 +6,11 @@ declare global {
6
  // interface Locals {}
7
  // interface PageData {}
8
  // interface Platform {}
 
 
 
 
 
9
  }
10
  }
11
-
12
- export {};
 
6
  // interface Locals {}
7
  // interface PageData {}
8
  // interface Platform {}
9
+ interface Window {
10
+ parentIFrame: {
11
+ scrollTo: (x: number, y: number) => void;
12
+ };
13
+ }
14
  }
15
  }
16
+ export { };
 
frontend/src/lib/App.svelte CHANGED
@@ -1,29 +1,44 @@
1
  <script lang="ts">
2
  import { PUBLIC_BACKEND_WS_URL } from '$env/static/public';
3
- import { onMount } from 'svelte';
4
  import { nanoid } from 'nanoid';
5
- import { chatsStore, selectedChatId, loadingState } from '$lib/store';
6
- import type { Message, Chat } from '$lib/types';
7
  import { MessageType, Sender } from '$lib/types';
 
8
  import ChatInput from '$lib/ChatInput.svelte';
9
  import ChatMessage from '$lib/ChatMessage.svelte';
10
  import ChatNewBtn from '$lib/ChatNewBtn.svelte';
 
11
 
12
- $: isLoading = false;
13
 
 
 
14
  function clearStateMsg(t = 5000) {
15
  setTimeout(() => {
16
  $loadingState = '';
17
  }, t);
18
  }
19
  onMount(() => {
 
 
 
 
 
 
 
20
  // generateImage();
21
  });
22
 
23
  $: chatData = $chatsStore.find((chat) => chat.id === $selectedChatId);
24
- $: messages = chatData?.messages || [];
25
-
26
- $: console.log($chatsStore);
 
 
 
 
27
 
28
  function newChat() {
29
  const chatId = nanoid();
@@ -36,16 +51,45 @@
36
  $chatsStore = [chat].concat($chatsStore);
37
  $selectedChatId = chat.id;
38
  }
 
 
 
39
 
40
  function submitMessage(event: CustomEvent) {
41
- const value = event.detail;
 
 
 
42
  const message: Message = {
43
  sender: Sender.USER,
44
  id: nanoid(),
45
- type: MessageType.TEXT,
46
- content: value,
47
  timestamp: new Date().getTime()
48
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
  $chatsStore = $chatsStore.map((chat) => {
50
  if (chat.id === $selectedChatId) {
51
  chat.messages.push(message);
@@ -53,14 +97,11 @@
53
  return chat;
54
  });
55
  }
56
- const timeFormater = new Intl.DateTimeFormat('en-US', {
57
- day: 'numeric',
58
- month: 'short',
59
- hour: 'numeric',
60
- minute: 'numeric'
61
- }).format;
62
- async function generateImage() {
63
- if (isLoading) return;
64
 
65
  $loadingState = 'Pending';
66
 
@@ -69,10 +110,9 @@
69
  fn_index: 1,
70
  session_hash: sessionHash
71
  };
72
- const image = '';
73
  const datapayload = {
74
  data: [
75
- 'make him wear shirts', // prompt
76
  10.5, // text guidance
77
  1.5, // image guidance
78
  image,
@@ -83,8 +123,7 @@
83
  0 // seed
84
  ]
85
  };
86
-
87
- const websocket = new WebSocket(PUBLIC_BACKEND_WS_URL);
88
  // websocket.onopen = async function (event) {
89
  // websocket.send(JSON.stringify({ hash: sessionHash }));
90
  // };
@@ -119,25 +158,37 @@
119
  break;
120
  case 'process_completed':
121
  try {
122
- console.log(data);
123
- // const params = data.output.data[0] as {
124
- // is_nsfw: boolean;
125
- // image: {
126
- // url: string;
127
- // filename: string;
128
- // };
129
- // };
130
- // const isNSWF = params.is_nsfw;
131
- // if (isNSWF) {
132
- // throw new Error('NFSW');
133
- // }
134
- // // const imgBlob = await base64ToBlob(imgBase64);
135
- // const promptImgParams = {
136
- // imgURL: params.image.filename
137
- // };
138
- // // const imgURL = await uploadImage(imgBlob, promptImgParams);
139
- // // $promptImgStorage.set(imageKey, promptImgParams);
140
- // console.log(params.image.url);
 
 
 
 
 
 
 
 
 
 
 
 
141
  $loadingState = data.success ? 'Complete' : 'Error';
142
  clearStateMsg();
143
  } catch (err) {
@@ -163,38 +214,57 @@
163
  <div>
164
  <h1 class="text-2xl">CHATS</h1>
165
  <div class="grid min-h-[40rem] grid-cols-4">
166
- <div class="col-span-1 flex flex-col border-r p-4">
167
- <ChatNewBtn on:click={newChat} />
168
- <div class="max-h-[40rem] flex flex-col gap-2 overflow-y-scroll">
169
- {#if $chatsStore.length}
170
- {#each $chatsStore as chat}
171
- <button on:click={() => ($selectedChatId = chat.id)}>
172
- <div
173
- class="flex h-16 flex-col items-start justify-center rounded-xl bg-gray-100 px-4 text-gray-900
 
 
 
 
 
 
174
  {chat.id === $selectedChatId ? 'bg-gray-400' : ''}"
175
- >
176
- <h3 class="w-full truncate font-semibold">{chat.blurb}</h3>
177
- <p class="w-full truncate text-sm text-gray-500">
178
- {timeFormater(new Date(chat.timestamp))}
179
- </p>
 
 
 
 
 
 
 
 
 
180
  </div>
181
- </button>
182
- {/each}
183
- {:else}
184
- <div
185
- class="flex h-16 flex-col items-start justify-center rounded-xl bg-gray-100 px-4 text-gray-900"
186
- >
187
- <h3 class="w-full truncate font-semibold">No chats</h3>
188
- <p class="w-full truncate text-sm text-gray-500">Start a new Chat!</p>
189
- </div>
190
- {/if}
191
  </div>
192
  </div>
193
- <div class="col-span-3 flex flex-col">
 
194
  {#each messages as message}
195
  <ChatMessage {message} />
196
  {/each}
197
- <ChatInput on:submitMessage={submitMessage} />
 
 
 
198
  </div>
199
  </div>
200
  </div>
 
1
  <script lang="ts">
2
  import { PUBLIC_BACKEND_WS_URL } from '$env/static/public';
3
+ import { onMount, tick } from 'svelte';
4
  import { nanoid } from 'nanoid';
5
+ import { chatsStore, selectedChatId, loadingState, lastBase64Image } from '$lib/store';
6
+ import type { Message, Chat, InferenceResponse, ImageFile } from '$lib/types';
7
  import { MessageType, Sender } from '$lib/types';
8
+ import { timeFormater, fetchImageBase64 } from '$lib/utils';
9
  import ChatInput from '$lib/ChatInput.svelte';
10
  import ChatMessage from '$lib/ChatMessage.svelte';
11
  import ChatNewBtn from '$lib/ChatNewBtn.svelte';
12
+ import IconDelete from './Icons/IconDelete.svelte';
13
 
14
+ $: isLoading = !($loadingState === '' || $loadingState === 'Complete');
15
 
16
+ let chatBoxEl: HTMLDivElement;
17
+ let chatInputEl: HTMLInputElement;
18
  function clearStateMsg(t = 5000) {
19
  setTimeout(() => {
20
  $loadingState = '';
21
  }, t);
22
  }
23
  onMount(() => {
24
+ const observer = new ResizeObserver(() => {
25
+ window.scrollTo(0, chatBoxEl.getBoundingClientRect().height);
26
+ if ('parentIFrame' in window) {
27
+ (window as any).parentIFrame.scrollTo(0, chatBoxEl.getBoundingClientRect().height);
28
+ }
29
+ });
30
+ observer.observe(chatBoxEl);
31
  // generateImage();
32
  });
33
 
34
  $: chatData = $chatsStore.find((chat) => chat.id === $selectedChatId);
35
+ $: messages = chatData?.messages.sort((a, b) => a.timestamp - b.timestamp) || [];
36
+ $: if ($selectedChatId) {
37
+ // find last message with image type
38
+ $lastBase64Image =
39
+ [...messages].reverse().find((message) => message.type === MessageType.IMAGE)?.content ||
40
+ null;
41
+ }
42
 
43
  function newChat() {
44
  const chatId = nanoid();
 
51
  $chatsStore = [chat].concat($chatsStore);
52
  $selectedChatId = chat.id;
53
  }
54
+ function deleteChat(id: string) {
55
+ $chatsStore = $chatsStore.filter((chat) => chat.id !== id);
56
+ }
57
 
58
  function submitMessage(event: CustomEvent) {
59
+ if ($chatsStore.length === 0) {
60
+ newChat();
61
+ }
62
+ const { type, content } = event.detail;
63
  const message: Message = {
64
  sender: Sender.USER,
65
  id: nanoid(),
66
+ type: type,
67
+ content: content,
68
  timestamp: new Date().getTime()
69
  };
70
+ updateChatStore(message);
71
+
72
+ if (type === MessageType.IMAGE) {
73
+ // upate last image to be the last image sent
74
+ $lastBase64Image = content;
75
+ } else if (type === MessageType.TEXT) {
76
+ // if the last message was an image, then we want to run inference
77
+ // on the image and the text
78
+ if (!$lastBase64Image) {
79
+ const message: Message = {
80
+ sender: Sender.BOT,
81
+ id: nanoid(),
82
+ type: MessageType.TEXT,
83
+ content: "Sorry, I don't have an image to work with.",
84
+ timestamp: new Date().getTime()
85
+ };
86
+ updateChatStore(message);
87
+ return;
88
+ }
89
+ runInference($lastBase64Image, content);
90
+ }
91
+ }
92
+ function updateChatStore(message: Message) {
93
  $chatsStore = $chatsStore.map((chat) => {
94
  if (chat.id === $selectedChatId) {
95
  chat.messages.push(message);
 
97
  return chat;
98
  });
99
  }
100
+
101
+ async function runInference(image: string, prompt: string) {
102
+ if (isLoading || image === '' || prompt === '') {
103
+ return;
104
+ }
 
 
 
105
 
106
  $loadingState = 'Pending';
107
 
 
110
  fn_index: 1,
111
  session_hash: sessionHash
112
  };
 
113
  const datapayload = {
114
  data: [
115
+ prompt, // prompt
116
  10.5, // text guidance
117
  1.5, // image guidance
118
  image,
 
123
  0 // seed
124
  ]
125
  };
126
+ const websocket = new WebSocket(`wss://${PUBLIC_BACKEND_WS_URL}/queue/join`);
 
127
  // websocket.onopen = async function (event) {
128
  // websocket.send(JSON.stringify({ hash: sessionHash }));
129
  // };
 
158
  break;
159
  case 'process_completed':
160
  try {
161
+ const response = data.output as InferenceResponse;
162
+ const imageData = response.data[0] as ImageFile[];
163
+ const nsfwData = data.output.data[1] as boolean[] | null;
164
+ console.log('imageData', imageData);
165
+ console.log('nsfwData', nsfwData);
166
+
167
+ if (nsfwData && nsfwData[0]) {
168
+ const message: Message = {
169
+ sender: Sender.BOT,
170
+ id: nanoid(),
171
+ type: MessageType.TEXT,
172
+ content:
173
+ 'Sorry this prompt possibly generates NSFW content. Please try another prompt.',
174
+ timestamp: new Date().getTime()
175
+ };
176
+ updateChatStore(message);
177
+ } else {
178
+ const fileName = imageData[0].name;
179
+ const imageBase64 = await fetchImageBase64(
180
+ `https://${PUBLIC_BACKEND_WS_URL}/file=${fileName}`
181
+ );
182
+ const message: Message = {
183
+ sender: Sender.BOT,
184
+ id: nanoid(),
185
+ type: MessageType.IMAGE,
186
+ content: imageBase64,
187
+ timestamp: new Date().getTime()
188
+ };
189
+ $lastBase64Image = imageBase64;
190
+ updateChatStore(message);
191
+ }
192
  $loadingState = data.success ? 'Complete' : 'Error';
193
  clearStateMsg();
194
  } catch (err) {
 
214
  <div>
215
  <h1 class="text-2xl">CHATS</h1>
216
  <div class="grid min-h-[40rem] grid-cols-4">
217
+ <div class="col-span-1 flex flex-col border-r p-4 relative">
218
+ <div class="sticky top-3">
219
+ <ChatNewBtn on:click={newChat} />
220
+ <div class="max-h-[40rem] flex flex-col gap-2 overflow-y-scroll">
221
+ {#if $chatsStore.length}
222
+ {#each $chatsStore as chat}
223
+ <div class="flex flex-col relative">
224
+ <button
225
+ class="disabled:opacity-60 disabled:cursor-progress"
226
+ on:click={() => ($selectedChatId = chat.id)}
227
+ disabled={isLoading}
228
+ >
229
+ <div
230
+ class=" flex flex-col h-16 items-start justify-center rounded-xl bg-gray-100 px-4 text-gray-900
231
  {chat.id === $selectedChatId ? 'bg-gray-400' : ''}"
232
+ >
233
+ <h3 class="w-full truncate font-semibold">{chat.blurb}</h3>
234
+ <p class="w-full truncate text-sm text-gray-500">
235
+ {timeFormater(new Date(chat.timestamp))}
236
+ </p>
237
+ </div>
238
+ </button>
239
+ <button
240
+ class="text-black absolute right-1 bottom-1 disabled:opacity-60"
241
+ on:click={() => deleteChat(chat.id)}
242
+ disabled={isLoading}
243
+ >
244
+ <IconDelete />
245
+ </button>
246
  </div>
247
+ {/each}
248
+ {:else}
249
+ <div
250
+ class="flex h-16 flex-col items-start justify-center rounded-xl bg-gray-100 px-4 text-gray-900"
251
+ >
252
+ <h3 class="w-full truncate font-semibold">No chats</h3>
253
+ <p class="w-full truncate text-sm text-gray-500">Start a new Chat!</p>
254
+ </div>
255
+ {/if}
256
+ </div>
257
  </div>
258
  </div>
259
+
260
+ <div class="col-span-3 flex flex-col" bind:this={chatBoxEl}>
261
  {#each messages as message}
262
  <ChatMessage {message} />
263
  {/each}
264
+ <ChatInput on:submitMessage={submitMessage} bind:inputEl={chatInputEl} disabled={isLoading} />
265
+ </div>
266
+ <div class="top-0 right-0 z-10">
267
+ Loading: {$loadingState}
268
  </div>
269
  </div>
270
  </div>
frontend/src/lib/ChatInput.svelte CHANGED
@@ -1,39 +1,89 @@
1
  <script lang="ts">
2
  import { createEventDispatcher, onMount } from 'svelte';
 
 
 
 
3
 
4
  const dispatch = createEventDispatcher();
5
 
6
  let textInput: string = '';
7
- let inputEl: HTMLInputElement;
8
 
9
  function onSubmit(event: Event) {
10
  event.stopPropagation();
11
  event.preventDefault();
12
  event.stopImmediatePropagation();
13
-
14
  if (textInput.trim() !== '') {
15
- dispatch('submitMessage', textInput);
 
 
 
16
  textInput = '';
17
  }
18
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  onMount(() => {
20
  inputEl.focus();
 
 
 
21
  });
22
  </script>
23
 
24
- <form class="mt-auto flex w-full gap-2 border-t px-3 pt-4 pb-6" on:submit={onSubmit}>
25
- <input
26
- bind:value={textInput}
27
- bind:this={inputEl}
28
- on:click|stopPropagation
29
- type="text"
30
- class="flex h-11 flex-1 items-center rounded-full border bg-gray-100 px-4 text-black"
31
- placeholder="Type here"
32
- title="Type here to send a message"
33
- />
34
- <button
35
- title="Send your message"
36
- type="submit"
37
- class="rounded-full bg-black dark:bg-white dark:text-black px-4 font-semibold text-gray-200">Submit</button
38
- >
39
- </form>
 
 
 
 
 
 
 
 
 
 
1
  <script lang="ts">
2
  import { createEventDispatcher, onMount } from 'svelte';
3
+ import { MessageType } from '$lib/types';
4
+
5
+ export let inputEl: HTMLInputElement;
6
+ export let disabled: boolean = true;
7
 
8
  const dispatch = createEventDispatcher();
9
 
10
  let textInput: string = '';
11
+ let isDragOver: boolean = false;
12
 
13
  function onSubmit(event: Event) {
14
  event.stopPropagation();
15
  event.preventDefault();
16
  event.stopImmediatePropagation();
 
17
  if (textInput.trim() !== '') {
18
+ dispatch('submitMessage', {
19
+ type: MessageType.TEXT,
20
+ content: textInput
21
+ });
22
  textInput = '';
23
  }
24
  }
25
+ function onDrop(event: DragEvent) {
26
+ event.stopPropagation();
27
+ event.preventDefault();
28
+ event.stopImmediatePropagation();
29
+ if (event && event.dataTransfer) {
30
+ const files = [...event.dataTransfer.files];
31
+ if (files.length) {
32
+ const file = files[0];
33
+ if (file.type.startsWith('image/') && file.size < 1.5e7) {
34
+ const reader = new FileReader();
35
+ reader.readAsDataURL(file);
36
+ reader.onload = () => {
37
+ dispatch('submitMessage', {
38
+ type: MessageType.IMAGE,
39
+ content: reader.result
40
+ });
41
+ };
42
+ }
43
+ }
44
+ }
45
+ isDragOver = false;
46
+ }
47
+
48
+ function onDragover(event: DragEvent) {
49
+ event.stopPropagation();
50
+ event.preventDefault();
51
+ if (event && event.dataTransfer) {
52
+ event.dataTransfer.dropEffect = 'copy';
53
+ }
54
+ isDragOver = true;
55
+ }
56
+
57
  onMount(() => {
58
  inputEl.focus();
59
+ window.addEventListener('dragover', onDragover, false);
60
+ window.addEventListener('drop', onDrop, false);
61
+ window.addEventListener('dragleave', () => (isDragOver = false), false);
62
  });
63
  </script>
64
 
65
+ <div class="mt-auto {isDragOver ? 'bg-yellow-50' : ''} py-4">
66
+ <form class="flex selection:w-full gap-2 border-t px-3 pt-4 pb-6" on:submit={onSubmit}>
67
+ <input
68
+ bind:value={textInput}
69
+ bind:this={inputEl}
70
+ on:click|stopPropagation
71
+ {disabled}
72
+ type="text"
73
+ class="flex h-11 flex-1 items-center rounded-full border bg-gray-100 px-4 text-black disabled:opacity-60 disabled:cursor-progress"
74
+ placeholder="Type here"
75
+ title="Type here to send a message"
76
+ />
77
+ <button
78
+ {disabled}
79
+ title="Send your message"
80
+ type="submit"
81
+ class="rounded-full bg-black dark:bg-white dark:text-black px-4 font-semibold text-gray-200 disabled:opacity-60 disabled:cursor-progress"
82
+ >Submit</button
83
+ >
84
+ </form>
85
+ <div class="flex items-center px-3 pt-4 pb-6 bg-yellow-50 py-3 rounded">
86
+ <i class="fas fa-image text-2xl text-gray-500 mr-2" />
87
+ <h2 class="text-xl font-bold text-gray-500">Drop your image here to start</h2>
88
+ </div>
89
+ </div>
frontend/src/lib/ChatMessage.svelte CHANGED
@@ -3,22 +3,24 @@
3
  import { MessageType, Sender } from '$lib/types';
4
 
5
  export let message: Message;
 
6
  </script>
7
 
8
  <div class="flex flex-col gap-4 p-4">
9
- <img
10
- class="self-end max-h-[328px] rounded-xl bg-gray-200"
11
- src="https://huggingface.co/datasets/victor/assets/resolve/main/Frame%201.png"
12
- alt=""
13
- />
14
- <p
15
- class="self-end rounded-3 -mt-2 rounded-t rounded-b-2xl bg-black dark:bg-white py-2 px-4 text-white dark:text-black"
16
- >
17
- {message.content}
18
- </p>
19
- <img
20
- class="max-h-[328px] self-start rounded-xl bg-gray-200"
21
- src="https://huggingface.co/datasets/victor/assets/resolve/main/Frame%202.png"
22
- alt=""
23
- />
 
24
  </div>
 
3
  import { MessageType, Sender } from '$lib/types';
4
 
5
  export let message: Message;
6
+ export let loading: boolean = false;
7
  </script>
8
 
9
  <div class="flex flex-col gap-4 p-4">
10
+ <div class={message.sender === Sender.USER ? 'self-end' : 'self-start'}>
11
+ {#if loading}
12
+ <div class="flex flex-col gap-2">
13
+ <div class="w-32 h-8 bg-gray-200 rounded-xl animate-pulse" />
14
+ <div class="w-32 h-8 bg-gray-200 rounded-xl animate-pulse" />
15
+ </div>
16
+ {:else if message.type === MessageType.TEXT}
17
+ <p
18
+ class="self-end rounded-3 -mt-2 rounded-t rounded-b-2xl bg-black dark:bg-white py-2 px-4 text-white dark:text-black"
19
+ >
20
+ {message.content}
21
+ </p>
22
+ {:else if message.type === MessageType.IMAGE}
23
+ <img class="max-h-[328px] self-start rounded-xl bg-gray-200" src={message.content} alt="" />
24
+ {/if}
25
+ </div>
26
  </div>
frontend/src/lib/Icons/IconDelete.svelte ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <script lang="ts">
2
+ export let classNames = "";
3
+ </script>
4
+
5
+ <svg
6
+ class={classNames}
7
+ xmlns="http://www.w3.org/2000/svg"
8
+ xmlns:xlink="http://www.w3.org/1999/xlink"
9
+ aria-hidden="true"
10
+ focusable="false"
11
+ role="img"
12
+ width="1em"
13
+ height="1em"
14
+ preserveAspectRatio="xMidYMid meet"
15
+ viewBox="0 0 32 32"
16
+ >
17
+ <path d="M12 12h2v12h-2z" fill="currentColor" /><path d="M18 12h2v12h-2z" fill="currentColor" /><path
18
+ d="M4 6v2h2v20a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V8h2V6zm4 22V8h16v20z"
19
+ fill="currentColor"
20
+ /><path d="M12 2h8v2h-8z" fill="currentColor" />
21
+ </svg>
frontend/src/lib/Input DELETED
File without changes
frontend/src/lib/initImage.ts ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ export const initImage =
2
+ "data:image/jpeg;base64,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";
frontend/src/lib/store.ts CHANGED
@@ -1,17 +1,25 @@
1
  import localforage from 'localforage';
2
  import { writable } from 'svelte/store';
3
- import type { ChatData } from './types';
4
  import { nanoid } from 'nanoid';
 
5
 
6
  const intitalChatId = nanoid();
 
 
 
 
 
 
 
7
  const initialData: ChatData = [{
8
  id: intitalChatId,
9
- messages: [],
10
  blurb: `New Chat - ${intitalChatId}`,
11
  timestamp: new Date().getTime()
12
  }];
13
 
14
-
15
  const loadingState = writable<string>('');
16
  const chatsStore = writable<ChatData>(initialData);
17
  const selectedChatId = writable<string>(intitalChatId);
@@ -29,7 +37,10 @@ localforage.getItem<ChatData>('chatsStore').then((value) => {
29
  }
30
  });
31
 
32
- chatsStore.subscribe((value) => localforage.setItem<ChatData>('chatsStore', value));
 
 
 
33
 
34
  localforage.getItem<string>('selectedChatId').then((value) => {
35
  if (value) {
@@ -39,39 +50,9 @@ localforage.getItem<string>('selectedChatId').then((value) => {
39
  }
40
  });
41
 
42
- selectedChatId.subscribe((value) => localforage.setItem<string>('selectedChatId', value));
43
-
44
- export { loadingState, chatsStore, selectedChatId };
45
-
46
- // import { writable } from 'svelte/store';
47
- // import { browser } from '$app/environment';
48
- // import type { ChatData } from './types';
49
- // import { nanoid } from 'nanoid';
50
-
51
-
52
- // const intitalChatId = nanoid();
53
- // const initialData: ChatData = [{
54
- // id: intitalChatId,
55
- // messages: [],
56
- // blurb: `New Chat - ${new Date().getTime()}`
57
- // }
58
- // ]
59
-
60
- // export const loadingState = writable<string>('');
61
- // export const chatsStore = writable<ChatData>(
62
- // browser ? JSON.parse(localStorage['chatsStore'] || JSON.stringify(initialData)) : initialData
63
- // );
64
- // chatsStore.subscribe((value) => {
65
- // if (browser) {
66
- // return (localStorage['chatsStore'] = JSON.stringify(value));
67
- // }
68
- // });
69
 
70
- // export const selectedChatId = writable<string>(
71
- // browser ? JSON.parse(localStorage['selectedChatId'] || JSON.stringify(intitalChatId)) : intitalChatId
72
- // );
73
- // selectedChatId.subscribe((value) => {
74
- // if (browser) {
75
- // return (localStorage['selectedChatId'] = JSON.stringify(value));
76
- // }
77
- // });
 
1
  import localforage from 'localforage';
2
  import { writable } from 'svelte/store';
3
+ import { MessageType, Sender, type ChatData, type Message } from './types';
4
  import { nanoid } from 'nanoid';
5
+ import { initImage } from './initImage';
6
 
7
  const intitalChatId = nanoid();
8
+ const initMessage: Message = {
9
+ id: nanoid(),
10
+ content: initImage,
11
+ sender: Sender.USER,
12
+ type: MessageType.IMAGE,
13
+ timestamp: new Date().getTime()
14
+ }
15
  const initialData: ChatData = [{
16
  id: intitalChatId,
17
+ messages: [initMessage],
18
  blurb: `New Chat - ${intitalChatId}`,
19
  timestamp: new Date().getTime()
20
  }];
21
 
22
+ const lastBase64Image = writable<string | null>(initMessage.content);
23
  const loadingState = writable<string>('');
24
  const chatsStore = writable<ChatData>(initialData);
25
  const selectedChatId = writable<string>(intitalChatId);
 
37
  }
38
  });
39
 
40
+ chatsStore.subscribe((value) => {
41
+ localforage.setItem<ChatData>('chatsStore', value)
42
+ return value;
43
+ });
44
 
45
  localforage.getItem<string>('selectedChatId').then((value) => {
46
  if (value) {
 
50
  }
51
  });
52
 
53
+ selectedChatId.subscribe((value) => {
54
+ localforage.setItem<string>('selectedChatId', value)
55
+ return value;
56
+ });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
 
58
+ export { loadingState, chatsStore, selectedChatId, lastBase64Image };
 
 
 
 
 
 
 
frontend/src/lib/types.ts CHANGED
@@ -26,4 +26,16 @@ export interface Chat {
26
  timestamp: number;
27
  }
28
 
29
- export type ChatData = Chat[];
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  timestamp: number;
27
  }
28
 
29
+ export type ChatData = Chat[];
30
+
31
+ export type ImageFile = {
32
+ name: string;
33
+ data: null | string;
34
+ is_file: boolean;
35
+ }
36
+ export interface InferenceResponse {
37
+ data: Array<Array<ImageFile | boolean> | null | number>;
38
+ is_generating: boolean;
39
+ duration: number;
40
+ average_duration: number;
41
+ }
frontend/src/lib/utils.ts ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ function blobToBase64(blob: Blob): Promise<string> {
2
+ return new Promise((resolve) => {
3
+ const reader = new FileReader();
4
+ reader.onloadend = () => resolve(reader.result as string);
5
+ reader.readAsDataURL(blob);
6
+ });
7
+ }
8
+
9
+ export const timeFormater = new Intl.DateTimeFormat('en-US', {
10
+ day: 'numeric',
11
+ month: 'short',
12
+ hour: 'numeric',
13
+ minute: 'numeric'
14
+ }).format;
15
+
16
+ export async function fetchImageBase64(url: string): Promise<string> {
17
+ const response = await fetch(url);
18
+ const blob = await response.blob();
19
+ const base64 = await blobToBase64(blob);
20
+ return base64;
21
+ }
22
+
frontend/static/statue.jpg ADDED