Merge pull request #235 from Webifi/main
Experimental support for Petals/Llama 2
This commit is contained in:
		
						commit
						f877ac09ab
					
				| 
						 | 
					@ -27,6 +27,7 @@
 | 
				
			||||||
        "eslint-plugin-svelte3": "^4.0.0",
 | 
					        "eslint-plugin-svelte3": "^4.0.0",
 | 
				
			||||||
        "flourite": "^1.2.4",
 | 
					        "flourite": "^1.2.4",
 | 
				
			||||||
        "gpt-tokenizer": "^2.0.0",
 | 
					        "gpt-tokenizer": "^2.0.0",
 | 
				
			||||||
 | 
					        "llama-tokenizer-js": "^1.1.1",
 | 
				
			||||||
        "postcss": "^8.4.26",
 | 
					        "postcss": "^8.4.26",
 | 
				
			||||||
        "sass": "^1.63.6",
 | 
					        "sass": "^1.63.6",
 | 
				
			||||||
        "stacking-order": "^2.0.0",
 | 
					        "stacking-order": "^2.0.0",
 | 
				
			||||||
| 
						 | 
					@ -3182,6 +3183,12 @@
 | 
				
			||||||
        "node": ">= 0.8.0"
 | 
					        "node": ">= 0.8.0"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "node_modules/llama-tokenizer-js": {
 | 
				
			||||||
 | 
					      "version": "1.1.1",
 | 
				
			||||||
 | 
					      "resolved": "https://registry.npmjs.org/llama-tokenizer-js/-/llama-tokenizer-js-1.1.1.tgz",
 | 
				
			||||||
 | 
					      "integrity": "sha512-5H2oSJnSufWGhOw6hcCGAqJeB3POmeIBzRklH3cXs0L4MSAYdwoYTodni4j5YVo6jApdhaqaNVU66gNRgXeBRg==",
 | 
				
			||||||
 | 
					      "dev": true
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "node_modules/locate-path": {
 | 
					    "node_modules/locate-path": {
 | 
				
			||||||
      "version": "6.0.0",
 | 
					      "version": "6.0.0",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -33,6 +33,7 @@
 | 
				
			||||||
    "eslint-plugin-svelte3": "^4.0.0",
 | 
					    "eslint-plugin-svelte3": "^4.0.0",
 | 
				
			||||||
    "flourite": "^1.2.4",
 | 
					    "flourite": "^1.2.4",
 | 
				
			||||||
    "gpt-tokenizer": "^2.0.0",
 | 
					    "gpt-tokenizer": "^2.0.0",
 | 
				
			||||||
 | 
					    "llama-tokenizer-js": "^1.1.1",
 | 
				
			||||||
    "postcss": "^8.4.26",
 | 
					    "postcss": "^8.4.26",
 | 
				
			||||||
    "sass": "^1.63.6",
 | 
					    "sass": "^1.63.6",
 | 
				
			||||||
    "stacking-order": "^2.0.0",
 | 
					    "stacking-order": "^2.0.0",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,10 +5,12 @@
 | 
				
			||||||
  const endpointGenerations = import.meta.env.VITE_ENDPOINT_GENERATIONS || '/v1/images/generations'
 | 
					  const endpointGenerations = import.meta.env.VITE_ENDPOINT_GENERATIONS || '/v1/images/generations'
 | 
				
			||||||
  const endpointModels = import.meta.env.VITE_ENDPOINT_MODELS || '/v1/models'
 | 
					  const endpointModels = import.meta.env.VITE_ENDPOINT_MODELS || '/v1/models'
 | 
				
			||||||
  const endpointEmbeddings = import.meta.env.VITE_ENDPOINT_EMBEDDINGS || '/v1/embeddings'
 | 
					  const endpointEmbeddings = import.meta.env.VITE_ENDPOINT_EMBEDDINGS || '/v1/embeddings'
 | 
				
			||||||
 | 
					  const endpointPetals = import.meta.env.VITE_PEDALS_WEBSOCKET || 'wss://chat.petals.dev/api/v2/generate'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export const getApiBase = ():string => apiBase
 | 
					  export const getApiBase = ():string => apiBase
 | 
				
			||||||
  export const getEndpointCompletions = ():string => endpointCompletions
 | 
					  export const getEndpointCompletions = ():string => endpointCompletions
 | 
				
			||||||
  export const getEndpointGenerations = ():string => endpointGenerations
 | 
					  export const getEndpointGenerations = ():string => endpointGenerations
 | 
				
			||||||
  export const getEndpointModels = ():string => endpointModels
 | 
					  export const getEndpointModels = ():string => endpointModels
 | 
				
			||||||
  export const getEndpointEmbeddings = ():string => endpointEmbeddings
 | 
					  export const getEndpointEmbeddings = ():string => endpointEmbeddings
 | 
				
			||||||
 | 
					  export const getPetals = ():string => endpointPetals
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
| 
						 | 
					@ -40,6 +40,7 @@
 | 
				
			||||||
  import { openModal } from 'svelte-modals'
 | 
					  import { openModal } from 'svelte-modals'
 | 
				
			||||||
  import PromptInput from './PromptInput.svelte'
 | 
					  import PromptInput from './PromptInput.svelte'
 | 
				
			||||||
  import { ChatRequest } from './ChatRequest.svelte'
 | 
					  import { ChatRequest } from './ChatRequest.svelte'
 | 
				
			||||||
 | 
					  import { getModelDetail } from './Models.svelte'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export let params = { chatId: '' }
 | 
					  export let params = { chatId: '' }
 | 
				
			||||||
  const chatId: number = parseInt(params.chatId)
 | 
					  const chatId: number = parseInt(params.chatId)
 | 
				
			||||||
| 
						 | 
					@ -245,6 +246,19 @@
 | 
				
			||||||
    chatRequest.updating = true
 | 
					    chatRequest.updating = true
 | 
				
			||||||
    chatRequest.updatingMessage = ''
 | 
					    chatRequest.updatingMessage = ''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let doScroll = true
 | 
				
			||||||
 | 
					    let didScroll = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const checkUserScroll = (e: Event) => {
 | 
				
			||||||
 | 
					      const el = e.target as HTMLElement
 | 
				
			||||||
 | 
					      if (el && e.isTrusted && didScroll) {
 | 
				
			||||||
 | 
					        // from user
 | 
				
			||||||
 | 
					        doScroll = (window.innerHeight + window.scrollY + 10) >= document.body.offsetHeight
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    window.addEventListener('scroll', checkUserScroll)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      const response = await chatRequest.sendRequest($currentChatMessages, {
 | 
					      const response = await chatRequest.sendRequest($currentChatMessages, {
 | 
				
			||||||
        chat,
 | 
					        chat,
 | 
				
			||||||
| 
						 | 
					@ -252,7 +266,8 @@
 | 
				
			||||||
        streaming: chatSettings.stream,
 | 
					        streaming: chatSettings.stream,
 | 
				
			||||||
        fillMessage,
 | 
					        fillMessage,
 | 
				
			||||||
        onMessageChange: (messages) => {
 | 
					        onMessageChange: (messages) => {
 | 
				
			||||||
          scrollToBottom(true)
 | 
					          if (doScroll) scrollToBottom(true)
 | 
				
			||||||
 | 
					          didScroll = !!messages[0]?.content
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
      await response.promiseToFinish()
 | 
					      await response.promiseToFinish()
 | 
				
			||||||
| 
						 | 
					@ -264,6 +279,8 @@
 | 
				
			||||||
      console.error(e)
 | 
					      console.error(e)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
 | 
					    window.removeEventListener('scroll', checkUserScroll)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    chatRequest.updating = false
 | 
					    chatRequest.updating = false
 | 
				
			||||||
    chatRequest.updatingMessage = ''
 | 
					    chatRequest.updatingMessage = ''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -273,13 +290,16 @@
 | 
				
			||||||
  const suggestName = async (): Promise<void> => {
 | 
					  const suggestName = async (): Promise<void> => {
 | 
				
			||||||
    const suggestMessage: Message = {
 | 
					    const suggestMessage: Message = {
 | 
				
			||||||
      role: 'user',
 | 
					      role: 'user',
 | 
				
			||||||
      content: "Using appropriate language, please give a 5 word summary of this conversation's topic.",
 | 
					      content: "Using appropriate language, please tell me a short 6 word summary of this conversation's topic for use as a book title. Only respond with the summary.",
 | 
				
			||||||
      uuid: uuidv4()
 | 
					      uuid: uuidv4()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const suggestMessages = $currentChatMessages.slice(0, 10) // limit to first 10 messages
 | 
					    const suggestMessages = $currentChatMessages.slice(0, 10) // limit to first 10 messages
 | 
				
			||||||
    suggestMessages.push(suggestMessage)
 | 
					    suggestMessages.push(suggestMessage)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    chatRequest.updating = true
 | 
				
			||||||
 | 
					    chatRequest.updatingMessage = 'Getting suggestion for chat name...'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const response = await chatRequest.sendRequest(suggestMessages, {
 | 
					    const response = await chatRequest.sendRequest(suggestMessages, {
 | 
				
			||||||
      chat,
 | 
					      chat,
 | 
				
			||||||
      autoAddMessages: false,
 | 
					      autoAddMessages: false,
 | 
				
			||||||
| 
						 | 
					@ -297,7 +317,7 @@
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      response.getMessages().forEach(m => {
 | 
					      response.getMessages().forEach(m => {
 | 
				
			||||||
        const name = m.content.split(/\s+/).slice(0, 8).join(' ').trim()
 | 
					        const name = m.content.split(/\s+/).slice(0, 8).join(' ').replace(/^[^a-z0-9!?]+|[^a-z0-9!?]+$/gi, '').trim()
 | 
				
			||||||
        if (name) chat.name = name
 | 
					        if (name) chat.name = name
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
      saveChatStore()
 | 
					      saveChatStore()
 | 
				
			||||||
| 
						 | 
					@ -420,7 +440,7 @@
 | 
				
			||||||
  <div class="content has-text-centered running-total-container">
 | 
					  <div class="content has-text-centered running-total-container">
 | 
				
			||||||
    {#each Object.entries(chat.usage || {}) as [model, usage]}
 | 
					    {#each Object.entries(chat.usage || {}) as [model, usage]}
 | 
				
			||||||
    <p class="is-size-7 running-totals">
 | 
					    <p class="is-size-7 running-totals">
 | 
				
			||||||
      <em>{model}</em> total <span class="has-text-weight-bold">{usage.total_tokens}</span>
 | 
					      <em>{getModelDetail(model || '').label || model}</em> total <span class="has-text-weight-bold">{usage.total_tokens}</span>
 | 
				
			||||||
      tokens ~= <span class="has-text-weight-bold">${getPrice(usage, model).toFixed(6)}</span>
 | 
					      tokens ~= <span class="has-text-weight-bold">${getPrice(usage, model).toFixed(6)}</span>
 | 
				
			||||||
    </p>
 | 
					    </p>
 | 
				
			||||||
    {/each}
 | 
					    {/each}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,9 +1,9 @@
 | 
				
			||||||
<script context="module" lang="ts">
 | 
					<script context="module" lang="ts">
 | 
				
			||||||
import { setImage } from './ImageStore.svelte'
 | 
					import { setImage } from './ImageStore.svelte'
 | 
				
			||||||
 | 
					import { countTokens } from './Models.svelte'
 | 
				
			||||||
// TODO: Integrate API calls
 | 
					// TODO: Integrate API calls
 | 
				
			||||||
import { addMessage, getLatestKnownModel, saveChatStore, setLatestKnownModel, subtractRunningTotal, updateRunningTotal } from './Storage.svelte'
 | 
					import { addMessage, getLatestKnownModel, setLatestKnownModel, subtractRunningTotal, updateMessages, updateRunningTotal } from './Storage.svelte'
 | 
				
			||||||
import type { Chat, ChatCompletionOpts, ChatImage, Message, Model, Response, ResponseImage, Usage } from './Types.svelte'
 | 
					import type { Chat, ChatCompletionOpts, ChatImage, Message, Model, Response, ResponseImage, Usage } from './Types.svelte'
 | 
				
			||||||
import { encode } from 'gpt-tokenizer'
 | 
					 | 
				
			||||||
import { v4 as uuidv4 } from 'uuid'
 | 
					import { v4 as uuidv4 } from 'uuid'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class ChatCompletionResponse {
 | 
					export class ChatCompletionResponse {
 | 
				
			||||||
| 
						 | 
					@ -65,6 +65,10 @@ export class ChatCompletionResponse {
 | 
				
			||||||
    this.promptTokenCount = tokens
 | 
					    this.promptTokenCount = tokens
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  getPromptTokenCount (): number {
 | 
				
			||||||
 | 
					    return this.promptTokenCount
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async updateImageFromSyncResponse (response: ResponseImage, prompt: string, model: Model) {
 | 
					  async updateImageFromSyncResponse (response: ResponseImage, prompt: string, model: Model) {
 | 
				
			||||||
    this.setModel(model)
 | 
					    this.setModel(model)
 | 
				
			||||||
    for (let i = 0; i < response.data.length; i++) {
 | 
					    for (let i = 0; i < response.data.length; i++) {
 | 
				
			||||||
| 
						 | 
					@ -138,10 +142,10 @@ export class ChatCompletionResponse {
 | 
				
			||||||
        message.content = this.initialFillMerge(message.content, choice.delta?.content)
 | 
					        message.content = this.initialFillMerge(message.content, choice.delta?.content)
 | 
				
			||||||
        message.content += choice.delta.content
 | 
					        message.content += choice.delta.content
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      completionTokenCount += encode(message.content).length
 | 
					      completionTokenCount += countTokens(this.model, message.content)
 | 
				
			||||||
      message.model = response.model
 | 
					      message.model = response.model
 | 
				
			||||||
      message.finish_reason = choice.finish_reason
 | 
					      message.finish_reason = choice.finish_reason
 | 
				
			||||||
      message.streaming = choice.finish_reason === null && !this.finished
 | 
					      message.streaming = !choice.finish_reason && !this.finished
 | 
				
			||||||
      this.messages[i] = message
 | 
					      this.messages[i] = message
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
    // total up the tokens
 | 
					    // total up the tokens
 | 
				
			||||||
| 
						 | 
					@ -171,15 +175,15 @@ export class ChatCompletionResponse {
 | 
				
			||||||
      } as Message)
 | 
					      } as Message)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    this.notifyMessageChange()
 | 
					    this.notifyMessageChange()
 | 
				
			||||||
    setTimeout(() => this.finish(), 250) // give others a chance to signal the finish first
 | 
					    setTimeout(() => this.finish(), 200) // give others a chance to signal the finish first
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  updateFromClose (force: boolean = false): void {
 | 
					  updateFromClose (force: boolean = false): void {
 | 
				
			||||||
    if (!this.finished && !this.error && !this.messages?.find(m => m.content)) {
 | 
					    if (!this.finished && !this.error && !this.messages?.find(m => m.content)) {
 | 
				
			||||||
      if (!force) return setTimeout(() => this.updateFromClose(true), 250) as any
 | 
					      if (!force) return setTimeout(() => this.updateFromClose(true), 300) as any
 | 
				
			||||||
      return this.updateFromError('Unexpected connection termination')
 | 
					      if (!this.finished) return this.updateFromError('Unexpected connection termination')
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    setTimeout(() => this.finish(), 250) // give others a chance to signal the finish first
 | 
					    setTimeout(() => this.finish(), 260) // give others a chance to signal the finish first
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  onMessageChange = (listener: (m: Message[]) => void): number =>
 | 
					  onMessageChange = (listener: (m: Message[]) => void): number =>
 | 
				
			||||||
| 
						 | 
					@ -209,10 +213,10 @@ export class ChatCompletionResponse {
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private finish = (): void => {
 | 
					  private finish = (): void => {
 | 
				
			||||||
 | 
					    this.messages.forEach(m => { m.streaming = false }) // make sure all are marked stopped
 | 
				
			||||||
 | 
					    updateMessages(this.chat.id)
 | 
				
			||||||
    if (this.finished) return
 | 
					    if (this.finished) return
 | 
				
			||||||
    this.finished = true
 | 
					    this.finished = true
 | 
				
			||||||
    this.messages.forEach(m => { m.streaming = false }) // make sure all are marked stopped
 | 
					 | 
				
			||||||
    saveChatStore()
 | 
					 | 
				
			||||||
    const message = this.messages[0]
 | 
					    const message = this.messages[0]
 | 
				
			||||||
    const model = this.model || getLatestKnownModel(this.chat.settings.model)
 | 
					    const model = this.model || getLatestKnownModel(this.chat.settings.model)
 | 
				
			||||||
    if (message) {
 | 
					    if (message) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
  import { replace } from 'svelte-spa-router'
 | 
					  import { replace } from 'svelte-spa-router'
 | 
				
			||||||
  import type { Chat } from './Types.svelte'
 | 
					  import type { Chat } from './Types.svelte'
 | 
				
			||||||
  import { apiKeyStorage, deleteChat, pinMainMenu, saveChatStore } from './Storage.svelte'
 | 
					  import { deleteChat, hasActiveModels, pinMainMenu, saveChatStore } from './Storage.svelte'
 | 
				
			||||||
  import Fa from 'svelte-fa/src/fa.svelte'
 | 
					  import Fa from 'svelte-fa/src/fa.svelte'
 | 
				
			||||||
  import { faTrash, faCircleCheck, faPencil } from '@fortawesome/free-solid-svg-icons/index'
 | 
					  import { faTrash, faCircleCheck, faPencil } from '@fortawesome/free-solid-svg-icons/index'
 | 
				
			||||||
  import { faMessage } from '@fortawesome/free-regular-svg-icons/index'
 | 
					  import { faMessage } from '@fortawesome/free-regular-svg-icons/index'
 | 
				
			||||||
| 
						 | 
					@ -86,7 +86,7 @@
 | 
				
			||||||
  <a 
 | 
					  <a 
 | 
				
			||||||
    href={`#/chat/${chat.id}`}
 | 
					    href={`#/chat/${chat.id}`}
 | 
				
			||||||
    class="chat-menu-item"
 | 
					    class="chat-menu-item"
 | 
				
			||||||
    class:is-waiting={waitingForConfirm} class:is-disabled={!$apiKeyStorage} class:is-active={activeChatId === chat.id}
 | 
					    class:is-waiting={waitingForConfirm} class:is-disabled={!hasActiveModels()} class:is-active={activeChatId === chat.id}
 | 
				
			||||||
    on:click={() => { $pinMainMenu = false }} >
 | 
					    on:click={() => { $pinMainMenu = false }} >
 | 
				
			||||||
    {#if waitingForConfirm}
 | 
					    {#if waitingForConfirm}
 | 
				
			||||||
    <a class="is-pulled-right is-hidden px-1 py-0 has-text-weight-bold delete-button" href={'$'} on:click|preventDefault={() => delChat()}><Fa icon={faCircleCheck} /></a>
 | 
					    <a class="is-pulled-right is-hidden px-1 py-0 has-text-weight-bold delete-button" href={'$'} on:click|preventDefault={() => delChat()}><Fa icon={faCircleCheck} /></a>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -18,7 +18,7 @@
 | 
				
			||||||
    faEyeSlash
 | 
					    faEyeSlash
 | 
				
			||||||
  } from '@fortawesome/free-solid-svg-icons/index'
 | 
					  } from '@fortawesome/free-solid-svg-icons/index'
 | 
				
			||||||
  import { faSquareMinus, faSquarePlus as faSquarePlusOutline } from '@fortawesome/free-regular-svg-icons/index'
 | 
					  import { faSquareMinus, faSquarePlus as faSquarePlusOutline } from '@fortawesome/free-regular-svg-icons/index'
 | 
				
			||||||
  import { apiKeyStorage, addChatFromJSON, chatsStorage, checkStateChange, clearChats, clearMessages, copyChat, globalStorage, setGlobalSettingValueByKey, showSetChatSettings, pinMainMenu, getChat, deleteChat, saveChatStore, saveCustomProfile } from './Storage.svelte'
 | 
					  import { addChatFromJSON, chatsStorage, checkStateChange, clearChats, clearMessages, copyChat, globalStorage, setGlobalSettingValueByKey, showSetChatSettings, pinMainMenu, getChat, deleteChat, saveChatStore, saveCustomProfile, hasActiveModels } from './Storage.svelte'
 | 
				
			||||||
  import { exportAsMarkdown, exportChatAsJSON } from './Export.svelte'
 | 
					  import { exportAsMarkdown, exportChatAsJSON } from './Export.svelte'
 | 
				
			||||||
  import { newNameForProfile, restartProfile } from './Profiles.svelte'
 | 
					  import { newNameForProfile, restartProfile } from './Profiles.svelte'
 | 
				
			||||||
  import { replace } from 'svelte-spa-router'
 | 
					  import { replace } from 'svelte-spa-router'
 | 
				
			||||||
| 
						 | 
					@ -173,7 +173,7 @@
 | 
				
			||||||
        <span class="menu-icon"><Fa icon={faGear}/></span> Chat Profile Settings
 | 
					        <span class="menu-icon"><Fa icon={faGear}/></span> Chat Profile Settings
 | 
				
			||||||
      </a>
 | 
					      </a>
 | 
				
			||||||
      <hr class="dropdown-divider">
 | 
					      <hr class="dropdown-divider">
 | 
				
			||||||
      <a href={'#'} class:is-disabled={!$apiKeyStorage} on:click|preventDefault={() => { $apiKeyStorage && close(); $apiKeyStorage && startNewChatWithWarning(chatId) }} class="dropdown-item">
 | 
					      <a href={'#'} class:is-disabled={!hasActiveModels()} on:click|preventDefault={() => { hasActiveModels() && close(); hasActiveModels() && startNewChatWithWarning(chatId) }} class="dropdown-item">
 | 
				
			||||||
        <span class="menu-icon"><Fa icon={faSquarePlus}/></span> New Chat from Default
 | 
					        <span class="menu-icon"><Fa icon={faSquarePlus}/></span> New Chat from Default
 | 
				
			||||||
      </a>
 | 
					      </a>
 | 
				
			||||||
      <a href={'#'} class:is-disabled={!chatId} on:click|preventDefault={() => { chatId && close(); chatId && startNewChatFromChatId(chatId) }} class="dropdown-item">
 | 
					      <a href={'#'} class:is-disabled={!chatId} on:click|preventDefault={() => { chatId && close(); chatId && startNewChatFromChatId(chatId) }} class="dropdown-item">
 | 
				
			||||||
| 
						 | 
					@ -196,14 +196,14 @@
 | 
				
			||||||
      <a href={'#'} class="dropdown-item" class:is-disabled={!chatId} on:click|preventDefault={() => { close(); exportChatAsJSON(chatId) }}>
 | 
					      <a href={'#'} class="dropdown-item" class:is-disabled={!chatId} on:click|preventDefault={() => { close(); exportChatAsJSON(chatId) }}>
 | 
				
			||||||
        <span class="menu-icon"><Fa icon={faDownload}/></span> Backup Chat JSON
 | 
					        <span class="menu-icon"><Fa icon={faDownload}/></span> Backup Chat JSON
 | 
				
			||||||
      </a>
 | 
					      </a>
 | 
				
			||||||
      <a href={'#'} class="dropdown-item" class:is-disabled={!$apiKeyStorage} on:click|preventDefault={() => { if (chatId) close(); chatFileInput.click() }}>
 | 
					      <a href={'#'} class="dropdown-item" class:is-disabled={!hasActiveModels()} on:click|preventDefault={() => { if (chatId) close(); chatFileInput.click() }}>
 | 
				
			||||||
        <span class="menu-icon"><Fa icon={faUpload}/></span> Restore Chat JSON
 | 
					        <span class="menu-icon"><Fa icon={faUpload}/></span> Restore Chat JSON
 | 
				
			||||||
      </a>
 | 
					      </a>
 | 
				
			||||||
      <a href={'#'} class="dropdown-item" class:is-disabled={!chatId} on:click|preventDefault={() => { if (chatId) close(); exportAsMarkdown(chatId) }}>
 | 
					      <a href={'#'} class="dropdown-item" class:is-disabled={!chatId} on:click|preventDefault={() => { if (chatId) close(); exportAsMarkdown(chatId) }}>
 | 
				
			||||||
        <span class="menu-icon"><Fa icon={faFileExport}/></span> Export Chat Markdown
 | 
					        <span class="menu-icon"><Fa icon={faFileExport}/></span> Export Chat Markdown
 | 
				
			||||||
      </a>
 | 
					      </a>
 | 
				
			||||||
      <hr class="dropdown-divider">
 | 
					      <hr class="dropdown-divider">
 | 
				
			||||||
      <a href={'#'} class="dropdown-item" class:is-disabled={!$apiKeyStorage} on:click|preventDefault={() => { if (chatId) close(); profileFileInput.click() }}>
 | 
					      <a href={'#'} class="dropdown-item" class:is-disabled={!hasActiveModels()} on:click|preventDefault={() => { if (chatId) close(); profileFileInput.click() }}>
 | 
				
			||||||
        <span class="menu-icon"><Fa icon={faUpload}/></span> Restore Profile JSON
 | 
					        <span class="menu-icon"><Fa icon={faUpload}/></span> Restore Profile JSON
 | 
				
			||||||
      </a>
 | 
					      </a>
 | 
				
			||||||
      <hr class="dropdown-divider">
 | 
					      <hr class="dropdown-divider">
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,11 +5,12 @@
 | 
				
			||||||
    import type { Chat, ChatCompletionOpts, ChatSettings, Message, Model, Request, RequestImageGeneration } from './Types.svelte'
 | 
					    import type { Chat, ChatCompletionOpts, ChatSettings, Message, Model, Request, RequestImageGeneration } from './Types.svelte'
 | 
				
			||||||
    import { deleteMessage, getChatSettingValueNullDefault, insertMessages, getApiKey, addError, currentChatMessages, getMessages, updateMessages, deleteSummaryMessage } from './Storage.svelte'
 | 
					    import { deleteMessage, getChatSettingValueNullDefault, insertMessages, getApiKey, addError, currentChatMessages, getMessages, updateMessages, deleteSummaryMessage } from './Storage.svelte'
 | 
				
			||||||
    import { scrollToBottom, scrollToMessage } from './Util.svelte'
 | 
					    import { scrollToBottom, scrollToMessage } from './Util.svelte'
 | 
				
			||||||
    import { getRequestSettingList, defaultModel } from './Settings.svelte'
 | 
					    import { getDefaultModel, getRequestSettingList } from './Settings.svelte'
 | 
				
			||||||
    import { EventStreamContentType, fetchEventSource } from '@microsoft/fetch-event-source'
 | 
					 | 
				
			||||||
    import { getApiBase, getEndpointCompletions, getEndpointGenerations } from './ApiUtil.svelte'
 | 
					 | 
				
			||||||
    import { v4 as uuidv4 } from 'uuid'
 | 
					    import { v4 as uuidv4 } from 'uuid'
 | 
				
			||||||
    import { get } from 'svelte/store'
 | 
					    import { get } from 'svelte/store'
 | 
				
			||||||
 | 
					    import { getEndpoint, getModelDetail } from './Models.svelte'
 | 
				
			||||||
 | 
					    import { runOpenAiCompletionRequest } from './ChatRequestOpenAi.svelte'
 | 
				
			||||||
 | 
					    import { runPetalsCompletionRequest } from './ChatRequestPetals.svelte'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class ChatRequest {
 | 
					export class ChatRequest {
 | 
				
			||||||
      constructor () {
 | 
					      constructor () {
 | 
				
			||||||
| 
						 | 
					@ -25,6 +26,15 @@ export class ChatRequest {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      setChat (chat: Chat) {
 | 
					      setChat (chat: Chat) {
 | 
				
			||||||
        this.chat = chat
 | 
					        this.chat = chat
 | 
				
			||||||
 | 
					        this.chat.settings.model = this.getModel()
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      getChat (): Chat {
 | 
				
			||||||
 | 
					        return this.chat
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      getChatSettings (): ChatSettings {
 | 
				
			||||||
 | 
					        return this.chat.settings
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // Common error handler
 | 
					      // Common error handler
 | 
				
			||||||
| 
						 | 
					@ -77,7 +87,7 @@ export class ChatRequest {
 | 
				
			||||||
        const chatResponse = new ChatCompletionResponse(opts)
 | 
					        const chatResponse = new ChatCompletionResponse(opts)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
          const response = await fetch(getApiBase() + getEndpointGenerations(), fetchOptions)
 | 
					          const response = await fetch(getEndpoint('dall-e-' + size), fetchOptions)
 | 
				
			||||||
          if (!response.ok) {
 | 
					          if (!response.ok) {
 | 
				
			||||||
            await _this.handleError(response)
 | 
					            await _this.handleError(response)
 | 
				
			||||||
          } else {
 | 
					          } else {
 | 
				
			||||||
| 
						 | 
					@ -159,6 +169,8 @@ export class ChatRequest {
 | 
				
			||||||
          const spl = chatSettings.sendSystemPromptLast
 | 
					          const spl = chatSettings.sendSystemPromptLast
 | 
				
			||||||
          const sp = messagePayload[0]
 | 
					          const sp = messagePayload[0]
 | 
				
			||||||
          if (sp) {
 | 
					          if (sp) {
 | 
				
			||||||
 | 
					            const lastSp = sp.content.split('::END-PROMPT::')
 | 
				
			||||||
 | 
					            sp.content = lastSp[0].trim()
 | 
				
			||||||
            if (messagePayload.length > 1) {
 | 
					            if (messagePayload.length > 1) {
 | 
				
			||||||
              sp.content = sp.content.replace(/::STARTUP::[\s\S]*::EOM::/, '::EOM::')
 | 
					              sp.content = sp.content.replace(/::STARTUP::[\s\S]*::EOM::/, '::EOM::')
 | 
				
			||||||
              sp.content = sp.content.replace(/::STARTUP::[\s\S]*::START-PROMPT::/, '::START-PROMPT::')
 | 
					              sp.content = sp.content.replace(/::STARTUP::[\s\S]*::START-PROMPT::/, '::START-PROMPT::')
 | 
				
			||||||
| 
						 | 
					@ -170,7 +182,7 @@ export class ChatRequest {
 | 
				
			||||||
            if (spl) {
 | 
					            if (spl) {
 | 
				
			||||||
              messagePayload.shift()
 | 
					              messagePayload.shift()
 | 
				
			||||||
              if (messagePayload[messagePayload.length - 1]?.role === 'user') {
 | 
					              if (messagePayload[messagePayload.length - 1]?.role === 'user') {
 | 
				
			||||||
                messagePayload.splice(-2, 0, sp)
 | 
					                messagePayload.splice(-1, 0, sp)
 | 
				
			||||||
              } else {
 | 
					              } else {
 | 
				
			||||||
                messagePayload.push(sp)
 | 
					                messagePayload.push(sp)
 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
| 
						 | 
					@ -196,11 +208,15 @@ export class ChatRequest {
 | 
				
			||||||
              }).filter(m => m.content.length)
 | 
					              }).filter(m => m.content.length)
 | 
				
			||||||
              messagePayload.splice(spl ? 0 : 1, 0, ...ms.concat(splitSystem.map(s => ({ role: 'system', content: s.trim() } as Message)).filter(m => m.content.length)))
 | 
					              messagePayload.splice(spl ? 0 : 1, 0, ...ms.concat(splitSystem.map(s => ({ role: 'system', content: s.trim() } as Message)).filter(m => m.content.length)))
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					            const lastSpC = lastSp[1]?.trim() || ''
 | 
				
			||||||
 | 
					            if (lastSpC.length) {
 | 
				
			||||||
 | 
					              messagePayload.push({ role: 'system', content: lastSpC } as Message)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Get token counts
 | 
					        // Get token counts
 | 
				
			||||||
        const promptTokenCount = countPromptTokens(messagePayload, model)
 | 
					        const promptTokenCount = countPromptTokens(messagePayload, model, chat)
 | 
				
			||||||
        const maxAllowed = maxTokens - (promptTokenCount + 1)
 | 
					        const maxAllowed = maxTokens - (promptTokenCount + 1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Build the API request body
 | 
					        // Build the API request body
 | 
				
			||||||
| 
						 | 
					@ -239,6 +255,9 @@ export class ChatRequest {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Set-up and make the request
 | 
					        // Set-up and make the request
 | 
				
			||||||
        const chatResponse = new ChatCompletionResponse(opts)
 | 
					        const chatResponse = new ChatCompletionResponse(opts)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const modelDetail = getModelDetail(model)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
          // Add out token count to the response handler
 | 
					          // Add out token count to the response handler
 | 
				
			||||||
          // (streaming doesn't return counts, so we need to do it client side)
 | 
					          // (streaming doesn't return counts, so we need to do it client side)
 | 
				
			||||||
| 
						 | 
					@ -248,88 +267,11 @@ export class ChatRequest {
 | 
				
			||||||
          // so we deal with it ourselves
 | 
					          // so we deal with it ourselves
 | 
				
			||||||
          _this.controller = new AbortController()
 | 
					          _this.controller = new AbortController()
 | 
				
			||||||
          const signal = _this.controller.signal
 | 
					          const signal = _this.controller.signal
 | 
				
			||||||
          const abortListener = (e:Event) => {
 | 
					 | 
				
			||||||
            _this.updating = false
 | 
					 | 
				
			||||||
            _this.updatingMessage = ''
 | 
					 | 
				
			||||||
            chatResponse.updateFromError('User aborted request.')
 | 
					 | 
				
			||||||
            signal.removeEventListener('abort', abortListener)
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
          signal.addEventListener('abort', abortListener)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
          const fetchOptions = {
 | 
					          if (modelDetail.type === 'Petals') {
 | 
				
			||||||
            method: 'POST',
 | 
					            await runPetalsCompletionRequest(request, _this as any, chatResponse as any, signal, opts)
 | 
				
			||||||
            headers: {
 | 
					 | 
				
			||||||
              Authorization: `Bearer ${getApiKey()}`,
 | 
					 | 
				
			||||||
              'Content-Type': 'application/json'
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            body: JSON.stringify(request),
 | 
					 | 
				
			||||||
            signal
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
          if (opts.streaming) {
 | 
					 | 
				
			||||||
            /**
 | 
					 | 
				
			||||||
             * Streaming request/response
 | 
					 | 
				
			||||||
             * We'll get the response a token at a time, as soon as they are ready
 | 
					 | 
				
			||||||
            */
 | 
					 | 
				
			||||||
            chatResponse.onFinish(() => {
 | 
					 | 
				
			||||||
              _this.updating = false
 | 
					 | 
				
			||||||
              _this.updatingMessage = ''
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
            fetchEventSource(getApiBase() + getEndpointCompletions(), {
 | 
					 | 
				
			||||||
              ...fetchOptions,
 | 
					 | 
				
			||||||
              openWhenHidden: true,
 | 
					 | 
				
			||||||
              onmessage (ev) {
 | 
					 | 
				
			||||||
              // Remove updating indicator
 | 
					 | 
				
			||||||
                _this.updating = 1 // hide indicator, but still signal we're updating
 | 
					 | 
				
			||||||
                _this.updatingMessage = ''
 | 
					 | 
				
			||||||
                // console.log('ev.data', ev.data)
 | 
					 | 
				
			||||||
                if (!chatResponse.hasFinished()) {
 | 
					 | 
				
			||||||
                  if (ev.data === '[DONE]') {
 | 
					 | 
				
			||||||
                  // ?? anything to do when "[DONE]"?
 | 
					 | 
				
			||||||
          } else {
 | 
					          } else {
 | 
				
			||||||
                    const data = JSON.parse(ev.data)
 | 
					            await runOpenAiCompletionRequest(request, _this as any, chatResponse as any, signal, opts)
 | 
				
			||||||
                    // console.log('data', data)
 | 
					 | 
				
			||||||
                    window.setTimeout(() => { chatResponse.updateFromAsyncResponse(data) }, 1)
 | 
					 | 
				
			||||||
                  }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
              },
 | 
					 | 
				
			||||||
              onclose () {
 | 
					 | 
				
			||||||
                _this.updating = false
 | 
					 | 
				
			||||||
                _this.updatingMessage = ''
 | 
					 | 
				
			||||||
                chatResponse.updateFromClose()
 | 
					 | 
				
			||||||
              },
 | 
					 | 
				
			||||||
              onerror (err) {
 | 
					 | 
				
			||||||
                console.error(err)
 | 
					 | 
				
			||||||
                throw err
 | 
					 | 
				
			||||||
              },
 | 
					 | 
				
			||||||
              async onopen (response) {
 | 
					 | 
				
			||||||
                if (response.ok && response.headers.get('content-type') === EventStreamContentType) {
 | 
					 | 
				
			||||||
                // everything's good
 | 
					 | 
				
			||||||
                } else {
 | 
					 | 
				
			||||||
                // client-side errors are usually non-retriable:
 | 
					 | 
				
			||||||
                  await _this.handleError(response)
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
              }
 | 
					 | 
				
			||||||
            }).catch(err => {
 | 
					 | 
				
			||||||
              _this.updating = false
 | 
					 | 
				
			||||||
              _this.updatingMessage = ''
 | 
					 | 
				
			||||||
              chatResponse.updateFromError(err.message)
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
          } else {
 | 
					 | 
				
			||||||
            /**
 | 
					 | 
				
			||||||
             * Non-streaming request/response
 | 
					 | 
				
			||||||
             * We'll get the response all at once, after a long delay
 | 
					 | 
				
			||||||
             */
 | 
					 | 
				
			||||||
            const response = await fetch(getApiBase() + getEndpointCompletions(), fetchOptions)
 | 
					 | 
				
			||||||
            if (!response.ok) {
 | 
					 | 
				
			||||||
              await _this.handleError(response)
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
              const json = await response.json()
 | 
					 | 
				
			||||||
              // Remove updating indicator
 | 
					 | 
				
			||||||
              _this.updating = false
 | 
					 | 
				
			||||||
              _this.updatingMessage = ''
 | 
					 | 
				
			||||||
              chatResponse.updateFromSyncResponse(json)
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        } catch (e) {
 | 
					        } catch (e) {
 | 
				
			||||||
        // console.error(e)
 | 
					        // console.error(e)
 | 
				
			||||||
| 
						 | 
					@ -341,12 +283,13 @@ export class ChatRequest {
 | 
				
			||||||
        return chatResponse
 | 
					        return chatResponse
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      private getModel (): Model {
 | 
					      getModel (): Model {
 | 
				
			||||||
        return this.chat.settings.model || defaultModel
 | 
					        return this.chat.settings.model || getDefaultModel()
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      private buildHiddenPromptPrefixMessages (messages: Message[], insert:boolean = false): Message[] {
 | 
					      private buildHiddenPromptPrefixMessages (messages: Message[], insert:boolean = false): Message[] {
 | 
				
			||||||
        const chatSettings = this.chat.settings
 | 
					        const chat = this.chat
 | 
				
			||||||
 | 
					        const chatSettings = chat.settings
 | 
				
			||||||
        const hiddenPromptPrefix = mergeProfileFields(chatSettings, chatSettings.hiddenPromptPrefix).trim()
 | 
					        const hiddenPromptPrefix = mergeProfileFields(chatSettings, chatSettings.hiddenPromptPrefix).trim()
 | 
				
			||||||
        const lastMessage = messages[messages.length - 1]
 | 
					        const lastMessage = messages[messages.length - 1]
 | 
				
			||||||
        const isContinue = lastMessage?.role === 'assistant' && lastMessage.finish_reason === 'length'
 | 
					        const isContinue = lastMessage?.role === 'assistant' && lastMessage.finish_reason === 'length'
 | 
				
			||||||
| 
						 | 
					@ -356,9 +299,9 @@ export class ChatRequest {
 | 
				
			||||||
          const results = hiddenPromptPrefix.split(/[\s\r\n]*::EOM::[\s\r\n]*/).reduce((a, m) => {
 | 
					          const results = hiddenPromptPrefix.split(/[\s\r\n]*::EOM::[\s\r\n]*/).reduce((a, m) => {
 | 
				
			||||||
            m = m.trim()
 | 
					            m = m.trim()
 | 
				
			||||||
            if (m.length) {
 | 
					            if (m.length) {
 | 
				
			||||||
              if (m.match(/[[USER_PROMPT]]/)) {
 | 
					              if (m.match(/\[\[USER_PROMPT\]\]/)) {
 | 
				
			||||||
                injectedPrompt = true
 | 
					                injectedPrompt = true
 | 
				
			||||||
                m.replace(/[[USER_PROMPT]]/g, lastMessage.content)
 | 
					                m = m.replace(/\[\[USER_PROMPT\]\]/g, lastMessage.content)
 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
              a.push({ role: a.length % 2 === 0 ? 'user' : 'assistant', content: m } as Message)
 | 
					              a.push({ role: a.length % 2 === 0 ? 'user' : 'assistant', content: m } as Message)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
| 
						 | 
					@ -377,7 +320,7 @@ export class ChatRequest {
 | 
				
			||||||
              lastMessage.skipOnce = true
 | 
					              lastMessage.skipOnce = true
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          if (injectedPrompt) results.pop()
 | 
					          if (injectedPrompt) messages.pop()
 | 
				
			||||||
          return results
 | 
					          return results
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return []
 | 
					        return []
 | 
				
			||||||
| 
						 | 
					@ -387,11 +330,11 @@ export class ChatRequest {
 | 
				
			||||||
       * Gets an estimate of how many extra tokens will be added that won't be part of the visible messages
 | 
					       * Gets an estimate of how many extra tokens will be added that won't be part of the visible messages
 | 
				
			||||||
       * @param filtered
 | 
					       * @param filtered
 | 
				
			||||||
       */
 | 
					       */
 | 
				
			||||||
      private getTokenCountPadding (filtered: Message[]): number {
 | 
					      private getTokenCountPadding (filtered: Message[], chat: Chat): number {
 | 
				
			||||||
        let result = 0
 | 
					        let result = 0
 | 
				
			||||||
        // add cost of hiddenPromptPrefix
 | 
					        // add cost of hiddenPromptPrefix
 | 
				
			||||||
        result += this.buildHiddenPromptPrefixMessages(filtered)
 | 
					        result += this.buildHiddenPromptPrefixMessages(filtered)
 | 
				
			||||||
          .reduce((a, m) => a + countMessageTokens(m, this.getModel()), 0)
 | 
					          .reduce((a, m) => a + countMessageTokens(m, this.getModel(), chat), 0)
 | 
				
			||||||
        // more here eventually?
 | 
					        // more here eventually?
 | 
				
			||||||
        return result
 | 
					        return result
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
| 
						 | 
					@ -413,10 +356,10 @@ export class ChatRequest {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Get extra counts for when the prompts are finally sent.
 | 
					        // Get extra counts for when the prompts are finally sent.
 | 
				
			||||||
        const countPadding = this.getTokenCountPadding(filtered)
 | 
					        const countPadding = this.getTokenCountPadding(filtered, chat)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // See if we have enough to apply any of the reduction modes
 | 
					        // See if we have enough to apply any of the reduction modes
 | 
				
			||||||
        const fullPromptSize = countPromptTokens(filtered, model) + countPadding
 | 
					        const fullPromptSize = countPromptTokens(filtered, model, chat) + countPadding
 | 
				
			||||||
        if (fullPromptSize < chatSettings.summaryThreshold) return await continueRequest() // nothing to do yet
 | 
					        if (fullPromptSize < chatSettings.summaryThreshold) return await continueRequest() // nothing to do yet
 | 
				
			||||||
        const overMax = fullPromptSize > maxTokens * 0.95
 | 
					        const overMax = fullPromptSize > maxTokens * 0.95
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -439,12 +382,12 @@ export class ChatRequest {
 | 
				
			||||||
           * *************************************************************
 | 
					           * *************************************************************
 | 
				
			||||||
           */
 | 
					           */
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
          let promptSize = countPromptTokens(top.concat(rw), model) + countPadding
 | 
					          let promptSize = countPromptTokens(top.concat(rw), model, chat) + countPadding
 | 
				
			||||||
          while (rw.length && rw.length > pinBottom && promptSize >= chatSettings.summaryThreshold) {
 | 
					          while (rw.length && rw.length > pinBottom && promptSize >= chatSettings.summaryThreshold) {
 | 
				
			||||||
            const rolled = rw.shift()
 | 
					            const rolled = rw.shift()
 | 
				
			||||||
            // Hide messages we're "rolling"
 | 
					            // Hide messages we're "rolling"
 | 
				
			||||||
            if (rolled) rolled.suppress = true
 | 
					            if (rolled) rolled.suppress = true
 | 
				
			||||||
            promptSize = countPromptTokens(top.concat(rw), model) + countPadding
 | 
					            promptSize = countPromptTokens(top.concat(rw), model, chat) + countPadding
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          // Run a new request, now with the rolled messages hidden
 | 
					          // Run a new request, now with the rolled messages hidden
 | 
				
			||||||
          return await _this.sendRequest(get(currentChatMessages), {
 | 
					          return await _this.sendRequest(get(currentChatMessages), {
 | 
				
			||||||
| 
						 | 
					@ -460,26 +403,26 @@ export class ChatRequest {
 | 
				
			||||||
          const bottom = rw.slice(0 - pinBottom)
 | 
					          const bottom = rw.slice(0 - pinBottom)
 | 
				
			||||||
          let continueCounter = chatSettings.summaryExtend + 1
 | 
					          let continueCounter = chatSettings.summaryExtend + 1
 | 
				
			||||||
          rw = rw.slice(0, 0 - pinBottom)
 | 
					          rw = rw.slice(0, 0 - pinBottom)
 | 
				
			||||||
          let reductionPoolSize = countPromptTokens(rw, model)
 | 
					          let reductionPoolSize = countPromptTokens(rw, model, chat)
 | 
				
			||||||
          const ss = Math.abs(chatSettings.summarySize)
 | 
					          const ss = Math.abs(chatSettings.summarySize)
 | 
				
			||||||
          const getSS = ():number => (ss < 1 && ss > 0)
 | 
					          const getSS = ():number => (ss < 1 && ss > 0)
 | 
				
			||||||
            ? Math.round(reductionPoolSize * ss) // If summarySize between 0 and 1, use percentage of reduced
 | 
					            ? Math.round(reductionPoolSize * ss) // If summarySize between 0 and 1, use percentage of reduced
 | 
				
			||||||
            : Math.min(ss, reductionPoolSize * 0.5) // If > 1, use token count
 | 
					            : Math.min(ss, reductionPoolSize * 0.5) // If > 1, use token count
 | 
				
			||||||
          const topSize = countPromptTokens(top, model)
 | 
					          const topSize = countPromptTokens(top, model, chat)
 | 
				
			||||||
          let maxSummaryTokens = getSS()
 | 
					          let maxSummaryTokens = getSS()
 | 
				
			||||||
          let promptSummary = prepareSummaryPrompt(chatId, maxSummaryTokens)
 | 
					          let promptSummary = prepareSummaryPrompt(chatId, maxSummaryTokens)
 | 
				
			||||||
          const summaryRequest = { role: 'user', content: promptSummary } as Message
 | 
					          const summaryRequest = { role: 'user', content: promptSummary } as Message
 | 
				
			||||||
          let promptSummarySize = countMessageTokens(summaryRequest, model)
 | 
					          let promptSummarySize = countMessageTokens(summaryRequest, model, chat)
 | 
				
			||||||
          // Make sure there is enough room to generate the summary, and try to make sure
 | 
					          // Make sure there is enough room to generate the summary, and try to make sure
 | 
				
			||||||
          // the last prompt is a user prompt as that seems to work better for summaries
 | 
					          // the last prompt is a user prompt as that seems to work better for summaries
 | 
				
			||||||
          while ((topSize + reductionPoolSize + promptSummarySize + maxSummaryTokens) >= maxTokens ||
 | 
					          while ((topSize + reductionPoolSize + promptSummarySize + maxSummaryTokens) >= maxTokens ||
 | 
				
			||||||
              (reductionPoolSize >= 100 && rw[rw.length - 1]?.role !== 'user')) {
 | 
					              (reductionPoolSize >= 100 && rw[rw.length - 1]?.role !== 'user')) {
 | 
				
			||||||
            bottom.unshift(rw.pop() as Message)
 | 
					            bottom.unshift(rw.pop() as Message)
 | 
				
			||||||
            reductionPoolSize = countPromptTokens(rw, model)
 | 
					            reductionPoolSize = countPromptTokens(rw, model, chat)
 | 
				
			||||||
            maxSummaryTokens = getSS()
 | 
					            maxSummaryTokens = getSS()
 | 
				
			||||||
            promptSummary = prepareSummaryPrompt(chatId, maxSummaryTokens)
 | 
					            promptSummary = prepareSummaryPrompt(chatId, maxSummaryTokens)
 | 
				
			||||||
            summaryRequest.content = promptSummary
 | 
					            summaryRequest.content = promptSummary
 | 
				
			||||||
            promptSummarySize = countMessageTokens(summaryRequest, model)
 | 
					            promptSummarySize = countMessageTokens(summaryRequest, model, chat)
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          if (reductionPoolSize < 50) {
 | 
					          if (reductionPoolSize < 50) {
 | 
				
			||||||
            if (overMax) addError(chatId, 'Check summary settings. Unable to summarize enough messages.')
 | 
					            if (overMax) addError(chatId, 'Check summary settings. Unable to summarize enough messages.')
 | 
				
			||||||
| 
						 | 
					@ -565,10 +508,10 @@ export class ChatRequest {
 | 
				
			||||||
              // Try to get more of it
 | 
					              // Try to get more of it
 | 
				
			||||||
              delete summaryResponse.finish_reason
 | 
					              delete summaryResponse.finish_reason
 | 
				
			||||||
              _this.updatingMessage = 'Summarizing more...'
 | 
					              _this.updatingMessage = 'Summarizing more...'
 | 
				
			||||||
              let _recount = countPromptTokens(top.concat(rw).concat([summaryRequest]).concat([summaryResponse]), model)
 | 
					              let _recount = countPromptTokens(top.concat(rw).concat([summaryRequest]).concat([summaryResponse]), model, chat)
 | 
				
			||||||
              while (rw.length && (_recount + maxSummaryTokens >= maxTokens)) {
 | 
					              while (rw.length && (_recount + maxSummaryTokens >= maxTokens)) {
 | 
				
			||||||
                rw.shift()
 | 
					                rw.shift()
 | 
				
			||||||
                _recount = countPromptTokens(top.concat(rw).concat([summaryRequest]).concat([summaryResponse]), model)
 | 
					                _recount = countPromptTokens(top.concat(rw).concat([summaryRequest]).concat([summaryResponse]), model, chat)
 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
              loopCount++
 | 
					              loopCount++
 | 
				
			||||||
              continue
 | 
					              continue
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,100 @@
 | 
				
			||||||
 | 
					<script context="module" lang="ts">
 | 
				
			||||||
 | 
					    import { EventStreamContentType, fetchEventSource } from '@microsoft/fetch-event-source'
 | 
				
			||||||
 | 
					    import ChatCompletionResponse from './ChatCompletionResponse.svelte'
 | 
				
			||||||
 | 
					    import ChatRequest from './ChatRequest.svelte'
 | 
				
			||||||
 | 
					    import { getEndpoint } from './Models.svelte'
 | 
				
			||||||
 | 
					    import { getApiKey } from './Storage.svelte'
 | 
				
			||||||
 | 
					    import type { ChatCompletionOpts, Request } from './Types.svelte'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const runOpenAiCompletionRequest = async (
 | 
				
			||||||
 | 
					  request: Request,
 | 
				
			||||||
 | 
					  chatRequest: ChatRequest,
 | 
				
			||||||
 | 
					  chatResponse: ChatCompletionResponse,
 | 
				
			||||||
 | 
					  signal: AbortSignal,
 | 
				
			||||||
 | 
					  opts: ChatCompletionOpts) => {
 | 
				
			||||||
 | 
					    // OpenAI Request
 | 
				
			||||||
 | 
					      const model = chatRequest.getModel()
 | 
				
			||||||
 | 
					      const abortListener = (e:Event) => {
 | 
				
			||||||
 | 
					        chatRequest.updating = false
 | 
				
			||||||
 | 
					        chatRequest.updatingMessage = ''
 | 
				
			||||||
 | 
					        chatResponse.updateFromError('User aborted request.')
 | 
				
			||||||
 | 
					        chatRequest.removeEventListener('abort', abortListener)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      signal.addEventListener('abort', abortListener)
 | 
				
			||||||
 | 
					      const fetchOptions = {
 | 
				
			||||||
 | 
					        method: 'POST',
 | 
				
			||||||
 | 
					        headers: {
 | 
				
			||||||
 | 
					          Authorization: `Bearer ${getApiKey()}`,
 | 
				
			||||||
 | 
					          'Content-Type': 'application/json'
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        body: JSON.stringify(request),
 | 
				
			||||||
 | 
					        signal
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (opts.streaming) {
 | 
				
			||||||
 | 
					      /**
 | 
				
			||||||
 | 
					             * Streaming request/response
 | 
				
			||||||
 | 
					             * We'll get the response a token at a time, as soon as they are ready
 | 
				
			||||||
 | 
					            */
 | 
				
			||||||
 | 
					        chatResponse.onFinish(() => {
 | 
				
			||||||
 | 
					          chatRequest.updating = false
 | 
				
			||||||
 | 
					          chatRequest.updatingMessage = ''
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					        fetchEventSource(getEndpoint(model), {
 | 
				
			||||||
 | 
					          ...fetchOptions,
 | 
				
			||||||
 | 
					          openWhenHidden: true,
 | 
				
			||||||
 | 
					          onmessage (ev) {
 | 
				
			||||||
 | 
					          // Remove updating indicator
 | 
				
			||||||
 | 
					            chatRequest.updating = 1 // hide indicator, but still signal we're updating
 | 
				
			||||||
 | 
					            chatRequest.updatingMessage = ''
 | 
				
			||||||
 | 
					            // console.log('ev.data', ev.data)
 | 
				
			||||||
 | 
					            if (!chatResponse.hasFinished()) {
 | 
				
			||||||
 | 
					              if (ev.data === '[DONE]') {
 | 
				
			||||||
 | 
					              // ?? anything to do when "[DONE]"?
 | 
				
			||||||
 | 
					              } else {
 | 
				
			||||||
 | 
					                const data = JSON.parse(ev.data)
 | 
				
			||||||
 | 
					                // console.log('data', data)
 | 
				
			||||||
 | 
					                window.setTimeout(() => { chatResponse.updateFromAsyncResponse(data) }, 1)
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          onclose () {
 | 
				
			||||||
 | 
					            chatRequest.updating = false
 | 
				
			||||||
 | 
					            chatRequest.updatingMessage = ''
 | 
				
			||||||
 | 
					            chatResponse.updateFromClose()
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          onerror (err) {
 | 
				
			||||||
 | 
					            console.error(err)
 | 
				
			||||||
 | 
					            throw err
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          async onopen (response) {
 | 
				
			||||||
 | 
					            if (response.ok && response.headers.get('content-type') === EventStreamContentType) {
 | 
				
			||||||
 | 
					            // everything's good
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					            // client-side errors are usually non-retriable:
 | 
				
			||||||
 | 
					              await chatRequest.handleError(response)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }).catch(err => {
 | 
				
			||||||
 | 
					          chatRequest.updating = false
 | 
				
			||||||
 | 
					          chatRequest.updatingMessage = ''
 | 
				
			||||||
 | 
					          chatResponse.updateFromError(err.message)
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					      /**
 | 
				
			||||||
 | 
					             * Non-streaming request/response
 | 
				
			||||||
 | 
					             * We'll get the response all at once, after a long delay
 | 
				
			||||||
 | 
					             */
 | 
				
			||||||
 | 
					        const response = await fetch(getEndpoint(model), fetchOptions)
 | 
				
			||||||
 | 
					        if (!response.ok) {
 | 
				
			||||||
 | 
					          await chatRequest.handleError(response)
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          const json = await response.json()
 | 
				
			||||||
 | 
					          // Remove updating indicator
 | 
				
			||||||
 | 
					          chatRequest.updating = false
 | 
				
			||||||
 | 
					          chatRequest.updatingMessage = ''
 | 
				
			||||||
 | 
					          chatResponse.updateFromSyncResponse(json)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,139 @@
 | 
				
			||||||
 | 
					<script context="module" lang="ts">
 | 
				
			||||||
 | 
					    import ChatCompletionResponse from './ChatCompletionResponse.svelte'
 | 
				
			||||||
 | 
					    import ChatRequest from './ChatRequest.svelte'
 | 
				
			||||||
 | 
					    import { getEndpoint, getModelDetail, getRoleTag, getStopSequence } from './Models.svelte'
 | 
				
			||||||
 | 
					    import type { ChatCompletionOpts, Message, Request } from './Types.svelte'
 | 
				
			||||||
 | 
					    import { getModelMaxTokens } from './Stats.svelte'
 | 
				
			||||||
 | 
					    import { updateMessages } from './Storage.svelte'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const runPetalsCompletionRequest = async (
 | 
				
			||||||
 | 
					  request: Request,
 | 
				
			||||||
 | 
					  chatRequest: ChatRequest,
 | 
				
			||||||
 | 
					  chatResponse: ChatCompletionResponse,
 | 
				
			||||||
 | 
					  signal: AbortSignal,
 | 
				
			||||||
 | 
					  opts: ChatCompletionOpts) => {
 | 
				
			||||||
 | 
					      // Petals
 | 
				
			||||||
 | 
					      const chat = chatRequest.getChat()
 | 
				
			||||||
 | 
					      const model = chatRequest.getModel()
 | 
				
			||||||
 | 
					      const modelDetail = getModelDetail(model)
 | 
				
			||||||
 | 
					      const ws = new WebSocket(getEndpoint(model))
 | 
				
			||||||
 | 
					      const abortListener = (e:Event) => {
 | 
				
			||||||
 | 
					        chatRequest.updating = false
 | 
				
			||||||
 | 
					        chatRequest.updatingMessage = ''
 | 
				
			||||||
 | 
					        chatResponse.updateFromError('User aborted request.')
 | 
				
			||||||
 | 
					        signal.removeEventListener('abort', abortListener)
 | 
				
			||||||
 | 
					        ws.close()
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      signal.addEventListener('abort', abortListener)
 | 
				
			||||||
 | 
					      const stopSequences = (modelDetail.stop || ['###', '</s>']).slice()
 | 
				
			||||||
 | 
					      const stopSequence = getStopSequence(chat)
 | 
				
			||||||
 | 
					      let stopSequenceC = stopSequence
 | 
				
			||||||
 | 
					      if (stopSequence !== '###') {
 | 
				
			||||||
 | 
					        stopSequences.push(stopSequence)
 | 
				
			||||||
 | 
					        stopSequenceC = '</s>'
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      const stopSequencesC = stopSequences.filter((ss) => {
 | 
				
			||||||
 | 
					        return ss !== '###' && ss !== stopSequenceC
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      const maxTokens = getModelMaxTokens(model)
 | 
				
			||||||
 | 
					      let maxLen = Math.min(opts.maxTokens || chatRequest.chat.max_tokens || maxTokens, maxTokens)
 | 
				
			||||||
 | 
					      const promptTokenCount = chatResponse.getPromptTokenCount()
 | 
				
			||||||
 | 
					      if (promptTokenCount > maxLen) {
 | 
				
			||||||
 | 
					        maxLen = Math.min(maxLen + promptTokenCount, maxTokens)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      chatResponse.onFinish(() => {
 | 
				
			||||||
 | 
					        const message = chatResponse.getMessages()[0]
 | 
				
			||||||
 | 
					        if (message) {
 | 
				
			||||||
 | 
					          for (let i = 0, l = stopSequences.length; i < l; i++) {
 | 
				
			||||||
 | 
					            const ss = stopSequences[i].trim()
 | 
				
			||||||
 | 
					            if (message.content.trim().endsWith(ss)) {
 | 
				
			||||||
 | 
					              message.content = message.content.trim().slice(0, message.content.trim().length - ss.length)
 | 
				
			||||||
 | 
					              updateMessages(chat.id)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        chatRequest.updating = false
 | 
				
			||||||
 | 
					        chatRequest.updatingMessage = ''
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					      ws.onopen = () => {
 | 
				
			||||||
 | 
					        ws.send(JSON.stringify({
 | 
				
			||||||
 | 
					          type: 'open_inference_session',
 | 
				
			||||||
 | 
					          model,
 | 
				
			||||||
 | 
					          max_length: maxLen
 | 
				
			||||||
 | 
					        }))
 | 
				
			||||||
 | 
					        ws.onmessage = event => {
 | 
				
			||||||
 | 
					          const response = JSON.parse(event.data)
 | 
				
			||||||
 | 
					          if (!response.ok) {
 | 
				
			||||||
 | 
					            const err = new Error('Error opening socket: ' + response.traceback)
 | 
				
			||||||
 | 
					            chatResponse.updateFromError(err.message)
 | 
				
			||||||
 | 
					            console.error(err)
 | 
				
			||||||
 | 
					            throw err
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          const rMessages = request.messages || [] as Message[]
 | 
				
			||||||
 | 
					          // make sure top_p and temperature are set the way we need
 | 
				
			||||||
 | 
					          let temperature = request.temperature
 | 
				
			||||||
 | 
					          if (temperature === undefined || isNaN(temperature as any)) temperature = 1
 | 
				
			||||||
 | 
					          if (!temperature || temperature <= 0) temperature = 0.01
 | 
				
			||||||
 | 
					          let topP = request.top_p
 | 
				
			||||||
 | 
					          if (topP === undefined || isNaN(topP as any)) topP = 1
 | 
				
			||||||
 | 
					          if (!topP || topP <= 0) topP = 0.01
 | 
				
			||||||
 | 
					          // build the message array
 | 
				
			||||||
 | 
					          const inputArray = (rMessages).reduce((a, m) => {
 | 
				
			||||||
 | 
					            const c = getRoleTag(m.role, model, chatRequest.chat) + m.content
 | 
				
			||||||
 | 
					            a.push(c.trim())
 | 
				
			||||||
 | 
					            return a
 | 
				
			||||||
 | 
					          }, [] as string[])
 | 
				
			||||||
 | 
					          const lastMessage = rMessages[rMessages.length - 1]
 | 
				
			||||||
 | 
					          if (lastMessage && lastMessage.role !== 'assistant') {
 | 
				
			||||||
 | 
					            inputArray.push(getRoleTag('assistant', model, chatRequest.chat))
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          const petalsRequest = {
 | 
				
			||||||
 | 
					            type: 'generate',
 | 
				
			||||||
 | 
					            inputs: inputArray.join(stopSequence),
 | 
				
			||||||
 | 
					            max_new_tokens: 1, // wait for up to 1 tokens before displaying
 | 
				
			||||||
 | 
					            stop_sequence: stopSequenceC,
 | 
				
			||||||
 | 
					            do_sample: 1, // enable top p and the like
 | 
				
			||||||
 | 
					            temperature,
 | 
				
			||||||
 | 
					            top_p: topP
 | 
				
			||||||
 | 
					          } as any
 | 
				
			||||||
 | 
					          if (stopSequencesC.length) petalsRequest.extra_stop_sequences = stopSequencesC
 | 
				
			||||||
 | 
					          ws.send(JSON.stringify(petalsRequest))
 | 
				
			||||||
 | 
					          ws.onmessage = event => {
 | 
				
			||||||
 | 
					            // Remove updating indicator
 | 
				
			||||||
 | 
					            chatRequest.updating = 1 // hide indicator, but still signal we're updating
 | 
				
			||||||
 | 
					            chatRequest.updatingMessage = ''
 | 
				
			||||||
 | 
					            const response = JSON.parse(event.data)
 | 
				
			||||||
 | 
					            if (!response.ok) {
 | 
				
			||||||
 | 
					              const err = new Error('Error in response: ' + response.traceback)
 | 
				
			||||||
 | 
					              console.error(err)
 | 
				
			||||||
 | 
					              chatResponse.updateFromError(err.message)
 | 
				
			||||||
 | 
					              throw err
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            window.setTimeout(() => {
 | 
				
			||||||
 | 
					              chatResponse.updateFromAsyncResponse(
 | 
				
			||||||
 | 
					                      {
 | 
				
			||||||
 | 
					                        model,
 | 
				
			||||||
 | 
					                        choices: [{
 | 
				
			||||||
 | 
					                          delta: {
 | 
				
			||||||
 | 
					                            content: response.outputs,
 | 
				
			||||||
 | 
					                            role: 'assistant'
 | 
				
			||||||
 | 
					                          },
 | 
				
			||||||
 | 
					                          finish_reason: (response.stop ? 'stop' : null)
 | 
				
			||||||
 | 
					                        }]
 | 
				
			||||||
 | 
					                      } as any
 | 
				
			||||||
 | 
					              )
 | 
				
			||||||
 | 
					            }, 1)
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        ws.onclose = () => {
 | 
				
			||||||
 | 
					          chatRequest.updating = false
 | 
				
			||||||
 | 
					          chatRequest.updatingMessage = ''
 | 
				
			||||||
 | 
					          chatResponse.updateFromClose()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        ws.onerror = err => {
 | 
				
			||||||
 | 
					          console.error(err)
 | 
				
			||||||
 | 
					          throw err
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
| 
						 | 
					@ -3,7 +3,7 @@
 | 
				
			||||||
  // import { getProfile } from './Profiles.svelte'
 | 
					  // import { getProfile } from './Profiles.svelte'
 | 
				
			||||||
  import { cleanSettingValue, setChatSettingValue } from './Storage.svelte'
 | 
					  import { cleanSettingValue, setChatSettingValue } from './Storage.svelte'
 | 
				
			||||||
  import type { Chat, ChatSetting, ChatSettings, ControlAction, FieldControl, SettingPrompt } from './Types.svelte'
 | 
					  import type { Chat, ChatSetting, ChatSettings, ControlAction, FieldControl, SettingPrompt } from './Types.svelte'
 | 
				
			||||||
  import { autoGrowInputOnEvent, errorNotice } from './Util.svelte'
 | 
					  import { autoGrowInputOnEvent, errorNotice, valueOf } from './Util.svelte'
 | 
				
			||||||
  // import { replace } from 'svelte-spa-router'
 | 
					  // import { replace } from 'svelte-spa-router'
 | 
				
			||||||
  import Fa from 'svelte-fa/src/fa.svelte'
 | 
					  import Fa from 'svelte-fa/src/fa.svelte'
 | 
				
			||||||
  import { openModal } from 'svelte-modals'
 | 
					  import { openModal } from 'svelte-modals'
 | 
				
			||||||
| 
						 | 
					@ -23,6 +23,10 @@
 | 
				
			||||||
  const chatId = chat.id
 | 
					  const chatId = chat.id
 | 
				
			||||||
  let show = false
 | 
					  let show = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let header = valueOf(chatId, setting.header)
 | 
				
			||||||
 | 
					  let headerClass = valueOf(chatId, setting.headerClass)
 | 
				
			||||||
 | 
					  let placeholder = valueOf(chatId, setting.placeholder)
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
  const buildFieldControls = () => {
 | 
					  const buildFieldControls = () => {
 | 
				
			||||||
    fieldControls = (setting.fieldControls || [] as FieldControl[]).map(fc => {
 | 
					    fieldControls = (setting.fieldControls || [] as FieldControl[]).map(fc => {
 | 
				
			||||||
      return fc.getAction(chatId, setting, chatSettings[setting.key])
 | 
					      return fc.getAction(chatId, setting, chatSettings[setting.key])
 | 
				
			||||||
| 
						 | 
					@ -38,6 +42,9 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  afterUpdate(() => {
 | 
					  afterUpdate(() => {
 | 
				
			||||||
    show = (typeof setting.hide !== 'function') || !setting.hide(chatId)
 | 
					    show = (typeof setting.hide !== 'function') || !setting.hide(chatId)
 | 
				
			||||||
 | 
					    header = valueOf(chatId, setting.header)
 | 
				
			||||||
 | 
					    headerClass = valueOf(chatId, setting.headerClass)
 | 
				
			||||||
 | 
					    placeholder = valueOf(chatId, setting.placeholder)
 | 
				
			||||||
    buildFieldControls()
 | 
					    buildFieldControls()
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -146,9 +153,9 @@
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{#if show}
 | 
					{#if show}
 | 
				
			||||||
  {#if setting.header}
 | 
					  {#if header}
 | 
				
			||||||
  <p class="notification {setting.headerClass}">
 | 
					  <p class="notification {headerClass}">
 | 
				
			||||||
    {@html setting.header}
 | 
					    {@html header}
 | 
				
			||||||
  </p>
 | 
					  </p>
 | 
				
			||||||
  {/if}
 | 
					  {/if}
 | 
				
			||||||
  <div class="field is-horizontal">
 | 
					  <div class="field is-horizontal">
 | 
				
			||||||
| 
						 | 
					@ -171,7 +178,7 @@
 | 
				
			||||||
      <label class="label" for="settings-{setting.key}" title="{setting.title}">{setting.name}</label>
 | 
					      <label class="label" for="settings-{setting.key}" title="{setting.title}">{setting.name}</label>
 | 
				
			||||||
      <textarea
 | 
					      <textarea
 | 
				
			||||||
        class="input is-info is-focused chat-input auto-size"
 | 
					        class="input is-info is-focused chat-input auto-size"
 | 
				
			||||||
        placeholder={setting.placeholder || ''}
 | 
					        placeholder={placeholder || ''}
 | 
				
			||||||
        rows="1"
 | 
					        rows="1"
 | 
				
			||||||
        on:input={e => autoGrowInputOnEvent(e)}
 | 
					        on:input={e => autoGrowInputOnEvent(e)}
 | 
				
			||||||
        on:change={e => { queueSettingValueChange(e, setting); autoGrowInputOnEvent(e) }}
 | 
					        on:change={e => { queueSettingValueChange(e, setting); autoGrowInputOnEvent(e) }}
 | 
				
			||||||
| 
						 | 
					@ -195,7 +202,7 @@
 | 
				
			||||||
            min={setting.min}
 | 
					            min={setting.min}
 | 
				
			||||||
            max={setting.max}
 | 
					            max={setting.max}
 | 
				
			||||||
            step={setting.step}
 | 
					            step={setting.step}
 | 
				
			||||||
            placeholder={String(setting.placeholder || chatDefaults[setting.key])}
 | 
					            placeholder={String(placeholder || chatDefaults[setting.key])}
 | 
				
			||||||
            on:change={e => queueSettingValueChange(e, setting)}
 | 
					            on:change={e => queueSettingValueChange(e, setting)}
 | 
				
			||||||
          />
 | 
					          />
 | 
				
			||||||
        {:else if setting.type === 'select' || setting.type === 'select-number'}
 | 
					        {:else if setting.type === 'select' || setting.type === 'select-number'}
 | 
				
			||||||
| 
						 | 
					@ -204,7 +211,7 @@
 | 
				
			||||||
            {#key rkey}
 | 
					            {#key rkey}
 | 
				
			||||||
            <select id="settings-{setting.key}" title="{setting.title}" on:change={e => queueSettingValueChange(e, setting) } >
 | 
					            <select id="settings-{setting.key}" title="{setting.title}" on:change={e => queueSettingValueChange(e, setting) } >
 | 
				
			||||||
              {#each setting.options as option}
 | 
					              {#each setting.options as option}
 | 
				
			||||||
                <option class:is-default={option.value === chatDefaults[setting.key]} value={option.value} selected={option.value === chatSettings[setting.key]}>{option.text}</option>
 | 
					                <option class:is-default={option.value === chatDefaults[setting.key]} value={option.value} selected={option.value === chatSettings[setting.key]} disabled={option.disabled}>{option.text}</option>
 | 
				
			||||||
              {/each}
 | 
					              {/each}
 | 
				
			||||||
            </select>
 | 
					            </select>
 | 
				
			||||||
            {/key}
 | 
					            {/key}
 | 
				
			||||||
| 
						 | 
					@ -233,6 +240,7 @@
 | 
				
			||||||
              title="{setting.title}"
 | 
					              title="{setting.title}"
 | 
				
			||||||
              class="input" 
 | 
					              class="input" 
 | 
				
			||||||
              value={chatSettings[setting.key]} 
 | 
					              value={chatSettings[setting.key]} 
 | 
				
			||||||
 | 
					              placeholder={String(placeholder || chatDefaults[setting.key])}
 | 
				
			||||||
              on:change={e => { queueSettingValueChange(e, setting) }}
 | 
					              on:change={e => { queueSettingValueChange(e, setting) }}
 | 
				
			||||||
            >
 | 
					            >
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,7 +3,6 @@
 | 
				
			||||||
  import { getChatDefaults, getChatSettingList, getChatSettingObjectByKey, getExcludeFromProfile } from './Settings.svelte'
 | 
					  import { getChatDefaults, getChatSettingList, getChatSettingObjectByKey, getExcludeFromProfile } from './Settings.svelte'
 | 
				
			||||||
  import {
 | 
					  import {
 | 
				
			||||||
    saveChatStore,
 | 
					    saveChatStore,
 | 
				
			||||||
    apiKeyStorage,
 | 
					 | 
				
			||||||
    chatsStorage,
 | 
					    chatsStorage,
 | 
				
			||||||
    globalStorage,
 | 
					    globalStorage,
 | 
				
			||||||
    saveCustomProfile,
 | 
					    saveCustomProfile,
 | 
				
			||||||
| 
						 | 
					@ -13,7 +12,7 @@
 | 
				
			||||||
    checkStateChange,
 | 
					    checkStateChange,
 | 
				
			||||||
    addChat
 | 
					    addChat
 | 
				
			||||||
  } from './Storage.svelte'
 | 
					  } from './Storage.svelte'
 | 
				
			||||||
  import type { Chat, ChatSetting, ResponseModels, SettingSelect, SelectOption, ChatSettings } from './Types.svelte'
 | 
					  import type { Chat, ChatSetting, SettingSelect, ChatSettings } from './Types.svelte'
 | 
				
			||||||
  import { errorNotice, sizeTextElements } from './Util.svelte'
 | 
					  import { errorNotice, sizeTextElements } from './Util.svelte'
 | 
				
			||||||
  import Fa from 'svelte-fa/src/fa.svelte'
 | 
					  import Fa from 'svelte-fa/src/fa.svelte'
 | 
				
			||||||
  import {
 | 
					  import {
 | 
				
			||||||
| 
						 | 
					@ -35,8 +34,7 @@
 | 
				
			||||||
  import { replace } from 'svelte-spa-router'
 | 
					  import { replace } from 'svelte-spa-router'
 | 
				
			||||||
  import { openModal } from 'svelte-modals'
 | 
					  import { openModal } from 'svelte-modals'
 | 
				
			||||||
  import PromptConfirm from './PromptConfirm.svelte'
 | 
					  import PromptConfirm from './PromptConfirm.svelte'
 | 
				
			||||||
  import { getApiBase, getEndpointModels } from './ApiUtil.svelte'
 | 
					  import { getModelOptions } from './Models.svelte'
 | 
				
			||||||
  import { supportedModelKeys } from './Models.svelte'
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export let chatId:number
 | 
					  export let chatId:number
 | 
				
			||||||
  export const show = () => { showSettings() }
 | 
					  export const show = () => { showSettings() }
 | 
				
			||||||
| 
						 | 
					@ -185,30 +183,9 @@
 | 
				
			||||||
    // Refresh settings modal
 | 
					    // Refresh settings modal
 | 
				
			||||||
    showSettingsModal++
 | 
					    showSettingsModal++
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Load available models from OpenAI
 | 
					 | 
				
			||||||
    const allModels = (await (
 | 
					 | 
				
			||||||
      await fetch(getApiBase() + getEndpointModels(), {
 | 
					 | 
				
			||||||
        method: 'GET',
 | 
					 | 
				
			||||||
        headers: {
 | 
					 | 
				
			||||||
          Authorization: `Bearer ${$apiKeyStorage}`,
 | 
					 | 
				
			||||||
          'Content-Type': 'application/json'
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
    ).json()) as ResponseModels
 | 
					 | 
				
			||||||
    const filteredModels = supportedModelKeys.filter((model) => allModels.data.find((m) => m.id === model))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const modelOptions:SelectOption[] = filteredModels.reduce((a, m) => {
 | 
					 | 
				
			||||||
      const o:SelectOption = {
 | 
					 | 
				
			||||||
        value: m,
 | 
					 | 
				
			||||||
        text: m
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      a.push(o)
 | 
					 | 
				
			||||||
      return a
 | 
					 | 
				
			||||||
    }, [] as SelectOption[])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Update the models in the settings
 | 
					    // Update the models in the settings
 | 
				
			||||||
    if (modelSetting) {
 | 
					    if (modelSetting) {
 | 
				
			||||||
      modelSetting.options = modelOptions
 | 
					      modelSetting.options = await getModelOptions()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    // Refresh settings modal
 | 
					    // Refresh settings modal
 | 
				
			||||||
    showSettingsModal++
 | 
					    showSettingsModal++
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,6 +11,7 @@
 | 
				
			||||||
  import { openModal } from 'svelte-modals'
 | 
					  import { openModal } from 'svelte-modals'
 | 
				
			||||||
  import PromptConfirm from './PromptConfirm.svelte'
 | 
					  import PromptConfirm from './PromptConfirm.svelte'
 | 
				
			||||||
  import { getImage } from './ImageStore.svelte'
 | 
					  import { getImage } from './ImageStore.svelte'
 | 
				
			||||||
 | 
					  import { getModelDetail } from './Models.svelte'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export let message:Message
 | 
					  export let message:Message
 | 
				
			||||||
  export let chatId:number
 | 
					  export let chatId:number
 | 
				
			||||||
| 
						 | 
					@ -245,7 +246,7 @@
 | 
				
			||||||
      <p class="is-size-7 message-note">System Prompt</p>
 | 
					      <p class="is-size-7 message-note">System Prompt</p>
 | 
				
			||||||
    {:else if message.usage}
 | 
					    {:else if message.usage}
 | 
				
			||||||
      <p class="is-size-7 message-note">
 | 
					      <p class="is-size-7 message-note">
 | 
				
			||||||
        <em>{message.model || defaultModel}</em> using <span class="has-text-weight-bold">{message.usage.total_tokens}</span>
 | 
					        <em>{getModelDetail(message.model || '').label || message.model || defaultModel}</em> using <span class="has-text-weight-bold">{message.usage.total_tokens}</span>
 | 
				
			||||||
        tokens ~= <span class="has-text-weight-bold">${getPrice(message.usage, message.model || defaultModel).toFixed(6)}</span>
 | 
					        tokens ~= <span class="has-text-weight-bold">${getPrice(message.usage, message.model || defaultModel).toFixed(6)}</span>
 | 
				
			||||||
      </p>
 | 
					      </p>
 | 
				
			||||||
    {/if}
 | 
					    {/if}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,16 +1,22 @@
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
  import { apiKeyStorage, lastChatId, getChat, started } from './Storage.svelte'
 | 
					  import { apiKeyStorage, globalStorage, lastChatId, getChat, started, setGlobalSettingValueByKey, hasActiveModels, checkStateChange } from './Storage.svelte'
 | 
				
			||||||
  import Footer from './Footer.svelte'
 | 
					  import Footer from './Footer.svelte'
 | 
				
			||||||
  import { replace } from 'svelte-spa-router'
 | 
					  import { replace } from 'svelte-spa-router'
 | 
				
			||||||
  import { onMount } from 'svelte'
 | 
					  import { afterUpdate, onMount } from 'svelte'
 | 
				
			||||||
 | 
					  import { getPetals } from './ApiUtil.svelte'
 | 
				
			||||||
 | 
					  import { clearModelOptionCache } from './Models.svelte'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
$: apiKey = $apiKeyStorage
 | 
					$: apiKey = $apiKeyStorage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let showPetalsSettings = $globalStorage.enablePetals
 | 
				
			||||||
 | 
					let pedalsEndpoint = $globalStorage.pedalsEndpoint
 | 
				
			||||||
 | 
					let hasModels = hasActiveModels()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
onMount(() => {
 | 
					onMount(() => {
 | 
				
			||||||
    if (!$started) {
 | 
					    if (!$started) {
 | 
				
			||||||
      $started = true
 | 
					      $started = true
 | 
				
			||||||
      // console.log('started', apiKey, $lastChatId, getChat($lastChatId))
 | 
					      // console.log('started', apiKey, $lastChatId, getChat($lastChatId))
 | 
				
			||||||
      if (apiKey && getChat($lastChatId)) {
 | 
					      if (hasActiveModels() && getChat($lastChatId)) {
 | 
				
			||||||
        const chatId = $lastChatId
 | 
					        const chatId = $lastChatId
 | 
				
			||||||
        $lastChatId = 0
 | 
					        $lastChatId = 0
 | 
				
			||||||
        replace(`/chat/${chatId}`)
 | 
					        replace(`/chat/${chatId}`)
 | 
				
			||||||
| 
						 | 
					@ -19,21 +25,39 @@ onMount(() => {
 | 
				
			||||||
    $lastChatId = 0
 | 
					    $lastChatId = 0
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					afterUpdate(() => {
 | 
				
			||||||
 | 
					    clearModelOptionCache()
 | 
				
			||||||
 | 
					    hasModels = hasActiveModels()
 | 
				
			||||||
 | 
					    pedalsEndpoint = $globalStorage.pedalsEndpoint
 | 
				
			||||||
 | 
					    $checkStateChange++
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const setPetalsEnabled = (event: Event) => {
 | 
				
			||||||
 | 
					    const el = (event.target as HTMLInputElement)
 | 
				
			||||||
 | 
					    setGlobalSettingValueByKey('enablePetals', !!el.checked)
 | 
				
			||||||
 | 
					    showPetalsSettings = $globalStorage.enablePetals
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
<section class="section">
 | 
					<section class="section">
 | 
				
			||||||
  <article class="message">
 | 
					  <article class="message">
 | 
				
			||||||
    <div class="message-body">
 | 
					    <div class="message-body">
 | 
				
			||||||
      <strong><a href="https://github.com/Niek/chatgpt-web">ChatGPT-web</a></strong>
 | 
					    <p class="mb-4">
 | 
				
			||||||
 | 
					      <strong><a href="https://github.com/Niek/chatgpt-web" target="_blank">ChatGPT-web</a></strong>
 | 
				
			||||||
      is a simple one-page web interface to the OpenAI ChatGPT API. To use it, you need to register for
 | 
					      is a simple one-page web interface to the OpenAI ChatGPT API. To use it, you need to register for
 | 
				
			||||||
      <a href="https://platform.openai.com/account/api-keys" target="_blank" rel="noreferrer">an OpenAI API key</a>
 | 
					      <a href="https://platform.openai.com/account/api-keys" target="_blank" rel="noreferrer">an OpenAI API key</a>
 | 
				
			||||||
      first. OpenAI bills per token (usage-based), which means it is a lot cheaper than
 | 
					      first. OpenAI bills per token (usage-based), which means it is a lot cheaper than
 | 
				
			||||||
      <a href="https://openai.com/blog/chatgpt-plus" target="_blank" rel="noreferrer">ChatGPT Plus</a>, unless you use
 | 
					      <a href="https://openai.com/blog/chatgpt-plus" target="_blank" rel="noreferrer">ChatGPT Plus</a>, unless you use
 | 
				
			||||||
      more than 10 million tokens per month. All messages are stored in your browser's local storage, so everything is
 | 
					      more than 10 million tokens per month. All messages are stored in your browser's local storage, so everything is
 | 
				
			||||||
      <strong>private</strong>. You can also close the browser tab and come back later to continue the conversation.
 | 
					      <strong>private</strong>. You can also close the browser tab and come back later to continue the conversation.
 | 
				
			||||||
 | 
					    </p>
 | 
				
			||||||
 | 
					    <p>
 | 
				
			||||||
 | 
					      As an alternative to OpenAI, you can also use Petals swarm as a free API option for open chat models like Llama 2. 
 | 
				
			||||||
 | 
					    </p>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
  </article>
 | 
					  </article>
 | 
				
			||||||
  <article class="message" class:is-danger={!apiKey} class:is-warning={apiKey}>
 | 
					  <article class="message" class:is-danger={!hasModels} class:is-warning={!apiKey} class:is-info={apiKey}>
 | 
				
			||||||
    <div class="message-body">
 | 
					    <div class="message-body">
 | 
				
			||||||
      Set your OpenAI API key below:
 | 
					      Set your OpenAI API key below:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -53,19 +77,81 @@ onMount(() => {
 | 
				
			||||||
            type="password"
 | 
					            type="password"
 | 
				
			||||||
            autocomplete="off"
 | 
					            autocomplete="off"
 | 
				
			||||||
            class="input"
 | 
					            class="input"
 | 
				
			||||||
            class:is-danger={!apiKey}
 | 
					            class:is-danger={!hasModels}
 | 
				
			||||||
 | 
					            class:is-warning={!apiKey} class:is-info={apiKey}
 | 
				
			||||||
            value={apiKey}
 | 
					            value={apiKey}
 | 
				
			||||||
          />
 | 
					          />
 | 
				
			||||||
        </p>
 | 
					        </p>
 | 
				
			||||||
        <p class="control">
 | 
					        <p class="control">
 | 
				
			||||||
          <button class="button is-info" type="submit">Save</button>
 | 
					          <button class="button is-info" type="submit">Save</button>
 | 
				
			||||||
        </p>
 | 
					        </p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      </form>
 | 
					      </form>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      {#if !apiKey}
 | 
					      {#if !apiKey}
 | 
				
			||||||
        <p class="help is-danger">
 | 
					        <p class:is-danger={!hasModels} class:is-warning={!apiKey}>
 | 
				
			||||||
          Please enter your <a href="https://platform.openai.com/account/api-keys">OpenAI API key</a> above to use ChatGPT-web.
 | 
					          Please enter your <a target="_blank" href="https://platform.openai.com/account/api-keys">OpenAI API key</a> above to use Open AI's ChatGPT API.
 | 
				
			||||||
          It is required to use ChatGPT-web.
 | 
					          At least one API must be enabled to use ChatGPT-web.
 | 
				
			||||||
 | 
					        </p>
 | 
				
			||||||
 | 
					      {/if}
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  </article>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  <article class="message" class:is-danger={!hasModels} class:is-warning={!showPetalsSettings} class:is-info={showPetalsSettings}>
 | 
				
			||||||
 | 
					    <div class="message-body">
 | 
				
			||||||
 | 
					      <label class="label" for="enablePetals">
 | 
				
			||||||
 | 
					        <input 
 | 
				
			||||||
 | 
					        type="checkbox"
 | 
				
			||||||
 | 
					        class="checkbox" 
 | 
				
			||||||
 | 
					        id="enablePetals"
 | 
				
			||||||
 | 
					        checked={!!$globalStorage.enablePetals} 
 | 
				
			||||||
 | 
					        on:click={setPetalsEnabled}
 | 
				
			||||||
 | 
					      >
 | 
				
			||||||
 | 
					        Use Petals API and Models (Llama 2)
 | 
				
			||||||
 | 
					      </label>
 | 
				
			||||||
 | 
					      {#if showPetalsSettings}
 | 
				
			||||||
 | 
					        <p>Set Petals API Endpoint:</p>
 | 
				
			||||||
 | 
					        <form
 | 
				
			||||||
 | 
					          class="field has-addons has-addons-right"
 | 
				
			||||||
 | 
					          on:submit|preventDefault={(event) => {
 | 
				
			||||||
 | 
					            if (event.target && event.target[0].value) {
 | 
				
			||||||
 | 
					              setGlobalSettingValueByKey('pedalsEndpoint', (event.target[0].value).trim())
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					              setGlobalSettingValueByKey('pedalsEndpoint', '')
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }}
 | 
				
			||||||
 | 
					        >
 | 
				
			||||||
 | 
					          <p class="control is-expanded">
 | 
				
			||||||
 | 
					            <input
 | 
				
			||||||
 | 
					              aria-label="PetalsAPI Endpoint"
 | 
				
			||||||
 | 
					              type="text"
 | 
				
			||||||
 | 
					              class="input"
 | 
				
			||||||
 | 
					              placeholder={getPetals()}
 | 
				
			||||||
 | 
					              value={$globalStorage.pedalsEndpoint || ''}
 | 
				
			||||||
 | 
					            />
 | 
				
			||||||
 | 
					          </p>
 | 
				
			||||||
 | 
					          <p class="control">
 | 
				
			||||||
 | 
					            <button class="button is-info" type="submit">Save</button>
 | 
				
			||||||
 | 
					          </p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          
 | 
				
			||||||
 | 
					        </form>
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
 | 
					        {#if !pedalsEndpoint}
 | 
				
			||||||
 | 
					          <p class="help is-warning">
 | 
				
			||||||
 | 
					            Please only use the default public API for testing. It's best to <a target="_blank" href="https://github.com/petals-infra/chat.petals.dev">configure a private endpoint</a> and enter it above for connection to the Petals swarm.
 | 
				
			||||||
 | 
					          </p>
 | 
				
			||||||
 | 
					        {/if}
 | 
				
			||||||
 | 
					        <p class="my-4">
 | 
				
			||||||
 | 
					          <a target="_blank" href="https://petals.dev/">Petals</a> lets you run large language models at home by connecting to a public swarm, BitTorrent-style, without hefty GPU requirements.
 | 
				
			||||||
 | 
					        </p>
 | 
				
			||||||
 | 
					        <p class="mb-4">
 | 
				
			||||||
 | 
					          You are encouraged to <a target="_blank" href="https://github.com/bigscience-workshop/petals/wiki/FAQ:-Frequently-asked-questions#running-a-server">set up a Petals server to share your GPU resources</a> with the public swarm. Minimum requirements to contribute Llama 2 completions are a GTX 1080 8GB, but the larger/faster the better.
 | 
				
			||||||
 | 
					        </p>
 | 
				
			||||||
 | 
					        <p class="help is-warning">
 | 
				
			||||||
 | 
					          Because Petals uses a public swarm, <b>do not send sensitive information</b> when using Petals.
 | 
				
			||||||
        </p>
 | 
					        </p>
 | 
				
			||||||
      {/if}
 | 
					      {/if}
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,43 +1,108 @@
 | 
				
			||||||
<script context="module" lang="ts">
 | 
					<script context="module" lang="ts">
 | 
				
			||||||
    import type { ModelDetail, Model } from './Types.svelte'
 | 
					    import { getApiBase, getEndpointCompletions, getEndpointGenerations, getEndpointModels, getPetals } from './ApiUtil.svelte'
 | 
				
			||||||
 | 
					    import { apiKeyStorage, globalStorage } from './Storage.svelte'
 | 
				
			||||||
 | 
					import { get, writable } from 'svelte/store'
 | 
				
			||||||
 | 
					    import type { ModelDetail, Model, ResponseModels, SelectOption, Chat } from './Types.svelte'
 | 
				
			||||||
 | 
					import { encode } from 'gpt-tokenizer'
 | 
				
			||||||
 | 
					import llamaTokenizer from 'llama-tokenizer-js'
 | 
				
			||||||
 | 
					    import { mergeProfileFields } from './Profiles.svelte'
 | 
				
			||||||
 | 
					    import { getChatSettingObjectByKey } from './Settings.svelte'
 | 
				
			||||||
 | 
					    import { valueOf } from './Util.svelte'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * TODO: All of this + what's scattered about need to be refactored to interfaces and classes
 | 
				
			||||||
 | 
					 *       to make it all more modular
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					const modelOptionCache = writable([] as SelectOption[])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Reference: https://openai.com/pricing#language-models
 | 
					// Reference: https://openai.com/pricing#language-models
 | 
				
			||||||
// Eventually we'll add API hosts and endpoints to this
 | 
					// Eventually we'll add API hosts and endpoints to this
 | 
				
			||||||
const modelDetails : Record<string, ModelDetail> = {
 | 
					const modelDetails : Record<string, ModelDetail> = {
 | 
				
			||||||
      'gpt-4-32k': {
 | 
					      'gpt-4-32k': {
 | 
				
			||||||
 | 
					        type: 'OpenAIChat',
 | 
				
			||||||
        prompt: 0.00006, // $0.06 per 1000 tokens prompt
 | 
					        prompt: 0.00006, // $0.06 per 1000 tokens prompt
 | 
				
			||||||
        completion: 0.00012, // $0.12 per 1000 tokens completion
 | 
					        completion: 0.00012, // $0.12 per 1000 tokens completion
 | 
				
			||||||
        max: 32768 // 32k max token buffer
 | 
					        max: 32768 // 32k max token buffer
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      'gpt-4': {
 | 
					      'gpt-4': {
 | 
				
			||||||
 | 
					        type: 'OpenAIChat',
 | 
				
			||||||
        prompt: 0.00003, // $0.03 per 1000 tokens prompt
 | 
					        prompt: 0.00003, // $0.03 per 1000 tokens prompt
 | 
				
			||||||
        completion: 0.00006, // $0.06 per 1000 tokens completion
 | 
					        completion: 0.00006, // $0.06 per 1000 tokens completion
 | 
				
			||||||
        max: 8192 // 8k max token buffer
 | 
					        max: 8192 // 8k max token buffer
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      'gpt-3.5': {
 | 
					      'gpt-3.5': {
 | 
				
			||||||
 | 
					        type: 'OpenAIChat',
 | 
				
			||||||
        prompt: 0.0000015, // $0.0015 per 1000 tokens prompt
 | 
					        prompt: 0.0000015, // $0.0015 per 1000 tokens prompt
 | 
				
			||||||
        completion: 0.000002, // $0.002 per 1000 tokens completion
 | 
					        completion: 0.000002, // $0.002 per 1000 tokens completion
 | 
				
			||||||
        max: 4096 // 4k max token buffer
 | 
					        max: 4096 // 4k max token buffer
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      'gpt-3.5-turbo-16k': {
 | 
					      'gpt-3.5-turbo-16k': {
 | 
				
			||||||
 | 
					        type: 'OpenAIChat',
 | 
				
			||||||
        prompt: 0.000003, // $0.003 per 1000 tokens prompt
 | 
					        prompt: 0.000003, // $0.003 per 1000 tokens prompt
 | 
				
			||||||
        completion: 0.000004, // $0.004 per 1000 tokens completion
 | 
					        completion: 0.000004, // $0.004 per 1000 tokens completion
 | 
				
			||||||
        max: 16384 // 16k max token buffer
 | 
					        max: 16384 // 16k max token buffer
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      'enoch/llama-65b-hf': {
 | 
				
			||||||
 | 
					        type: 'Petals',
 | 
				
			||||||
 | 
					        label: 'Petals - Llama-65b',
 | 
				
			||||||
 | 
					        stop: ['###', '</s>'],
 | 
				
			||||||
 | 
					        userStart: '<|user|>',
 | 
				
			||||||
 | 
					        assistantStart: '<|[[CHARACTER_NAME]]|>',
 | 
				
			||||||
 | 
					        systemStart: '',
 | 
				
			||||||
 | 
					        prompt: 0.000000, // $0.000 per 1000 tokens prompt
 | 
				
			||||||
 | 
					        completion: 0.000000, // $0.000 per 1000 tokens completion
 | 
				
			||||||
 | 
					        max: 2048 // 2k max token buffer
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      'timdettmers/guanaco-65b': {
 | 
				
			||||||
 | 
					        type: 'Petals',
 | 
				
			||||||
 | 
					        label: 'Petals - Guanaco-65b',
 | 
				
			||||||
 | 
					        stop: ['###', '</s>'],
 | 
				
			||||||
 | 
					        userStart: '<|user|>',
 | 
				
			||||||
 | 
					        assistantStart: '<|[[CHARACTER_NAME]]|>',
 | 
				
			||||||
 | 
					        systemStart: '',
 | 
				
			||||||
 | 
					        prompt: 0.000000, // $0.000 per 1000 tokens prompt
 | 
				
			||||||
 | 
					        completion: 0.000000, // $0.000 per 1000 tokens completion
 | 
				
			||||||
 | 
					        max: 2048 // 2k max token buffer
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      'meta-llama/Llama-2-70b-chat-hf': {
 | 
				
			||||||
 | 
					        type: 'Petals',
 | 
				
			||||||
 | 
					        label: 'Petals - Llama-2-70b-chat',
 | 
				
			||||||
 | 
					        stop: ['###', '</s>'],
 | 
				
			||||||
 | 
					        userStart: '<|user|>',
 | 
				
			||||||
 | 
					        assistantStart: '<|[[CHARACTER_NAME]]|>',
 | 
				
			||||||
 | 
					        systemStart: '',
 | 
				
			||||||
 | 
					        prompt: 0.000000, // $0.000 per 1000 tokens prompt
 | 
				
			||||||
 | 
					        completion: 0.000000, // $0.000 per 1000 tokens completion
 | 
				
			||||||
 | 
					        max: 4096 // 4k max token buffer
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      'meta-llama/Llama-2-70b-hf': {
 | 
				
			||||||
 | 
					        type: 'Petals',
 | 
				
			||||||
 | 
					        label: 'Petals - Llama-2-70b',
 | 
				
			||||||
 | 
					        stop: ['###', '</s>'],
 | 
				
			||||||
 | 
					        userStart: '<|user|>',
 | 
				
			||||||
 | 
					        assistantStart: '<|[[CHARACTER_NAME]]|>',
 | 
				
			||||||
 | 
					        systemStart: '',
 | 
				
			||||||
 | 
					        prompt: 0.000000, // $0.000 per 1000 tokens prompt
 | 
				
			||||||
 | 
					        completion: 0.000000, // $0.000 per 1000 tokens completion
 | 
				
			||||||
 | 
					        max: 4096 // 4k max token buffer
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const imageModels : Record<string, ModelDetail> = {
 | 
					export const imageModels : Record<string, ModelDetail> = {
 | 
				
			||||||
      'dall-e-1024x1024': {
 | 
					      'dall-e-1024x1024': {
 | 
				
			||||||
 | 
					        type: 'OpenAIDall-e',
 | 
				
			||||||
        prompt: 0.00,
 | 
					        prompt: 0.00,
 | 
				
			||||||
        completion: 0.020, // $0.020 per image
 | 
					        completion: 0.020, // $0.020 per image
 | 
				
			||||||
        max: 1000 // 1000 char prompt, max
 | 
					        max: 1000 // 1000 char prompt, max
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      'dall-e-512x512': {
 | 
					      'dall-e-512x512': {
 | 
				
			||||||
 | 
					        type: 'OpenAIDall-e',
 | 
				
			||||||
        prompt: 0.00,
 | 
					        prompt: 0.00,
 | 
				
			||||||
        completion: 0.018, // $0.018 per image
 | 
					        completion: 0.018, // $0.018 per image
 | 
				
			||||||
        max: 1000 // 1000 char prompt, max
 | 
					        max: 1000 // 1000 char prompt, max
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      'dall-e-256x256': {
 | 
					      'dall-e-256x256': {
 | 
				
			||||||
 | 
					        type: 'OpenAIDall-e',
 | 
				
			||||||
        prompt: 0.00,
 | 
					        prompt: 0.00,
 | 
				
			||||||
        completion: 0.016, // $0.016 per image
 | 
					        completion: 0.016, // $0.016 per image
 | 
				
			||||||
        max: 1000 // 1000 char prompt, max
 | 
					        max: 1000 // 1000 char prompt, max
 | 
				
			||||||
| 
						 | 
					@ -47,22 +112,27 @@ const imageModels : Record<string, ModelDetail> = {
 | 
				
			||||||
const unknownDetail = {
 | 
					const unknownDetail = {
 | 
				
			||||||
  prompt: 0,
 | 
					  prompt: 0,
 | 
				
			||||||
  completion: 0,
 | 
					  completion: 0,
 | 
				
			||||||
  max: 4096
 | 
					  max: 4096,
 | 
				
			||||||
}
 | 
					  type: 'OpenAIChat'
 | 
				
			||||||
 | 
					} as ModelDetail
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// See: https://platform.openai.com/docs/models/model-endpoint-compatibility
 | 
					// See: https://platform.openai.com/docs/models/model-endpoint-compatibility
 | 
				
			||||||
// Eventually we'll add UI for managing this
 | 
					// Eventually we'll add UI for managing this
 | 
				
			||||||
export const supportedModels : Record<string, ModelDetail> = {
 | 
					export const supportedModels : Record<string, ModelDetail> = {
 | 
				
			||||||
 | 
					      'gpt-3.5-turbo': modelDetails['gpt-3.5'],
 | 
				
			||||||
 | 
					      'gpt-3.5-turbo-0301': modelDetails['gpt-3.5'],
 | 
				
			||||||
 | 
					      'gpt-3.5-turbo-0613': modelDetails['gpt-3.5'],
 | 
				
			||||||
 | 
					      'gpt-3.5-turbo-16k': modelDetails['gpt-3.5-turbo-16k'],
 | 
				
			||||||
      'gpt-4': modelDetails['gpt-4'],
 | 
					      'gpt-4': modelDetails['gpt-4'],
 | 
				
			||||||
      'gpt-4-0314': modelDetails['gpt-4'],
 | 
					      'gpt-4-0314': modelDetails['gpt-4'],
 | 
				
			||||||
      'gpt-4-0613': modelDetails['gpt-4'],
 | 
					      'gpt-4-0613': modelDetails['gpt-4'],
 | 
				
			||||||
      'gpt-4-32k': modelDetails['gpt-4-32k'],
 | 
					      'gpt-4-32k': modelDetails['gpt-4-32k'],
 | 
				
			||||||
      'gpt-4-32k-0314': modelDetails['gpt-4-32k'],
 | 
					      'gpt-4-32k-0314': modelDetails['gpt-4-32k'],
 | 
				
			||||||
      'gpt-4-32k-0613': modelDetails['gpt-4-32k'],
 | 
					      'gpt-4-32k-0613': modelDetails['gpt-4-32k'],
 | 
				
			||||||
      'gpt-3.5-turbo': modelDetails['gpt-3.5'],
 | 
					      // 'enoch/llama-65b-hf': modelDetails['enoch/llama-65b-hf'],
 | 
				
			||||||
      'gpt-3.5-turbo-16k': modelDetails['gpt-3.5-turbo-16k'],
 | 
					      // 'timdettmers/guanaco-65b': modelDetails['timdettmers/guanaco-65b'],
 | 
				
			||||||
      'gpt-3.5-turbo-0301': modelDetails['gpt-3.5'],
 | 
					      'meta-llama/Llama-2-70b-hf': modelDetails['meta-llama/Llama-2-70b-hf'],
 | 
				
			||||||
      'gpt-3.5-turbo-0613': modelDetails['gpt-3.5']
 | 
					      'meta-llama/Llama-2-70b-chat-hf': modelDetails['meta-llama/Llama-2-70b-chat-hf']
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const lookupList = {
 | 
					const lookupList = {
 | 
				
			||||||
| 
						 | 
					@ -75,7 +145,7 @@ export const supportedModelKeys = Object.keys({ ...supportedModels, ...imageMode
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const tpCache : Record<string, ModelDetail> = {}
 | 
					const tpCache : Record<string, ModelDetail> = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const getModelDetail = (model: Model) => {
 | 
					export const getModelDetail = (model: Model): ModelDetail => {
 | 
				
			||||||
      // First try to get exact match, then from cache
 | 
					      // First try to get exact match, then from cache
 | 
				
			||||||
      let r = supportedModels[model] || tpCache[model]
 | 
					      let r = supportedModels[model] || tpCache[model]
 | 
				
			||||||
      if (r) return r
 | 
					      if (r) return r
 | 
				
			||||||
| 
						 | 
					@ -93,4 +163,140 @@ export const getModelDetail = (model: Model) => {
 | 
				
			||||||
      return r
 | 
					      return r
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const getEndpoint = (model: Model): string => {
 | 
				
			||||||
 | 
					  const modelDetails = getModelDetail(model)
 | 
				
			||||||
 | 
					  const gSettings = get(globalStorage)
 | 
				
			||||||
 | 
					  switch (modelDetails.type) {
 | 
				
			||||||
 | 
					        case 'Petals':
 | 
				
			||||||
 | 
					          return gSettings.pedalsEndpoint || getPetals()
 | 
				
			||||||
 | 
					        case 'OpenAIDall-e':
 | 
				
			||||||
 | 
					          return getApiBase() + getEndpointGenerations()
 | 
				
			||||||
 | 
					        case 'OpenAIChat':
 | 
				
			||||||
 | 
					        default:
 | 
				
			||||||
 | 
					          return gSettings.openAICompletionEndpoint || (getApiBase() + getEndpointCompletions())
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const getStopSequence = (chat: Chat): string => {
 | 
				
			||||||
 | 
					  return chat.settings.stopSequence || valueOf(chat.id, getChatSettingObjectByKey('stopSequence').placeholder)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const getUserStart = (chat: Chat): string => {
 | 
				
			||||||
 | 
					  return mergeProfileFields(
 | 
				
			||||||
 | 
					        chat.settings,
 | 
				
			||||||
 | 
					        chat.settings.userMessageStart || valueOf(chat.id, getChatSettingObjectByKey('userMessageStart').placeholder)
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const getAssistantStart = (chat: Chat): string => {
 | 
				
			||||||
 | 
					  return mergeProfileFields(
 | 
				
			||||||
 | 
					        chat.settings,
 | 
				
			||||||
 | 
					        chat.settings.assistantMessageStart || valueOf(chat.id, getChatSettingObjectByKey('assistantMessageStart').placeholder)
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const getSystemStart = (chat: Chat): string => {
 | 
				
			||||||
 | 
					  return mergeProfileFields(
 | 
				
			||||||
 | 
					        chat.settings,
 | 
				
			||||||
 | 
					        chat.settings.systemMessageStart || valueOf(chat.id, getChatSettingObjectByKey('systemMessageStart').placeholder)
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const getRoleTag = (role: string, model: Model, chat: Chat): string => {
 | 
				
			||||||
 | 
					  const modelDetails = getModelDetail(model)
 | 
				
			||||||
 | 
					  switch (modelDetails.type) {
 | 
				
			||||||
 | 
					        case 'Petals':
 | 
				
			||||||
 | 
					          if (role === 'assistant') return getAssistantStart(chat) + ' '
 | 
				
			||||||
 | 
					          if (role === 'user') return getUserStart(chat) + ' '
 | 
				
			||||||
 | 
					          return getSystemStart(chat) + ' '
 | 
				
			||||||
 | 
					        case 'OpenAIDall-e':
 | 
				
			||||||
 | 
					          return role
 | 
				
			||||||
 | 
					        case 'OpenAIChat':
 | 
				
			||||||
 | 
					        default:
 | 
				
			||||||
 | 
					          return role
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const getTokens = (model: Model, value: string): number[] => {
 | 
				
			||||||
 | 
					  const modelDetails = getModelDetail(model)
 | 
				
			||||||
 | 
					  switch (modelDetails.type) {
 | 
				
			||||||
 | 
					        case 'Petals':
 | 
				
			||||||
 | 
					          return llamaTokenizer.encode(value)
 | 
				
			||||||
 | 
					        case 'OpenAIDall-e':
 | 
				
			||||||
 | 
					          return [0]
 | 
				
			||||||
 | 
					        case 'OpenAIChat':
 | 
				
			||||||
 | 
					        default:
 | 
				
			||||||
 | 
					          return encode(value)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const countTokens = (model: Model, value: string): number => {
 | 
				
			||||||
 | 
					  return getTokens(model, value).length
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const clearModelOptionCache = () => {
 | 
				
			||||||
 | 
					  modelOptionCache.set([])
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export async function getModelOptions (): Promise<SelectOption[]> {
 | 
				
			||||||
 | 
					  const gSettings = get(globalStorage)
 | 
				
			||||||
 | 
					  const openAiKey = get(apiKeyStorage)
 | 
				
			||||||
 | 
					  const cachedOptions = get(modelOptionCache)
 | 
				
			||||||
 | 
					  if (cachedOptions && cachedOptions.length) return cachedOptions
 | 
				
			||||||
 | 
					  // Load available models from OpenAI
 | 
				
			||||||
 | 
					  let openAiModels
 | 
				
			||||||
 | 
					  let allowCache = true
 | 
				
			||||||
 | 
					  if (openAiKey) {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					          openAiModels = (await (
 | 
				
			||||||
 | 
					            await fetch(getApiBase() + getEndpointModels(), {
 | 
				
			||||||
 | 
					              method: 'GET',
 | 
				
			||||||
 | 
					              headers: {
 | 
				
			||||||
 | 
					                Authorization: `Bearer ${openAiKey}`,
 | 
				
			||||||
 | 
					                'Content-Type': 'application/json'
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					          ).json()) as ResponseModels
 | 
				
			||||||
 | 
					        } catch (e) {
 | 
				
			||||||
 | 
					          allowCache = false
 | 
				
			||||||
 | 
					          openAiModels = { data: [] }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					        openAiModels = { data: [] }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  // const filteredModels = Object.keys(supportedModels).filter((model) => {
 | 
				
			||||||
 | 
					  //       switch (getModelDetail(model).type) {
 | 
				
			||||||
 | 
					  //         case 'Petals':
 | 
				
			||||||
 | 
					  //           return gSettings.enablePetals
 | 
				
			||||||
 | 
					  //         case 'OpenAIChat':
 | 
				
			||||||
 | 
					  //         default:
 | 
				
			||||||
 | 
					  //           return openAiModels.data && openAiModels.data.find((m) => m.id === model)
 | 
				
			||||||
 | 
					  //       }
 | 
				
			||||||
 | 
					  // })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const modelOptions:SelectOption[] = Object.keys(supportedModels).reduce((a, m) => {
 | 
				
			||||||
 | 
					        let disabled
 | 
				
			||||||
 | 
					        const modelDetail = getModelDetail(m)
 | 
				
			||||||
 | 
					        switch (modelDetail.type) {
 | 
				
			||||||
 | 
					          case 'Petals':
 | 
				
			||||||
 | 
					            disabled = !gSettings.enablePetals
 | 
				
			||||||
 | 
					            break
 | 
				
			||||||
 | 
					          case 'OpenAIChat':
 | 
				
			||||||
 | 
					          default:
 | 
				
			||||||
 | 
					            disabled = !(openAiModels.data && openAiModels.data.find((m) => m.id === m))
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        const o:SelectOption = {
 | 
				
			||||||
 | 
					          value: m,
 | 
				
			||||||
 | 
					          text: modelDetail.label || m,
 | 
				
			||||||
 | 
					          disabled
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        a.push(o)
 | 
				
			||||||
 | 
					        return a
 | 
				
			||||||
 | 
					  }, [] as SelectOption[])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (allowCache) modelOptionCache.set(modelOptions)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return modelOptions
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
</script>
 | 
					</script>
 | 
				
			||||||
| 
						 | 
					@ -1,5 +1,5 @@
 | 
				
			||||||
<script context="module" lang="ts">
 | 
					<script context="module" lang="ts">
 | 
				
			||||||
  import { getChatDefaults, getExcludeFromProfile } from './Settings.svelte'
 | 
					  import { getChatDefaults, getDefaultModel, getExcludeFromProfile } from './Settings.svelte'
 | 
				
			||||||
  import { get, writable } from 'svelte/store'
 | 
					  import { get, writable } from 'svelte/store'
 | 
				
			||||||
  // Profile definitions
 | 
					  // Profile definitions
 | 
				
			||||||
  import { addMessage, clearMessages, deleteMessage, getChat, getChatSettings, getCustomProfiles, getGlobalSettings, getMessages, newName, resetChatSettings, saveChatStore, setGlobalSettingValueByKey, setMessages, updateProfile } from './Storage.svelte'
 | 
					  import { addMessage, clearMessages, deleteMessage, getChat, getChatSettings, getCustomProfiles, getGlobalSettings, getMessages, newName, resetChatSettings, saveChatStore, setGlobalSettingValueByKey, setMessages, updateProfile } from './Storage.svelte'
 | 
				
			||||||
| 
						 | 
					@ -22,7 +22,9 @@ export const getProfiles = (forceUpdate:boolean = false):Record<string, ChatSett
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    const result = Object.entries(profiles
 | 
					    const result = Object.entries(profiles
 | 
				
			||||||
    ).reduce((a, [k, v]) => {
 | 
					    ).reduce((a, [k, v]) => {
 | 
				
			||||||
 | 
					      v = JSON.parse(JSON.stringify(v))
 | 
				
			||||||
      a[k] = v
 | 
					      a[k] = v
 | 
				
			||||||
 | 
					      v.model = v.model || getDefaultModel()
 | 
				
			||||||
      return a
 | 
					      return a
 | 
				
			||||||
    }, {} as Record<string, ChatSettings>)
 | 
					    }, {} as Record<string, ChatSettings>)
 | 
				
			||||||
    Object.entries(getCustomProfiles()).forEach(([k, v]) => {
 | 
					    Object.entries(getCustomProfiles()).forEach(([k, v]) => {
 | 
				
			||||||
| 
						 | 
					@ -72,7 +74,7 @@ export const getProfile = (key:string, forReset:boolean = false):ChatSettings =>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const mergeProfileFields = (settings: ChatSettings, content: string|undefined, maxWords: number|undefined = undefined): string => {
 | 
					export const mergeProfileFields = (settings: ChatSettings, content: string|undefined, maxWords: number|undefined = undefined): string => {
 | 
				
			||||||
    if (!content?.toString) return ''
 | 
					    if (!content?.toString) return ''
 | 
				
			||||||
    content = (content + '').replaceAll('[[CHARACTER_NAME]]', settings.characterName || 'ChatGPT')
 | 
					    content = (content + '').replaceAll('[[CHARACTER_NAME]]', settings.characterName || 'Assistant')
 | 
				
			||||||
    if (maxWords) content = (content + '').replaceAll('[[MAX_WORDS]]', maxWords.toString())
 | 
					    if (maxWords) content = (content + '').replaceAll('[[MAX_WORDS]]', maxWords.toString())
 | 
				
			||||||
    return content
 | 
					    return content
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
<script context="module" lang="ts">
 | 
					<script context="module" lang="ts">
 | 
				
			||||||
    import { applyProfile } from './Profiles.svelte'
 | 
					    import { applyProfile } from './Profiles.svelte'
 | 
				
			||||||
    import { getChatSettings, getGlobalSettings, setGlobalSettingValueByKey } from './Storage.svelte'
 | 
					    import { get } from 'svelte/store'
 | 
				
			||||||
    import { encode } from 'gpt-tokenizer'
 | 
					    import { apiKeyStorage, getChatSettings, getGlobalSettings, setGlobalSettingValueByKey } from './Storage.svelte'
 | 
				
			||||||
    import { faArrowDown91, faArrowDownAZ, faCheck, faThumbTack } from '@fortawesome/free-solid-svg-icons/index'
 | 
					    import { faArrowDown91, faArrowDownAZ, faCheck, faThumbTack } from '@fortawesome/free-solid-svg-icons/index'
 | 
				
			||||||
// Setting definitions
 | 
					// Setting definitions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -18,8 +18,15 @@ import {
 | 
				
			||||||
      type ChatSortOption
 | 
					      type ChatSortOption
 | 
				
			||||||
 | 
					
 | 
				
			||||||
} from './Types.svelte'
 | 
					} from './Types.svelte'
 | 
				
			||||||
 | 
					    import { getModelDetail, getTokens } from './Models.svelte'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const defaultModel:Model = 'gpt-3.5-turbo'
 | 
					const defaultModel:Model = 'gpt-3.5-turbo'
 | 
				
			||||||
 | 
					const defaultModelPetals:Model = 'meta-llama/Llama-2-70b-chat-hf'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const getDefaultModel = (): Model => {
 | 
				
			||||||
 | 
					  if (!get(apiKeyStorage)) return defaultModelPetals
 | 
				
			||||||
 | 
					  return defaultModel
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const getChatSettingList = (): ChatSetting[] => {
 | 
					export const getChatSettingList = (): ChatSetting[] => {
 | 
				
			||||||
      return chatSettingsList
 | 
					      return chatSettingsList
 | 
				
			||||||
| 
						 | 
					@ -55,8 +62,16 @@ export const getExcludeFromProfile = () => {
 | 
				
			||||||
  return excludeFromProfile
 | 
					  return excludeFromProfile
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const isNotOpenAI = (chatId) => {
 | 
				
			||||||
 | 
					  return getModelDetail(getChatSettings(chatId).model).type !== 'OpenAIChat'
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const isNotPetals = (chatId) => {
 | 
				
			||||||
 | 
					  return getModelDetail(getChatSettings(chatId).model).type !== 'Petals'
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const gptDefaults = {
 | 
					const gptDefaults = {
 | 
				
			||||||
  model: defaultModel,
 | 
					  model: '',
 | 
				
			||||||
  messages: [],
 | 
					  messages: [],
 | 
				
			||||||
  temperature: 1,
 | 
					  temperature: 1,
 | 
				
			||||||
  top_p: 1,
 | 
					  top_p: 1,
 | 
				
			||||||
| 
						 | 
					@ -94,6 +109,10 @@ const defaults:ChatSettings = {
 | 
				
			||||||
  hppContinuePrompt: '',
 | 
					  hppContinuePrompt: '',
 | 
				
			||||||
  hppWithSummaryPrompt: false,
 | 
					  hppWithSummaryPrompt: false,
 | 
				
			||||||
  imageGenerationSize: '',
 | 
					  imageGenerationSize: '',
 | 
				
			||||||
 | 
					  stopSequence: '',
 | 
				
			||||||
 | 
					  userMessageStart: '',
 | 
				
			||||||
 | 
					  assistantMessageStart: '',
 | 
				
			||||||
 | 
					  systemMessageStart: '',
 | 
				
			||||||
  // useResponseAlteration: false,
 | 
					  // useResponseAlteration: false,
 | 
				
			||||||
  // responseAlterations: [],
 | 
					  // responseAlterations: [],
 | 
				
			||||||
  isDirty: false
 | 
					  isDirty: false
 | 
				
			||||||
| 
						 | 
					@ -104,7 +123,10 @@ export const globalDefaults: GlobalSettings = {
 | 
				
			||||||
  lastProfile: 'default',
 | 
					  lastProfile: 'default',
 | 
				
			||||||
  defaultProfile: 'default',
 | 
					  defaultProfile: 'default',
 | 
				
			||||||
  hideSummarized: false,
 | 
					  hideSummarized: false,
 | 
				
			||||||
  chatSort: 'created'
 | 
					  chatSort: 'created',
 | 
				
			||||||
 | 
					  openAICompletionEndpoint: '',
 | 
				
			||||||
 | 
					  enablePetals: false,
 | 
				
			||||||
 | 
					  pedalsEndpoint: ''
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const excludeFromProfile = {
 | 
					const excludeFromProfile = {
 | 
				
			||||||
| 
						 | 
					@ -399,7 +421,13 @@ const modelSetting: ChatSetting & SettingSelect = {
 | 
				
			||||||
      key: 'model',
 | 
					      key: 'model',
 | 
				
			||||||
      name: 'Model',
 | 
					      name: 'Model',
 | 
				
			||||||
      title: 'The model to use - GPT-3.5 is cheaper, but GPT-4 is more powerful.',
 | 
					      title: 'The model to use - GPT-3.5 is cheaper, but GPT-4 is more powerful.',
 | 
				
			||||||
      header: 'Below are the settings that OpenAI allows to be changed for the API calls. See the <a target="_blank" href="https://platform.openai.com/docs/api-reference/chat/create">OpenAI API docs</a> for more details.',
 | 
					      header: (chatId) => {
 | 
				
			||||||
 | 
					        if (isNotOpenAI(chatId)) {
 | 
				
			||||||
 | 
					          return 'Below are the settings that can be changed for the API calls. See <a target="_blank" href="https://platform.openai.com/docs/api-reference/chat/create">this overview</a> to start, though not all settings translate to Petals.'
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          return 'Below are the settings that OpenAI allows to be changed for the API calls. See the <a target="_blank" href="https://platform.openai.com/docs/api-reference/chat/create">OpenAI API docs</a> for more details.'
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
      headerClass: 'is-warning',
 | 
					      headerClass: 'is-warning',
 | 
				
			||||||
      options: [],
 | 
					      options: [],
 | 
				
			||||||
      type: 'select',
 | 
					      type: 'select',
 | 
				
			||||||
| 
						 | 
					@ -417,7 +445,8 @@ const chatSettingsList: ChatSetting[] = [
 | 
				
			||||||
        key: 'stream',
 | 
					        key: 'stream',
 | 
				
			||||||
        name: 'Stream Response',
 | 
					        name: 'Stream Response',
 | 
				
			||||||
        title: 'Stream responses as they are generated.',
 | 
					        title: 'Stream responses as they are generated.',
 | 
				
			||||||
        type: 'boolean'
 | 
					        type: 'boolean',
 | 
				
			||||||
 | 
					        hide: isNotOpenAI
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
        key: 'temperature',
 | 
					        key: 'temperature',
 | 
				
			||||||
| 
						 | 
					@ -432,7 +461,7 @@ const chatSettingsList: ChatSetting[] = [
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
        key: 'top_p',
 | 
					        key: 'top_p',
 | 
				
			||||||
        name: 'Nucleus Sampling',
 | 
					        name: 'Nucleus Sampling (Top-p)',
 | 
				
			||||||
        title: 'An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered.\n' +
 | 
					        title: 'An alternative to sampling with temperature, called nucleus sampling, where the model considers the results of the tokens with top_p probability mass. So 0.1 means only the tokens comprising the top 10% probability mass are considered.\n' +
 | 
				
			||||||
              '\n' +
 | 
					              '\n' +
 | 
				
			||||||
              'We generally recommend altering this or temperature but not both',
 | 
					              'We generally recommend altering this or temperature but not both',
 | 
				
			||||||
| 
						 | 
					@ -448,7 +477,8 @@ const chatSettingsList: ChatSetting[] = [
 | 
				
			||||||
        min: 1,
 | 
					        min: 1,
 | 
				
			||||||
        max: 10,
 | 
					        max: 10,
 | 
				
			||||||
        step: 1,
 | 
					        step: 1,
 | 
				
			||||||
        type: 'number'
 | 
					        type: 'number',
 | 
				
			||||||
 | 
					        hide: isNotOpenAI
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
        key: 'max_tokens',
 | 
					        key: 'max_tokens',
 | 
				
			||||||
| 
						 | 
					@ -460,6 +490,7 @@ const chatSettingsList: ChatSetting[] = [
 | 
				
			||||||
        max: 32768,
 | 
					        max: 32768,
 | 
				
			||||||
        step: 1,
 | 
					        step: 1,
 | 
				
			||||||
        type: 'number',
 | 
					        type: 'number',
 | 
				
			||||||
 | 
					        hide: isNotOpenAI,
 | 
				
			||||||
        forceApi: true // Since default here is different than gpt default, will make sure we always send it
 | 
					        forceApi: true // Since default here is different than gpt default, will make sure we always send it
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
| 
						 | 
					@ -469,7 +500,8 @@ const chatSettingsList: ChatSetting[] = [
 | 
				
			||||||
        min: -2,
 | 
					        min: -2,
 | 
				
			||||||
        max: 2,
 | 
					        max: 2,
 | 
				
			||||||
        step: 0.2,
 | 
					        step: 0.2,
 | 
				
			||||||
        type: 'number'
 | 
					        type: 'number',
 | 
				
			||||||
 | 
					        hide: isNotOpenAI
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
        key: 'frequency_penalty',
 | 
					        key: 'frequency_penalty',
 | 
				
			||||||
| 
						 | 
					@ -478,7 +510,52 @@ const chatSettingsList: ChatSetting[] = [
 | 
				
			||||||
        min: -2,
 | 
					        min: -2,
 | 
				
			||||||
        max: 2,
 | 
					        max: 2,
 | 
				
			||||||
        step: 0.2,
 | 
					        step: 0.2,
 | 
				
			||||||
        type: 'number'
 | 
					        type: 'number',
 | 
				
			||||||
 | 
					        hide: isNotOpenAI
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        key: 'stopSequence',
 | 
				
			||||||
 | 
					        name: 'Stop Sequence',
 | 
				
			||||||
 | 
					        title: 'Characters used to separate messages in the message chain.',
 | 
				
			||||||
 | 
					        type: 'text',
 | 
				
			||||||
 | 
					        placeholder: (chatId) => {
 | 
				
			||||||
 | 
					          const val = getModelDetail(getChatSettings(chatId).model).stop
 | 
				
			||||||
 | 
					          return (val && val[0]) || ''
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        hide: isNotPetals
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        key: 'userMessageStart',
 | 
				
			||||||
 | 
					        name: 'User Message Start Sequence',
 | 
				
			||||||
 | 
					        title: 'Sequence to denote user messages in the message chain.',
 | 
				
			||||||
 | 
					        type: 'text',
 | 
				
			||||||
 | 
					        placeholder: (chatId) => {
 | 
				
			||||||
 | 
					          const val = getModelDetail(getChatSettings(chatId).model).userStart
 | 
				
			||||||
 | 
					          return val || ''
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        hide: isNotPetals
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        key: 'assistantMessageStart',
 | 
				
			||||||
 | 
					        name: 'Assistant Message Start Sequence',
 | 
				
			||||||
 | 
					        title: 'Sequence to denote assistant messages in the message chain.',
 | 
				
			||||||
 | 
					        type: 'text',
 | 
				
			||||||
 | 
					        placeholder: (chatId) => {
 | 
				
			||||||
 | 
					          const val = getModelDetail(getChatSettings(chatId).model).assistantStart
 | 
				
			||||||
 | 
					          return val || ''
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        hide: isNotPetals
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        key: 'systemMessageStart',
 | 
				
			||||||
 | 
					        name: 'System Message Start Sequence',
 | 
				
			||||||
 | 
					        title: 'Sequence to denote system messages in the message chain.',
 | 
				
			||||||
 | 
					        type: 'text',
 | 
				
			||||||
 | 
					        placeholder: (chatId) => {
 | 
				
			||||||
 | 
					          const val = getModelDetail(getChatSettings(chatId).model).systemStart
 | 
				
			||||||
 | 
					          return val || ''
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        hide: isNotPetals
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
        // logit bias editor not implemented yet
 | 
					        // logit bias editor not implemented yet
 | 
				
			||||||
| 
						 | 
					@ -497,7 +574,7 @@ const chatSettingsList: ChatSetting[] = [
 | 
				
			||||||
          // console.log('logit_bias', val, getChatSettings(chatId).logit_bias)
 | 
					          // console.log('logit_bias', val, getChatSettings(chatId).logit_bias)
 | 
				
			||||||
          if (!val) return null
 | 
					          if (!val) return null
 | 
				
			||||||
          const tokenized:Record<number, number> = Object.entries(val).reduce((a, [k, v]) => {
 | 
					          const tokenized:Record<number, number> = Object.entries(val).reduce((a, [k, v]) => {
 | 
				
			||||||
            const tokens:number[] = encode(k)
 | 
					            const tokens:number[] = getTokens(getChatSettings(chatId).model, k)
 | 
				
			||||||
            tokens.forEach(t => { a[t] = v })
 | 
					            tokens.forEach(t => { a[t] = v })
 | 
				
			||||||
            return a
 | 
					            return a
 | 
				
			||||||
          }, {} as Record<number, number>)
 | 
					          }, {} as Record<number, number>)
 | 
				
			||||||
| 
						 | 
					@ -536,6 +613,21 @@ const globalSettingsList:GlobalSetting[] = [
 | 
				
			||||||
        key: 'hideSummarized',
 | 
					        key: 'hideSummarized',
 | 
				
			||||||
        name: 'Hide Summarized Messages',
 | 
					        name: 'Hide Summarized Messages',
 | 
				
			||||||
        type: 'boolean'
 | 
					        type: 'boolean'
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        key: 'openAICompletionEndpoint',
 | 
				
			||||||
 | 
					        name: 'OpenAI Completions Endpoint',
 | 
				
			||||||
 | 
					        type: 'text'
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        key: 'enablePetals',
 | 
				
			||||||
 | 
					        name: 'Enable Petals APIs',
 | 
				
			||||||
 | 
					        type: 'boolean'
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        key: 'pedalsEndpoint',
 | 
				
			||||||
 | 
					        name: 'Petals API Endpoint',
 | 
				
			||||||
 | 
					        type: 'text'
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
<script lang="ts">
 | 
					<script lang="ts">
 | 
				
			||||||
  import { params } from 'svelte-spa-router'
 | 
					  import { params } from 'svelte-spa-router'
 | 
				
			||||||
  import ChatMenuItem from './ChatMenuItem.svelte'
 | 
					  import ChatMenuItem from './ChatMenuItem.svelte'
 | 
				
			||||||
  import { apiKeyStorage, chatsStorage, pinMainMenu, checkStateChange, getChatSortOption, setChatSortOption } from './Storage.svelte'
 | 
					  import { apiKeyStorage, chatsStorage, pinMainMenu, checkStateChange, getChatSortOption, setChatSortOption, hasActiveModels } from './Storage.svelte'
 | 
				
			||||||
  import Fa from 'svelte-fa/src/fa.svelte'
 | 
					  import Fa from 'svelte-fa/src/fa.svelte'
 | 
				
			||||||
  import { faSquarePlus, faKey } from '@fortawesome/free-solid-svg-icons/index'
 | 
					  import { faSquarePlus, faKey } from '@fortawesome/free-solid-svg-icons/index'
 | 
				
			||||||
  import ChatOptionMenu from './ChatOptionMenu.svelte'
 | 
					  import ChatOptionMenu from './ChatOptionMenu.svelte'
 | 
				
			||||||
| 
						 | 
					@ -14,10 +14,12 @@
 | 
				
			||||||
  $: activeChatId = $params && $params.chatId ? parseInt($params.chatId) : undefined
 | 
					  $: activeChatId = $params && $params.chatId ? parseInt($params.chatId) : undefined
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  let sortOption = getChatSortOption()
 | 
					  let sortOption = getChatSortOption()
 | 
				
			||||||
 | 
					  let hasModels = hasActiveModels()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const onStateChange = (...args:any) => {
 | 
					  const onStateChange = (...args:any) => {
 | 
				
			||||||
    sortOption = getChatSortOption()
 | 
					    sortOption = getChatSortOption()
 | 
				
			||||||
    sortedChats = $chatsStorage.sort(sortOption.sortFn)
 | 
					    sortedChats = $chatsStorage.sort(sortOption.sortFn)
 | 
				
			||||||
 | 
					    hasModels = hasActiveModels()
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  $: onStateChange($checkStateChange)
 | 
					  $: onStateChange($checkStateChange)
 | 
				
			||||||
| 
						 | 
					@ -72,14 +74,14 @@
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      <div class="level-right">
 | 
					      <div class="level-right">
 | 
				
			||||||
        {#if !$apiKeyStorage}
 | 
					        {#if !hasModels}
 | 
				
			||||||
        <div class="level-item">
 | 
					        <div class="level-item">
 | 
				
			||||||
          <a href={'#/'} class="panel-block" class:is-disabled={!$apiKeyStorage}
 | 
					          <a href={'#/'} class="panel-block" class:is-disabled={!$apiKeyStorage}
 | 
				
			||||||
            ><span class="greyscale mr-1"><Fa icon={faKey} /></span> API key</a
 | 
					            ><span class="greyscale mr-1"><Fa icon={faKey} /></span> API key</a
 | 
				
			||||||
          ></div>
 | 
					          ></div>
 | 
				
			||||||
        {:else}
 | 
					        {:else}
 | 
				
			||||||
        <div class="level-item">
 | 
					        <div class="level-item">
 | 
				
			||||||
          <button on:click={() => { $pinMainMenu = false; startNewChatWithWarning(activeChatId) }} class="panel-block button" title="Start new chat with default profile" class:is-disabled={!$apiKeyStorage}
 | 
					          <button on:click={() => { $pinMainMenu = false; startNewChatWithWarning(activeChatId) }} class="panel-block button" title="Start new chat with default profile" class:is-disabled={!hasModels}
 | 
				
			||||||
            ><span class="greyscale mr-1"><Fa icon={faSquarePlus} /></span> New chat</button>
 | 
					            ><span class="greyscale mr-1"><Fa icon={faSquarePlus} /></span> New chat</button>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
        {/if}
 | 
					        {/if}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,25 +1,43 @@
 | 
				
			||||||
<script context="module" lang="ts">
 | 
					<script context="module" lang="ts">
 | 
				
			||||||
  import { getModelDetail } from './Models.svelte'
 | 
					  import { countTokens, getModelDetail, getRoleTag, getStopSequence } from './Models.svelte'
 | 
				
			||||||
  import type { Message, Model, Usage } from './Types.svelte'
 | 
					  import type { Chat, Message, Model, Usage } from './Types.svelte'
 | 
				
			||||||
  import { encode } from 'gpt-tokenizer'
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export const getPrice = (tokens: Usage, model: Model): number => {
 | 
					  export const getPrice = (tokens: Usage, model: Model): number => {
 | 
				
			||||||
    const t = getModelDetail(model)
 | 
					    const t = getModelDetail(model)
 | 
				
			||||||
    return ((tokens.prompt_tokens * t.prompt) + (tokens.completion_tokens * t.completion))
 | 
					    return ((tokens.prompt_tokens * t.prompt) + (tokens.completion_tokens * t.completion))
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export const countPromptTokens = (prompts:Message[], model:Model):number => {
 | 
					  export const countPromptTokens = (prompts:Message[], model:Model, chat: Chat):number => {
 | 
				
			||||||
    return prompts.reduce((a, m) => {
 | 
					    const detail = getModelDetail(model)
 | 
				
			||||||
      a += countMessageTokens(m, model)
 | 
					    const count = prompts.reduce((a, m) => {
 | 
				
			||||||
 | 
					      a += countMessageTokens(m, model, chat)
 | 
				
			||||||
      return a
 | 
					      return a
 | 
				
			||||||
    }, 0) + 3 // Always seems to be message counts + 3
 | 
					    }, 0)
 | 
				
			||||||
  }
 | 
					    switch (detail.type) {
 | 
				
			||||||
 | 
					      case 'Petals':
 | 
				
			||||||
  export const countMessageTokens = (message:Message, model:Model):number => {
 | 
					        return count
 | 
				
			||||||
 | 
					      case 'OpenAIChat':
 | 
				
			||||||
 | 
					      default:
 | 
				
			||||||
        // Not sure how OpenAI formats it, but this seems to get close to the right counts.
 | 
					        // Not sure how OpenAI formats it, but this seems to get close to the right counts.
 | 
				
			||||||
        // Would be nice to know. This works for gpt-3.5.  gpt-4 could be different.
 | 
					        // Would be nice to know. This works for gpt-3.5.  gpt-4 could be different.
 | 
				
			||||||
        // Complete stab in the dark here -- update if you know where all the extra tokens really come from.
 | 
					        // Complete stab in the dark here -- update if you know where all the extra tokens really come from.
 | 
				
			||||||
    return encode('## ' + message.role + ' ##:\r\n\r\n' + message.content + '\r\n\r\n\r\n').length
 | 
					        return count + 3 // Always seems to be message counts + 3
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  export const countMessageTokens = (message:Message, model:Model, chat: Chat):number => {
 | 
				
			||||||
 | 
					    const detail = getModelDetail(model)
 | 
				
			||||||
 | 
					    const stop = getStopSequence(chat)
 | 
				
			||||||
 | 
					    switch (detail.type) {
 | 
				
			||||||
 | 
					      case 'Petals':
 | 
				
			||||||
 | 
					        return countTokens(model, getRoleTag(message.role, model, chat) + ': ' + message.content + (stop || '###'))
 | 
				
			||||||
 | 
					      case 'OpenAIChat':
 | 
				
			||||||
 | 
					      default:
 | 
				
			||||||
 | 
					        // Not sure how OpenAI formats it, but this seems to get close to the right counts.
 | 
				
			||||||
 | 
					        // Would be nice to know. This works for gpt-3.5.  gpt-4 could be different.
 | 
				
			||||||
 | 
					        // Complete stab in the dark here -- update if you know where all the extra tokens really come from.
 | 
				
			||||||
 | 
					        return countTokens(model, '## ' + message.role + ' ##:\r\n\r\n' + message.content + '\r\n\r\n\r\n')
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export const getModelMaxTokens = (model:Model):number => {
 | 
					  export const getModelMaxTokens = (model:Model):number => {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -30,6 +30,11 @@
 | 
				
			||||||
    return get(apiKeyStorage)
 | 
					    return get(apiKeyStorage)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  export const hasActiveModels = (): boolean => {
 | 
				
			||||||
 | 
					    const globalSettings = get(globalStorage) || {}
 | 
				
			||||||
 | 
					    return !!get(apiKeyStorage) || !!globalSettings.enablePetals
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export const newChatID = (): number => {
 | 
					  export const newChatID = (): number => {
 | 
				
			||||||
    const chats = get(chatsStorage)
 | 
					    const chats = get(chatsStorage)
 | 
				
			||||||
    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
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,7 +7,15 @@ export type Model = typeof supportedModelKeys[number];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type ImageGenerationSizes = typeof imageGenerationSizeTypes[number];
 | 
					export type ImageGenerationSizes = typeof imageGenerationSizeTypes[number];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type RequestType = 'OpenAIChat' | 'OpenAIDall-e' | 'Petals'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type ModelDetail = {
 | 
					export type ModelDetail = {
 | 
				
			||||||
 | 
					    type: RequestType;
 | 
				
			||||||
 | 
					    label?: string;
 | 
				
			||||||
 | 
					    stop?: string[];
 | 
				
			||||||
 | 
					    userStart?: string,
 | 
				
			||||||
 | 
					    assistantStart?: string,
 | 
				
			||||||
 | 
					    systemStart?: string,
 | 
				
			||||||
    prompt: number;
 | 
					    prompt: number;
 | 
				
			||||||
    completion: number;
 | 
					    completion: number;
 | 
				
			||||||
    max: number;
 | 
					    max: number;
 | 
				
			||||||
| 
						 | 
					@ -105,6 +113,10 @@ export type ChatSettings = {
 | 
				
			||||||
    trainingPrompts?: Message[];
 | 
					    trainingPrompts?: Message[];
 | 
				
			||||||
    useResponseAlteration?: boolean;
 | 
					    useResponseAlteration?: boolean;
 | 
				
			||||||
    responseAlterations?: ResponseAlteration[];
 | 
					    responseAlterations?: ResponseAlteration[];
 | 
				
			||||||
 | 
					    stopSequence: string;
 | 
				
			||||||
 | 
					    userMessageStart: string;
 | 
				
			||||||
 | 
					    assistantMessageStart: string;
 | 
				
			||||||
 | 
					    systemMessageStart: string;
 | 
				
			||||||
    isDirty?: boolean;
 | 
					    isDirty?: boolean;
 | 
				
			||||||
  } & Request;
 | 
					  } & Request;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -122,16 +134,16 @@ export type Chat = {
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  type ResponseOK = {
 | 
					  type ResponseOK = {
 | 
				
			||||||
    id: string;
 | 
					    id?: string;
 | 
				
			||||||
    object: string;
 | 
					    object?: string;
 | 
				
			||||||
    created: number;
 | 
					    created?: number;
 | 
				
			||||||
    choices: {
 | 
					    choices?: {
 | 
				
			||||||
      index: number;
 | 
					      index?: number;
 | 
				
			||||||
      message: Message;
 | 
					      message: Message;
 | 
				
			||||||
      finish_reason: string;
 | 
					      finish_reason?: string;
 | 
				
			||||||
      delta: Message;
 | 
					      delta: Message;
 | 
				
			||||||
    }[];
 | 
					    }[];
 | 
				
			||||||
    usage: Usage;
 | 
					    usage?: Usage;
 | 
				
			||||||
    model: Model;
 | 
					    model: Model;
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -172,6 +184,9 @@ export type GlobalSettings = {
 | 
				
			||||||
    defaultProfile: string;
 | 
					    defaultProfile: string;
 | 
				
			||||||
    hideSummarized: boolean;
 | 
					    hideSummarized: boolean;
 | 
				
			||||||
    chatSort: ChatSortOptions;
 | 
					    chatSort: ChatSortOptions;
 | 
				
			||||||
 | 
					    openAICompletionEndpoint: string;
 | 
				
			||||||
 | 
					    enablePetals: boolean;
 | 
				
			||||||
 | 
					    pedalsEndpoint: string;
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  type SettingNumber = {
 | 
					  type SettingNumber = {
 | 
				
			||||||
| 
						 | 
					@ -184,6 +199,7 @@ export type GlobalSettings = {
 | 
				
			||||||
export type SelectOption = {
 | 
					export type SelectOption = {
 | 
				
			||||||
    value: string|number;
 | 
					    value: string|number;
 | 
				
			||||||
    text: string;
 | 
					    text: string;
 | 
				
			||||||
 | 
					    disabled?: boolean;
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type ChatSortOption = SelectOption & {
 | 
					export type ChatSortOption = SelectOption & {
 | 
				
			||||||
| 
						 | 
					@ -236,15 +252,17 @@ export type SubSetting = {
 | 
				
			||||||
    settings: any[];
 | 
					    settings: any[];
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export type ValueFn = (chatId:number) => string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export type ChatSetting = {
 | 
					export type ChatSetting = {
 | 
				
			||||||
    key: keyof ChatSettings;
 | 
					    key: keyof ChatSettings;
 | 
				
			||||||
    name: string;
 | 
					    name: string;
 | 
				
			||||||
    title: string;
 | 
					    title: string;
 | 
				
			||||||
    forceApi?: boolean; // force in api requests, even if set to default
 | 
					    forceApi?: boolean; // force in api requests, even if set to default
 | 
				
			||||||
    hidden?: boolean; // Hide from setting menus
 | 
					    hidden?: boolean; // Hide from setting menus
 | 
				
			||||||
    header?: string;
 | 
					    header?: string | ValueFn;
 | 
				
			||||||
    headerClass?: string;
 | 
					    headerClass?: string | ValueFn;
 | 
				
			||||||
    placeholder?: string;
 | 
					    placeholder?: string | ValueFn;
 | 
				
			||||||
    hide?: (chatId:number) => boolean;
 | 
					    hide?: (chatId:number) => boolean;
 | 
				
			||||||
    apiTransform?: (chatId:number, setting:ChatSetting, value:any) => any;
 | 
					    apiTransform?: (chatId:number, setting:ChatSetting, value:any) => any;
 | 
				
			||||||
    fieldControls?: FieldControl[];
 | 
					    fieldControls?: FieldControl[];
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -147,4 +147,9 @@
 | 
				
			||||||
    newChat()
 | 
					    newChat()
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  export const valueOf = (chatId: number, value: any) => {
 | 
				
			||||||
 | 
					    if (typeof value === 'function') return value(chatId)
 | 
				
			||||||
 | 
					    return value
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
</script> 
 | 
					</script> 
 | 
				
			||||||
		Loading…
	
		Reference in New Issue