Add add streaming responses based on #107
This commit is contained in:
		
							parent
							
								
									fffe34c80c
								
							
						
					
					
						commit
						15272de1d4
					
				
							
								
								
									
										14
									
								
								src/app.scss
								
								
								
								
							
							
						
						
									
										14
									
								
								src/app.scss
								
								
								
								
							| 
						 | 
					@ -607,6 +607,20 @@ aside.menu.main-menu .menu-expanse {
 | 
				
			||||||
  border-top-left-radius: 0px !important;
 | 
					  border-top-left-radius: 0px !important;
 | 
				
			||||||
  border-bottom-left-radius: 0px !important;
 | 
					  border-bottom-left-radius: 0px !important;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					.message.streaming .tool-drawer, .message.streaming .tool-drawer-mask {
 | 
				
			||||||
 | 
					  display: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					@keyframes cursor-blink {
 | 
				
			||||||
 | 
					  0% {
 | 
				
			||||||
 | 
					    opacity: 0;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.message.streaming .message-display p:last-of-type::after {
 | 
				
			||||||
 | 
					  position: relative;
 | 
				
			||||||
 | 
					  content: '❚';
 | 
				
			||||||
 | 
					  animation: cursor-blink 1s steps(2) infinite;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.modal {
 | 
					.modal {
 | 
				
			||||||
  z-index:100;
 | 
					  z-index:100;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,17 +8,23 @@
 | 
				
			||||||
    insertMessages,
 | 
					    insertMessages,
 | 
				
			||||||
    getChatSettingValueNullDefault,
 | 
					    getChatSettingValueNullDefault,
 | 
				
			||||||
    updateChatSettings,
 | 
					    updateChatSettings,
 | 
				
			||||||
    updateRunningTotal,
 | 
					 | 
				
			||||||
    checkStateChange,
 | 
					    checkStateChange,
 | 
				
			||||||
    showSetChatSettings,
 | 
					    showSetChatSettings,
 | 
				
			||||||
    submitExitingPromptsNow
 | 
					    submitExitingPromptsNow,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    deleteMessage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  } from './Storage.svelte'
 | 
					  } from './Storage.svelte'
 | 
				
			||||||
  import { getRequestSettingList, defaultModel } from './Settings.svelte'
 | 
					  import { getRequestSettingList, defaultModel } from './Settings.svelte'
 | 
				
			||||||
  import {
 | 
					  import {
 | 
				
			||||||
    type Request,
 | 
					    type Request,
 | 
				
			||||||
    type Response,
 | 
					 | 
				
			||||||
    type Message,
 | 
					    type Message,
 | 
				
			||||||
    type Chat
 | 
					    type Chat,
 | 
				
			||||||
 | 
					    type ChatCompletionOpts,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    type Usage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  } from './Types.svelte'
 | 
					  } from './Types.svelte'
 | 
				
			||||||
  import Prompts from './Prompts.svelte'
 | 
					  import Prompts from './Prompts.svelte'
 | 
				
			||||||
  import Messages from './Messages.svelte'
 | 
					  import Messages from './Messages.svelte'
 | 
				
			||||||
| 
						 | 
					@ -37,11 +43,13 @@
 | 
				
			||||||
  // import { encode } from 'gpt-tokenizer'
 | 
					  // import { encode } from 'gpt-tokenizer'
 | 
				
			||||||
  import { v4 as uuidv4 } from 'uuid'
 | 
					  import { v4 as uuidv4 } from 'uuid'
 | 
				
			||||||
  import { countPromptTokens, getModelMaxTokens, getPrice } from './Stats.svelte'
 | 
					  import { countPromptTokens, getModelMaxTokens, getPrice } from './Stats.svelte'
 | 
				
			||||||
  import { autoGrowInputOnEvent, sizeTextElements } from './Util.svelte'
 | 
					  import { autoGrowInputOnEvent, scrollToMessage, sizeTextElements } from './Util.svelte'
 | 
				
			||||||
  import ChatSettingsModal from './ChatSettingsModal.svelte'
 | 
					  import ChatSettingsModal from './ChatSettingsModal.svelte'
 | 
				
			||||||
  import Footer from './Footer.svelte'
 | 
					  import Footer from './Footer.svelte'
 | 
				
			||||||
  import { openModal } from 'svelte-modals'
 | 
					  import { openModal } from 'svelte-modals'
 | 
				
			||||||
  import PromptInput from './PromptInput.svelte'
 | 
					  import PromptInput from './PromptInput.svelte'
 | 
				
			||||||
 | 
					  import { ChatCompletionResponse } from './ChatCompletionResponse.svelte'
 | 
				
			||||||
 | 
					  import { fetchEventSource } from '@microsoft/fetch-event-source'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // This makes it possible to override the OpenAI API base URL in the .env file
 | 
					  // This makes it possible to override the OpenAI API base URL in the .env file
 | 
				
			||||||
  const apiBase = import.meta.env.VITE_API_BASE || 'https://api.openai.com'
 | 
					  const apiBase = import.meta.env.VITE_API_BASE || 'https://api.openai.com'
 | 
				
			||||||
| 
						 | 
					@ -52,8 +60,6 @@
 | 
				
			||||||
  let updating: boolean = false
 | 
					  let updating: boolean = false
 | 
				
			||||||
  let updatingMessage: string = ''
 | 
					  let updatingMessage: string = ''
 | 
				
			||||||
  let input: HTMLTextAreaElement
 | 
					  let input: HTMLTextAreaElement
 | 
				
			||||||
  // let settings: HTMLDivElement
 | 
					 | 
				
			||||||
  // let chatNameSettings: HTMLFormElement
 | 
					 | 
				
			||||||
  let recognition: any = null
 | 
					  let recognition: any = null
 | 
				
			||||||
  let recording = false
 | 
					  let recording = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -141,20 +147,24 @@
 | 
				
			||||||
  // Scroll to the bottom of the chat on update
 | 
					  // Scroll to the bottom of the chat on update
 | 
				
			||||||
  const focusInput = () => {
 | 
					  const focusInput = () => {
 | 
				
			||||||
    input.focus()
 | 
					    input.focus()
 | 
				
			||||||
    setTimeout(() => document.querySelector('body')?.scrollIntoView({ behavior: 'smooth', block: 'end' }), 0)
 | 
					    setTimeout(() => scrollToBottom(), 0)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const scrollToBottom = (instant:boolean = false) => {
 | 
				
			||||||
 | 
					    document.querySelector('body')?.scrollIntoView({ behavior: (instant ? 'instant' : 'smooth') as any, block: 'end' })
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Send API request
 | 
					  // Send API request
 | 
				
			||||||
  const sendRequest = async (messages: Message[], summaryTarget:number|undefined = undefined, withSummary:boolean = false): Promise<Response> => {
 | 
					  const sendRequest = async (messages: Message[], opts:ChatCompletionOpts): Promise<ChatCompletionResponse> => {
 | 
				
			||||||
    // Show updating bar
 | 
					    // Show updating bar
 | 
				
			||||||
 | 
					    opts.chat = chat
 | 
				
			||||||
 | 
					    const chatResponse = new ChatCompletionResponse(opts)
 | 
				
			||||||
    updating = true
 | 
					    updating = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const model = chat.settings.model || defaultModel
 | 
					    const model = chat.settings.model || defaultModel
 | 
				
			||||||
    const maxTokens = getModelMaxTokens(model) // max tokens for model
 | 
					    const maxTokens = getModelMaxTokens(model) // max tokens for model
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let response: Response
 | 
					    const messageFilter = (m:Message) => !m.suppress && m.role !== 'error' && m.content && !m.summarized
 | 
				
			||||||
 | 
					 | 
				
			||||||
    const messageFilter = (m) => !m.suppress && m.role !== 'error' && m.content && !m.summarized
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Submit only the role and content of the messages, provide the previous messages as well for context
 | 
					    // Submit only the role and content of the messages, provide the previous messages as well for context
 | 
				
			||||||
    let filtered = messages.filter(messageFilter)
 | 
					    let filtered = messages.filter(messageFilter)
 | 
				
			||||||
| 
						 | 
					@ -166,8 +176,8 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // console.log('Estimated',promptTokenCount,'prompt token for this request')
 | 
					    // console.log('Estimated',promptTokenCount,'prompt token for this request')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (chatSettings.useSummarization &&
 | 
					    if (chatSettings.useSummarization && !opts.didSummary &&
 | 
				
			||||||
          !withSummary && !summaryTarget &&
 | 
					          !opts.summaryRequest && !opts.maxTokens &&
 | 
				
			||||||
          promptTokenCount > chatSettings.summaryThreshold) {
 | 
					          promptTokenCount > chatSettings.summaryThreshold) {
 | 
				
			||||||
      // Too many tokens -- well need to sumarize some past ones else we'll run out of space
 | 
					      // Too many tokens -- well need to sumarize some past ones else we'll run out of space
 | 
				
			||||||
      // Get a block of past prompts we'll summarize
 | 
					      // Get a block of past prompts we'll summarize
 | 
				
			||||||
| 
						 | 
					@ -239,35 +249,47 @@
 | 
				
			||||||
          summarizeReq.push(summaryMessage)
 | 
					          summarizeReq.push(summaryMessage)
 | 
				
			||||||
          summaryPromptSize = countPromptTokens(summarizeReq, model)
 | 
					          summaryPromptSize = countPromptTokens(summarizeReq, model)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          const summaryResponse:Message = {
 | 
				
			||||||
 | 
					            role: 'assistant',
 | 
				
			||||||
 | 
					            content: '',
 | 
				
			||||||
 | 
					            uuid: uuidv4(),
 | 
				
			||||||
 | 
					            streaming: opts.streaming,
 | 
				
			||||||
 | 
					            summary: []
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          summaryResponse.usage = {
 | 
				
			||||||
 | 
					            prompt_tokens: 0
 | 
				
			||||||
 | 
					          } as Usage
 | 
				
			||||||
 | 
					          summaryResponse.model = model
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          // Insert summary prompt
 | 
				
			||||||
 | 
					          insertMessages(chatId, endPrompt, [summaryResponse])
 | 
				
			||||||
 | 
					          if (opts.streaming) setTimeout(() => scrollToMessage(summaryResponse.uuid, 150, true, true), 0)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          // Wait for the summary completion
 | 
					          // Wait for the summary completion
 | 
				
			||||||
          updatingMessage = 'Building Summary...'
 | 
					          updatingMessage = 'Summarizing...'
 | 
				
			||||||
          const summary = await sendRequest(summarizeReq, summarySize)
 | 
					          const summary = await sendRequest(summarizeReq, {
 | 
				
			||||||
          if (summary.error) {
 | 
					            summaryRequest: true,
 | 
				
			||||||
 | 
					            streaming: opts.streaming,
 | 
				
			||||||
 | 
					            maxTokens: summarySize,
 | 
				
			||||||
 | 
					            fillMessage: summaryResponse,
 | 
				
			||||||
 | 
					            autoAddMessages: true,
 | 
				
			||||||
 | 
					            onMessageChange: (m) => {
 | 
				
			||||||
 | 
					              if (opts.streaming) scrollToMessage(summaryResponse.uuid, 150, true, true)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          } as ChatCompletionOpts)
 | 
				
			||||||
 | 
					          if (!summary.hasFinished()) await summary.promiseToFinish()
 | 
				
			||||||
 | 
					          if (summary.hasError()) {
 | 
				
			||||||
            // Failed to some API issue. let the original caller handle it.
 | 
					            // Failed to some API issue. let the original caller handle it.
 | 
				
			||||||
 | 
					            deleteMessage(chatId, summaryResponse.uuid)
 | 
				
			||||||
            return summary
 | 
					            return summary
 | 
				
			||||||
          } else {
 | 
					          } else {
 | 
				
			||||||
            // Get response
 | 
					 | 
				
			||||||
            const summaryPromptContent: string = summary.choices.reduce((a, c) => {
 | 
					 | 
				
			||||||
              if (a.length > c.message.content.length) return a
 | 
					 | 
				
			||||||
              a = c.message.content
 | 
					 | 
				
			||||||
              return a
 | 
					 | 
				
			||||||
            }, '')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // Looks like we got our summarized messages.
 | 
					            // Looks like we got our summarized messages.
 | 
				
			||||||
            // get ids of messages we summarized
 | 
					            // get ids of messages we summarized
 | 
				
			||||||
            const summarizedIds = summarize.slice(pinTop + systemPad).map(m => m.uuid)
 | 
					            const summarizedIds = summarize.slice(pinTop + systemPad).map(m => m.uuid)
 | 
				
			||||||
            // Mark the new summaries as such
 | 
					            // Mark the new summaries as such
 | 
				
			||||||
            const summaryPrompt:Message = {
 | 
					            summaryResponse.summary = summarizedIds
 | 
				
			||||||
              role: 'assistant',
 | 
					
 | 
				
			||||||
              content: summaryPromptContent,
 | 
					            const summaryIds = [summaryResponse.uuid]
 | 
				
			||||||
              uuid: uuidv4(),
 | 
					 | 
				
			||||||
              summary: summarizedIds,
 | 
					 | 
				
			||||||
              usage: summary.usage,
 | 
					 | 
				
			||||||
              model
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            const summaryIds = [summaryPrompt.uuid]
 | 
					 | 
				
			||||||
            // Insert messages
 | 
					 | 
				
			||||||
            insertMessages(chatId, endPrompt, [summaryPrompt])
 | 
					 | 
				
			||||||
            // Disable the messages we summarized so they still show in history
 | 
					            // Disable the messages we summarized so they still show in history
 | 
				
			||||||
            summarize.forEach((m, i) => {
 | 
					            summarize.forEach((m, i) => {
 | 
				
			||||||
              if (i - systemPad >= pinTop) {
 | 
					              if (i - systemPad >= pinTop) {
 | 
				
			||||||
| 
						 | 
					@ -278,7 +300,8 @@
 | 
				
			||||||
            // Re-run request with summarized prompts
 | 
					            // Re-run request with summarized prompts
 | 
				
			||||||
            // return { error: { message: "End for now" } } as Response
 | 
					            // return { error: { message: "End for now" } } as Response
 | 
				
			||||||
            updatingMessage = 'Continuing...'
 | 
					            updatingMessage = 'Continuing...'
 | 
				
			||||||
            return await sendRequest(chat.messages, undefined, true)
 | 
					            opts.didSummary = true
 | 
				
			||||||
 | 
					            return await sendRequest(chat.messages, opts)
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        } else if (!summaryPrompt) {
 | 
					        } else if (!summaryPrompt) {
 | 
				
			||||||
          addMessage(chatId, { role: 'error', content: 'Unable to summarize. No summary prompt defined.', uuid: uuidv4() })
 | 
					          addMessage(chatId, { role: 'error', content: 'Unable to summarize. No summary prompt defined.', uuid: uuidv4() })
 | 
				
			||||||
| 
						 | 
					@ -315,67 +338,79 @@
 | 
				
			||||||
          if (typeof setting.apiTransform === 'function') {
 | 
					          if (typeof setting.apiTransform === 'function') {
 | 
				
			||||||
            value = setting.apiTransform(chatId, setting, value)
 | 
					            value = setting.apiTransform(chatId, setting, value)
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          if (summaryTarget) {
 | 
					          if (opts.summaryRequest && opts.maxTokens) {
 | 
				
			||||||
            // requesting summary. do overrides
 | 
					            // requesting summary. do overrides
 | 
				
			||||||
            if (setting.key === 'max_tokens') value = summaryTarget // only as large as we need for summary
 | 
					            if (setting.key === 'max_tokens') value = opts.maxTokens // only as large as we need for summary
 | 
				
			||||||
            if (setting.key === 'n') value = 1 // never more than one completion
 | 
					            if (setting.key === 'n') value = 1 // never more than one completion for summary
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          if (opts.streaming) {
 | 
				
			||||||
 | 
					            /*
 | 
				
			||||||
 | 
					            Streaming goes insane with more than one completion.
 | 
				
			||||||
 | 
					            Doesn't seem like there's any way to separate the jumbled mess of deltas for the
 | 
				
			||||||
 | 
					            different completions.
 | 
				
			||||||
 | 
					            */
 | 
				
			||||||
 | 
					            if (setting.key === 'n') value = 1
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
          if (value !== null) acc[setting.key] = value
 | 
					          if (value !== null) acc[setting.key] = value
 | 
				
			||||||
          return acc
 | 
					          return acc
 | 
				
			||||||
        }, {})
 | 
					        }, {})
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // Not working yet: a way to get the response as a stream
 | 
					      request.stream = opts.streaming
 | 
				
			||||||
      /*
 | 
					 | 
				
			||||||
      request.stream = true
 | 
					 | 
				
			||||||
      await fetchEventSource(apiBase + '/v1/chat/completions', {
 | 
					 | 
				
			||||||
        method: 'POST',
 | 
					 | 
				
			||||||
        headers: {
 | 
					 | 
				
			||||||
          Authorization:
 | 
					 | 
				
			||||||
          `Bearer ${$apiKeyStorage}`,
 | 
					 | 
				
			||||||
          'Content-Type': 'application/json'
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        body: JSON.stringify(request),
 | 
					 | 
				
			||||||
        onmessage (ev) {
 | 
					 | 
				
			||||||
          const data = JSON.parse(ev.data)
 | 
					 | 
				
			||||||
          console.log(data)
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        onerror (err) {
 | 
					 | 
				
			||||||
          throw err
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
      */
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      response = await (
 | 
					      chatResponse.setPromptTokenCount(promptTokenCount)
 | 
				
			||||||
        await fetch(apiBase + '/v1/chat/completions', {
 | 
					
 | 
				
			||||||
 | 
					      const fetchOptions = {
 | 
				
			||||||
        method: 'POST',
 | 
					        method: 'POST',
 | 
				
			||||||
        headers: {
 | 
					        headers: {
 | 
				
			||||||
          Authorization: `Bearer ${$apiKeyStorage}`,
 | 
					          Authorization: `Bearer ${$apiKeyStorage}`,
 | 
				
			||||||
          'Content-Type': 'application/json'
 | 
					          'Content-Type': 'application/json'
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        body: JSON.stringify(request)
 | 
					        body: JSON.stringify(request)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (opts.streaming) {
 | 
				
			||||||
 | 
					        fetchEventSource(apiBase + '/v1/chat/completions', {
 | 
				
			||||||
 | 
					          ...fetchOptions,
 | 
				
			||||||
 | 
					          onmessage (ev) {
 | 
				
			||||||
 | 
					            // Remove updating indicator
 | 
				
			||||||
 | 
					            updating = false
 | 
				
			||||||
 | 
					            updatingMessage = ''
 | 
				
			||||||
 | 
					            if (!chatResponse.hasFinished()) {
 | 
				
			||||||
 | 
					              // console.log('ev.data', ev.data)
 | 
				
			||||||
 | 
					              if (ev.data === '[DONE]') {
 | 
				
			||||||
 | 
					                // ?? anything to do when "[DONE]"?
 | 
				
			||||||
 | 
					              } else {
 | 
				
			||||||
 | 
					                const data = JSON.parse(ev.data)
 | 
				
			||||||
 | 
					                // console.log('data', data)
 | 
				
			||||||
 | 
					                window.requestAnimationFrame(() => { chatResponse.updateFromAsyncResponse(data) })
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          onerror (err) {
 | 
				
			||||||
 | 
					            throw err
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }).catch(err => {
 | 
				
			||||||
 | 
					          chatResponse.updateFromError(err.message)
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
      ).json()
 | 
					      } else {
 | 
				
			||||||
 | 
					        const response = await fetch(apiBase + '/v1/chat/completions', fetchOptions)
 | 
				
			||||||
 | 
					        const json = await response.json()
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					        // Remove updating indicator
 | 
				
			||||||
 | 
					        updating = false
 | 
				
			||||||
 | 
					        updatingMessage = ''
 | 
				
			||||||
 | 
					        chatResponse.updateFromSyncResponse(json)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    } catch (e) {
 | 
					    } catch (e) {
 | 
				
			||||||
      response = { error: { message: e.message } } as Response
 | 
					      chatResponse.updateFromError(e.message)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Hide updating bar
 | 
					    // Hide updating bar
 | 
				
			||||||
    updating = false
 | 
					    updating = false
 | 
				
			||||||
    updatingMessage = ''
 | 
					    updatingMessage = ''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!response.error) {
 | 
					    return chatResponse
 | 
				
			||||||
      // Add response counts to usage totals
 | 
					 | 
				
			||||||
      updateRunningTotal(chatId, response.usage, response.model)
 | 
					 | 
				
			||||||
      // const completionTokenCount:number = response.choices.reduce((a, c) => {
 | 
					 | 
				
			||||||
      //   // unlike the prompts, token count of the completion is just the completion.
 | 
					 | 
				
			||||||
      //   a += encode(c.message.content).length
 | 
					 | 
				
			||||||
      //   return a
 | 
					 | 
				
			||||||
      // }, 0)
 | 
					 | 
				
			||||||
      // console.log('estimated response token count', completionTokenCount)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return response
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const addNewMessage = () => {
 | 
					  const addNewMessage = () => {
 | 
				
			||||||
| 
						 | 
					@ -397,6 +432,14 @@
 | 
				
			||||||
    focusInput()
 | 
					    focusInput()
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const tts = (text:string, recorded:boolean) => {
 | 
				
			||||||
 | 
					    // Use TTS to read the response, if query was recorded
 | 
				
			||||||
 | 
					    if (recorded && 'SpeechSynthesisUtterance' in window) {
 | 
				
			||||||
 | 
					      const utterance = new SpeechSynthesisUtterance(text)
 | 
				
			||||||
 | 
					      window.speechSynthesis.speak(utterance)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const submitForm = async (recorded: boolean = false, skipInput: boolean = false): Promise<void> => {
 | 
					  const submitForm = async (recorded: boolean = false, skipInput: boolean = false): Promise<void> => {
 | 
				
			||||||
    // Compose the system prompt message if there are no messages yet - disabled for now
 | 
					    // Compose the system prompt message if there are no messages yet - disabled for now
 | 
				
			||||||
    if (updating) return
 | 
					    if (updating) return
 | 
				
			||||||
| 
						 | 
					@ -419,29 +462,18 @@
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    focusInput()
 | 
					    focusInput()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const response = await sendRequest(chat.messages)
 | 
					    const response = await sendRequest(chat.messages, {
 | 
				
			||||||
 | 
					      chat,
 | 
				
			||||||
    if (response.error) {
 | 
					      autoAddMessages: true, // Auto-add and update messages in array
 | 
				
			||||||
      addMessage(chatId, {
 | 
					      streaming: chatSettings.stream,
 | 
				
			||||||
        role: 'error',
 | 
					      onMessageChange: (messages) => {
 | 
				
			||||||
        content: `Error: ${response.error.message}`,
 | 
					        scrollToBottom(true)
 | 
				
			||||||
        uuid: uuidv4()
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      response.choices.forEach((choice) => {
 | 
					 | 
				
			||||||
        // Store usage and model in the message
 | 
					 | 
				
			||||||
        choice.message.usage = response.usage
 | 
					 | 
				
			||||||
        choice.message.model = response.model
 | 
					 | 
				
			||||||
  
 | 
					 | 
				
			||||||
        // Remove whitespace around the message that the OpenAI API sometimes returns
 | 
					 | 
				
			||||||
        choice.message.content = choice.message.content.trim()
 | 
					 | 
				
			||||||
        addMessage(chatId, choice.message)
 | 
					 | 
				
			||||||
        // Use TTS to read the response, if query was recorded
 | 
					 | 
				
			||||||
        if (recorded && 'SpeechSynthesisUtterance' in window) {
 | 
					 | 
				
			||||||
          const utterance = new SpeechSynthesisUtterance(choice.message.content)
 | 
					 | 
				
			||||||
          window.speechSynthesis.speak(utterance)
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
 | 
					    await response.promiseToFinish()
 | 
				
			||||||
 | 
					    const message = response.getMessages()[0]
 | 
				
			||||||
 | 
					    if (message) {
 | 
				
			||||||
 | 
					      tts(message.content, recorded)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    focusInput()
 | 
					    focusInput()
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					@ -456,17 +488,22 @@
 | 
				
			||||||
    const suggestMessages = chat.messages.slice(0, 10) // limit to first 10 messages
 | 
					    const suggestMessages = chat.messages.slice(0, 10) // limit to first 10 messages
 | 
				
			||||||
    suggestMessages.push(suggestMessage)
 | 
					    suggestMessages.push(suggestMessage)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const response = await sendRequest(suggestMessages, 20)
 | 
					    const response = await sendRequest(suggestMessages, {
 | 
				
			||||||
 | 
					      chat,
 | 
				
			||||||
 | 
					      autoAddMessages: false,
 | 
				
			||||||
 | 
					      streaming: false
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    await response.promiseToFinish()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (response.error) {
 | 
					    if (response.hasError()) {
 | 
				
			||||||
      addMessage(chatId, {
 | 
					      addMessage(chatId, {
 | 
				
			||||||
        role: 'error',
 | 
					        role: 'error',
 | 
				
			||||||
        content: `Unable to get suggested name: ${response.error.message}`,
 | 
					        content: `Unable to get suggested name: ${response.getError()}`,
 | 
				
			||||||
        uuid: uuidv4()
 | 
					        uuid: uuidv4()
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      response.choices.forEach((choice) => {
 | 
					      response.getMessages().forEach(m => {
 | 
				
			||||||
        chat.name = choice.message.content
 | 
					        chat.name = m.content
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,132 @@
 | 
				
			||||||
 | 
					<script context="module" lang="ts">
 | 
				
			||||||
 | 
					// TODO: Integrate API calls
 | 
				
			||||||
 | 
					import { addMessage, updateRunningTotal } from './Storage.svelte'
 | 
				
			||||||
 | 
					import type { Chat, ChatCompletionOpts, Message, Response, Usage } from './Types.svelte'
 | 
				
			||||||
 | 
					import { encode } from 'gpt-tokenizer'
 | 
				
			||||||
 | 
					import { v4 as uuidv4 } from 'uuid'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class ChatCompletionResponse {
 | 
				
			||||||
 | 
					  constructor (opts: ChatCompletionOpts) {
 | 
				
			||||||
 | 
					    this.opts = opts
 | 
				
			||||||
 | 
					    this.chat = opts.chat
 | 
				
			||||||
 | 
					    this.messages = []
 | 
				
			||||||
 | 
					    if (opts.fillMessage) this.messages.push(opts.fillMessage)
 | 
				
			||||||
 | 
					    if (opts.onMessageChange) this.messageChangeListeners.push(opts.onMessageChange)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private opts: ChatCompletionOpts
 | 
				
			||||||
 | 
					  private chat: Chat
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private messages: Message[]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private error: string
 | 
				
			||||||
 | 
					  private didFinish: boolean
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private finishResolver: (value: Message[]) => void
 | 
				
			||||||
 | 
					  private errorResolver: (error: string) => void
 | 
				
			||||||
 | 
					  private finishPromise = new Promise<Message[]>((resolve, reject) => {
 | 
				
			||||||
 | 
					    this.finishResolver = resolve
 | 
				
			||||||
 | 
					    this.errorResolver = reject
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private promptTokenCount:number
 | 
				
			||||||
 | 
					  private finished = false
 | 
				
			||||||
 | 
					  private messageChangeListeners: ((m: Message[]) => void)[] = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  setPromptTokenCount (tokens:number) {
 | 
				
			||||||
 | 
					    this.promptTokenCount = tokens
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  updateFromSyncResponse (response: Response) {
 | 
				
			||||||
 | 
					    response.choices.forEach((choice, i) => {
 | 
				
			||||||
 | 
					      const message = this.messages[i] || choice.message
 | 
				
			||||||
 | 
					      message.content = choice.message.content
 | 
				
			||||||
 | 
					      message.usage = response.usage
 | 
				
			||||||
 | 
					      message.model = response.model
 | 
				
			||||||
 | 
					      message.role = choice.message.role
 | 
				
			||||||
 | 
					      this.messages[i] = message
 | 
				
			||||||
 | 
					      if (this.opts.autoAddMessages) addMessage(this.chat.id, message)
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    this.notifyMessageChange()
 | 
				
			||||||
 | 
					    this.finish()
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  updateFromAsyncResponse (response: Response) {
 | 
				
			||||||
 | 
					    let completionTokenCount = 0
 | 
				
			||||||
 | 
					    response.choices.forEach((choice, i) => {
 | 
				
			||||||
 | 
					      const message = this.messages[i] || {
 | 
				
			||||||
 | 
					        role: 'assistant',
 | 
				
			||||||
 | 
					        content: '',
 | 
				
			||||||
 | 
					        uuid: uuidv4()
 | 
				
			||||||
 | 
					      } as Message
 | 
				
			||||||
 | 
					      choice.delta?.role && (message.role = choice.delta.role)
 | 
				
			||||||
 | 
					      choice.delta?.content && (message.content += choice.delta.content)
 | 
				
			||||||
 | 
					      completionTokenCount += encode(message.content).length
 | 
				
			||||||
 | 
					      message.usage = response.usage || {
 | 
				
			||||||
 | 
					        prompt_tokens: this.promptTokenCount
 | 
				
			||||||
 | 
					      } as Usage
 | 
				
			||||||
 | 
					      message.model = response.model
 | 
				
			||||||
 | 
					      message.finish_reason = choice.finish_reason
 | 
				
			||||||
 | 
					      message.streaming = choice.finish_reason === null
 | 
				
			||||||
 | 
					      this.messages[i] = message
 | 
				
			||||||
 | 
					      if (this.opts.autoAddMessages) addMessage(this.chat.id, message)
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    // total up the tokens
 | 
				
			||||||
 | 
					    const totalTokens = this.promptTokenCount + completionTokenCount
 | 
				
			||||||
 | 
					    this.messages.forEach(m => {
 | 
				
			||||||
 | 
					      if (m.usage) {
 | 
				
			||||||
 | 
					        m.usage.completion_tokens = completionTokenCount
 | 
				
			||||||
 | 
					        m.usage.total_tokens = totalTokens
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    const finished = !this.messages.find(m => m.streaming)
 | 
				
			||||||
 | 
					    this.notifyMessageChange()
 | 
				
			||||||
 | 
					    if (finished) this.finish()
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  updateFromError (errorMessage: string): void {
 | 
				
			||||||
 | 
					    this.error = errorMessage
 | 
				
			||||||
 | 
					    if (this.opts.autoAddMessages) {
 | 
				
			||||||
 | 
					      addMessage(this.chat.id, {
 | 
				
			||||||
 | 
					        role: 'error',
 | 
				
			||||||
 | 
					        content: `Error: ${errorMessage}`,
 | 
				
			||||||
 | 
					        uuid: uuidv4()
 | 
				
			||||||
 | 
					      } as Message)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    this.notifyMessageChange()
 | 
				
			||||||
 | 
					    this.finish()
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  onMessageChange = (listener: (m: Message[]) => void): number =>
 | 
				
			||||||
 | 
					    this.messageChangeListeners.push(listener)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  promiseToFinish = (): Promise<Message[]> => this.finishPromise
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  hasFinished = (): boolean => this.finished
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  getError = (): string => this.error
 | 
				
			||||||
 | 
					  hasError = (): boolean => !!this.error
 | 
				
			||||||
 | 
					  getMessages = (): Message[] => this.messages
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private notifyMessageChange (): void {
 | 
				
			||||||
 | 
					    this.messageChangeListeners.forEach((listener) => {
 | 
				
			||||||
 | 
					      listener(this.messages)
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private finish = (): void => {
 | 
				
			||||||
 | 
					    if (this.didFinish) return
 | 
				
			||||||
 | 
					    this.didFinish = true
 | 
				
			||||||
 | 
					    const message = this.messages[0]
 | 
				
			||||||
 | 
					    if (message) {
 | 
				
			||||||
 | 
					      updateRunningTotal(this.chat.id, message.usage as any, message.model as any)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    this.finished = true
 | 
				
			||||||
 | 
					    if (this.error) {
 | 
				
			||||||
 | 
					      this.errorResolver(this.error)
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      this.finishResolver(this.messages)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
| 
						 | 
					@ -7,7 +7,7 @@
 | 
				
			||||||
  import type { Message, Model, Chat } from './Types.svelte'
 | 
					  import type { Message, Model, Chat } from './Types.svelte'
 | 
				
			||||||
  import Fa from 'svelte-fa/src/fa.svelte'
 | 
					  import Fa from 'svelte-fa/src/fa.svelte'
 | 
				
			||||||
  import { faTrash, faDiagramPredecessor, faDiagramNext, faCircleCheck, faPaperPlane, faEye, faEyeSlash } from '@fortawesome/free-solid-svg-icons/index'
 | 
					  import { faTrash, faDiagramPredecessor, faDiagramNext, faCircleCheck, faPaperPlane, faEye, faEyeSlash } from '@fortawesome/free-solid-svg-icons/index'
 | 
				
			||||||
  import { errorNotice, scrollIntoViewWithOffset } from './Util.svelte'
 | 
					  import { errorNotice, scrollToMessage } from './Util.svelte'
 | 
				
			||||||
  import { openModal } from 'svelte-modals'
 | 
					  import { openModal } from 'svelte-modals'
 | 
				
			||||||
  import PromptConfirm from './PromptConfirm.svelte'
 | 
					  import PromptConfirm from './PromptConfirm.svelte'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -43,7 +43,7 @@
 | 
				
			||||||
  })
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const edit = () => {
 | 
					  const edit = () => {
 | 
				
			||||||
    if (noEdit) return
 | 
					    if (noEdit || message.streaming) return
 | 
				
			||||||
    editing = true
 | 
					    editing = true
 | 
				
			||||||
    setTimeout(() => {
 | 
					    setTimeout(() => {
 | 
				
			||||||
      const el = document.getElementById('edit-' + message.uuid)
 | 
					      const el = document.getElementById('edit-' + message.uuid)
 | 
				
			||||||
| 
						 | 
					@ -77,22 +77,6 @@
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const scrollToMessage = (uuid:string | string[] | undefined) => {
 | 
					 | 
				
			||||||
    if (Array.isArray(uuid)) {
 | 
					 | 
				
			||||||
      uuid = uuid[0]
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (!uuid) {
 | 
					 | 
				
			||||||
      console.error('Not a valid uuid', uuid)
 | 
					 | 
				
			||||||
      return
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    const el = document.getElementById('message-' + uuid)
 | 
					 | 
				
			||||||
    if (el) {
 | 
					 | 
				
			||||||
      scrollIntoViewWithOffset(el, 80)
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      console.error("Can't find element with message ID", uuid)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // Double click for mobile support
 | 
					  // Double click for mobile support
 | 
				
			||||||
  let lastTap: number = 0
 | 
					  let lastTap: number = 0
 | 
				
			||||||
  const editOnDoubleTap = () => {
 | 
					  const editOnDoubleTap = () => {
 | 
				
			||||||
| 
						 | 
					@ -146,7 +130,6 @@
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
  let waitingForTruncateConfirm:any = 0
 | 
					  let waitingForTruncateConfirm:any = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const checkTruncate = () => {
 | 
					  const checkTruncate = () => {
 | 
				
			||||||
| 
						 | 
					@ -195,6 +178,7 @@
 | 
				
			||||||
  class:summarized={message.summarized} 
 | 
					  class:summarized={message.summarized} 
 | 
				
			||||||
  class:suppress={message.suppress} 
 | 
					  class:suppress={message.suppress} 
 | 
				
			||||||
  class:editing={editing}
 | 
					  class:editing={editing}
 | 
				
			||||||
 | 
					  class:streaming={message.streaming}
 | 
				
			||||||
>
 | 
					>
 | 
				
			||||||
  <div class="message-body content">
 | 
					  <div class="message-body content">
 | 
				
			||||||
 
 | 
					 
 | 
				
			||||||
| 
						 | 
					@ -210,6 +194,9 @@
 | 
				
			||||||
        on:touchend={editOnDoubleTap}
 | 
					        on:touchend={editOnDoubleTap}
 | 
				
			||||||
          on:dblclick|preventDefault={() => edit()}
 | 
					          on:dblclick|preventDefault={() => edit()}
 | 
				
			||||||
        >
 | 
					        >
 | 
				
			||||||
 | 
					        {#if message.summary && !message.summary.length}
 | 
				
			||||||
 | 
					        <p><b>Summarizing...</b></p>
 | 
				
			||||||
 | 
					        {/if}
 | 
				
			||||||
        <SvelteMarkdown 
 | 
					        <SvelteMarkdown 
 | 
				
			||||||
          source={message.content} 
 | 
					          source={message.content} 
 | 
				
			||||||
          options={markdownOptions} 
 | 
					          options={markdownOptions} 
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -60,7 +60,7 @@ const gptDefaults = {
 | 
				
			||||||
  temperature: 1,
 | 
					  temperature: 1,
 | 
				
			||||||
  top_p: 1,
 | 
					  top_p: 1,
 | 
				
			||||||
  n: 1,
 | 
					  n: 1,
 | 
				
			||||||
  stream: false,
 | 
					  stream: true,
 | 
				
			||||||
  stop: null,
 | 
					  stop: null,
 | 
				
			||||||
  max_tokens: 512,
 | 
					  max_tokens: 512,
 | 
				
			||||||
  presence_penalty: 0,
 | 
					  presence_penalty: 0,
 | 
				
			||||||
| 
						 | 
					@ -312,6 +312,12 @@ const chatSettingsList: ChatSetting[] = [
 | 
				
			||||||
      ...summarySettings,
 | 
					      ...summarySettings,
 | 
				
			||||||
      // ...responseAlterationSettings,
 | 
					      // ...responseAlterationSettings,
 | 
				
			||||||
      modelSetting,
 | 
					      modelSetting,
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        key: 'stream',
 | 
				
			||||||
 | 
					        name: 'Stream Response',
 | 
				
			||||||
 | 
					        title: 'Stream responses as they are generated.',
 | 
				
			||||||
 | 
					        type: 'boolean'
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
      {
 | 
					      {
 | 
				
			||||||
        key: 'temperature',
 | 
					        key: 'temperature',
 | 
				
			||||||
        name: 'Sampling Temperature',
 | 
					        name: 'Sampling Temperature',
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -89,6 +89,7 @@
 | 
				
			||||||
    // make sure old chat messages have UUID
 | 
					    // make sure old chat messages have UUID
 | 
				
			||||||
    chat.messages.forEach((m) => {
 | 
					    chat.messages.forEach((m) => {
 | 
				
			||||||
      m.uuid = m.uuid || uuidv4()
 | 
					      m.uuid = m.uuid || uuidv4()
 | 
				
			||||||
 | 
					      delete m.streaming
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
    // Make sure the usage totals object is set
 | 
					    // Make sure the usage totals object is set
 | 
				
			||||||
    // (some earlier versions of this had different structures)
 | 
					    // (some earlier versions of this had different structures)
 | 
				
			||||||
| 
						 | 
					@ -163,7 +164,10 @@
 | 
				
			||||||
    const chats = get(chatsStorage)
 | 
					    const chats = get(chatsStorage)
 | 
				
			||||||
    const chat = chats.find((chat) => chat.id === chatId) as Chat
 | 
					    const chat = chats.find((chat) => chat.id === chatId) as Chat
 | 
				
			||||||
    if (!message.uuid) message.uuid = uuidv4()
 | 
					    if (!message.uuid) message.uuid = uuidv4()
 | 
				
			||||||
 | 
					    if (chat.messages.indexOf(message) < 0) {
 | 
				
			||||||
 | 
					      // Don't have message, add it
 | 
				
			||||||
      chat.messages.push(message)
 | 
					      chat.messages.push(message)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    chatsStorage.set(chats)
 | 
					    chatsStorage.set(chats)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -27,6 +27,8 @@
 | 
				
			||||||
    summarized?: string[];
 | 
					    summarized?: string[];
 | 
				
			||||||
    summary?: string[];
 | 
					    summary?: string[];
 | 
				
			||||||
    suppress?: boolean;
 | 
					    suppress?: boolean;
 | 
				
			||||||
 | 
					    finish_reason?: string;
 | 
				
			||||||
 | 
					    streaming?: boolean;
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export type ResponseAlteration = {
 | 
					  export type ResponseAlteration = {
 | 
				
			||||||
| 
						 | 
					@ -88,6 +90,7 @@
 | 
				
			||||||
      index: number;
 | 
					      index: number;
 | 
				
			||||||
      message: Message;
 | 
					      message: Message;
 | 
				
			||||||
      finish_reason: string;
 | 
					      finish_reason: string;
 | 
				
			||||||
 | 
					      delta: Message;
 | 
				
			||||||
    }[];
 | 
					    }[];
 | 
				
			||||||
    usage: Usage;
 | 
					    usage: Usage;
 | 
				
			||||||
    model: Model;
 | 
					    model: Model;
 | 
				
			||||||
| 
						 | 
					@ -111,6 +114,17 @@
 | 
				
			||||||
    }[];
 | 
					    }[];
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  export type ChatCompletionOpts = {
 | 
				
			||||||
 | 
					    chat: Chat;
 | 
				
			||||||
 | 
					    autoAddMessages: boolean;
 | 
				
			||||||
 | 
					    maxTokens?:number;
 | 
				
			||||||
 | 
					    summaryRequest?:boolean;
 | 
				
			||||||
 | 
					    didSummary?:boolean;
 | 
				
			||||||
 | 
					    streaming?:boolean;
 | 
				
			||||||
 | 
					    onMessageChange?: (messages: Message[]) => void;
 | 
				
			||||||
 | 
					    fillMessage?:Message,
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export type GlobalSettings = {
 | 
					  export type GlobalSettings = {
 | 
				
			||||||
    profiles: Record<string, ChatSettings>;
 | 
					    profiles: Record<string, ChatSettings>;
 | 
				
			||||||
    lastProfile?: string;
 | 
					    lastProfile?: string;
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -21,15 +21,41 @@
 | 
				
			||||||
    anyEl.__didAutoGrow = true // don't resize this one again unless it's via an event
 | 
					    anyEl.__didAutoGrow = true // don't resize this one again unless it's via an event
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export const scrollIntoViewWithOffset = (element:HTMLElement, offset:number) => {
 | 
					  export const scrollIntoViewWithOffset = (element:HTMLElement, offset:number, instant:boolean = false, bottom:boolean = false) => {
 | 
				
			||||||
 | 
					    const behavior = instant ? 'instant' : 'smooth'
 | 
				
			||||||
 | 
					    if (bottom) {
 | 
				
			||||||
      window.scrollTo({
 | 
					      window.scrollTo({
 | 
				
			||||||
      behavior: 'smooth',
 | 
					        behavior: behavior as any,
 | 
				
			||||||
 | 
					        top:
 | 
				
			||||||
 | 
					        (element.getBoundingClientRect().bottom) -
 | 
				
			||||||
 | 
					        document.body.getBoundingClientRect().top - (window.innerHeight - offset)
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      window.scrollTo({
 | 
				
			||||||
 | 
					        behavior: behavior as any,
 | 
				
			||||||
        top:
 | 
					        top:
 | 
				
			||||||
        element.getBoundingClientRect().top -
 | 
					        element.getBoundingClientRect().top -
 | 
				
			||||||
        document.body.getBoundingClientRect().top -
 | 
					        document.body.getBoundingClientRect().top -
 | 
				
			||||||
        offset
 | 
					        offset
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  export const scrollToMessage = (uuid:string | string[] | undefined, offset:number = 60, instant:boolean = false, bottom:boolean = false) => {
 | 
				
			||||||
 | 
					    if (Array.isArray(uuid)) {
 | 
				
			||||||
 | 
					      uuid = uuid[0]
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if (!uuid) {
 | 
				
			||||||
 | 
					      console.error('Not a valid uuid', uuid)
 | 
				
			||||||
 | 
					      return
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    const el = document.getElementById('message-' + uuid)
 | 
				
			||||||
 | 
					    if (el) {
 | 
				
			||||||
 | 
					      scrollIntoViewWithOffset(el, offset, instant, bottom)
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      console.error("Can't find element with message ID", uuid)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  export const checkModalEsc = (event:KeyboardEvent|undefined):boolean|void => {
 | 
					  export const checkModalEsc = (event:KeyboardEvent|undefined):boolean|void => {
 | 
				
			||||||
    if (!event || event.key !== 'Escape') return
 | 
					    if (!event || event.key !== 'Escape') return
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue