chatgpt-web/src/lib/Chat.svelte

160 lines
4.4 KiB
Svelte

<script lang="ts">
//import { fetchEventSource } from "@microsoft/fetch-event-source";
import { apiKeyStorage, chatsStorage, addMessage, clearMessages } from "./Storage.svelte";
import type { Message } from "./Types.svelte";
import { marked } from "marked";
import { afterUpdate, onMount } from "svelte";
export let chatId: number;
let updating: boolean = false;
let input: HTMLInputElement;
$: chat = $chatsStorage.find((chat) => chat.id === chatId);
const token_price = 0.000002; // $0.002 per 1000 tokens
// Focus the input on mount
onMount(() => input.focus());
// Scroll to the bottom of the chat on update
afterUpdate(() => {
// Scroll to the bottom of the page after any updates to the messages array
window.scrollTo(0, document.body.scrollHeight);
input.focus();
});
const send = async () => {
// Compose the input message
const inputMessage: Message = { role: "user", content: input.value };
addMessage(chatId, inputMessage);
// Clear the input value
input.value = "";
// Show updating bar
updating = true;
// Send API request
/*
await fetchEventSource("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: {
Authorization:
`Bearer ${$apiKeyStorage}`,
"Content-Type": "text/event-stream",
},
body: JSON.stringify({
model: "gpt-3.5-turbo",
messages, // Provide the previous messages as well for context
// temperature: 1
// top_p: 1
// n: 1
stream: false,
// stop: null
max_tokens: 4096,
}),
onmessage(ev) {
console.log(ev);
},
onerror(err) {
throw err;
},
});
*/
const response = await (
await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: {
Authorization: `Bearer ${$apiKeyStorage}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
model: "gpt-3.5-turbo",
// Remove the usage property from all messages
messages: chat.messages.map((message): Message => {
const { usage, ...rest } = message;
return rest;
}),
// Provide the previous messages as well for context
// temperature: 1
// top_p: 1
// n: 1
//stream: false,
// stop: null
//max_tokens: 4096,
}),
})
).json();
console.log(response);
// Hide updating bar
updating = false;
if (response.error) {
addMessage(chatId, {
role: "system",
content: `Error: ${response.error.message}`,
});
} else {
response.choices.map((choice) => {
choice.message.usage = response.usage;
addMessage(chatId, choice.message);
});
}
};
</script>
<button
class="button is-warning is-pulled-right"
on:click={() => {
clearMessages(chatId);
}}><span class="greyscale mr-2">🗑️</span> Clear messages</button
>
<p class="subtitle">Chat {chatId}</p>
{#each chat.messages as message}
{#if message.role === "user"}
<article class="message is-info has-text-right">
<div class="message-body">{@html marked(message.content)}</div>
</article>
{:else if message.role === "system"}
<article class="message is-danger">
<div class="message-body">{@html marked(message.content)}</div>
</article>
{:else}
<article class="message is-success">
<div class="message-body">
{@html marked(message.content)}
{#if message.usage}
<p class="is-size-7">
This message was generated using <span class="has-text-weight-bold">{message.usage.total_tokens}</span>
tokens ~=
<span class="has-text-weight-bold">${(message.usage.total_tokens * token_price).toFixed(6)}</span>
</p>
{/if}
</div>
</article>
{/if}
{/each}
{#if updating}
<progress class="progress is-small is-dark" max="100" />
{/if}
<form class="field has-addons has-addons-right" on:submit|preventDefault={send}>
<p class="control is-expanded">
<input
class="input is-info is-medium is-focused"
type="text"
placeholder="Type your message here..."
bind:this={input}
/>
</p>
<p class="control">
<button class="button is-info is-medium" type="submit">Send</button>
</p>
</form>