Linting
This commit is contained in:
		
							parent
							
								
									271a88e3a0
								
							
						
					
					
						commit
						5fb12c1f41
					
				| 
						 | 
					@ -1,20 +1,20 @@
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
  import Router, { location } from "svelte-spa-router";
 | 
					  import Router, { location } from 'svelte-spa-router'
 | 
				
			||||||
  import routes from "./routes";
 | 
					  import routes from './routes'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  import Navbar from "./lib/Navbar.svelte";
 | 
					  import Navbar from './lib/Navbar.svelte'
 | 
				
			||||||
  import Sidebar from "./lib/Sidebar.svelte";
 | 
					  import Sidebar from './lib/Sidebar.svelte'
 | 
				
			||||||
  import Footer from "./lib/Footer.svelte";
 | 
					  import Footer from './lib/Footer.svelte'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  import { apiKeyStorage, chatsStorage } from "./lib/Storage.svelte";
 | 
					  import { apiKeyStorage, chatsStorage } from './lib/Storage.svelte'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  $: sortedChats = $chatsStorage.sort((a, b) => b.id - a.id);
 | 
					  $: sortedChats = $chatsStorage.sort((a, b) => b.id - a.id)
 | 
				
			||||||
  $: apiKey = $apiKeyStorage;
 | 
					  $: apiKey = $apiKeyStorage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Check if the API key is passed in as a "key" query parameter - if so, save it
 | 
					  // Check if the API key is passed in as a "key" query parameter - if so, save it
 | 
				
			||||||
  const urlParams: URLSearchParams = new URLSearchParams(window.location.search);
 | 
					  const urlParams: URLSearchParams = new URLSearchParams(window.location.search)
 | 
				
			||||||
  if (urlParams.has("key")) {
 | 
					  if (urlParams.has('key')) {
 | 
				
			||||||
    apiKeyStorage.set(urlParams.get("key") as string);
 | 
					    apiKeyStorage.set(urlParams.get('key') as string)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
  //import { fetchEventSource } from "@microsoft/fetch-event-source";
 | 
					  // import { fetchEventSource } from "@microsoft/fetch-event-source";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  import { apiKeyStorage, chatsStorage, addMessage, clearMessages } from "./Storage.svelte";
 | 
					  import { apiKeyStorage, chatsStorage, addMessage, clearMessages } from './Storage.svelte'
 | 
				
			||||||
  import {
 | 
					  import {
 | 
				
			||||||
    type Request,
 | 
					    type Request,
 | 
				
			||||||
    type Response,
 | 
					    type Response,
 | 
				
			||||||
| 
						 | 
					@ -9,135 +9,135 @@
 | 
				
			||||||
    type Settings,
 | 
					    type Settings,
 | 
				
			||||||
    supportedModels,
 | 
					    supportedModels,
 | 
				
			||||||
    type ResponseModels,
 | 
					    type ResponseModels,
 | 
				
			||||||
    type SettingsSelect,
 | 
					    type SettingsSelect
 | 
				
			||||||
  } from "./Types.svelte";
 | 
					  } from './Types.svelte'
 | 
				
			||||||
  import Code from "./Code.svelte";
 | 
					  import Code from './Code.svelte'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  import { afterUpdate, onMount } from "svelte";
 | 
					  import { afterUpdate, onMount } from 'svelte'
 | 
				
			||||||
  import { replace } from "svelte-spa-router";
 | 
					  import { replace } from 'svelte-spa-router'
 | 
				
			||||||
  import SvelteMarkdown from "svelte-markdown";
 | 
					  import SvelteMarkdown from 'svelte-markdown'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export let params = { chatId: undefined };
 | 
					  export let params = { chatId: undefined }
 | 
				
			||||||
  let chatId: number = parseInt(params.chatId);
 | 
					  const chatId: number = parseInt(params.chatId)
 | 
				
			||||||
  let updating: boolean = false;
 | 
					  let updating: boolean = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  let input: HTMLTextAreaElement;
 | 
					  let input: HTMLTextAreaElement
 | 
				
			||||||
  let settings: HTMLDivElement;
 | 
					  let settings: HTMLDivElement
 | 
				
			||||||
  let recognition: any = null;
 | 
					  let recognition: any = null
 | 
				
			||||||
  let recording = false;
 | 
					  let recording = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const settingsMap: Settings[] = [
 | 
					  const settingsMap: Settings[] = [
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      key: "model",
 | 
					      key: 'model',
 | 
				
			||||||
      name: "Model",
 | 
					      name: 'Model',
 | 
				
			||||||
      default: "gpt-3.5-turbo",
 | 
					      default: 'gpt-3.5-turbo',
 | 
				
			||||||
      options: supportedModels,
 | 
					      options: supportedModels,
 | 
				
			||||||
      type: "select",
 | 
					      type: 'select'
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      key: "temperature",
 | 
					      key: 'temperature',
 | 
				
			||||||
      name: "Sampling Temperature",
 | 
					      name: 'Sampling Temperature',
 | 
				
			||||||
      default: 1,
 | 
					      default: 1,
 | 
				
			||||||
      min: 0,
 | 
					      min: 0,
 | 
				
			||||||
      max: 2,
 | 
					      max: 2,
 | 
				
			||||||
      step: 0.1,
 | 
					      step: 0.1,
 | 
				
			||||||
      type: "number",
 | 
					      type: 'number'
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      key: "top_p",
 | 
					      key: 'top_p',
 | 
				
			||||||
      name: "Nucleus Sampling",
 | 
					      name: 'Nucleus Sampling',
 | 
				
			||||||
      default: 1,
 | 
					      default: 1,
 | 
				
			||||||
      min: 0,
 | 
					      min: 0,
 | 
				
			||||||
      max: 1,
 | 
					      max: 1,
 | 
				
			||||||
      step: 0.1,
 | 
					      step: 0.1,
 | 
				
			||||||
      type: "number",
 | 
					      type: 'number'
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      key: "n",
 | 
					      key: 'n',
 | 
				
			||||||
      name: "Number of Messages",
 | 
					      name: 'Number of Messages',
 | 
				
			||||||
      default: 1,
 | 
					      default: 1,
 | 
				
			||||||
      min: 1,
 | 
					      min: 1,
 | 
				
			||||||
      max: 10,
 | 
					      max: 10,
 | 
				
			||||||
      step: 1,
 | 
					      step: 1,
 | 
				
			||||||
      type: "number",
 | 
					      type: 'number'
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      key: "max_tokens",
 | 
					      key: 'max_tokens',
 | 
				
			||||||
      name: "Max Tokens",
 | 
					      name: 'Max Tokens',
 | 
				
			||||||
      default: 0,
 | 
					      default: 0,
 | 
				
			||||||
      min: 0,
 | 
					      min: 0,
 | 
				
			||||||
      max: 32768,
 | 
					      max: 32768,
 | 
				
			||||||
      step: 1024,
 | 
					      step: 1024,
 | 
				
			||||||
      type: "number",
 | 
					      type: 'number'
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      key: "presence_penalty",
 | 
					      key: 'presence_penalty',
 | 
				
			||||||
      name: "Presence Penalty",
 | 
					      name: 'Presence Penalty',
 | 
				
			||||||
      default: 0,
 | 
					      default: 0,
 | 
				
			||||||
      min: -2,
 | 
					      min: -2,
 | 
				
			||||||
      max: 2,
 | 
					      max: 2,
 | 
				
			||||||
      step: 0.2,
 | 
					      step: 0.2,
 | 
				
			||||||
      type: "number",
 | 
					      type: 'number'
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
      key: "frequency_penalty",
 | 
					      key: 'frequency_penalty',
 | 
				
			||||||
      name: "Frequency Penalty",
 | 
					      name: 'Frequency Penalty',
 | 
				
			||||||
      default: 0,
 | 
					      default: 0,
 | 
				
			||||||
      min: -2,
 | 
					      min: -2,
 | 
				
			||||||
      max: 2,
 | 
					      max: 2,
 | 
				
			||||||
      step: 0.2,
 | 
					      step: 0.2,
 | 
				
			||||||
      type: "number",
 | 
					      type: 'number'
 | 
				
			||||||
    },
 | 
					    }
 | 
				
			||||||
  ];
 | 
					  ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  $: chat = $chatsStorage.find((chat) => chat.id === chatId);
 | 
					  $: chat = $chatsStorage.find((chat) => chat.id === chatId)
 | 
				
			||||||
  const token_price = 0.000002; // $0.002 per 1000 tokens
 | 
					  const tokenPrice = 0.000002 // $0.002 per 1000 tokens
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Focus the input on mount
 | 
					  // Focus the input on mount
 | 
				
			||||||
  onMount(async () => {
 | 
					  onMount(async () => {
 | 
				
			||||||
    input.focus();
 | 
					    input.focus()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Try to detect speech recognition support
 | 
					    // Try to detect speech recognition support
 | 
				
			||||||
    if ("SpeechRecognition" in window) {
 | 
					    if ('SpeechRecognition' in window) {
 | 
				
			||||||
      // @ts-ignore
 | 
					      // @ts-ignore
 | 
				
			||||||
      recognition = new SpeechRecognition();
 | 
					      recognition = new window.SpeechRecognition()
 | 
				
			||||||
    } else if ("webkitSpeechRecognition" in window) {
 | 
					    } else if ('webkitSpeechRecognition' in window) {
 | 
				
			||||||
      // @ts-ignore
 | 
					      // @ts-ignore
 | 
				
			||||||
      recognition = new webkitSpeechRecognition();
 | 
					      recognition = new window.webkitSpeechRecognition() // eslint-disable-line new-cap
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (recognition) {
 | 
					    if (recognition) {
 | 
				
			||||||
      recognition.interimResults = false;
 | 
					      recognition.interimResults = false
 | 
				
			||||||
      recognition.onstart = () => {
 | 
					      recognition.onstart = () => {
 | 
				
			||||||
        recording = true;
 | 
					        recording = true
 | 
				
			||||||
      };
 | 
					      }
 | 
				
			||||||
      recognition.onresult = (event) => {
 | 
					      recognition.onresult = (event) => {
 | 
				
			||||||
        // Stop speech recognition, submit the form and remove the pulse
 | 
					        // Stop speech recognition, submit the form and remove the pulse
 | 
				
			||||||
        const last = event.results.length - 1;
 | 
					        const last = event.results.length - 1
 | 
				
			||||||
        const text = event.results[last][0].transcript;
 | 
					        const text = event.results[last][0].transcript
 | 
				
			||||||
        input.value = text;
 | 
					        input.value = text
 | 
				
			||||||
        recognition.stop();
 | 
					        recognition.stop()
 | 
				
			||||||
        recording = false;
 | 
					        recording = false
 | 
				
			||||||
        submitForm(true);
 | 
					        submitForm(true)
 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      console.log("Speech recognition not supported");
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
  });
 | 
					    } else {
 | 
				
			||||||
 | 
					      console.log('Speech recognition not supported')
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Scroll to the bottom of the chat on update
 | 
					  // Scroll to the bottom of the chat on update
 | 
				
			||||||
  afterUpdate(() => {
 | 
					  afterUpdate(() => {
 | 
				
			||||||
    // Scroll to the bottom of the page after any updates to the messages array
 | 
					    // Scroll to the bottom of the page after any updates to the messages array
 | 
				
			||||||
    window.scrollTo(0, document.body.scrollHeight);
 | 
					    window.scrollTo(0, document.body.scrollHeight)
 | 
				
			||||||
    input.focus();
 | 
					    input.focus()
 | 
				
			||||||
  });
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Marked options
 | 
					  // Marked options
 | 
				
			||||||
  const markedownOptions = {
 | 
					  const markedownOptions = {
 | 
				
			||||||
    gfm: true, // Use GitHub Flavored Markdown
 | 
					    gfm: true, // Use GitHub Flavored Markdown
 | 
				
			||||||
    breaks: true, // Enable line breaks in markdown
 | 
					    breaks: true, // Enable line breaks in markdown
 | 
				
			||||||
    mangle: false, // Do not mangle email addresses
 | 
					    mangle: false // Do not mangle email addresses
 | 
				
			||||||
  };
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const sendRequest = async (messages: Message[]): Promise<Response> => {
 | 
					  const sendRequest = async (messages: Message[]): Promise<Response> => {
 | 
				
			||||||
    // Send API request
 | 
					    // Send API request
 | 
				
			||||||
| 
						 | 
					@ -160,154 +160,154 @@
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
    */
 | 
					    */
 | 
				
			||||||
    // Show updating bar
 | 
					    // Show updating bar
 | 
				
			||||||
    updating = true;
 | 
					    updating = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let response: Response;
 | 
					    let response: Response
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      const request: Request = {
 | 
					      const request: Request = {
 | 
				
			||||||
        // Submit only the role and content of the messages, provide the previous messages as well for context
 | 
					        // Submit only the role and content of the messages, provide the previous messages as well for context
 | 
				
			||||||
        messages: messages
 | 
					        messages: messages
 | 
				
			||||||
          .map((message): Message => {
 | 
					          .map((message): Message => {
 | 
				
			||||||
            const { role, content } = message;
 | 
					            const { role, content } = message
 | 
				
			||||||
            return { role, content };
 | 
					            return { role, content }
 | 
				
			||||||
          })
 | 
					          })
 | 
				
			||||||
          // Skip error messages
 | 
					          // Skip error messages
 | 
				
			||||||
          .filter((message) => message.role !== "error"),
 | 
					          .filter((message) => message.role !== 'error'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Provide the settings by mapping the settingsMap to key/value pairs
 | 
					        // Provide the settings by mapping the settingsMap to key/value pairs
 | 
				
			||||||
        ...settingsMap.reduce((acc, setting) => {
 | 
					        ...settingsMap.reduce((acc, setting) => {
 | 
				
			||||||
          const value = (settings.querySelector(`#settings-${setting.key}`) as HTMLInputElement).value;
 | 
					          const value = (settings.querySelector(`#settings-${setting.key}`) as HTMLInputElement).value
 | 
				
			||||||
          if (value) {
 | 
					          if (value) {
 | 
				
			||||||
            acc[setting.key] = setting.type === "number" ? parseFloat(value) : value;
 | 
					            acc[setting.key] = setting.type === 'number' ? parseFloat(value) : value
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          return acc
 | 
				
			||||||
 | 
					        }, {})
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
          return acc;
 | 
					 | 
				
			||||||
        }, {}),
 | 
					 | 
				
			||||||
      };
 | 
					 | 
				
			||||||
      response = await (
 | 
					      response = await (
 | 
				
			||||||
        await fetch("https://api.openai.com/v1/chat/completions", {
 | 
					        await fetch('https://api.openai.com/v1/chat/completions', {
 | 
				
			||||||
          method: "POST",
 | 
					          method: 'POST',
 | 
				
			||||||
          headers: {
 | 
					          headers: {
 | 
				
			||||||
            Authorization: `Bearer ${$apiKeyStorage}`,
 | 
					            Authorization: `Bearer ${$apiKeyStorage}`,
 | 
				
			||||||
            "Content-Type": "application/json",
 | 
					            'Content-Type': 'application/json'
 | 
				
			||||||
          },
 | 
					          },
 | 
				
			||||||
          body: JSON.stringify(request),
 | 
					          body: JSON.stringify(request)
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
      ).json();
 | 
					      ).json()
 | 
				
			||||||
    } catch (e) {
 | 
					    } catch (e) {
 | 
				
			||||||
      response = { error: { message: e.message } } as Response;
 | 
					      response = { error: { message: e.message } } as Response
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Hide updating bar
 | 
					    // Hide updating bar
 | 
				
			||||||
    updating = false;
 | 
					    updating = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return response;
 | 
					    return response
 | 
				
			||||||
  };
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const submitForm = async (recorded: boolean = false): Promise<void> => {
 | 
					  const submitForm = async (recorded: boolean = false): Promise<void> => {
 | 
				
			||||||
    // Compose the input message
 | 
					    // Compose the input message
 | 
				
			||||||
    const inputMessage: Message = { role: "user", content: input.value };
 | 
					    const inputMessage: Message = { role: 'user', content: input.value }
 | 
				
			||||||
    addMessage(chatId, inputMessage);
 | 
					    addMessage(chatId, inputMessage)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Clear the input value
 | 
					    // Clear the input value
 | 
				
			||||||
    input.value = "";
 | 
					    input.value = ''
 | 
				
			||||||
    input.blur();
 | 
					    input.blur()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Resize back to single line height
 | 
					    // Resize back to single line height
 | 
				
			||||||
    input.style.height = "auto";
 | 
					    input.style.height = 'auto'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const response = await sendRequest(chat.messages);
 | 
					    const response = await sendRequest(chat.messages)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (response.error) {
 | 
					    if (response.error) {
 | 
				
			||||||
      addMessage(chatId, {
 | 
					      addMessage(chatId, {
 | 
				
			||||||
        role: "error",
 | 
					        role: 'error',
 | 
				
			||||||
        content: `Error: ${response.error.message}`,
 | 
					        content: `Error: ${response.error.message}`
 | 
				
			||||||
      });
 | 
					      })
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      response.choices.map((choice) => {
 | 
					      response.choices.forEach((choice) => {
 | 
				
			||||||
        choice.message.usage = response.usage;
 | 
					        choice.message.usage = response.usage
 | 
				
			||||||
        // Remove whitespace around the message that the OpenAI API sometimes returns
 | 
					        // Remove whitespace around the message that the OpenAI API sometimes returns
 | 
				
			||||||
        choice.message.content = choice.message.content.trim();
 | 
					        choice.message.content = choice.message.content.trim()
 | 
				
			||||||
        addMessage(chatId, choice.message);
 | 
					        addMessage(chatId, choice.message)
 | 
				
			||||||
        // Use TTS to read the response, if query was recorded
 | 
					        // Use TTS to read the response, if query was recorded
 | 
				
			||||||
        if (recorded && "SpeechSynthesisUtterance" in window) {
 | 
					        if (recorded && 'SpeechSynthesisUtterance' in window) {
 | 
				
			||||||
          const utterance = new SpeechSynthesisUtterance(choice.message.content);
 | 
					          const utterance = new SpeechSynthesisUtterance(choice.message.content)
 | 
				
			||||||
          speechSynthesis.speak(utterance);
 | 
					          window.speechSynthesis.speak(utterance)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const suggestName = async (): Promise<void> => {
 | 
					  const suggestName = async (): Promise<void> => {
 | 
				
			||||||
    const suggestMessage: Message = {
 | 
					    const suggestMessage: Message = {
 | 
				
			||||||
      role: "user",
 | 
					      role: 'user',
 | 
				
			||||||
      content: "Can you give me a 5 word summary of this conversation's topic?",
 | 
					      content: "Can you give me a 5 word summary of this conversation's topic?"
 | 
				
			||||||
    };
 | 
					    }
 | 
				
			||||||
    addMessage(chatId, suggestMessage);
 | 
					    addMessage(chatId, suggestMessage)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const response = await sendRequest(chat.messages);
 | 
					    const response = await sendRequest(chat.messages)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (response.error) {
 | 
					    if (response.error) {
 | 
				
			||||||
      addMessage(chatId, {
 | 
					      addMessage(chatId, {
 | 
				
			||||||
        role: "error",
 | 
					        role: 'error',
 | 
				
			||||||
        content: `Error: ${response.error.message}`,
 | 
					        content: `Error: ${response.error.message}`
 | 
				
			||||||
      });
 | 
					      })
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      response.choices.map((choice) => {
 | 
					      response.choices.forEach((choice) => {
 | 
				
			||||||
        choice.message.usage = response.usage;
 | 
					        choice.message.usage = response.usage
 | 
				
			||||||
        addMessage(chatId, choice.message);
 | 
					        addMessage(chatId, choice.message)
 | 
				
			||||||
        chat.name = choice.message.content;
 | 
					        chat.name = choice.message.content
 | 
				
			||||||
        chatsStorage.set($chatsStorage);
 | 
					        chatsStorage.set($chatsStorage)
 | 
				
			||||||
      });
 | 
					      })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const deleteChat = () => {
 | 
					  const deleteChat = () => {
 | 
				
			||||||
    if (confirm("Are you sure you want to delete this chat?")) {
 | 
					    if (window.confirm('Are you sure you want to delete this chat?')) {
 | 
				
			||||||
      replace("/").then(() => {
 | 
					      replace('/').then(() => {
 | 
				
			||||||
        chatsStorage.update((chats) => chats.filter((chat) => chat.id !== chatId));
 | 
					        chatsStorage.update((chats) => chats.filter((chat) => chat.id !== chatId))
 | 
				
			||||||
      });
 | 
					      })
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const showSettings = async () => {
 | 
					  const showSettings = async () => {
 | 
				
			||||||
    settings.classList.add("is-active");
 | 
					    settings.classList.add('is-active')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Load available models from OpenAI
 | 
					    // Load available models from OpenAI
 | 
				
			||||||
    const allModels = (await (
 | 
					    const allModels = (await (
 | 
				
			||||||
      await fetch("https://api.openai.com/v1/models", {
 | 
					      await fetch('https://api.openai.com/v1/models', {
 | 
				
			||||||
        method: "GET",
 | 
					        method: 'GET',
 | 
				
			||||||
        headers: {
 | 
					        headers: {
 | 
				
			||||||
          Authorization: `Bearer ${$apiKeyStorage}`,
 | 
					          Authorization: `Bearer ${$apiKeyStorage}`,
 | 
				
			||||||
          "Content-Type": "application/json",
 | 
					          'Content-Type': 'application/json'
 | 
				
			||||||
        },
 | 
					        }
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
    ).json()) as ResponseModels;
 | 
					    ).json()) as ResponseModels
 | 
				
			||||||
    const filteredModels = supportedModels.filter((model) => allModels.data.find((m) => m.id === model));
 | 
					    const filteredModels = supportedModels.filter((model) => allModels.data.find((m) => m.id === model));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Update the models in the settings
 | 
					    // Update the models in the settings
 | 
				
			||||||
    (settingsMap[0] as SettingsSelect).options = filteredModels;
 | 
					    (settingsMap[0] as SettingsSelect).options = filteredModels
 | 
				
			||||||
  };
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const closeSettings = () => {
 | 
					  const closeSettings = () => {
 | 
				
			||||||
    settings.classList.remove("is-active");
 | 
					    settings.classList.remove('is-active')
 | 
				
			||||||
  };
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const clearSettings = () => {
 | 
					  const clearSettings = () => {
 | 
				
			||||||
    settingsMap.forEach((setting) => {
 | 
					    settingsMap.forEach((setting) => {
 | 
				
			||||||
      const input = settings.querySelector(`#settings-${setting.key}`) as HTMLInputElement;
 | 
					      const input = settings.querySelector(`#settings-${setting.key}`) as HTMLInputElement
 | 
				
			||||||
      input.value = "";
 | 
					      input.value = ''
 | 
				
			||||||
    });
 | 
					    })
 | 
				
			||||||
  };
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const recordToggle = () => {
 | 
					  const recordToggle = () => {
 | 
				
			||||||
    // Check if already recording - if so, stop - else start
 | 
					    // Check if already recording - if so, stop - else start
 | 
				
			||||||
    if (recording) {
 | 
					    if (recording) {
 | 
				
			||||||
      recognition?.stop();
 | 
					      recognition?.stop()
 | 
				
			||||||
      recording = false;
 | 
					      recording = false
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      recognition?.start();
 | 
					      recognition?.start()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<nav class="level chat-header">
 | 
					<nav class="level chat-header">
 | 
				
			||||||
| 
						 | 
					@ -316,21 +316,21 @@
 | 
				
			||||||
      <p class="subtitle is-5">
 | 
					      <p class="subtitle is-5">
 | 
				
			||||||
        {chat.name || `Chat ${chat.id}`}
 | 
					        {chat.name || `Chat ${chat.id}`}
 | 
				
			||||||
        <a
 | 
					        <a
 | 
				
			||||||
          href={"#"}
 | 
					          href={'#'}
 | 
				
			||||||
          class="greyscale ml-2 is-hidden has-text-weight-bold editbutton"
 | 
					          class="greyscale ml-2 is-hidden has-text-weight-bold editbutton"
 | 
				
			||||||
          title="Rename chat"
 | 
					          title="Rename chat"
 | 
				
			||||||
          on:click|preventDefault={() => {
 | 
					          on:click|preventDefault={() => {
 | 
				
			||||||
            let newChatName = prompt("Enter a new name for this chat", chat.name);
 | 
					            const newChatName = window.prompt('Enter a new name for this chat', chat.name)
 | 
				
			||||||
            if (newChatName) {
 | 
					            if (newChatName) {
 | 
				
			||||||
              chat.name = newChatName;
 | 
					              chat.name = newChatName
 | 
				
			||||||
              chatsStorage.set($chatsStorage);
 | 
					              chatsStorage.set($chatsStorage)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          }}
 | 
					          }}
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
          ✏️
 | 
					          ✏️
 | 
				
			||||||
        </a>
 | 
					        </a>
 | 
				
			||||||
        <a
 | 
					        <a
 | 
				
			||||||
          href={"#"}
 | 
					          href={'#'}
 | 
				
			||||||
          class="greyscale ml-2 is-hidden has-text-weight-bold editbutton"
 | 
					          class="greyscale ml-2 is-hidden has-text-weight-bold editbutton"
 | 
				
			||||||
          title="Suggest a chat name"
 | 
					          title="Suggest a chat name"
 | 
				
			||||||
          on:click|preventDefault={suggestName}
 | 
					          on:click|preventDefault={suggestName}
 | 
				
			||||||
| 
						 | 
					@ -338,7 +338,7 @@
 | 
				
			||||||
          💡
 | 
					          💡
 | 
				
			||||||
        </a>
 | 
					        </a>
 | 
				
			||||||
        <a
 | 
					        <a
 | 
				
			||||||
          href={"#"}
 | 
					          href={'#'}
 | 
				
			||||||
          class="greyscale ml-2 is-hidden editbutton"
 | 
					          class="greyscale ml-2 is-hidden editbutton"
 | 
				
			||||||
          title="Delete this chat"
 | 
					          title="Delete this chat"
 | 
				
			||||||
          on:click|preventDefault={deleteChat}
 | 
					          on:click|preventDefault={deleteChat}
 | 
				
			||||||
| 
						 | 
					@ -354,7 +354,7 @@
 | 
				
			||||||
      <button
 | 
					      <button
 | 
				
			||||||
        class="button is-warning"
 | 
					        class="button is-warning"
 | 
				
			||||||
        on:click={() => {
 | 
					        on:click={() => {
 | 
				
			||||||
          clearMessages(chatId);
 | 
					          clearMessages(chatId)
 | 
				
			||||||
        }}><span class="greyscale mr-2">🗑️</span> Clear messages</button
 | 
					        }}><span class="greyscale mr-2">🗑️</span> Clear messages</button
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
    </p>
 | 
					    </p>
 | 
				
			||||||
| 
						 | 
					@ -362,18 +362,18 @@
 | 
				
			||||||
</nav>
 | 
					</nav>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{#each chat.messages as message}
 | 
					{#each chat.messages as message}
 | 
				
			||||||
  {#if message.role === "user"}
 | 
					  {#if message.role === 'user'}
 | 
				
			||||||
    <article
 | 
					    <article
 | 
				
			||||||
      class="message is-info user-message"
 | 
					      class="message is-info user-message"
 | 
				
			||||||
      class:has-text-right={message.content.split("\n").filter((line) => line.trim()).length === 1}
 | 
					      class:has-text-right={message.content.split('\n').filter((line) => line.trim()).length === 1}
 | 
				
			||||||
    >
 | 
					    >
 | 
				
			||||||
      <div class="message-body content">
 | 
					      <div class="message-body content">
 | 
				
			||||||
        <a
 | 
					        <a
 | 
				
			||||||
          href={"#"}
 | 
					          href={'#'}
 | 
				
			||||||
          class="greyscale is-pulled-right ml-2 is-hidden editbutton"
 | 
					          class="greyscale is-pulled-right ml-2 is-hidden editbutton"
 | 
				
			||||||
          on:click={() => {
 | 
					          on:click={() => {
 | 
				
			||||||
            input.value = message.content;
 | 
					            input.value = message.content
 | 
				
			||||||
            input.focus();
 | 
					            input.focus()
 | 
				
			||||||
          }}
 | 
					          }}
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
          ✏️
 | 
					          ✏️
 | 
				
			||||||
| 
						 | 
					@ -382,19 +382,19 @@
 | 
				
			||||||
          source={message.content}
 | 
					          source={message.content}
 | 
				
			||||||
          options={markedownOptions}
 | 
					          options={markedownOptions}
 | 
				
			||||||
          renderers={{
 | 
					          renderers={{
 | 
				
			||||||
            code: Code,
 | 
					            code: Code
 | 
				
			||||||
          }}
 | 
					          }}
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
    </article>
 | 
					    </article>
 | 
				
			||||||
  {:else if message.role === "system" || message.role === "error"}
 | 
					  {:else if message.role === 'system' || message.role === 'error'}
 | 
				
			||||||
    <article class="message is-danger">
 | 
					    <article class="message is-danger">
 | 
				
			||||||
      <div class="message-body content">
 | 
					      <div class="message-body content">
 | 
				
			||||||
        <SvelteMarkdown
 | 
					        <SvelteMarkdown
 | 
				
			||||||
          source={message.content}
 | 
					          source={message.content}
 | 
				
			||||||
          options={markedownOptions}
 | 
					          options={markedownOptions}
 | 
				
			||||||
          renderers={{
 | 
					          renderers={{
 | 
				
			||||||
            code: Code,
 | 
					            code: Code
 | 
				
			||||||
          }}
 | 
					          }}
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
| 
						 | 
					@ -406,14 +406,14 @@
 | 
				
			||||||
          source={message.content}
 | 
					          source={message.content}
 | 
				
			||||||
          options={markedownOptions}
 | 
					          options={markedownOptions}
 | 
				
			||||||
          renderers={{
 | 
					          renderers={{
 | 
				
			||||||
            code: Code,
 | 
					            code: Code
 | 
				
			||||||
          }}
 | 
					          }}
 | 
				
			||||||
        />
 | 
					        />
 | 
				
			||||||
        {#if message.usage}
 | 
					        {#if message.usage}
 | 
				
			||||||
          <p class="is-size-7">
 | 
					          <p class="is-size-7">
 | 
				
			||||||
            This message was generated using <span class="has-text-weight-bold">{message.usage.total_tokens}</span>
 | 
					            This message was generated using <span class="has-text-weight-bold">{message.usage.total_tokens}</span>
 | 
				
			||||||
            tokens ~=
 | 
					            tokens ~=
 | 
				
			||||||
            <span class="has-text-weight-bold">${(message.usage.total_tokens * token_price).toFixed(6)}</span>
 | 
					            <span class="has-text-weight-bold">${(message.usage.total_tokens * tokenPrice).toFixed(6)}</span>
 | 
				
			||||||
          </p>
 | 
					          </p>
 | 
				
			||||||
        {/if}
 | 
					        {/if}
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
| 
						 | 
					@ -433,15 +433,15 @@
 | 
				
			||||||
      rows="1"
 | 
					      rows="1"
 | 
				
			||||||
      on:keydown={(e) => {
 | 
					      on:keydown={(e) => {
 | 
				
			||||||
        // Only send if Enter is pressed, not Shift+Enter
 | 
					        // Only send if Enter is pressed, not Shift+Enter
 | 
				
			||||||
        if (e.key === "Enter" && !e.shiftKey) {
 | 
					        if (e.key === 'Enter' && !e.shiftKey) {
 | 
				
			||||||
          submitForm();
 | 
					          submitForm()
 | 
				
			||||||
          e.preventDefault();
 | 
					          e.preventDefault()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }}
 | 
					      }}
 | 
				
			||||||
      on:input={(e) => {
 | 
					      on:input={(e) => {
 | 
				
			||||||
        // Resize the textarea to fit the content - auto is important to reset the height after deleting content
 | 
					        // Resize the textarea to fit the content - auto is important to reset the height after deleting content
 | 
				
			||||||
        input.style.height = "auto";
 | 
					        input.style.height = 'auto'
 | 
				
			||||||
        input.style.height = input.scrollHeight + "px";
 | 
					        input.style.height = input.scrollHeight + 'px'
 | 
				
			||||||
      }}
 | 
					      }}
 | 
				
			||||||
      bind:this={input}
 | 
					      bind:this={input}
 | 
				
			||||||
    />
 | 
					    />
 | 
				
			||||||
| 
						 | 
					@ -461,8 +461,8 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<svelte:window
 | 
					<svelte:window
 | 
				
			||||||
  on:keydown={(event) => {
 | 
					  on:keydown={(event) => {
 | 
				
			||||||
    if (event.key === "Escape") {
 | 
					    if (event.key === 'Escape') {
 | 
				
			||||||
      closeSettings();
 | 
					      closeSettings()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }}
 | 
					  }}
 | 
				
			||||||
/>
 | 
					/>
 | 
				
			||||||
| 
						 | 
					@ -482,7 +482,7 @@
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
          <div class="field-body">
 | 
					          <div class="field-body">
 | 
				
			||||||
            <div class="field">
 | 
					            <div class="field">
 | 
				
			||||||
              {#if setting.type === "number"}
 | 
					              {#if setting.type === 'number'}
 | 
				
			||||||
                <input
 | 
					                <input
 | 
				
			||||||
                  class="input"
 | 
					                  class="input"
 | 
				
			||||||
                  inputmode="decimal"
 | 
					                  inputmode="decimal"
 | 
				
			||||||
| 
						 | 
					@ -493,11 +493,11 @@
 | 
				
			||||||
                  step={setting.step}
 | 
					                  step={setting.step}
 | 
				
			||||||
                  placeholder={String(setting.default)}
 | 
					                  placeholder={String(setting.default)}
 | 
				
			||||||
                />
 | 
					                />
 | 
				
			||||||
              {:else if setting.type === "select"}
 | 
					              {:else if setting.type === 'select'}
 | 
				
			||||||
                <div class="select">
 | 
					                <div class="select">
 | 
				
			||||||
                  <select id="settings-{setting.key}">
 | 
					                  <select id="settings-{setting.key}">
 | 
				
			||||||
                    {#each setting.options as option}
 | 
					                    {#each setting.options as option}
 | 
				
			||||||
                      <option value={option} selected={option == setting.default}>{option}</option>
 | 
					                      <option value={option} selected={option === setting.default}>{option}</option>
 | 
				
			||||||
                    {/each}
 | 
					                    {/each}
 | 
				
			||||||
                  </select>
 | 
					                  </select>
 | 
				
			||||||
                </div>
 | 
					                </div>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,14 +1,14 @@
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
  import { Highlight } from "svelte-highlight";
 | 
					  import { Highlight } from 'svelte-highlight'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Import both dark and light styles
 | 
					  // Import both dark and light styles
 | 
				
			||||||
  import { github, githubDark } from "svelte-highlight/styles";
 | 
					  import { github, githubDark } from 'svelte-highlight/styles'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Style depends on system theme
 | 
					  // Style depends on system theme
 | 
				
			||||||
  const style = window.matchMedia("(prefers-color-scheme: dark)").matches ? githubDark : github;
 | 
					  const style = window.matchMedia('(prefers-color-scheme: dark)').matches ? githubDark : github
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Copy function for the code block
 | 
					  // Copy function for the code block
 | 
				
			||||||
  import copy from "copy-to-clipboard";
 | 
					  import copy from 'copy-to-clipboard'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Import all supported languages
 | 
					  // Import all supported languages
 | 
				
			||||||
  import {
 | 
					  import {
 | 
				
			||||||
| 
						 | 
					@ -22,80 +22,80 @@
 | 
				
			||||||
    shell,
 | 
					    shell,
 | 
				
			||||||
    php,
 | 
					    php,
 | 
				
			||||||
    plaintext,
 | 
					    plaintext,
 | 
				
			||||||
    type LanguageType,
 | 
					    type LanguageType
 | 
				
			||||||
  } from "svelte-highlight/languages";
 | 
					  } from 'svelte-highlight/languages'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export const type: "code" = "code";
 | 
					  export const type: 'code' = 'code'
 | 
				
			||||||
  export const raw: string = "";
 | 
					  export const raw: string = ''
 | 
				
			||||||
  export const codeBlockStyle: "indented" | undefined = undefined;
 | 
					  export const codeBlockStyle: 'indented' | undefined = undefined
 | 
				
			||||||
  export let lang: string | undefined = undefined;
 | 
					  export let lang: string | undefined
 | 
				
			||||||
  export let text: string;
 | 
					  export let text: string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Map lang string to LanguageType
 | 
					  // Map lang string to LanguageType
 | 
				
			||||||
  let language: LanguageType<string>;
 | 
					  let language: LanguageType<string>
 | 
				
			||||||
  switch (lang) {
 | 
					  switch (lang) {
 | 
				
			||||||
    case "js":
 | 
					    case 'js':
 | 
				
			||||||
    case "javascript":
 | 
					    case 'javascript':
 | 
				
			||||||
      language = javascript;
 | 
					      language = javascript
 | 
				
			||||||
      break;
 | 
					      break
 | 
				
			||||||
    case "py":
 | 
					    case 'py':
 | 
				
			||||||
    case "python":
 | 
					    case 'python':
 | 
				
			||||||
      language = python;
 | 
					      language = python
 | 
				
			||||||
      break;
 | 
					      break
 | 
				
			||||||
    case "ts":
 | 
					    case 'ts':
 | 
				
			||||||
    case "typescript":
 | 
					    case 'typescript':
 | 
				
			||||||
      language = typescript;
 | 
					      language = typescript
 | 
				
			||||||
      break;
 | 
					      break
 | 
				
			||||||
    case "rb":
 | 
					    case 'rb':
 | 
				
			||||||
    case "ruby":
 | 
					    case 'ruby':
 | 
				
			||||||
      language = ruby;
 | 
					      language = ruby
 | 
				
			||||||
      break;
 | 
					      break
 | 
				
			||||||
    case "go":
 | 
					    case 'go':
 | 
				
			||||||
    case "golang":
 | 
					    case 'golang':
 | 
				
			||||||
      language = go;
 | 
					      language = go
 | 
				
			||||||
      break;
 | 
					      break
 | 
				
			||||||
    case "java":
 | 
					    case 'java':
 | 
				
			||||||
      language = java;
 | 
					      language = java
 | 
				
			||||||
      break;
 | 
					      break
 | 
				
			||||||
    case "sql":
 | 
					    case 'sql':
 | 
				
			||||||
      language = sql;
 | 
					      language = sql
 | 
				
			||||||
      break;
 | 
					      break
 | 
				
			||||||
    case "sh":
 | 
					    case 'sh':
 | 
				
			||||||
    case "shell":
 | 
					    case 'shell':
 | 
				
			||||||
    case "bash":
 | 
					    case 'bash':
 | 
				
			||||||
      language = shell;
 | 
					      language = shell
 | 
				
			||||||
      break;
 | 
					      break
 | 
				
			||||||
    case "php":
 | 
					    case 'php':
 | 
				
			||||||
      language = php;
 | 
					      language = php
 | 
				
			||||||
      break;
 | 
					      break
 | 
				
			||||||
    default:
 | 
					    default:
 | 
				
			||||||
      language = plaintext;
 | 
					      language = plaintext
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // For copying code - reference: https://vyacheslavbasharov.com/blog/adding-click-to-copy-code-markdown-blog
 | 
					  // For copying code - reference: https://vyacheslavbasharov.com/blog/adding-click-to-copy-code-markdown-blog
 | 
				
			||||||
  const copyFunction = (event) => {
 | 
					  const copyFunction = (event) => {
 | 
				
			||||||
    // Get the button the user clicked on
 | 
					    // Get the button the user clicked on
 | 
				
			||||||
    const clickedElement = event.target as HTMLButtonElement;
 | 
					    const clickedElement = event.target as HTMLButtonElement
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Get the next element
 | 
					    // Get the next element
 | 
				
			||||||
    const nextElement = clickedElement.nextElementSibling;
 | 
					    const nextElement = clickedElement.nextElementSibling
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Modify the appearance of the button
 | 
					    // Modify the appearance of the button
 | 
				
			||||||
    const originalButtonContent = clickedElement.innerHTML;
 | 
					    const originalButtonContent = clickedElement.innerHTML
 | 
				
			||||||
    clickedElement.classList.add("is-success");
 | 
					    clickedElement.classList.add('is-success')
 | 
				
			||||||
    clickedElement.innerHTML = "Copied!";
 | 
					    clickedElement.innerHTML = 'Copied!'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Retrieve the code in the code block
 | 
					    // Retrieve the code in the code block
 | 
				
			||||||
    const codeBlock = (nextElement.querySelector("pre > code") as HTMLPreElement).innerText;
 | 
					    const codeBlock = (nextElement.querySelector('pre > code') as HTMLPreElement).innerText
 | 
				
			||||||
    copy(codeBlock);
 | 
					    copy(codeBlock)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Restored the button after copying the text in 1 second.
 | 
					    // Restored the button after copying the text in 1 second.
 | 
				
			||||||
    setTimeout(() => {
 | 
					    setTimeout(() => {
 | 
				
			||||||
      clickedElement.innerHTML = originalButtonContent;
 | 
					      clickedElement.innerHTML = originalButtonContent
 | 
				
			||||||
      clickedElement.classList.remove("is-success");
 | 
					      clickedElement.classList.remove('is-success')
 | 
				
			||||||
      clickedElement.blur();
 | 
					      clickedElement.blur()
 | 
				
			||||||
    }, 1000);
 | 
					    }, 1000)
 | 
				
			||||||
  };
 | 
					  }
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<svelte:head>
 | 
					<svelte:head>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,28 +1,28 @@
 | 
				
			||||||
<script context="module" lang="ts">
 | 
					<script context="module" lang="ts">
 | 
				
			||||||
  import { get } from "svelte/store";
 | 
					  import { get } from 'svelte/store'
 | 
				
			||||||
  import { chatsStorage } from "./Storage.svelte";
 | 
					  import { chatsStorage } from './Storage.svelte'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export const exportAsMarkdown = (chatId: number) => {
 | 
					  export const exportAsMarkdown = (chatId: number) => {
 | 
				
			||||||
    const chats = get(chatsStorage);
 | 
					    const chats = get(chatsStorage)
 | 
				
			||||||
    const chat = chats.find((chat) => chat.id === chatId);
 | 
					    const chat = chats.find((chat) => chat.id === chatId)
 | 
				
			||||||
    const messages = chat.messages;
 | 
					    const messages = chat.messages
 | 
				
			||||||
    console.log(chat);
 | 
					    console.log(chat)
 | 
				
			||||||
    let markdownContent = `# ${chat.name}\n`;
 | 
					    let markdownContent = `# ${chat.name}\n`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    messages.forEach((message) => {
 | 
					    messages.forEach((message) => {
 | 
				
			||||||
      const author = message.role;
 | 
					      const author = message.role
 | 
				
			||||||
      const content = message.content;
 | 
					      const content = message.content
 | 
				
			||||||
      const messageMarkdown = `## ${author}\n${content}\n\n`;
 | 
					      const messageMarkdown = `## ${author}\n${content}\n\n`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      markdownContent += messageMarkdown;
 | 
					      markdownContent += messageMarkdown
 | 
				
			||||||
    });
 | 
					    })
 | 
				
			||||||
    const blob = new Blob([markdownContent], { type: "text/markdown" });
 | 
					    const blob = new Blob([markdownContent], { type: 'text/markdown' })
 | 
				
			||||||
    const url = URL.createObjectURL(blob);
 | 
					    const url = URL.createObjectURL(blob)
 | 
				
			||||||
    const a = document.createElement("a");
 | 
					    const a = document.createElement('a')
 | 
				
			||||||
    a.download = `${chat.name}.md`;
 | 
					    a.download = `${chat.name}.md`
 | 
				
			||||||
    a.href = url;
 | 
					    a.href = url
 | 
				
			||||||
    document.body.appendChild(a);
 | 
					    document.body.appendChild(a)
 | 
				
			||||||
    a.click();
 | 
					    a.click()
 | 
				
			||||||
    document.body.removeChild(a);
 | 
					    document.body.removeChild(a)
 | 
				
			||||||
  };
 | 
					  }
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,21 +1,21 @@
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
  import { params, replace } from "svelte-spa-router";
 | 
					  import { params, replace } from 'svelte-spa-router'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  import { clearChats } from "./Storage.svelte";
 | 
					  import { clearChats } from './Storage.svelte'
 | 
				
			||||||
  import { exportAsMarkdown } from "./Export.svelte";
 | 
					  import { exportAsMarkdown } from './Export.svelte'
 | 
				
			||||||
  import type { Chat } from "./Types.svelte";
 | 
					  import type { Chat } from './Types.svelte'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export let sortedChats: Chat[];
 | 
					  export let sortedChats: Chat[]
 | 
				
			||||||
  export let apiKey: string;
 | 
					  export let apiKey: string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  $: activeChatId = $params && $params.chatId ? parseInt($params.chatId) : undefined;
 | 
					  $: activeChatId = $params && $params.chatId ? parseInt($params.chatId) : undefined
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<aside class="menu">
 | 
					<aside class="menu">
 | 
				
			||||||
  <p class="menu-label">Chats</p>
 | 
					  <p class="menu-label">Chats</p>
 | 
				
			||||||
  <ul class="menu-list">
 | 
					  <ul class="menu-list">
 | 
				
			||||||
    {#if sortedChats.length === 0}
 | 
					    {#if sortedChats.length === 0}
 | 
				
			||||||
      <li><a href={"#"}>No chats yet...</a></li>
 | 
					      <li><a href={'#'}>No chats yet...</a></li>
 | 
				
			||||||
    {:else}
 | 
					    {:else}
 | 
				
			||||||
      <li>
 | 
					      <li>
 | 
				
			||||||
        <ul>
 | 
					        <ul>
 | 
				
			||||||
| 
						 | 
					@ -33,35 +33,35 @@
 | 
				
			||||||
  <p class="menu-label">Actions</p>
 | 
					  <p class="menu-label">Actions</p>
 | 
				
			||||||
  <ul class="menu-list">
 | 
					  <ul class="menu-list">
 | 
				
			||||||
    <li>
 | 
					    <li>
 | 
				
			||||||
      <a href={"#/"} class="panel-block" class:is-disabled={!apiKey} class:is-active={!activeChatId}
 | 
					      <a href={'#/'} class="panel-block" class:is-disabled={!apiKey} class:is-active={!activeChatId}
 | 
				
			||||||
        ><span class="greyscale mr-2">🔑</span> API key</a
 | 
					        ><span class="greyscale mr-2">🔑</span> API key</a
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
    </li>
 | 
					    </li>
 | 
				
			||||||
    <li>
 | 
					    <li>
 | 
				
			||||||
      <a href={"#/chat/new"} class="panel-block" class:is-disabled={!apiKey}
 | 
					      <a href={'#/chat/new'} class="panel-block" class:is-disabled={!apiKey}
 | 
				
			||||||
        ><span class="greyscale mr-2">➕</span> New chat</a
 | 
					        ><span class="greyscale mr-2">➕</span> New chat</a
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
    </li>
 | 
					    </li>
 | 
				
			||||||
    <li>
 | 
					    <li>
 | 
				
			||||||
      <a
 | 
					      <a
 | 
				
			||||||
        href={"#/"}
 | 
					        href={'#/'}
 | 
				
			||||||
        class="panel-block"
 | 
					        class="panel-block"
 | 
				
			||||||
        class:is-disabled={!apiKey}
 | 
					        class:is-disabled={!apiKey}
 | 
				
			||||||
        on:click={() => {
 | 
					        on:click={() => {
 | 
				
			||||||
          replace("#/").then(() => {
 | 
					          replace('#/').then(() => {
 | 
				
			||||||
            clearChats();
 | 
					            clearChats()
 | 
				
			||||||
          });
 | 
					          })
 | 
				
			||||||
        }}><span class="greyscale mr-2">🗑️</span> Clear chats</a
 | 
					        }}><span class="greyscale mr-2">🗑️</span> Clear chats</a
 | 
				
			||||||
      >
 | 
					      >
 | 
				
			||||||
    </li>
 | 
					    </li>
 | 
				
			||||||
    {#if activeChatId}
 | 
					    {#if activeChatId}
 | 
				
			||||||
      <li>
 | 
					      <li>
 | 
				
			||||||
        <a
 | 
					        <a
 | 
				
			||||||
          href={"#/"}
 | 
					          href={'#/'}
 | 
				
			||||||
          class="panel-block"
 | 
					          class="panel-block"
 | 
				
			||||||
          class:is-disabled={!apiKey}
 | 
					          class:is-disabled={!apiKey}
 | 
				
			||||||
          on:click|preventDefault={() => {
 | 
					          on:click|preventDefault={() => {
 | 
				
			||||||
            exportAsMarkdown(activeChatId);
 | 
					            exportAsMarkdown(activeChatId)
 | 
				
			||||||
          }}><span class="greyscale mr-2">📥</span> Export chat</a
 | 
					          }}><span class="greyscale mr-2">📥</span> Export chat</a
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
      </li>
 | 
					      </li>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,57 +1,57 @@
 | 
				
			||||||
<script context="module" lang="ts">
 | 
					<script context="module" lang="ts">
 | 
				
			||||||
  import { persisted } from "svelte-local-storage-store";
 | 
					  import { persisted } from 'svelte-local-storage-store'
 | 
				
			||||||
  import { get } from "svelte/store";
 | 
					  import { get } from 'svelte/store'
 | 
				
			||||||
  import type { Chat, Message } from "./Types.svelte";
 | 
					  import type { Chat, Message } from './Types.svelte'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export const chatsStorage = persisted("chats", [] as Chat[]);
 | 
					  export const chatsStorage = persisted('chats', [] as Chat[])
 | 
				
			||||||
  export const apiKeyStorage = persisted("apiKey", null as string);
 | 
					  export const apiKeyStorage = persisted('apiKey', null as string)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export const addChat = (): number => {
 | 
					  export const addChat = (): number => {
 | 
				
			||||||
    const chats = get(chatsStorage);
 | 
					    const chats = get(chatsStorage)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Find the max chatId
 | 
					    // Find the max chatId
 | 
				
			||||||
    const chatId = chats.reduce((maxId, chat) => Math.max(maxId, chat.id), 0) + 1;
 | 
					    const chatId = chats.reduce((maxId, chat) => Math.max(maxId, chat.id), 0) + 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Add a new chat
 | 
					    // Add a new chat
 | 
				
			||||||
    chats.push({
 | 
					    chats.push({
 | 
				
			||||||
      id: chatId,
 | 
					      id: chatId,
 | 
				
			||||||
      name: `Chat ${chatId}`,
 | 
					      name: `Chat ${chatId}`,
 | 
				
			||||||
      messages: [],
 | 
					      messages: []
 | 
				
			||||||
    });
 | 
					    })
 | 
				
			||||||
    chatsStorage.set(chats);
 | 
					    chatsStorage.set(chats)
 | 
				
			||||||
    return chatId;
 | 
					    return chatId
 | 
				
			||||||
  };
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export const clearChats = () => {
 | 
					  export const clearChats = () => {
 | 
				
			||||||
    chatsStorage.set([]);
 | 
					    chatsStorage.set([])
 | 
				
			||||||
  };
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export const addMessage = (chatId: number, message: Message) => {
 | 
					  export const addMessage = (chatId: number, message: Message) => {
 | 
				
			||||||
    const chats = get(chatsStorage);
 | 
					    const chats = get(chatsStorage)
 | 
				
			||||||
    const chat = chats.find((chat) => chat.id === chatId);
 | 
					    const chat = chats.find((chat) => chat.id === chatId)
 | 
				
			||||||
    chat.messages.push(message);
 | 
					    chat.messages.push(message)
 | 
				
			||||||
    chatsStorage.set(chats);
 | 
					    chatsStorage.set(chats)
 | 
				
			||||||
  };
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export const editMessage = (chatId: number, index: number, newMessage: Message) => {
 | 
					  export const editMessage = (chatId: number, index: number, newMessage: Message) => {
 | 
				
			||||||
    const chats = get(chatsStorage);
 | 
					    const chats = get(chatsStorage)
 | 
				
			||||||
    const chat = chats.find((chat) => chat.id === chatId);
 | 
					    const chat = chats.find((chat) => chat.id === chatId)
 | 
				
			||||||
    chat.messages[index] = newMessage;
 | 
					    chat.messages[index] = newMessage
 | 
				
			||||||
    chat.messages.splice(index + 1); // remove the rest of the messages
 | 
					    chat.messages.splice(index + 1) // remove the rest of the messages
 | 
				
			||||||
    chatsStorage.set(chats);
 | 
					    chatsStorage.set(chats)
 | 
				
			||||||
  };
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export const clearMessages = (chatId: number) => {
 | 
					  export const clearMessages = (chatId: number) => {
 | 
				
			||||||
    const chats = get(chatsStorage);
 | 
					    const chats = get(chatsStorage)
 | 
				
			||||||
    const chat = chats.find((chat) => chat.id === chatId);
 | 
					    const chat = chats.find((chat) => chat.id === chatId)
 | 
				
			||||||
    chat.messages = [];
 | 
					    chat.messages = []
 | 
				
			||||||
    chatsStorage.set(chats);
 | 
					    chatsStorage.set(chats)
 | 
				
			||||||
  };
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export const deleteChat = (chatId: number) => {
 | 
					  export const deleteChat = (chatId: number) => {
 | 
				
			||||||
    const chats = get(chatsStorage);
 | 
					    const chats = get(chatsStorage)
 | 
				
			||||||
    const chatIndex = chats.findIndex((chat) => chat.id === chatId);
 | 
					    const chatIndex = chats.findIndex((chat) => chat.id === chatId)
 | 
				
			||||||
    chats.splice(chatIndex, 1);
 | 
					    chats.splice(chatIndex, 1)
 | 
				
			||||||
    chatsStorage.set(chats);
 | 
					    chatsStorage.set(chats)
 | 
				
			||||||
  };
 | 
					  }
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,21 +1,32 @@
 | 
				
			||||||
<script context="module" lang="ts">
 | 
					<script context="module" lang="ts">
 | 
				
			||||||
 | 
					  export type Usage = {
 | 
				
			||||||
 | 
					    completion_tokens: number;
 | 
				
			||||||
 | 
					    prompt_tokens: number;
 | 
				
			||||||
 | 
					    total_tokens: number;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  export type Message = {
 | 
				
			||||||
 | 
					    role: 'user' | 'assistant' | 'system' | 'error';
 | 
				
			||||||
 | 
					    content: string;
 | 
				
			||||||
 | 
					    usage?: Usage;
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export type Chat = {
 | 
					  export type Chat = {
 | 
				
			||||||
    id: number;
 | 
					    id: number;
 | 
				
			||||||
    name: string;
 | 
					    name: string;
 | 
				
			||||||
    messages: Message[];
 | 
					    messages: Message[];
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export type Message = {
 | 
					  // See: https://platform.openai.com/docs/models/model-endpoint-compatibility
 | 
				
			||||||
    role: "user" | "assistant" | "system" | "error";
 | 
					  export const supportedModels = [
 | 
				
			||||||
    content: string;
 | 
					    'gpt-4',
 | 
				
			||||||
    usage?: Usage;
 | 
					    'gpt-4-0314',
 | 
				
			||||||
  };
 | 
					    'gpt-4-32k',
 | 
				
			||||||
 | 
					    'gpt-4-32k-0314',
 | 
				
			||||||
  export type Usage = {
 | 
					    'gpt-3.5-turbo',
 | 
				
			||||||
    completion_tokens: number;
 | 
					    'gpt-3.5-turbo-0301'
 | 
				
			||||||
    prompt_tokens: number;
 | 
					  ]
 | 
				
			||||||
    total_tokens: number;
 | 
					  type Model = typeof supportedModels[number];
 | 
				
			||||||
  };
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export type Request = {
 | 
					  export type Request = {
 | 
				
			||||||
    model?: Model;
 | 
					    model?: Model;
 | 
				
			||||||
| 
						 | 
					@ -32,19 +43,8 @@
 | 
				
			||||||
    user?: string;
 | 
					    user?: string;
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // See: https://platform.openai.com/docs/models/model-endpoint-compatibility
 | 
					 | 
				
			||||||
  export const supportedModels = [
 | 
					 | 
				
			||||||
    "gpt-4",
 | 
					 | 
				
			||||||
    "gpt-4-0314",
 | 
					 | 
				
			||||||
    "gpt-4-32k",
 | 
					 | 
				
			||||||
    "gpt-4-32k-0314",
 | 
					 | 
				
			||||||
    "gpt-3.5-turbo",
 | 
					 | 
				
			||||||
    "gpt-3.5-turbo-0301",
 | 
					 | 
				
			||||||
  ];
 | 
					 | 
				
			||||||
  type Model = typeof supportedModels[number];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  type SettingsNumber = {
 | 
					  type SettingsNumber = {
 | 
				
			||||||
    type: "number";
 | 
					    type: 'number';
 | 
				
			||||||
    default: number;
 | 
					    default: number;
 | 
				
			||||||
    min: number;
 | 
					    min: number;
 | 
				
			||||||
    max: number;
 | 
					    max: number;
 | 
				
			||||||
| 
						 | 
					@ -52,7 +52,7 @@
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export type SettingsSelect = {
 | 
					  export type SettingsSelect = {
 | 
				
			||||||
    type: "select";
 | 
					    type: 'select';
 | 
				
			||||||
    default: Model;
 | 
					    default: Model;
 | 
				
			||||||
    options: Model[];
 | 
					    options: Model[];
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
| 
						 | 
					@ -86,7 +86,7 @@
 | 
				
			||||||
  export type Response = ResponseOK & ResponseError;
 | 
					  export type Response = ResponseOK & ResponseError;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export type ResponseModels = {
 | 
					  export type ResponseModels = {
 | 
				
			||||||
    object: "list";
 | 
					    object: 'list';
 | 
				
			||||||
    data: {
 | 
					    data: {
 | 
				
			||||||
      id: string;
 | 
					      id: string;
 | 
				
			||||||
    }[];
 | 
					    }[];
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue