mirror of
https://github.com/arc53/DocsGPT.git
synced 2025-11-30 17:13:15 +00:00
inititated shadcn
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
"use client";
|
||||
import {useEffect, useRef, useState} from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { PaperPlaneIcon } from '@radix-ui/react-icons';
|
||||
import { Input } from './ui/input';
|
||||
import { Button } from './ui/button';
|
||||
|
||||
//import './style.css'
|
||||
|
||||
interface HistoryItem {
|
||||
@@ -33,7 +37,7 @@ function fetchAnswerStreaming({
|
||||
history = [],
|
||||
conversationId = null,
|
||||
apiHost = '',
|
||||
onEvent = () => {console.log("Event triggered, but no handler provided.");}
|
||||
onEvent = () => { console.log("Event triggered, but no handler provided."); }
|
||||
}: FetchAnswerStreamingProps): Promise<void> {
|
||||
let docPath = 'default';
|
||||
if (selectedDocs) {
|
||||
@@ -107,140 +111,169 @@ function fetchAnswerStreaming({
|
||||
});
|
||||
}
|
||||
|
||||
export const DocsGPTWidget = ({ apiHost = 'https://gptcloud.arc53.com', selectDocs = 'default', apiKey = 'docsgpt-public'}) => {
|
||||
// processing states
|
||||
const [chatState, setChatState] = useState<ChatStates>(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
return localStorage.getItem('docsGPTChatState') as ChatStates || ChatStates.Init;
|
||||
}
|
||||
return ChatStates.Init;
|
||||
});
|
||||
|
||||
const [answer, setAnswer] = useState<string>('');
|
||||
|
||||
//const selectDocs = 'local/1706.03762.pdf/'
|
||||
const answerRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (answerRef.current) {
|
||||
const element = answerRef.current;
|
||||
element.scrollTop = element.scrollHeight;
|
||||
}
|
||||
}, [answer]);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem('docsGPTChatState', chatState);
|
||||
}, [chatState]);
|
||||
|
||||
|
||||
|
||||
// submit handler
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
setAnswer('')
|
||||
e.preventDefault()
|
||||
// get question
|
||||
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)
|
||||
|
||||
} else if (data.type === 'id') {
|
||||
console.log(data.id);
|
||||
} else {
|
||||
const result = data.answer;
|
||||
// set answer by appending answer
|
||||
setAnswer(prevAnswer => prevAnswer + result);
|
||||
}
|
||||
},
|
||||
});
|
||||
export const DocsGPTWidget = ({ apiHost = 'https://gptcloud.arc53.com', selectDocs = 'default', apiKey = 'docsgpt-public' }) => {
|
||||
// processing states
|
||||
const [chatState, setChatState] = useState<ChatStates>(() => {
|
||||
if (typeof window !== 'undefined') {
|
||||
return localStorage.getItem('docsGPTChatState') as ChatStates || ChatStates.Init;
|
||||
}
|
||||
return ChatStates.Init;
|
||||
});
|
||||
|
||||
const [answer, setAnswer] = useState<string>('');
|
||||
|
||||
//const selectDocs = 'local/1706.03762.pdf/'
|
||||
const answerRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (answerRef.current) {
|
||||
const element = answerRef.current;
|
||||
element.scrollTop = element.scrollHeight;
|
||||
}
|
||||
}, [answer]);
|
||||
|
||||
useEffect(() => {
|
||||
localStorage.setItem('docsGPTChatState', chatState);
|
||||
}, [chatState]);
|
||||
|
||||
|
||||
|
||||
// submit handler
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
setAnswer('')
|
||||
e.preventDefault()
|
||||
// get question
|
||||
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)
|
||||
|
||||
} 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">
|
||||
<img
|
||||
src="https://d3dg1063dc54p9.cloudfront.net/cute-docsgpt.png"
|
||||
alt="DocsGPT"
|
||||
className="cursor-pointer hover:opacity-50 h-14"
|
||||
/>
|
||||
</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>
|
||||
<img
|
||||
src="https://d3dg1063dc54p9.cloudfront.net/exit.svg"
|
||||
alt="Exit"
|
||||
className="cursor-pointer hover:opacity-50 h-2 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>
|
||||
<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">
|
||||
<img
|
||||
src="https://d3dg1063dc54p9.cloudfront.net/cute-docsgpt.png"
|
||||
alt="DocsGPT"
|
||||
className="cursor-pointer hover:opacity-50 h-14"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<button onClick={() => setChatState(ChatStates.Typing)}
|
||||
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') && (
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
className="relative w-full m-0" style={{ opacity: 1 }}>
|
||||
<input type="text"
|
||||
className="w-full bg-transparent px-5 py-3 pr-8 text-sm text-gray-700 dark:text-white focus:outline-none" placeholder="What do you want to do?" />
|
||||
<button className="absolute text-gray-400 dark:text-gray-500 text-sm inset-y-0 right-2 -mx-2 px-2" type="submit" >Submit</button>
|
||||
</form>
|
||||
)}
|
||||
<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 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>
|
||||
<img
|
||||
src="https://d3dg1063dc54p9.cloudfront.net/exit.svg"
|
||||
alt="Exit"
|
||||
className="cursor-pointer hover:opacity-50 h-2 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>
|
||||
</div>
|
||||
</div>
|
||||
<div className="w-full">
|
||||
<button onClick={() => setChatState(ChatStates.Typing)}
|
||||
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>
|
||||
<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>
|
||||
</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>
|
||||
</div>
|
||||
|
||||
</>
|
||||
)
|
||||
|
||||
57
extensions/react-widget/src/components/ui/button.tsx
Normal file
57
extensions/react-widget/src/components/ui/button.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
import * as React from "react"
|
||||
import { Slot } from "@radix-ui/react-slot"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"bg-primary text-primary-foreground shadow hover:bg-primary/90",
|
||||
destructive:
|
||||
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
|
||||
outline:
|
||||
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
|
||||
secondary:
|
||||
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
},
|
||||
size: {
|
||||
default: "h-9 px-4 py-2",
|
||||
sm: "h-8 rounded-md px-3 text-xs",
|
||||
lg: "h-10 rounded-md px-8",
|
||||
icon: "h-9 w-9",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
size: "default",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
export interface ButtonProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
VariantProps<typeof buttonVariants> {
|
||||
asChild?: boolean
|
||||
}
|
||||
|
||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||
const Comp = asChild ? Slot : "button"
|
||||
return (
|
||||
<Comp
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
Button.displayName = "Button"
|
||||
|
||||
export { Button, buttonVariants }
|
||||
25
extensions/react-widget/src/components/ui/input.tsx
Normal file
25
extensions/react-widget/src/components/ui/input.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import * as React from "react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
export interface InputProps
|
||||
extends React.InputHTMLAttributes<HTMLInputElement> {}
|
||||
|
||||
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
({ className, type, ...props }, ref) => {
|
||||
return (
|
||||
<input
|
||||
type={type}
|
||||
className={cn(
|
||||
"flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
Input.displayName = "Input"
|
||||
|
||||
export { Input }
|
||||
24
extensions/react-widget/src/components/ui/textarea.tsx
Normal file
24
extensions/react-widget/src/components/ui/textarea.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import * as React from "react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
export interface TextareaProps
|
||||
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
|
||||
|
||||
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
||||
({ className, ...props }, ref) => {
|
||||
return (
|
||||
<textarea
|
||||
className={cn(
|
||||
"flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
)
|
||||
Textarea.displayName = "Textarea"
|
||||
|
||||
export { Textarea }
|
||||
Reference in New Issue
Block a user