2023-05-24 13:27:23 +00:00
import React , { FC , useEffect , useRef } from 'react' ;
import { observer } from 'mobx-react-lite' ;
import { WorkHeader } from '../components/WorkHeader' ;
import { Button , Dropdown , Input , Option , Textarea } from '@fluentui/react-components' ;
import { Labeled } from '../components/Labeled' ;
import { ValuedSlider } from '../components/ValuedSlider' ;
import { useTranslation } from 'react-i18next' ;
import commonStore , { ModelStatus } from '../stores/commonStore' ;
import { fetchEventSource } from '@microsoft/fetch-event-source' ;
import { toast } from 'react-toastify' ;
2023-06-14 12:45:52 +00:00
import { DialogButton } from '../components/DialogButton' ;
2023-06-24 16:07:14 +00:00
import { PresetsButton } from './PresetsManager/PresetsButton' ;
2023-06-28 12:46:21 +00:00
import { ToolTipButton } from '../components/ToolTipButton' ;
import { ArrowSync20Regular } from '@fluentui/react-icons' ;
2023-07-28 04:30:05 +00:00
import { defaultPresets } from './defaultConfigs' ;
2023-11-07 11:27:21 +00:00
import { CompletionParams , CompletionPreset } from '../types/completion' ;
2023-05-24 13:27:23 +00:00
2023-06-14 12:06:19 +00:00
let completionSseController : AbortController | null = null ;
2023-05-24 13:27:23 +00:00
const CompletionPanel : FC = observer ( ( ) = > {
const { t } = useTranslation ( ) ;
const inputRef = useRef < HTMLTextAreaElement > ( null ) ;
const port = commonStore . getCurrentModelConfig ( ) . apiParameters . apiPort ;
const scrollToBottom = ( ) = > {
if ( inputRef . current )
inputRef . current . scrollTop = inputRef . current . scrollHeight ;
} ;
useEffect ( ( ) = > {
if ( inputRef . current )
inputRef . current . style . height = '100%' ;
scrollToBottom ( ) ;
} , [ ] ) ;
2023-05-24 13:48:12 +00:00
const setPreset = ( preset : CompletionPreset ) = > {
2023-06-28 12:46:21 +00:00
commonStore . setCompletionSubmittedPrompt ( t ( preset . prompt ) ) ;
2023-05-24 13:48:12 +00:00
commonStore . setCompletionPreset ( {
. . . preset ,
prompt : t ( preset . prompt )
} ) ;
} ;
2023-05-24 13:27:23 +00:00
if ( ! commonStore . completionPreset )
2023-05-24 13:48:12 +00:00
setPreset ( defaultPresets [ 0 ] ) ;
2023-05-24 13:27:23 +00:00
const name = commonStore . completionPreset ! . name ;
const prompt = commonStore . completionPreset ! . prompt ;
const setPrompt = ( prompt : string ) = > {
commonStore . setCompletionPreset ( {
. . . commonStore . completionPreset ! ,
prompt
} ) ;
} ;
const params = commonStore . completionPreset ! . params ;
const setParams = ( newParams : Partial < CompletionParams > ) = > {
commonStore . setCompletionPreset ( {
. . . commonStore . completionPreset ! ,
params : {
. . . commonStore . completionPreset ! . params ,
. . . newParams
}
} ) ;
} ;
const onSubmit = ( prompt : string ) = > {
2023-06-28 12:46:21 +00:00
commonStore . setCompletionSubmittedPrompt ( prompt ) ;
2023-11-07 11:27:21 +00:00
if ( commonStore . status . status === ModelStatus . Offline && ! commonStore . settings . apiUrl && commonStore . platform !== 'web' ) {
2023-05-24 13:27:23 +00:00
toast ( t ( 'Please click the button in the top right corner to start the model' ) , { type : 'warning' } ) ;
2023-05-24 15:17:08 +00:00
commonStore . setCompletionGenerating ( false ) ;
2023-05-24 13:27:23 +00:00
return ;
}
2023-05-25 12:36:32 +00:00
prompt += params . injectStart . replaceAll ( '\\n' , '\n' ) ;
2023-05-24 13:27:23 +00:00
let answer = '' ;
2023-06-14 12:06:19 +00:00
completionSseController = new AbortController ( ) ;
2023-06-20 15:24:51 +00:00
fetchEventSource ( // https://api.openai.com/v1/completions || http://127.0.0.1:${port}/completions
commonStore . settings . apiUrl ?
commonStore . settings . apiUrl + '/v1/completions' :
` http://127.0.0.1: ${ port } /completions ` ,
2023-05-24 13:27:23 +00:00
{
method : 'POST' ,
headers : {
'Content-Type' : 'application/json' ,
2023-06-20 15:24:51 +00:00
Authorization : ` Bearer ${ commonStore . settings . apiKey } `
2023-05-24 13:27:23 +00:00
} ,
body : JSON.stringify ( {
prompt ,
stream : true ,
2023-06-20 15:24:51 +00:00
model : commonStore.settings.apiCompletionModelName , // 'text-davinci-003'
2023-05-24 13:27:23 +00:00
max_tokens : params.maxResponseToken ,
temperature : params.temperature ,
top_p : params.topP ,
presence_penalty : params.presencePenalty ,
frequency_penalty : params.frequencyPenalty ,
2023-05-25 12:36:32 +00:00
stop : params.stop.replaceAll ( '\\n' , '\n' ) || undefined
2023-05-24 13:27:23 +00:00
} ) ,
2023-06-14 12:06:19 +00:00
signal : completionSseController?.signal ,
2023-05-24 13:27:23 +00:00
onmessage ( e ) {
scrollToBottom ( ) ;
2023-07-25 07:59:37 +00:00
if ( e . data . trim ( ) === '[DONE]' ) {
2023-05-24 13:27:23 +00:00
commonStore . setCompletionGenerating ( false ) ;
return ;
}
let data ;
try {
data = JSON . parse ( e . data ) ;
} catch ( error ) {
console . debug ( 'json error' , error ) ;
return ;
}
if ( data . choices && Array . isArray ( data . choices ) && data . choices . length > 0 ) {
2023-07-25 07:59:37 +00:00
answer += data . choices [ 0 ] ? . text || data . choices [ 0 ] ? . delta ? . content || '' ;
setPrompt ( prompt + answer . replace ( /\s+$/ , '' ) + params . injectEnd . replaceAll ( '\\n' , '\n' ) ) ;
2023-05-24 13:27:23 +00:00
}
} ,
2023-06-20 16:26:50 +00:00
async onopen ( response ) {
if ( response . status !== 200 ) {
toast ( response . statusText + '\n' + ( await response . text ( ) ) , {
type : 'error'
} ) ;
}
} ,
2023-05-24 13:27:23 +00:00
onclose() {
console . log ( 'Connection closed' ) ;
} ,
onerror ( err ) {
2023-06-20 16:26:50 +00:00
err = err . message || err ;
if ( err && ! err . includes ( 'ReadableStreamDefaultReader' ) )
toast ( err , {
type : 'error'
} ) ;
2023-05-24 13:27:23 +00:00
commonStore . setCompletionGenerating ( false ) ;
throw err ;
}
} ) ;
} ;
return (
< div className = "flex flex-col sm:flex-row gap-2 overflow-hidden grow" >
< Textarea
ref = { inputRef }
className = "grow"
value = { prompt }
2023-06-28 12:46:21 +00:00
onChange = { ( e ) = > {
commonStore . setCompletionSubmittedPrompt ( e . target . value ) ;
setPrompt ( e . target . value ) ;
} }
2023-05-24 13:27:23 +00:00
/ >
< div className = "flex flex-col gap-1 max-h-48 sm:max-w-sm sm:max-h-full" >
2023-06-24 16:07:14 +00:00
< div className = "flex gap-2" >
< Dropdown style = { { minWidth : 0 } }
className = "grow"
value = { t ( commonStore . completionPreset ! . name ) ! }
selectedOptions = { [ commonStore . completionPreset ! . name ] }
onOptionSelect = { ( _ , data ) = > {
if ( data . optionValue ) {
setPreset ( defaultPresets . find ( ( preset ) = > preset . name === data . optionValue ) ! ) ;
}
} } >
{
defaultPresets . map ( ( preset ) = >
< Option key = { preset . name } value = { preset . name } > { t ( preset . name ) ! } < / Option > )
2023-05-24 13:27:23 +00:00
}
2023-06-24 16:07:14 +00:00
< / Dropdown >
< PresetsButton tab = "Completion" / >
< / div >
2023-06-20 14:18:45 +00:00
< div className = "flex flex-col gap-1 overflow-x-hidden overflow-y-auto p-1" >
2023-05-24 13:27:23 +00:00
< Labeled flex breakline label = { t ( 'Max Response Token' ) }
desc = { t ( 'By default, the maximum number of tokens that can be answered in a single response, it can be changed by the user by specifying API parameters.' ) }
content = {
< ValuedSlider value = { params . maxResponseToken } min = { 100 } max = { 8100 }
step = { 400 }
input
onChange = { ( e , data ) = > {
setParams ( {
maxResponseToken : data.value
} ) ;
} } / >
} / >
< Labeled flex breakline label = { t ( 'Temperature' ) }
2023-06-13 15:18:04 +00:00
desc = { t ( 'Sampling temperature, it\'s like giving alcohol to a model, the higher the stronger the randomness and creativity, while the lower, the more focused and deterministic it will be.' ) }
2023-05-24 13:27:23 +00:00
content = {
< ValuedSlider value = { params . temperature } min = { 0 } max = { 2 } step = { 0.1 }
input
onChange = { ( e , data ) = > {
setParams ( {
temperature : data.value
} ) ;
} } / >
} / >
< Labeled flex breakline label = { t ( 'Top_P' ) }
2023-06-13 15:18:04 +00:00
desc = { t ( 'Just like feeding sedatives to the model. Consider the results of the top n% probability mass, 0.1 considers the top 10%, with higher quality but more conservative, 1 considers all results, with lower quality but more diverse.' ) }
2023-05-24 13:27:23 +00:00
content = {
< ValuedSlider value = { params . topP } min = { 0 } max = { 1 } step = { 0.1 } input
onChange = { ( e , data ) = > {
setParams ( {
topP : data.value
} ) ;
} } / >
} / >
< Labeled flex breakline label = { t ( 'Presence Penalty' ) }
desc = { t ( 'Positive values penalize new tokens based on whether they appear in the text so far, increasing the model\'s likelihood to talk about new topics.' ) }
content = {
2023-06-21 09:13:04 +00:00
< ValuedSlider value = { params . presencePenalty } min = { 0 } max = { 2 }
2023-05-24 13:27:23 +00:00
step = { 0.1 } input
onChange = { ( e , data ) = > {
setParams ( {
presencePenalty : data.value
} ) ;
} } / >
} / >
< Labeled flex breakline label = { t ( 'Frequency Penalty' ) }
desc = { t ( 'Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model\'s likelihood to repeat the same line verbatim.' ) }
content = {
2023-06-21 09:13:04 +00:00
< ValuedSlider value = { params . frequencyPenalty } min = { 0 } max = { 2 }
2023-05-24 13:27:23 +00:00
step = { 0.1 } input
onChange = { ( e , data ) = > {
setParams ( {
frequencyPenalty : data.value
} ) ;
} } / >
} / >
< Labeled flex breakline label = { t ( 'Stop Sequences' ) }
desc = { t ( 'When this content appears in the response result, the generation will end.' ) }
content = {
< Input value = { params . stop }
onChange = { ( e , data ) = > {
setParams ( {
stop : data.value
2023-05-25 12:36:32 +00:00
} ) ;
} } / >
} / >
< Labeled flex breakline label = { t ( 'Inject start text' ) }
desc = { t ( 'Before the response starts, inject this content.' ) }
content = {
< Input value = { params . injectStart }
onChange = { ( e , data ) = > {
setParams ( {
injectStart : data.value
} ) ;
} } / >
} / >
< Labeled flex breakline label = { t ( 'Inject end text' ) }
desc = { t ( 'When response finished, inject this content.' ) }
content = {
< Input value = { params . injectEnd }
onChange = { ( e , data ) = > {
setParams ( {
injectEnd : data.value
2023-05-24 13:27:23 +00:00
} ) ;
} } / >
} / >
< / div >
< div className = "grow" / >
2023-11-07 11:27:21 +00:00
< div className = "hidden justify-between gap-2 sm:flex" >
2023-10-03 06:54:36 +00:00
< Button className = "grow" onClick = { ( ) = > {
const newPrompt = prompt . replace ( /\n+\ /g , '\n' ) . split ( '\n' ) . map ( ( line ) = > line . trim ( ) ) . join ( '\n' ) ;
setPrompt ( newPrompt ) ;
commonStore . setCompletionSubmittedPrompt ( newPrompt ) ;
} } > { t ( 'Format Content' ) } < / Button >
< / div >
2023-05-24 13:27:23 +00:00
< div className = "flex justify-between gap-2" >
2023-06-28 12:46:21 +00:00
< ToolTipButton desc = { t ( 'Regenerate' ) } icon = { < ArrowSync20Regular / > } onClick = { ( ) = > {
completionSseController ? . abort ( ) ;
commonStore . setCompletionGenerating ( true ) ;
setPrompt ( commonStore . completionSubmittedPrompt ) ;
onSubmit ( commonStore . completionSubmittedPrompt ) ;
} } / >
2023-06-14 12:45:52 +00:00
< DialogButton className = "grow" text = { t ( 'Reset' ) } title = { t ( 'Reset' ) }
contentText = { t ( 'Are you sure you want to reset this page? It cannot be undone.' ) }
onConfirm = { ( ) = > {
setPreset ( defaultPresets . find ( ( preset ) = > preset . name === name ) ! ) ;
} } / >
2023-05-24 13:27:23 +00:00
< Button className = "grow" appearance = "primary" onClick = { ( ) = > {
if ( commonStore . completionGenerating ) {
2023-06-14 12:06:19 +00:00
completionSseController ? . abort ( ) ;
2023-05-24 13:27:23 +00:00
commonStore . setCompletionGenerating ( false ) ;
} else {
commonStore . setCompletionGenerating ( true ) ;
onSubmit ( prompt ) ;
}
} } > { ! commonStore . completionGenerating ? t ( 'Generate' ) : t ( 'Stop' ) } < / Button >
< / div >
< / div >
< / div >
) ;
} ) ;
2023-11-07 11:27:21 +00:00
const Completion : FC = observer ( ( ) = > {
2023-05-24 13:27:23 +00:00
return (
< div className = "flex flex-col gap-1 p-2 h-full overflow-hidden" >
< WorkHeader / >
< CompletionPanel / >
< / div >
) ;
} ) ;
2023-11-07 11:27:21 +00:00
export default Completion ;