Spaces:
Runtime error
Runtime error
first
Browse files- frontend/src/app.d.ts +6 -2
- frontend/src/lib/App.svelte +136 -66
- frontend/src/lib/ChatInput.svelte +69 -19
- frontend/src/lib/ChatMessage.svelte +17 -15
- frontend/src/lib/Icons/IconDelete.svelte +21 -0
- frontend/src/lib/Input +0 -0
- frontend/src/lib/initImage.ts +2 -0
- frontend/src/lib/store.ts +20 -39
- frontend/src/lib/types.ts +13 -1
- frontend/src/lib/utils.ts +22 -0
- frontend/static/statue.jpg +0 -0
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 =
|
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 |
-
|
|
|
|
|
|
|
|
|
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 |
-
|
|
|
|
|
|
|
42 |
const message: Message = {
|
43 |
sender: Sender.USER,
|
44 |
id: nanoid(),
|
45 |
-
type:
|
46 |
-
content:
|
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 |
-
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
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 |
-
|
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 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
|
128 |
-
|
129 |
-
|
130 |
-
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
-
|
139 |
-
|
140 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
<
|
168 |
-
|
169 |
-
|
170 |
-
{#
|
171 |
-
|
172 |
-
<div
|
173 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
174 |
{chat.id === $selectedChatId ? 'bg-gray-400' : ''}"
|
175 |
-
|
176 |
-
|
177 |
-
|
178 |
-
|
179 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
180 |
</div>
|
181 |
-
|
182 |
-
{
|
183 |
-
|
184 |
-
|
185 |
-
|
186 |
-
|
187 |
-
|
188 |
-
|
189 |
-
|
190 |
-
|
191 |
</div>
|
192 |
</div>
|
193 |
-
|
|
|
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
|
8 |
|
9 |
function onSubmit(event: Event) {
|
10 |
event.stopPropagation();
|
11 |
event.preventDefault();
|
12 |
event.stopImmediatePropagation();
|
13 |
-
|
14 |
if (textInput.trim() !== '') {
|
15 |
-
dispatch('submitMessage',
|
|
|
|
|
|
|
16 |
textInput = '';
|
17 |
}
|
18 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
19 |
onMount(() => {
|
20 |
inputEl.focus();
|
|
|
|
|
|
|
21 |
});
|
22 |
</script>
|
23 |
|
24 |
-
<
|
25 |
-
<
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
<
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
|
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
|
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) =>
|
|
|
|
|
|
|
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) =>
|
43 |
-
|
44 |
-
|
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 |
-
|
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
![]() |