'use client' import * as React from 'react' import { Button } from '@/components/ui/button' import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog' import { Input } from '@/components/ui/input' import { SSE } from 'sse.js' import type { CreateChatCompletionResponse } from 'openai' import { X, Loader, User, Frown, CornerDownLeft, Search, Wand } from 'lucide-react' function promptDataReducer( state: any[], action: { index?: number answer?: string | undefined status?: string query?: string | undefined type?: 'remove-last-item' | string } ) { // set a standard state to use later let current = [...state] if (action.type) { switch (action.type) { case 'remove-last-item': current.pop() return [...current] default: break } } // check that an index is present if (action.index === undefined) return [...state] if (!current[action.index]) { current[action.index] = { query: '', answer: '', status: '' } } current[action.index].answer = action.answer if (action.query) { current[action.index].query = action.query } if (action.status) { current[action.index].status = action.status } return [...current] } export function SearchDialog() { const [open, setOpen] = React.useState(false) const [search, setSearch] = React.useState('') const [question, setQuestion] = React.useState('') const [answer, setAnswer] = React.useState('') const eventSourceRef = React.useRef() const [isLoading, setIsLoading] = React.useState(false) const [hasError, setHasError] = React.useState(false) const [promptIndex, setPromptIndex] = React.useState(0) const [promptData, dispatchPromptData] = React.useReducer(promptDataReducer, []) const cantHelp = answer?.trim() === "Sorry, I don't know how to help with that." React.useEffect(() => { const down = (e: KeyboardEvent) => { if (e.key === 'k' && e.metaKey) { setOpen(true) } if (e.key === 'Escape') { console.log('esc') handleModalToggle() } } document.addEventListener('keydown', down) return () => document.removeEventListener('keydown', down) }, []) function handleModalToggle() { setOpen(!open) setSearch('') setQuestion('') setAnswer(undefined) setPromptIndex(0) dispatchPromptData({ type: 'remove-last-item' }) setHasError(false) setIsLoading(false) } const handleConfirm = React.useCallback( async (query: string) => { setAnswer(undefined) setQuestion(query) setSearch('') dispatchPromptData({ index: promptIndex, answer: undefined, query }) setHasError(false) setIsLoading(true) const eventSource = new SSE(`api/vector-search`, { headers: { apikey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY ?? '', Authorization: `Bearer ${process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY}`, 'Content-Type': 'application/json', }, payload: JSON.stringify({ query }), }) function handleError(err: T) { setIsLoading(false) setHasError(true) console.error(err) } eventSource.addEventListener('error', handleError) eventSource.addEventListener('message', (e: any) => { try { setIsLoading(false) if (e.data === '[DONE]') { setPromptIndex((x) => { return x + 1 }) return } // 应该在代码顶部放置断言,以确保 `e.data` 符合 `string` 类型 // 另外,请注意,使用类型断言会带来运行时错误的风险,因此对于不确定类型的值,请确保进行有效的类型验证 const completionResponse= JSON.parse(e.data); const text = completionResponse.choices[0].delta?.content || ""; setAnswer((answer) => { const currentAnswer = answer ?? '' dispatchPromptData({ index: promptIndex, answer: currentAnswer + text, }) return (answer ?? '') + text }) } catch (err) { handleError(err) } }) eventSource.stream() eventSourceRef.current = eventSource setIsLoading(true) }, [promptIndex, promptData] ) const handleSubmit: React.FormEventHandler = (e) => { e.preventDefault() console.log(search) handleConfirm(search) } return ( <> AI 法律助手 我是您的法律助手,请输入您想查询的问题
{question && (
{' '}

{question}

)} {isLoading && (
)} {hasError && (
服务器繁忙,请稍后再试! 或者自行部署
)} {answer && !hasError ? (

Answer:

{answer}
) : null}
setSearch(e.target.value)} className="col-span-3" />
Or try:{' '}
* 回答由 AI 检索法律文件后生成,不保证准确率,仅供参考学习!打赏赞助
) }