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",
|
||||
"flourite": "^1.2.4",
|
||||
"gpt-tokenizer": "^2.0.0",
|
||||
"llama-tokenizer-js": "^1.1.1",
|
||||
"postcss": "^8.4.26",
|
||||
"sass": "^1.63.6",
|
||||
"stacking-order": "^2.0.0",
|
||||
|
@ -3182,6 +3183,12 @@
|
|||
"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": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
"eslint-plugin-svelte3": "^4.0.0",
|
||||
"flourite": "^1.2.4",
|
||||
"gpt-tokenizer": "^2.0.0",
|
||||
"llama-tokenizer-js": "^1.1.1",
|
||||
"postcss": "^8.4.26",
|
||||
"sass": "^1.63.6",
|
||||
"stacking-order": "^2.0.0",
|
||||
|
|
|
@ -5,10 +5,12 @@
|
|||
const endpointGenerations = import.meta.env.VITE_ENDPOINT_GENERATIONS || '/v1/images/generations'
|
||||
const endpointModels = import.meta.env.VITE_ENDPOINT_MODELS || '/v1/models'
|
||||
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 getEndpointCompletions = ():string => endpointCompletions
|
||||
export const getEndpointGenerations = ():string => endpointGenerations
|
||||
export const getEndpointModels = ():string => endpointModels
|
||||
export const getEndpointEmbeddings = ():string => endpointEmbeddings
|
||||
export const getPetals = ():string => endpointPetals
|
||||
</script>
|
|
@ -40,6 +40,7 @@
|
|||
import { openModal } from 'svelte-modals'
|
||||
import PromptInput from './PromptInput.svelte'
|
||||
import { ChatRequest } from './ChatRequest.svelte'
|
||||
import { getModelDetail } from './Models.svelte'
|
||||
|
||||
export let params = { chatId: '' }
|
||||
const chatId: number = parseInt(params.chatId)
|
||||
|
@ -245,6 +246,19 @@
|
|||
chatRequest.updating = true
|
||||
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 {
|
||||
const response = await chatRequest.sendRequest($currentChatMessages, {
|
||||
chat,
|
||||
|
@ -252,7 +266,8 @@
|
|||
streaming: chatSettings.stream,
|
||||
fillMessage,
|
||||
onMessageChange: (messages) => {
|
||||
scrollToBottom(true)
|
||||
if (doScroll) scrollToBottom(true)
|
||||
didScroll = !!messages[0]?.content
|
||||
}
|
||||
})
|
||||
await response.promiseToFinish()
|
||||
|
@ -264,6 +279,8 @@
|
|||
console.error(e)
|
||||
}
|
||||
|
||||
window.removeEventListener('scroll', checkUserScroll)
|
||||
|
||||
chatRequest.updating = false
|
||||
chatRequest.updatingMessage = ''
|
||||
|
||||
|
@ -273,13 +290,16 @@
|
|||
const suggestName = async (): Promise<void> => {
|
||||
const suggestMessage: Message = {
|
||||
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()
|
||||
}
|
||||
|
||||
const suggestMessages = $currentChatMessages.slice(0, 10) // limit to first 10 messages
|
||||
suggestMessages.push(suggestMessage)
|
||||
|
||||
chatRequest.updating = true
|
||||
chatRequest.updatingMessage = 'Getting suggestion for chat name...'
|
||||
|
||||
const response = await chatRequest.sendRequest(suggestMessages, {
|
||||
chat,
|
||||
autoAddMessages: false,
|
||||
|
@ -297,7 +317,7 @@
|
|||
})
|
||||
} else {
|
||||
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
|
||||
})
|
||||
saveChatStore()
|
||||
|
@ -420,7 +440,7 @@
|
|||
<div class="content has-text-centered running-total-container">
|
||||
{#each Object.entries(chat.usage || {}) as [model, usage]}
|
||||
<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>
|
||||
</p>
|
||||
{/each}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<script context="module" lang="ts">
|
||||
import { setImage } from './ImageStore.svelte'
|
||||
import { countTokens } from './Models.svelte'
|
||||
// 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 { encode } from 'gpt-tokenizer'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
export class ChatCompletionResponse {
|
||||
|
@ -65,6 +65,10 @@ export class ChatCompletionResponse {
|
|||
this.promptTokenCount = tokens
|
||||
}
|
||||
|
||||
getPromptTokenCount (): number {
|
||||
return this.promptTokenCount
|
||||
}
|
||||
|
||||
async updateImageFromSyncResponse (response: ResponseImage, prompt: string, model: Model) {
|
||||
this.setModel(model)
|
||||
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 += choice.delta.content
|
||||
}
|
||||
completionTokenCount += encode(message.content).length
|
||||
completionTokenCount += countTokens(this.model, message.content)
|
||||
message.model = response.model
|
||||
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
|
||||
})
|
||||
// total up the tokens
|
||||
|
@ -171,15 +175,15 @@ export class ChatCompletionResponse {
|
|||
} as Message)
|
||||
}
|
||||
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 {
|
||||
if (!this.finished && !this.error && !this.messages?.find(m => m.content)) {
|
||||
if (!force) return setTimeout(() => this.updateFromClose(true), 250) as any
|
||||
return this.updateFromError('Unexpected connection termination')
|
||||
if (!force) return setTimeout(() => this.updateFromClose(true), 300) as any
|
||||
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 =>
|
||||
|
@ -209,10 +213,10 @@ export class ChatCompletionResponse {
|
|||
}
|
||||
|
||||
private finish = (): void => {
|
||||
this.messages.forEach(m => { m.streaming = false }) // make sure all are marked stopped
|
||||
updateMessages(this.chat.id)
|
||||
if (this.finished) return
|
||||
this.finished = true
|
||||
this.messages.forEach(m => { m.streaming = false }) // make sure all are marked stopped
|
||||
saveChatStore()
|
||||
const message = this.messages[0]
|
||||
const model = this.model || getLatestKnownModel(this.chat.settings.model)
|
||||
if (message) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { replace } from 'svelte-spa-router'
|
||||
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 { faTrash, faCircleCheck, faPencil } from '@fortawesome/free-solid-svg-icons/index'
|
||||
import { faMessage } from '@fortawesome/free-regular-svg-icons/index'
|
||||
|
@ -86,7 +86,7 @@
|
|||
<a
|
||||
href={`#/chat/${chat.id}`}
|
||||
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 }} >
|
||||
{#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>
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
faEyeSlash
|
||||
} from '@fortawesome/free-solid-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 { newNameForProfile, restartProfile } from './Profiles.svelte'
|
||||
import { replace } from 'svelte-spa-router'
|
||||
|
@ -173,7 +173,7 @@
|
|||
<span class="menu-icon"><Fa icon={faGear}/></span> Chat Profile Settings
|
||||
</a>
|
||||
<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
|
||||
</a>
|
||||
<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) }}>
|
||||
<span class="menu-icon"><Fa icon={faDownload}/></span> Backup Chat JSON
|
||||
</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
|
||||
</a>
|
||||
<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
|
||||
</a>
|
||||
<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
|
||||
</a>
|
||||
<hr class="dropdown-divider">
|
||||
|
|
|
@ -5,11 +5,12 @@
|
|||
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 { scrollToBottom, scrollToMessage } from './Util.svelte'
|
||||
import { getRequestSettingList, defaultModel } from './Settings.svelte'
|
||||
import { EventStreamContentType, fetchEventSource } from '@microsoft/fetch-event-source'
|
||||
import { getApiBase, getEndpointCompletions, getEndpointGenerations } from './ApiUtil.svelte'
|
||||
import { getDefaultModel, getRequestSettingList } from './Settings.svelte'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { get } from 'svelte/store'
|
||||
import { getEndpoint, getModelDetail } from './Models.svelte'
|
||||
import { runOpenAiCompletionRequest } from './ChatRequestOpenAi.svelte'
|
||||
import { runPetalsCompletionRequest } from './ChatRequestPetals.svelte'
|
||||
|
||||
export class ChatRequest {
|
||||
constructor () {
|
||||
|
@ -25,6 +26,15 @@ export class ChatRequest {
|
|||
|
||||
setChat (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
|
||||
|
@ -77,7 +87,7 @@ export class ChatRequest {
|
|||
const chatResponse = new ChatCompletionResponse(opts)
|
||||
|
||||
try {
|
||||
const response = await fetch(getApiBase() + getEndpointGenerations(), fetchOptions)
|
||||
const response = await fetch(getEndpoint('dall-e-' + size), fetchOptions)
|
||||
if (!response.ok) {
|
||||
await _this.handleError(response)
|
||||
} else {
|
||||
|
@ -159,6 +169,8 @@ export class ChatRequest {
|
|||
const spl = chatSettings.sendSystemPromptLast
|
||||
const sp = messagePayload[0]
|
||||
if (sp) {
|
||||
const lastSp = sp.content.split('::END-PROMPT::')
|
||||
sp.content = lastSp[0].trim()
|
||||
if (messagePayload.length > 1) {
|
||||
sp.content = sp.content.replace(/::STARTUP::[\s\S]*::EOM::/, '::EOM::')
|
||||
sp.content = sp.content.replace(/::STARTUP::[\s\S]*::START-PROMPT::/, '::START-PROMPT::')
|
||||
|
@ -170,7 +182,7 @@ export class ChatRequest {
|
|||
if (spl) {
|
||||
messagePayload.shift()
|
||||
if (messagePayload[messagePayload.length - 1]?.role === 'user') {
|
||||
messagePayload.splice(-2, 0, sp)
|
||||
messagePayload.splice(-1, 0, sp)
|
||||
} else {
|
||||
messagePayload.push(sp)
|
||||
}
|
||||
|
@ -196,11 +208,15 @@ export class ChatRequest {
|
|||
}).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
|
||||
const promptTokenCount = countPromptTokens(messagePayload, model)
|
||||
const promptTokenCount = countPromptTokens(messagePayload, model, chat)
|
||||
const maxAllowed = maxTokens - (promptTokenCount + 1)
|
||||
|
||||
// Build the API request body
|
||||
|
@ -239,6 +255,9 @@ export class ChatRequest {
|
|||
|
||||
// Set-up and make the request
|
||||
const chatResponse = new ChatCompletionResponse(opts)
|
||||
|
||||
const modelDetail = getModelDetail(model)
|
||||
|
||||
try {
|
||||
// Add out token count to the response handler
|
||||
// (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
|
||||
_this.controller = new AbortController()
|
||||
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 = {
|
||||
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(() => {
|
||||
_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 {
|
||||
const data = JSON.parse(ev.data)
|
||||
// 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)
|
||||
})
|
||||
if (modelDetail.type === 'Petals') {
|
||||
await runPetalsCompletionRequest(request, _this as any, chatResponse as any, signal, opts)
|
||||
} 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)
|
||||
}
|
||||
await runOpenAiCompletionRequest(request, _this as any, chatResponse as any, signal, opts)
|
||||
}
|
||||
} catch (e) {
|
||||
// console.error(e)
|
||||
|
@ -341,12 +283,13 @@ export class ChatRequest {
|
|||
return chatResponse
|
||||
}
|
||||
|
||||
private getModel (): Model {
|
||||
return this.chat.settings.model || defaultModel
|
||||
getModel (): Model {
|
||||
return this.chat.settings.model || getDefaultModel()
|
||||
}
|
||||
|
||||
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 lastMessage = messages[messages.length - 1]
|
||||
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) => {
|
||||
m = m.trim()
|
||||
if (m.length) {
|
||||
if (m.match(/[[USER_PROMPT]]/)) {
|
||||
if (m.match(/\[\[USER_PROMPT\]\]/)) {
|
||||
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)
|
||||
}
|
||||
|
@ -377,7 +320,7 @@ export class ChatRequest {
|
|||
lastMessage.skipOnce = true
|
||||
}
|
||||
}
|
||||
if (injectedPrompt) results.pop()
|
||||
if (injectedPrompt) messages.pop()
|
||||
return results
|
||||
}
|
||||
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
|
||||
* @param filtered
|
||||
*/
|
||||
private getTokenCountPadding (filtered: Message[]): number {
|
||||
private getTokenCountPadding (filtered: Message[], chat: Chat): number {
|
||||
let result = 0
|
||||
// add cost of hiddenPromptPrefix
|
||||
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?
|
||||
return result
|
||||
}
|
||||
|
@ -413,10 +356,10 @@ export class ChatRequest {
|
|||
}
|
||||
|
||||
// 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
|
||||
const fullPromptSize = countPromptTokens(filtered, model) + countPadding
|
||||
const fullPromptSize = countPromptTokens(filtered, model, chat) + countPadding
|
||||
if (fullPromptSize < chatSettings.summaryThreshold) return await continueRequest() // nothing to do yet
|
||||
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) {
|
||||
const rolled = rw.shift()
|
||||
// Hide messages we're "rolling"
|
||||
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
|
||||
return await _this.sendRequest(get(currentChatMessages), {
|
||||
|
@ -460,26 +403,26 @@ export class ChatRequest {
|
|||
const bottom = rw.slice(0 - pinBottom)
|
||||
let continueCounter = chatSettings.summaryExtend + 1
|
||||
rw = rw.slice(0, 0 - pinBottom)
|
||||
let reductionPoolSize = countPromptTokens(rw, model)
|
||||
let reductionPoolSize = countPromptTokens(rw, model, chat)
|
||||
const ss = Math.abs(chatSettings.summarySize)
|
||||
const getSS = ():number => (ss < 1 && ss > 0)
|
||||
? 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
|
||||
const topSize = countPromptTokens(top, model)
|
||||
const topSize = countPromptTokens(top, model, chat)
|
||||
let maxSummaryTokens = getSS()
|
||||
let promptSummary = prepareSummaryPrompt(chatId, maxSummaryTokens)
|
||||
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
|
||||
// the last prompt is a user prompt as that seems to work better for summaries
|
||||
while ((topSize + reductionPoolSize + promptSummarySize + maxSummaryTokens) >= maxTokens ||
|
||||
(reductionPoolSize >= 100 && rw[rw.length - 1]?.role !== 'user')) {
|
||||
bottom.unshift(rw.pop() as Message)
|
||||
reductionPoolSize = countPromptTokens(rw, model)
|
||||
reductionPoolSize = countPromptTokens(rw, model, chat)
|
||||
maxSummaryTokens = getSS()
|
||||
promptSummary = prepareSummaryPrompt(chatId, maxSummaryTokens)
|
||||
summaryRequest.content = promptSummary
|
||||
promptSummarySize = countMessageTokens(summaryRequest, model)
|
||||
promptSummarySize = countMessageTokens(summaryRequest, model, chat)
|
||||
}
|
||||
if (reductionPoolSize < 50) {
|
||||
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
|
||||
delete summaryResponse.finish_reason
|
||||
_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)) {
|
||||
rw.shift()
|
||||
_recount = countPromptTokens(top.concat(rw).concat([summaryRequest]).concat([summaryResponse]), model)
|
||||
_recount = countPromptTokens(top.concat(rw).concat([summaryRequest]).concat([summaryResponse]), model, chat)
|
||||
}
|
||||
loopCount++
|
||||
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 { cleanSettingValue, setChatSettingValue } from './Storage.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 Fa from 'svelte-fa/src/fa.svelte'
|
||||
import { openModal } from 'svelte-modals'
|
||||
|
@ -23,6 +23,10 @@
|
|||
const chatId = chat.id
|
||||
let show = false
|
||||
|
||||
let header = valueOf(chatId, setting.header)
|
||||
let headerClass = valueOf(chatId, setting.headerClass)
|
||||
let placeholder = valueOf(chatId, setting.placeholder)
|
||||
|
||||
const buildFieldControls = () => {
|
||||
fieldControls = (setting.fieldControls || [] as FieldControl[]).map(fc => {
|
||||
return fc.getAction(chatId, setting, chatSettings[setting.key])
|
||||
|
@ -38,6 +42,9 @@
|
|||
|
||||
afterUpdate(() => {
|
||||
show = (typeof setting.hide !== 'function') || !setting.hide(chatId)
|
||||
header = valueOf(chatId, setting.header)
|
||||
headerClass = valueOf(chatId, setting.headerClass)
|
||||
placeholder = valueOf(chatId, setting.placeholder)
|
||||
buildFieldControls()
|
||||
})
|
||||
|
||||
|
@ -146,9 +153,9 @@
|
|||
</script>
|
||||
|
||||
{#if show}
|
||||
{#if setting.header}
|
||||
<p class="notification {setting.headerClass}">
|
||||
{@html setting.header}
|
||||
{#if header}
|
||||
<p class="notification {headerClass}">
|
||||
{@html header}
|
||||
</p>
|
||||
{/if}
|
||||
<div class="field is-horizontal">
|
||||
|
@ -171,7 +178,7 @@
|
|||
<label class="label" for="settings-{setting.key}" title="{setting.title}">{setting.name}</label>
|
||||
<textarea
|
||||
class="input is-info is-focused chat-input auto-size"
|
||||
placeholder={setting.placeholder || ''}
|
||||
placeholder={placeholder || ''}
|
||||
rows="1"
|
||||
on:input={e => autoGrowInputOnEvent(e)}
|
||||
on:change={e => { queueSettingValueChange(e, setting); autoGrowInputOnEvent(e) }}
|
||||
|
@ -195,7 +202,7 @@
|
|||
min={setting.min}
|
||||
max={setting.max}
|
||||
step={setting.step}
|
||||
placeholder={String(setting.placeholder || chatDefaults[setting.key])}
|
||||
placeholder={String(placeholder || chatDefaults[setting.key])}
|
||||
on:change={e => queueSettingValueChange(e, setting)}
|
||||
/>
|
||||
{:else if setting.type === 'select' || setting.type === 'select-number'}
|
||||
|
@ -204,7 +211,7 @@
|
|||
{#key rkey}
|
||||
<select id="settings-{setting.key}" title="{setting.title}" on:change={e => queueSettingValueChange(e, setting) } >
|
||||
{#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}
|
||||
</select>
|
||||
{/key}
|
||||
|
@ -233,6 +240,7 @@
|
|||
title="{setting.title}"
|
||||
class="input"
|
||||
value={chatSettings[setting.key]}
|
||||
placeholder={String(placeholder || chatDefaults[setting.key])}
|
||||
on:change={e => { queueSettingValueChange(e, setting) }}
|
||||
>
|
||||
</div>
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
import { getChatDefaults, getChatSettingList, getChatSettingObjectByKey, getExcludeFromProfile } from './Settings.svelte'
|
||||
import {
|
||||
saveChatStore,
|
||||
apiKeyStorage,
|
||||
chatsStorage,
|
||||
globalStorage,
|
||||
saveCustomProfile,
|
||||
|
@ -13,7 +12,7 @@
|
|||
checkStateChange,
|
||||
addChat
|
||||
} 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 Fa from 'svelte-fa/src/fa.svelte'
|
||||
import {
|
||||
|
@ -35,8 +34,7 @@
|
|||
import { replace } from 'svelte-spa-router'
|
||||
import { openModal } from 'svelte-modals'
|
||||
import PromptConfirm from './PromptConfirm.svelte'
|
||||
import { getApiBase, getEndpointModels } from './ApiUtil.svelte'
|
||||
import { supportedModelKeys } from './Models.svelte'
|
||||
import { getModelOptions } from './Models.svelte'
|
||||
|
||||
export let chatId:number
|
||||
export const show = () => { showSettings() }
|
||||
|
@ -185,30 +183,9 @@
|
|||
// Refresh settings modal
|
||||
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
|
||||
if (modelSetting) {
|
||||
modelSetting.options = modelOptions
|
||||
modelSetting.options = await getModelOptions()
|
||||
}
|
||||
// Refresh settings modal
|
||||
showSettingsModal++
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
import { openModal } from 'svelte-modals'
|
||||
import PromptConfirm from './PromptConfirm.svelte'
|
||||
import { getImage } from './ImageStore.svelte'
|
||||
import { getModelDetail } from './Models.svelte'
|
||||
|
||||
export let message:Message
|
||||
export let chatId:number
|
||||
|
@ -245,7 +246,7 @@
|
|||
<p class="is-size-7 message-note">System Prompt</p>
|
||||
{:else if message.usage}
|
||||
<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>
|
||||
</p>
|
||||
{/if}
|
||||
|
|
|
@ -1,16 +1,22 @@
|
|||
<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 { 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
|
||||
|
||||
let showPetalsSettings = $globalStorage.enablePetals
|
||||
let pedalsEndpoint = $globalStorage.pedalsEndpoint
|
||||
let hasModels = hasActiveModels()
|
||||
|
||||
onMount(() => {
|
||||
if (!$started) {
|
||||
$started = true
|
||||
// console.log('started', apiKey, $lastChatId, getChat($lastChatId))
|
||||
if (apiKey && getChat($lastChatId)) {
|
||||
if (hasActiveModels() && getChat($lastChatId)) {
|
||||
const chatId = $lastChatId
|
||||
$lastChatId = 0
|
||||
replace(`/chat/${chatId}`)
|
||||
|
@ -19,21 +25,39 @@ onMount(() => {
|
|||
$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>
|
||||
|
||||
<section class="section">
|
||||
<article class="message">
|
||||
<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
|
||||
<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
|
||||
<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
|
||||
<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>
|
||||
</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">
|
||||
Set your OpenAI API key below:
|
||||
|
||||
|
@ -53,19 +77,81 @@ onMount(() => {
|
|||
type="password"
|
||||
autocomplete="off"
|
||||
class="input"
|
||||
class:is-danger={!apiKey}
|
||||
class:is-danger={!hasModels}
|
||||
class:is-warning={!apiKey} class:is-info={apiKey}
|
||||
value={apiKey}
|
||||
/>
|
||||
</p>
|
||||
<p class="control">
|
||||
<button class="button is-info" type="submit">Save</button>
|
||||
</p>
|
||||
|
||||
|
||||
</form>
|
||||
|
||||
{#if !apiKey}
|
||||
<p class="help is-danger">
|
||||
Please enter your <a href="https://platform.openai.com/account/api-keys">OpenAI API key</a> above to use ChatGPT-web.
|
||||
It is required to use ChatGPT-web.
|
||||
<p class:is-danger={!hasModels} class:is-warning={!apiKey}>
|
||||
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.
|
||||
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>
|
||||
{/if}
|
||||
</div>
|
||||
|
|
|
@ -1,43 +1,108 @@
|
|||
<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
|
||||
// Eventually we'll add API hosts and endpoints to this
|
||||
const modelDetails : Record<string, ModelDetail> = {
|
||||
'gpt-4-32k': {
|
||||
type: 'OpenAIChat',
|
||||
prompt: 0.00006, // $0.06 per 1000 tokens prompt
|
||||
completion: 0.00012, // $0.12 per 1000 tokens completion
|
||||
max: 32768 // 32k max token buffer
|
||||
},
|
||||
'gpt-4': {
|
||||
type: 'OpenAIChat',
|
||||
prompt: 0.00003, // $0.03 per 1000 tokens prompt
|
||||
completion: 0.00006, // $0.06 per 1000 tokens completion
|
||||
max: 8192 // 8k max token buffer
|
||||
},
|
||||
'gpt-3.5': {
|
||||
type: 'OpenAIChat',
|
||||
prompt: 0.0000015, // $0.0015 per 1000 tokens prompt
|
||||
completion: 0.000002, // $0.002 per 1000 tokens completion
|
||||
max: 4096 // 4k max token buffer
|
||||
},
|
||||
'gpt-3.5-turbo-16k': {
|
||||
type: 'OpenAIChat',
|
||||
prompt: 0.000003, // $0.003 per 1000 tokens prompt
|
||||
completion: 0.000004, // $0.004 per 1000 tokens completion
|
||||
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': {
|
||||
type: 'OpenAIDall-e',
|
||||
prompt: 0.00,
|
||||
completion: 0.020, // $0.020 per image
|
||||
max: 1000 // 1000 char prompt, max
|
||||
},
|
||||
'dall-e-512x512': {
|
||||
type: 'OpenAIDall-e',
|
||||
prompt: 0.00,
|
||||
completion: 0.018, // $0.018 per image
|
||||
max: 1000 // 1000 char prompt, max
|
||||
},
|
||||
'dall-e-256x256': {
|
||||
type: 'OpenAIDall-e',
|
||||
prompt: 0.00,
|
||||
completion: 0.016, // $0.016 per image
|
||||
max: 1000 // 1000 char prompt, max
|
||||
|
@ -47,22 +112,27 @@ const imageModels : Record<string, ModelDetail> = {
|
|||
const unknownDetail = {
|
||||
prompt: 0,
|
||||
completion: 0,
|
||||
max: 4096
|
||||
}
|
||||
max: 4096,
|
||||
type: 'OpenAIChat'
|
||||
} as ModelDetail
|
||||
|
||||
// See: https://platform.openai.com/docs/models/model-endpoint-compatibility
|
||||
// Eventually we'll add UI for managing this
|
||||
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-0314': modelDetails['gpt-4'],
|
||||
'gpt-4-0613': modelDetails['gpt-4'],
|
||||
'gpt-4-32k': modelDetails['gpt-4-32k'],
|
||||
'gpt-4-32k-0314': modelDetails['gpt-4-32k'],
|
||||
'gpt-4-32k-0613': modelDetails['gpt-4-32k'],
|
||||
'gpt-3.5-turbo': modelDetails['gpt-3.5'],
|
||||
'gpt-3.5-turbo-16k': modelDetails['gpt-3.5-turbo-16k'],
|
||||
'gpt-3.5-turbo-0301': modelDetails['gpt-3.5'],
|
||||
'gpt-3.5-turbo-0613': modelDetails['gpt-3.5']
|
||||
// 'enoch/llama-65b-hf': modelDetails['enoch/llama-65b-hf'],
|
||||
// 'timdettmers/guanaco-65b': modelDetails['timdettmers/guanaco-65b'],
|
||||
'meta-llama/Llama-2-70b-hf': modelDetails['meta-llama/Llama-2-70b-hf'],
|
||||
'meta-llama/Llama-2-70b-chat-hf': modelDetails['meta-llama/Llama-2-70b-chat-hf']
|
||||
}
|
||||
|
||||
const lookupList = {
|
||||
|
@ -75,7 +145,7 @@ export const supportedModelKeys = Object.keys({ ...supportedModels, ...imageMode
|
|||
|
||||
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
|
||||
let r = supportedModels[model] || tpCache[model]
|
||||
if (r) return r
|
||||
|
@ -93,4 +163,140 @@ export const getModelDetail = (model: Model) => {
|
|||
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>
|
|
@ -1,5 +1,5 @@
|
|||
<script context="module" lang="ts">
|
||||
import { getChatDefaults, getExcludeFromProfile } from './Settings.svelte'
|
||||
import { getChatDefaults, getDefaultModel, getExcludeFromProfile } from './Settings.svelte'
|
||||
import { get, writable } from 'svelte/store'
|
||||
// Profile definitions
|
||||
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
|
||||
).reduce((a, [k, v]) => {
|
||||
v = JSON.parse(JSON.stringify(v))
|
||||
a[k] = v
|
||||
v.model = v.model || getDefaultModel()
|
||||
return a
|
||||
}, {} as Record<string, ChatSettings>)
|
||||
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 => {
|
||||
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())
|
||||
return content
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script context="module" lang="ts">
|
||||
import { applyProfile } from './Profiles.svelte'
|
||||
import { getChatSettings, getGlobalSettings, setGlobalSettingValueByKey } from './Storage.svelte'
|
||||
import { encode } from 'gpt-tokenizer'
|
||||
import { get } from 'svelte/store'
|
||||
import { apiKeyStorage, getChatSettings, getGlobalSettings, setGlobalSettingValueByKey } from './Storage.svelte'
|
||||
import { faArrowDown91, faArrowDownAZ, faCheck, faThumbTack } from '@fortawesome/free-solid-svg-icons/index'
|
||||
// Setting definitions
|
||||
|
||||
|
@ -18,8 +18,15 @@ import {
|
|||
type ChatSortOption
|
||||
|
||||
} 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[] => {
|
||||
return chatSettingsList
|
||||
|
@ -55,8 +62,16 @@ export const getExcludeFromProfile = () => {
|
|||
return excludeFromProfile
|
||||
}
|
||||
|
||||
const isNotOpenAI = (chatId) => {
|
||||
return getModelDetail(getChatSettings(chatId).model).type !== 'OpenAIChat'
|
||||
}
|
||||
|
||||
const isNotPetals = (chatId) => {
|
||||
return getModelDetail(getChatSettings(chatId).model).type !== 'Petals'
|
||||
}
|
||||
|
||||
const gptDefaults = {
|
||||
model: defaultModel,
|
||||
model: '',
|
||||
messages: [],
|
||||
temperature: 1,
|
||||
top_p: 1,
|
||||
|
@ -94,6 +109,10 @@ const defaults:ChatSettings = {
|
|||
hppContinuePrompt: '',
|
||||
hppWithSummaryPrompt: false,
|
||||
imageGenerationSize: '',
|
||||
stopSequence: '',
|
||||
userMessageStart: '',
|
||||
assistantMessageStart: '',
|
||||
systemMessageStart: '',
|
||||
// useResponseAlteration: false,
|
||||
// responseAlterations: [],
|
||||
isDirty: false
|
||||
|
@ -104,7 +123,10 @@ export const globalDefaults: GlobalSettings = {
|
|||
lastProfile: 'default',
|
||||
defaultProfile: 'default',
|
||||
hideSummarized: false,
|
||||
chatSort: 'created'
|
||||
chatSort: 'created',
|
||||
openAICompletionEndpoint: '',
|
||||
enablePetals: false,
|
||||
pedalsEndpoint: ''
|
||||
}
|
||||
|
||||
const excludeFromProfile = {
|
||||
|
@ -399,7 +421,13 @@ const modelSetting: ChatSetting & SettingSelect = {
|
|||
key: 'model',
|
||||
name: 'Model',
|
||||
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',
|
||||
options: [],
|
||||
type: 'select',
|
||||
|
@ -417,7 +445,8 @@ const chatSettingsList: ChatSetting[] = [
|
|||
key: 'stream',
|
||||
name: 'Stream Response',
|
||||
title: 'Stream responses as they are generated.',
|
||||
type: 'boolean'
|
||||
type: 'boolean',
|
||||
hide: isNotOpenAI
|
||||
},
|
||||
{
|
||||
key: 'temperature',
|
||||
|
@ -432,7 +461,7 @@ const chatSettingsList: ChatSetting[] = [
|
|||
},
|
||||
{
|
||||
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' +
|
||||
'\n' +
|
||||
'We generally recommend altering this or temperature but not both',
|
||||
|
@ -448,7 +477,8 @@ const chatSettingsList: ChatSetting[] = [
|
|||
min: 1,
|
||||
max: 10,
|
||||
step: 1,
|
||||
type: 'number'
|
||||
type: 'number',
|
||||
hide: isNotOpenAI
|
||||
},
|
||||
{
|
||||
key: 'max_tokens',
|
||||
|
@ -460,6 +490,7 @@ const chatSettingsList: ChatSetting[] = [
|
|||
max: 32768,
|
||||
step: 1,
|
||||
type: 'number',
|
||||
hide: isNotOpenAI,
|
||||
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,
|
||||
max: 2,
|
||||
step: 0.2,
|
||||
type: 'number'
|
||||
type: 'number',
|
||||
hide: isNotOpenAI
|
||||
},
|
||||
{
|
||||
key: 'frequency_penalty',
|
||||
|
@ -478,7 +510,52 @@ const chatSettingsList: ChatSetting[] = [
|
|||
min: -2,
|
||||
max: 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
|
||||
|
@ -497,7 +574,7 @@ const chatSettingsList: ChatSetting[] = [
|
|||
// console.log('logit_bias', val, getChatSettings(chatId).logit_bias)
|
||||
if (!val) return null
|
||||
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 })
|
||||
return a
|
||||
}, {} as Record<number, number>)
|
||||
|
@ -536,6 +613,21 @@ const globalSettingsList:GlobalSetting[] = [
|
|||
key: 'hideSummarized',
|
||||
name: 'Hide Summarized Messages',
|
||||
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">
|
||||
import { params } from 'svelte-spa-router'
|
||||
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 { faSquarePlus, faKey } from '@fortawesome/free-solid-svg-icons/index'
|
||||
import ChatOptionMenu from './ChatOptionMenu.svelte'
|
||||
|
@ -14,10 +14,12 @@
|
|||
$: activeChatId = $params && $params.chatId ? parseInt($params.chatId) : undefined
|
||||
|
||||
let sortOption = getChatSortOption()
|
||||
let hasModels = hasActiveModels()
|
||||
|
||||
const onStateChange = (...args:any) => {
|
||||
sortOption = getChatSortOption()
|
||||
sortedChats = $chatsStorage.sort(sortOption.sortFn)
|
||||
hasModels = hasActiveModels()
|
||||
}
|
||||
|
||||
$: onStateChange($checkStateChange)
|
||||
|
@ -72,14 +74,14 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="level-right">
|
||||
{#if !$apiKeyStorage}
|
||||
{#if !hasModels}
|
||||
<div class="level-item">
|
||||
<a href={'#/'} class="panel-block" class:is-disabled={!$apiKeyStorage}
|
||||
><span class="greyscale mr-1"><Fa icon={faKey} /></span> API key</a
|
||||
></div>
|
||||
{:else}
|
||||
<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>
|
||||
</div>
|
||||
{/if}
|
||||
|
|
|
@ -1,25 +1,43 @@
|
|||
<script context="module" lang="ts">
|
||||
import { getModelDetail } from './Models.svelte'
|
||||
import type { Message, Model, Usage } from './Types.svelte'
|
||||
import { encode } from 'gpt-tokenizer'
|
||||
import { countTokens, getModelDetail, getRoleTag, getStopSequence } from './Models.svelte'
|
||||
import type { Chat, Message, Model, Usage } from './Types.svelte'
|
||||
|
||||
export const getPrice = (tokens: Usage, model: Model): number => {
|
||||
const t = getModelDetail(model)
|
||||
return ((tokens.prompt_tokens * t.prompt) + (tokens.completion_tokens * t.completion))
|
||||
}
|
||||
|
||||
export const countPromptTokens = (prompts:Message[], model:Model):number => {
|
||||
return prompts.reduce((a, m) => {
|
||||
a += countMessageTokens(m, model)
|
||||
export const countPromptTokens = (prompts:Message[], model:Model, chat: Chat):number => {
|
||||
const detail = getModelDetail(model)
|
||||
const count = prompts.reduce((a, m) => {
|
||||
a += countMessageTokens(m, model, chat)
|
||||
return a
|
||||
}, 0) + 3 // Always seems to be message counts + 3
|
||||
}, 0)
|
||||
switch (detail.type) {
|
||||
case 'Petals':
|
||||
return count
|
||||
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 count + 3 // Always seems to be message counts + 3
|
||||
}
|
||||
}
|
||||
|
||||
export const countMessageTokens = (message:Message, model:Model):number => {
|
||||
// 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 encode('## ' + message.role + ' ##:\r\n\r\n' + message.content + '\r\n\r\n\r\n').length
|
||||
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 => {
|
||||
|
|
|
@ -30,6 +30,11 @@
|
|||
return get(apiKeyStorage)
|
||||
}
|
||||
|
||||
export const hasActiveModels = (): boolean => {
|
||||
const globalSettings = get(globalStorage) || {}
|
||||
return !!get(apiKeyStorage) || !!globalSettings.enablePetals
|
||||
}
|
||||
|
||||
export const newChatID = (): number => {
|
||||
const chats = get(chatsStorage)
|
||||
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 RequestType = 'OpenAIChat' | 'OpenAIDall-e' | 'Petals'
|
||||
|
||||
export type ModelDetail = {
|
||||
type: RequestType;
|
||||
label?: string;
|
||||
stop?: string[];
|
||||
userStart?: string,
|
||||
assistantStart?: string,
|
||||
systemStart?: string,
|
||||
prompt: number;
|
||||
completion: number;
|
||||
max: number;
|
||||
|
@ -105,6 +113,10 @@ export type ChatSettings = {
|
|||
trainingPrompts?: Message[];
|
||||
useResponseAlteration?: boolean;
|
||||
responseAlterations?: ResponseAlteration[];
|
||||
stopSequence: string;
|
||||
userMessageStart: string;
|
||||
assistantMessageStart: string;
|
||||
systemMessageStart: string;
|
||||
isDirty?: boolean;
|
||||
} & Request;
|
||||
|
||||
|
@ -122,16 +134,16 @@ export type Chat = {
|
|||
};
|
||||
|
||||
type ResponseOK = {
|
||||
id: string;
|
||||
object: string;
|
||||
created: number;
|
||||
choices: {
|
||||
index: number;
|
||||
id?: string;
|
||||
object?: string;
|
||||
created?: number;
|
||||
choices?: {
|
||||
index?: number;
|
||||
message: Message;
|
||||
finish_reason: string;
|
||||
finish_reason?: string;
|
||||
delta: Message;
|
||||
}[];
|
||||
usage: Usage;
|
||||
usage?: Usage;
|
||||
model: Model;
|
||||
};
|
||||
|
||||
|
@ -172,6 +184,9 @@ export type GlobalSettings = {
|
|||
defaultProfile: string;
|
||||
hideSummarized: boolean;
|
||||
chatSort: ChatSortOptions;
|
||||
openAICompletionEndpoint: string;
|
||||
enablePetals: boolean;
|
||||
pedalsEndpoint: string;
|
||||
};
|
||||
|
||||
type SettingNumber = {
|
||||
|
@ -184,6 +199,7 @@ export type GlobalSettings = {
|
|||
export type SelectOption = {
|
||||
value: string|number;
|
||||
text: string;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
export type ChatSortOption = SelectOption & {
|
||||
|
@ -236,15 +252,17 @@ export type SubSetting = {
|
|||
settings: any[];
|
||||
};
|
||||
|
||||
export type ValueFn = (chatId:number) => string
|
||||
|
||||
export type ChatSetting = {
|
||||
key: keyof ChatSettings;
|
||||
name: string;
|
||||
title: string;
|
||||
forceApi?: boolean; // force in api requests, even if set to default
|
||||
hidden?: boolean; // Hide from setting menus
|
||||
header?: string;
|
||||
headerClass?: string;
|
||||
placeholder?: string;
|
||||
header?: string | ValueFn;
|
||||
headerClass?: string | ValueFn;
|
||||
placeholder?: string | ValueFn;
|
||||
hide?: (chatId:number) => boolean;
|
||||
apiTransform?: (chatId:number, setting:ChatSetting, value:any) => any;
|
||||
fieldControls?: FieldControl[];
|
||||
|
|
|
@ -147,4 +147,9 @@
|
|||
newChat()
|
||||
}
|
||||
|
||||
export const valueOf = (chatId: number, value: any) => {
|
||||
if (typeof value === 'function') return value(chatId)
|
||||
return value
|
||||
}
|
||||
|
||||
</script>
|
Loading…
Reference in New Issue