Skip to content

Commit

Permalink
integrated chatGPT
Browse files Browse the repository at this point in the history
  • Loading branch information
kholmogorov27 committed Mar 10, 2023
1 parent 2f06a8c commit ed69efa
Show file tree
Hide file tree
Showing 10 changed files with 1,664 additions and 14 deletions.
1,447 changes: 1,447 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"react-fast-marquee": "^1.3.5",
"react-icons": "^4.7.1",
"react-inlinesvg": "^3.0.2",
"react-markdown": "^8.0.5",
"react-scroll-into-view-if-needed": "^3.0.1"
},
"devDependencies": {
Expand Down
8 changes: 8 additions & 0 deletions settings/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,14 @@ const template = {
5,
{ min: 0, max: 50 }
)
},
AI: {
enabled: new types.Switch(true),
apiKey: new types.Input('', 'Enter your openai api key'),
temperature: new types.Range(
0.4,
{ min: 0, max: 1, step: 0.05 }
)
}
},
menu: {
Expand Down
1 change: 1 addition & 0 deletions src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
}
.app *::-webkit-scrollbar {
width: 10px;
height: 10px;
}
.app *::-webkit-scrollbar-thumb {
background: var(--primary);
Expand Down
1 change: 0 additions & 1 deletion src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,6 @@ function App() {
:
<motion.div
key={timestamp}
onClick={() => inputRef.current && inputRef.current.focus()}
className={classes['container']}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
Expand Down
13 changes: 13 additions & 0 deletions src/chatGPT/Icon.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
function Icon() {

return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="-16.5 -16.5 338.667 338.667"
fill="currentColor">
<path d="M138.9,0c-34.5,0-65.1,22.2-75.7,54.9C41,59.5,21.9,73.4,10.7,93c-17.3,29.9-13.3,67.4,9.8,93 c-7.1,21.4-4.7,44.9,6.7,64.3c17.2,30,51.7,45.3,85.5,38.2c15,16.9,36.5,26.5,59.1,26.5c34.5,0,65.1-22.2,75.7-54.9 c22.2-4.6,41.3-18.5,52.4-38.1c17.4-29.9,13.4-67.4-9.7-93v-0.1c7.1-21.4,4.7-44.9-6.7-64.4c-17.2-29.9-51.7-45.2-85.4-38.1 C183.1,9.5,161.5-0.1,138.9,0z M138.9,20.5l-0.1,0.1c13.9,0,27.2,4.8,37.9,13.7c-0.4,0.2-1.3,0.7-1.9,1.1L112,71.5 c-3.2,1.8-5.1,5.2-5.1,9v84.9l-27-15.6V79.6C79.9,47,106.3,20.5,138.9,20.5L138.9,20.5z M214.5,45.2c21.2,0,40.7,11.2,51.3,29.6 c6.8,11.9,9.4,25.9,7,39.5c-0.4-0.3-1.3-0.7-1.8-1.1l-62.7-36.3c-3.2-1.8-7.1-1.8-10.3,0l-73.5,42.5V88.2l60.7-35.1 C194,47.9,204.1,45.2,214.5,45.2L214.5,45.2z M59.3,77.4V152c0,3.7,1.9,7,5.1,9l73.4,42.3L110.7,219l-60.6-35 c-28.2-16.3-37.8-52.4-21.5-80.6C35.5,91.4,46.4,82.2,59.3,77.4L59.3,77.4z M199.9,95.9l60.7,35c28.3,16.3,37.9,52.4,21.5,80.6 l0.1,0.1c-6.9,11.9-17.8,21.1-30.7,25.8v-74.6c0-3.7-1.9-7.1-5.1-9l-73.5-42.5L199.9,95.9z M155.3,121.6l30.9,17.9v35.7l-30.9,17.9 l-30.9-17.9v-35.7L155.3,121.6z M204,149.8l27,15.6v70.1c0,32.6-26.5,59.1-59,59.1v-0.1c-13.8,0-27.2-4.8-37.8-13.7 c0.4-0.2,1.4-0.7,1.9-1.1l62.7-36.2c3.2-1.8,5.2-5.2,5.1-9L204,149.8z M186.4,195.6v31.1l-60.7,35C97.4,278,61.4,268.4,45,240.2h0.1 c-6.9-11.8-9.4-25.9-7-39.5c0.4,0.3,1.3,0.7,1.8,1.1l62.7,36.3c3.2,1.8,7.1,1.8,10.3,0L186.4,195.6z"/>
</svg>
)
}

export default Icon
73 changes: 73 additions & 0 deletions src/chatGPT/createCompletion.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
const API_URL = 'https://api.openai.com/v1/chat/completions'
const PARAMS = {
model: 'gpt-3.5-turbo',
temperature: 0.4,
stream: true,
// max_tokens: 4096,
// frequency_penalty: 1.0,
}

function createCompletion(stateSetter, query, temperature, key) {
const controller = new AbortController()

fetch(API_URL, {
signal: controller.signal,
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + String(key)
},
body: JSON.stringify({ ...PARAMS, messages: [{ role: 'user', content: query }], temperature })
}).then(result => {
const stream = result.body
fetchStream(stream, stateSetter)
})

return controller
}

async function fetchStream(stream, stateSetter) {
const reader = stream.getReader()
let charsReceived = 0
const li = document.createElement('li')

// read() returns a promise that resolves
// when a value has been received
reader.read().then(
function processText({ done, value }) {
// Result objects contain two properties:
// done - true if the stream has already given you all its data.
// value - some data. Always undefined when done is true.
if (done)
return li.innerText
// value for fetch streams is a Uint8Array
charsReceived += value.length
const chunk = value
li.appendChild(document.createTextNode(chunk))

const list = li.innerText.split(',')
const numList = list.map(item => parseInt(item))

stateSetter('')

for (const entry of new TextDecoder('utf-8').decode(new Uint8Array(numList)).split('\n'))
if (entry) {
const text = entry.slice(entry.indexOf(':') + 2)

let response
try {
response = JSON.parse(text)
} catch (error) { /* pass */ }

if (response && typeof response.choices[0].delta.content === 'string')
stateSetter(state => state + response.choices[0].delta.content)
}

return reader.read().then(processText)
}
)

return reader
}

export default createCompletion
42 changes: 42 additions & 0 deletions src/components/AIcompletion/AIcompletion.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { useContext, useEffect } from 'react'
import { useState } from 'react'
import { SettingsContext } from '../../contexts/Settings'
import { ReactMarkdown } from 'react-markdown/lib/react-markdown'
import createCompletion from '../../chatGPT/createCompletion'

function AIcompletion({ query, className }) {
// settings
const settings = useContext(SettingsContext)
const enabled = settings.query.AI.enabled
const apiKey = settings.query.AI.apiKey
const temperature = settings.query.AI.temperature

const [state, setState] = useState('')

useEffect(() => {
if (!enabled)
return

let controller = null

if (query)
controller = createCompletion(setState, query, temperature, apiKey)
else
setState('')

return () => controller && controller.abort()
}, [query, temperature, apiKey, enabled])

if (!state || !enabled)
return null

return (
<div className={className}>
<div className='md-container'>
<ReactMarkdown children={state}/>
</div>
</div>
)
}

export default AIcompletion
53 changes: 41 additions & 12 deletions src/components/QueryField/QueryField.jsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import { useContext, useCallback, useEffect, memo, forwardRef } from 'react'
import { useContext, useCallback, useEffect, memo, forwardRef, useRef } from 'react'
import useSuggestions from '../../hooks/useSuggestions'
import useParseQuery from '../../hooks/useParseQuery'
import useRedirect from '../../hooks/useRedirect'
import { SettingsContext } from '../../contexts/Settings'
import { useStateSelector, useUpdate } from '../../contexts/Store'
import Suggestions from '../Suggestions/Suggestions'
import AIcompletion from '../AIcompletion/AIcompletion'
import { allowedModes, activeKeys } from '../../rules'
import googleAutocomplete from '../../autocomplete/googleAutocomplete'
import History from '../../classes/localStorage/history'
import gC from '../../functions/generationUtils/getClasses'
import classes from './QueryField.module.css'
import { useState } from 'react'

const DOUBLE_PRESS_THRESHOLD = 300

const QueryField = forwardRef((props, inputRef) => {
// settings
Expand All @@ -36,6 +40,9 @@ const QueryField = forwardRef((props, inputRef) => {
selectedSuggestion ? selectedSuggestion.type : undefined,
query)

// query for AI
const [aiQuery, setAiQuery] = useState('')

const redirect = useRedirect()

const handleRedirect = useCallback(() => {
Expand All @@ -48,13 +55,19 @@ const QueryField = forwardRef((props, inputRef) => {
}, [parsedQuery, searchHistory, redirect])

const handleQueryChange = useCallback(value => {
if (allowedModes.get('QueryField').has(mode))
updateStore({
//!
query: value.replace(/\s{2,}/g, ' '),
selectedSuggestion: null
})
}, [mode, updateStore])
if (allowedModes.get('QueryField').has(mode)) {
//!
const newValue = value.replace(/\s{2,}/g, ' ')

if (newValue !== query) {
setAiQuery('')
updateStore({
query: newValue,
selectedSuggestion: null
})
}
}
}, [mode, query, updateStore])

const onKeyDown = useCallback((e) => {
switch (e.key) {
Expand All @@ -69,6 +82,7 @@ const QueryField = forwardRef((props, inputRef) => {
case 'Escape':
// clearing the query
updateStore({ query: '' })
setAiQuery('')
break
default:
if (allowedModes.get('Suggestions').has(mode) && activeKeys.get('Suggestions').has(e.key)) {
Expand All @@ -84,13 +98,27 @@ const QueryField = forwardRef((props, inputRef) => {
}
}
}, [mode, updateStore, handleRedirect, suggestions, selectedSuggestion])

// add event listeners
// onKeyDown listener
useEffect(() => {
window.addEventListener('keydown', onKeyDown)
return () => window.removeEventListener('keydown', onKeyDown)
}, [onKeyDown])

const spacebarLastPressRef = useRef(-1)
const onKeyPress = useCallback((e) => {
if (e.code === 'Space') {
if (Date.now() - spacebarLastPressRef.current < DOUBLE_PRESS_THRESHOLD)
setAiQuery(query)

spacebarLastPressRef.current = Date.now()
}
}, [query])
// onKeyPress listener
useEffect(() => {
window.addEventListener('keypress', onKeyPress)
return () => window.removeEventListener('keypress', onKeyPress)
}, [onKeyPress])

// re-focusing the input inputField to focus on the caret
useEffect(() => {
inputRef.current.blur()
Expand All @@ -116,8 +144,9 @@ const QueryField = forwardRef((props, inputRef) => {

return (
<div
className={`${classes['container']}`}
style={ variables }>
className={classes['container']}
style={variables}>
<AIcompletion query={aiQuery} className={classes['ai-completion']} />
{ input }
{ parsedQuery.value && <Suggestions
queryMode={settings.appearance.style}
Expand Down
39 changes: 38 additions & 1 deletion src/components/QueryField/QueryField.module.css
Original file line number Diff line number Diff line change
@@ -1,10 +1,47 @@
.container {
width: 100%;
position: relative;
margin-left: calc(50vh + 5vw);
margin-left: calc(50vh + 5vw);
margin-right: 5vw;
}

.ai-completion {
bottom: 100%;
position: absolute;
width: 100%;
height: calc(50vh - 50% - 20px - 20px);
font-size: 1.3em;
font-family: Onest;
margin-bottom: 20px;
overflow-x: hidden;
overflow-y: auto;
}
.ai-completion > :global(.md-container) {
margin-right: 1em;
}
.ai-completion > :global(.md-container) * {
/*
user-select: text !important;
*/
}
.ai-completion > :global(.md-container) code {
backdrop-filter: contrast(0.8);
padding-left: 6px;
padding-right: 6px;
}
.ai-completion > :global(.md-container) pre {
backdrop-filter: contrast(0.8);
padding: 10px;
border-radius: 10px;
overflow-x: auto;
}
.ai-completion > :global(.md-container) > *:first-child {
margin-top: 0;
}
.ai-completion > :global(.md-container) > *:last-child {
margin-bottom: 0;
}

.field {
outline: none;
background: transparent;
Expand Down

0 comments on commit ed69efa

Please sign in to comment.