refactored UI strategy, added prompt response in chat box

pull/861/head
ManishMadan2882 3 months ago
parent 63878e7ffd
commit f60e88573a

@ -9,6 +9,7 @@
"version": "0.0.0",
"dependencies": {
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-scroll-area": "^1.0.5",
"@radix-ui/react-slot": "^1.0.2",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
@ -1064,6 +1065,22 @@
"node": ">=14"
}
},
"node_modules/@radix-ui/number": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.0.1.tgz",
"integrity": "sha512-T5gIdVO2mmPW3NNhjNgEP3cqMXjXL9UbO0BzWcXfvdBs+BohbQxvd/K5hSVKmn9/lbTdsQVKbUcP5WLCwvUbBg==",
"dependencies": {
"@babel/runtime": "^7.13.10"
}
},
"node_modules/@radix-ui/primitive": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.1.tgz",
"integrity": "sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==",
"dependencies": {
"@babel/runtime": "^7.13.10"
}
},
"node_modules/@radix-ui/react-compose-refs": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz",
@ -1081,6 +1098,40 @@
}
}
},
"node_modules/@radix-ui/react-context": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.0.1.tgz",
"integrity": "sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==",
"dependencies": {
"@babel/runtime": "^7.13.10"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-direction": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.0.1.tgz",
"integrity": "sha512-RXcvnXgyvYvBEOhCBuddKecVkoMiI10Jcm5cTI7abJRAHYfFxeu+FBQs/DvdxSYucxR5mna0dNsL6QFlds5TMA==",
"dependencies": {
"@babel/runtime": "^7.13.10"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-icons": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-icons/-/react-icons-1.3.0.tgz",
@ -1089,6 +1140,84 @@
"react": "^16.x || ^17.x || ^18.x"
}
},
"node_modules/@radix-ui/react-presence": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.0.1.tgz",
"integrity": "sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-compose-refs": "1.0.1",
"@radix-ui/react-use-layout-effect": "1.0.1"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-primitive": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.3.tgz",
"integrity": "sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-slot": "1.0.2"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-scroll-area": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.0.5.tgz",
"integrity": "sha512-b6PAgH4GQf9QEn8zbT2XUHpW5z8BzqEc7Kl11TwDrvuTrxlkcjTD5qa/bxgKr+nmuXKu4L/W5UZ4mlP/VG/5Gw==",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/number": "1.0.1",
"@radix-ui/primitive": "1.0.1",
"@radix-ui/react-compose-refs": "1.0.1",
"@radix-ui/react-context": "1.0.1",
"@radix-ui/react-direction": "1.0.1",
"@radix-ui/react-presence": "1.0.1",
"@radix-ui/react-primitive": "1.0.3",
"@radix-ui/react-use-callback-ref": "1.0.1",
"@radix-ui/react-use-layout-effect": "1.0.1"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-slot": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz",
@ -1107,6 +1236,40 @@
}
}
},
"node_modules/@radix-ui/react-use-callback-ref": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz",
"integrity": "sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==",
"dependencies": {
"@babel/runtime": "^7.13.10"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-use-layout-effect": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.1.tgz",
"integrity": "sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==",
"dependencies": {
"@babel/runtime": "^7.13.10"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.12.0.tgz",
@ -1359,7 +1522,7 @@
"version": "18.2.19",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.19.tgz",
"integrity": "sha512-aZvQL6uUbIJpjZk4U8JZGbau9KDeAwMfmhyWorxgBkqDIEf6ROjRozcmPIicqsUwPUjbkDfHKgGee1Lq65APcA==",
"dev": true,
"devOptional": true,
"dependencies": {
"@types/react": "*"
}

@ -11,6 +11,7 @@
},
"dependencies": {
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-scroll-area": "^1.0.5",
"@radix-ui/react-slot": "^1.0.2",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",

@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M18 6L6 18" stroke="#A3A3A3" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M6 6L18 18" stroke="#A3A3A3" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 311 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 256 KiB

@ -0,0 +1,7 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.37891 9.44824H7.75821" stroke="white" stroke-width="1.68965" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M11.1377 9.44824H12.8273" stroke="white" stroke-width="1.68965" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M4.37891 6.06934H6.06856" stroke="white" stroke-width="1.68965" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9.44824 6.06934H12.8276" stroke="white" stroke-width="1.68965" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M16.2069 11.1379C16.2069 11.5861 16.0289 12.0158 15.712 12.3327C15.3951 12.6496 14.9654 12.8276 14.5172 12.8276H4.37931L1 16.2069V2.68965C1 2.24153 1.17802 1.81176 1.49489 1.49489C1.81176 1.17802 2.24153 1 2.68965 1H14.5172C14.9654 1 15.3951 1.17802 15.712 1.49489C16.0289 1.81176 16.2069 2.24153 16.2069 2.68965V11.1379Z" stroke="white" stroke-width="1.68965" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 1010 B

@ -1,16 +1,25 @@
"use client";
import { useEffect, useRef, useState } from 'react'
import { Fragment, useEffect, useRef, useState } from 'react'
import { PaperPlaneIcon } from '@radix-ui/react-icons';
import { Input } from './ui/input';
import { Button } from './ui/button';
import { ScrollArea, ScrollBar } from './ui/scroll-area'
import Dragon from '../assets/cute-docsgpt.svg'
import MessageIcon from '../assets/message.svg'
import Cancel from '../assets/cancel.svg'
import { Doc, Query } from '@/models/customTypes';
import { fetchAnswerStreaming } from '@/requests/streamingApi';
//import './style.css'
interface HistoryItem {
prompt: string;
response: string;
}
interface Message {
type: 'PROMPT' | 'RESPONSE' | 'ERROR',
message: string
id: string | null
}
interface FetchAnswerStreamingProps {
question?: string;
apiKey?: string;
@ -21,6 +30,7 @@ interface FetchAnswerStreamingProps {
onEvent?: (event: MessageEvent) => void;
}
type Status = 'idle' | 'loading' | 'failed';
enum ChatStates {
Init = 'init',
@ -30,87 +40,6 @@ enum ChatStates {
Minimized = 'minimized',
}
function fetchAnswerStreaming({
question = '',
apiKey = '',
selectedDocs = '',
history = [],
conversationId = null,
apiHost = '',
onEvent = () => { console.log("Event triggered, but no handler provided."); }
}: FetchAnswerStreamingProps): Promise<void> {
let docPath = 'default';
if (selectedDocs) {
docPath = selectedDocs;
}
return new Promise<void>((resolve, reject) => {
const body = {
question: question,
api_key: apiKey,
embeddings_key: apiKey,
active_docs: docPath,
history: JSON.stringify(history),
conversation_id: conversationId,
model: 'default'
};
fetch(apiHost + '/stream', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
})
.then((response) => {
if (!response.body) throw Error('No response body');
const reader = response.body.getReader();
const decoder = new TextDecoder('utf-8');
let counterrr = 0;
const processStream = ({
done,
value,
}: ReadableStreamReadResult<Uint8Array>) => {
if (done) {
console.log(counterrr);
resolve();
return;
}
counterrr += 1;
const chunk = decoder.decode(value);
const lines = chunk.split('\n');
for (let line of lines) {
if (line.trim() == '') {
continue;
}
if (line.startsWith('data:')) {
line = line.substring(5);
}
const messageEvent = new MessageEvent('message', {
data: line,
});
onEvent(messageEvent); // handle each message
}
reader.read().then(processStream).catch(reject);
};
reader.read().then(processStream).catch(reject);
})
.catch((error) => {
console.error('Connection failed:', error);
reject(error);
});
});
}
export const DocsGPTWidget = ({ apiHost = 'https://gptcloud.arc53.com', selectDocs = 'default', apiKey = 'docsgpt-public' }) => {
// processing states
const [chatState, setChatState] = useState<ChatStates>(() => {
@ -119,109 +48,131 @@ export const DocsGPTWidget = ({ apiHost = 'https://gptcloud.arc53.com', selectDo
}
return ChatStates.Init;
});
const [answer, setAnswer] = useState<string>('');
const [prompt, setPrompt] = useState('');
const [status, setStatus] = useState<Status>('idle');
const [queries, setQueries] = useState<Query[]>([
{
prompt: 'dasasfafa fafajfiaf agad gagadjga gadgadgadijgaf',
response: 'dkadfafadfa fadfafa fa df adgdfaeye5uttr sr s srt rssr '
},
{
prompt: 'dasasfafa fafajfiaf agad gagadjga gadgadgadijgaf',
response: 'dkadfafadfa fadfafa fa df adgdfaeye5uttr sr s srt rssr '
},
{
prompt: 'dasasfafa fafajfiaf agad gagadjga gadgadgadijgaf',
response: 'dkadfafadfa fadfafa fa df adgdfaeye5uttr sr s srt rssr '
},
{
prompt: 'dasasfafa fafajfiaf agad gagadjga gadgadgadijgaf',
response: 'dkadfafadfa fadfafa fa df adgdfaeye5uttr sr s srt rssr '
},
{
prompt: 'LAST PROMPT',
response: 'dkadfafadfa fadfafa fa df adgdfaeye5uttr sr s srt rssr '
}
])
const [conversationId, setConversationId] = useState<string | null>(null)
//const selectDocs = 'local/1706.03762.pdf/'
const answerRef = useRef<HTMLDivElement | null>(null);
const scrollRef = useRef<HTMLDivElement | null>(null);
const scrollIntoView = () => {
scrollRef.current?.scrollIntoView({
behavior: 'smooth',
block: 'start',
});
};
useEffect(() => {
if (answerRef.current) {
const element = answerRef.current;
element.scrollTop = element.scrollHeight;
}
}, [answer]);
scrollIntoView();
}, [queries.length, queries[queries.length - 1].response]);
useEffect(() => {
localStorage.setItem('docsGPTChatState', chatState);
}, [chatState]);
async function stream(question: string) {
setStatus('loading');
try {
await fetchAnswerStreaming(
{
question: question,
apiKey: apiKey,
apiHost: apiHost,
selectedDocs: selectDocs,
history: queries,
conversationId: conversationId,
onEvent: (event: MessageEvent) => {
const data = JSON.parse(event.data);
// check if the 'end' event has been received
if (data.type === 'end') {
// set status to 'idle'
setStatus('idle');
} else if (data.type === 'id') {
setConversationId(data.id)
} else {
const result = data.answer;
let streamingResponse = queries[queries.length - 1].response ? queries[queries.length - 1].response : '';
let updatedQueries = [...queries];
updatedQueries[updatedQueries.length - 1].response = streamingResponse + result;
setQueries(updatedQueries);
}
}
}
);
} catch (error) {
console.log(error);
let updatedQueries = [...queries];
updatedQueries[updatedQueries.length - 1].error = 'error'
setQueries(updatedQueries);
setStatus('idle')
}
}
// submit handler
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
setAnswer('')
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
// get question
queries.push({ prompt })
setPrompt('')
setChatState(ChatStates.Processing)
setTimeout(() => {
setChatState(ChatStates.Answer)
}, 800)
const inputElement = e.currentTarget[0] as HTMLInputElement;
const questionValue = inputElement.value;
fetchAnswerStreaming({
question: questionValue,
apiKey: apiKey,
selectedDocs: selectDocs,
history: [],
conversationId: null,
apiHost: apiHost,
onEvent: (event) => {
const data = JSON.parse(event.data);
// check if the 'end' event has been received
if (data.type === 'end') {
setChatState(ChatStates.Answer)
} else if (data.type === 'source') {
// check if data.metadata exists
let result;
if (data.metadata && data.metadata.title) {
const titleParts = data.metadata.title.split('/');
result = {
title: titleParts[titleParts.length - 1],
text: data.doc,
};
} else {
result = { title: data.doc, text: data.doc };
}
console.log(result)
await stream(prompt)
setChatState(ChatStates.Answer)
} else if (data.type === 'id') {
console.log(data.id);
} else {
const result = data.answer;
// set answer by appending answer
setAnswer(prevAnswer => prevAnswer + result);
}
},
});
}
return (
<>
<div className="dark widget-container">
<div onClick={() => setChatState(ChatStates.Init)}
className={`${chatState !== 'minimized' ? 'hidden' : ''} cursor-pointer`}>
<div className="mr-2 mb-2 w-20 h-20 rounded-full overflow-hidden dark:divide-gray-700 border dark:border-gray-700 bg-gradient-to-br from-gray-100/80 via-white to-white dark:from-gray-900/80 dark:via-gray-900 dark:to-gray-900 font-sans shadow backdrop-blur-sm flex items-center justify-center">
<div className="mr-2 mb-2 bottom-2 right-2 absolute w-20 h-20 rounded-full overflow-hidden dark:divide-gray-700 border dark:border-gray-100 bg-gradient-to-br dark:from-[#5AF0EC] dark:to-[#E80D9D] from-gray-900/80 via-gray-900 to-gray-900 font-sans shadow backdrop-blur-sm flex items-center justify-center">
<img
src="https://d3dg1063dc54p9.cloudfront.net/cute-docsgpt.png"
src={MessageIcon}
alt="DocsGPT"
className="cursor-pointer hover:opacity-50 h-14"
className="cursor-pointer hover:opacity-50 w-12"
/>
</div>
</div>
<div className={` ${chatState !== 'minimized' ? '' : 'hidden'} divide-y dark:divide-gray-700 rounded-md border dark:border-gray-700 bg-gradient-to-br from-gray-100/80 via-white to-white dark:from-gray-900/80 dark:via-gray-900 dark:to-gray-900 font-sans shadow backdrop-blur-sm`} style={{ width: '18rem', transform: 'translateY(0%) translateZ(0px)' }}>
<div className={` ${chatState !== 'minimized' ? '' : 'hidden'} absolute bottom-0 divide-y dark:divide-gray-700 rounded-md border dark:bg-[#222327] dark:border-gray-700 font-sans shadow backdrop-blur-sm w-full`} style={{ transform: 'translateY(0%) translateZ(0px)' }}>
<div>
<img
src="https://d3dg1063dc54p9.cloudfront.net/exit.svg"
src={Cancel}
alt="Exit"
className="cursor-pointer hover:opacity-50 h-2 absolute top-0 right-0 m-2 white-filter"
className="cursor-pointer hover:opacity-50 absolute top-0 right-0 m-2 white-filter"
onClick={(event) => {
event.stopPropagation();
setChatState(ChatStates.Minimized);
}}
/>
<div className="flex items-center gap-2 p-3">
<div className={`${chatState === 'init' ? '' :
chatState === 'processing' ? '' :
chatState === 'typing' ? '' :
'hidden'} flex-1`}>
<h3 className="text-sm font-bold text-gray-700 dark:text-gray-200">Need help with documentation?</h3>
<p className="mt-1 text-xs text-gray-400 dark:text-gray-500">DocsGPT AI assistant will help you with docs</p>
</div>
<div id="docsgpt-answer" ref={answerRef} className={`${chatState !== 'answer' ? 'hidden' : ''}`}>
<p className="mt-1 text-sm text-gray-600 dark:text-white text-left">{answer}</p>
<div className={` flex justify-between`}>
<img src={Dragon} />
<div className='mx-2 w-full'>
<h3 className="text-sm font-bold text-gray-700 dark:text-gray-200">Get AI assistance</h3>
<p className="mt-1 text-xs text-gray-400 dark:text-gray-500">DocsGPT's AI Chatbot is here to help</p>
</div>
</div>
</div>
</div>
@ -230,47 +181,47 @@ export const DocsGPTWidget = ({ apiHost = 'https://gptcloud.arc53.com', selectDo
className={`flex w-full justify-center px-5 py-3 text-sm text-gray-800 font-bold dark:text-white transition duration-300 hover:bg-gray-100 rounded-b dark:hover:bg-gray-800/70 ${chatState !== 'init' ? 'hidden' : ''}`}>
Ask DocsGPT
</button>
{(chatState === 'typing' || chatState === 'answer') && (
<div>
<div>
<div className='flex justify-start m-2 '>
<p className='dark:bg-slate-600 max-w-[80%] dark:text-white block p-2 rounded-lg'>
Hi, How can I help you today ?
</p>
</div>
<div className='flex justify-end m-2 '>
<p className='dark:bg-blue-500 max-w-[80%] dark:text-black block p-2 rounded-lg '>
Hey, I am having trouble with my account !
</p>
</div>
<div className='flex justify-start m-2 '>
<p className='dark:bg-slate-600 max-w-[80%] dark:text-white block p-2 rounded-lg'>
What seems to be the problem ?
</p>
</div>
<div className='flex justify-end m-2 '>
<p className='dark:bg-blue-500 max-w-[80%] dark:text-black block p-2 rounded-lg '>
I can't login
</p>
</div>
</div>
{(chatState === 'typing' || chatState === 'answer' || chatState === 'processing') && (
<div className='h-full'>
<ScrollArea className='h-72 rounded-md border'>
{
queries?.map((query, index) => {
return (
<Fragment key={index}>
{
query.prompt && <div className='flex justify-end m-2 '>
<p className='bg-gradient-to-br dark:from-[#8860DB] dark:to-[#6D42C5] max-w-[80%] dark:text-white block p-2 rounded-lg '>
{query.prompt}
</p>
</div>
}
{
query.response && <div ref={(index === queries.length - 1) ? scrollRef : null} className='flex justify-start m-2 '>
<p className='dark:bg-[#38383B] max-w-[80%] dark:text-white block p-2 rounded-lg'>
{query.response}
</p>
</div>
}
</Fragment>)
})
}
</ScrollArea>
<form
onSubmit={handleSubmit}
className="relative w-full m-0" style={{ opacity: 1 }}>
<div className='p-2 flex justify-between'>
<Input type='text'
className="w-[85%] h-8 bg-transparent px-5 py-4 text-sm text-gray-700 dark:text-white focus:outline-none" placeholder="What do you want to do?" />
<Button className="text-gray-400 dark:text-gray-500 dark:bg-blue-700 dark:hover:bg-blue-600 text-sm inset-y-0 px-2" type="submit" ><PaperPlaneIcon className='text-white' /></Button>
<Input
value={prompt} onChange={(event) => setPrompt(event.target.value)}
type='text'
className="w-[85%] border border-[#686877] h-8 bg-transparent px-5 py-4 text-sm text-gray-700 dark:text-white focus:outline-none" placeholder="What do you want to do?" />
<Button className="text-gray-400 dark:text-gray-500 bg-gradient-to-br dark:from-[#5AF0EC] dark:to-[#E80D9D] disabled:bg-black text-sm inset-y-0 px-2" type="submit" disabled={prompt.length == 0 || status !== 'idle'}>
<PaperPlaneIcon className='text-white' />
</Button>
</div>
</form>
</div>
)}
<p className={`${chatState !== 'processing' ? 'hidden' : ''} flex w-full justify-center px-5 py-3 text-sm text-gray-800 font-bold dark:text-white transition duration-300 rounded-b`}>
Processing<span className="dot-animation">.</span><span className="dot-animation delay-200">.</span><span className="dot-animation delay-400">.</span>
</p>
</div>
</div>
</div>

@ -0,0 +1,48 @@
"use client"
import * as React from "react"
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
import { cn } from "@/lib/utils"
const ScrollArea = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
>(({ className, children, ...props }, ref) => (
<ScrollAreaPrimitive.Root
ref={ref}
className={cn("relative overflow-hidden", className)}
{...props}
>
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
{children}
</ScrollAreaPrimitive.Viewport>
<ScrollBar />
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
))
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
const ScrollBar = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
>(({ className, orientation = "vertical", ...props }, ref) => (
<ScrollAreaPrimitive.ScrollAreaScrollbar
ref={ref}
orientation={orientation}
className={cn(
"flex touch-none select-none transition-colors",
orientation === "vertical" &&
"h-full w-2.5 border-l border-l-transparent p-[1px]",
orientation === "horizontal" &&
"h-2.5 flex-col border-t border-t-transparent p-[1px]",
className
)}
{...props}
>
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
</ScrollAreaPrimitive.ScrollAreaScrollbar>
))
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
export { ScrollArea, ScrollBar }

@ -15,6 +15,8 @@ z-index: 1000; /* to ensure it appears on top of other content, if any */
display: flex;
flex-direction: column;
align-items: center;
width: 355px;
height: 405px;
}
@keyframes dotBounce {

@ -0,0 +1,95 @@
import { Answer } from "@/models/customTypes";
import { Doc } from "@/models/customTypes";
interface HistoryItem {
prompt: string;
response?: string;
}
interface FetchAnswerStreamingProps {
question?: string;
apiKey?: string;
selectedDocs?: string;
history?: HistoryItem[];
conversationId?: string | null;
apiHost?: string;
onEvent?: (event: MessageEvent) => void;
}
export function fetchAnswerStreaming({
question = '',
apiKey = '',
selectedDocs = '',
history = [],
conversationId = null,
apiHost = '',
onEvent = () => {console.log("Event triggered, but no handler provided.");}
}: FetchAnswerStreamingProps): Promise<void> {
let docPath = 'default';
if (selectedDocs) {
docPath = selectedDocs;
}
return new Promise<void>((resolve, reject) => {
const body = {
question: question,
api_key: apiKey,
embeddings_key: apiKey,
active_docs: docPath,
history: JSON.stringify(history),
conversation_id: conversationId,
model: 'default'
};
fetch(apiHost + '/stream', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
})
.then((response) => {
if (!response.body) throw Error('No response body');
const reader = response.body.getReader();
const decoder = new TextDecoder('utf-8');
let counterrr = 0;
const processStream = ({
done,
value,
}: ReadableStreamReadResult<Uint8Array>) => {
if (done) {
console.log(counterrr);
resolve();
return;
}
counterrr += 1;
const chunk = decoder.decode(value);
const lines = chunk.split('\n');
for (let line of lines) {
if (line.trim() == '') {
continue;
}
if (line.startsWith('data:')) {
line = line.substring(5);
}
const messageEvent = new MessageEvent('message', {
data: line,
});
onEvent(messageEvent); // handle each message
}
reader.read().then(processStream).catch(reject);
};
reader.read().then(processStream).catch(reject);
})
.catch((error) => {
console.error('Connection failed:', error);
reject(error);
});
});
}
Loading…
Cancel
Save