hts/apps/blogai/components/chat-model.tsx

700 lines
20 KiB
TypeScript

'use client'
import { usePathname, useRouter, useSearchParams } from 'next/navigation'
import { PrefetchKind } from 'next/dist/client/components/router-reducer/router-reducer-types'
import toast from 'react-hot-toast'
import { cn } from '@/lib/utils'
import { ChatList } from '@/components/chat-list'
import { ChatPanel } from '@/components/chat-panel'
import { ChatScrollAnchor } from '@/components/chat-scroll-anchor'
import { nanoid } from '@/lib/utils'
import { functionSchemas } from '@/lib/functions/schemas'
import { Landing } from '@/components/landing'
import { useGlobalStore } from '@/app/state/global-store'
import { useW3GPTDeploy } from '@/lib/hooks/use-w3gpt-deploy'
// import { useNetwork } from 'wagmi'
import { useEffect, useState } from 'react'
import { FadeIn } from './landing/fade-in'
import Image from 'next/image';
import logoImage from '@/components/images/logo.png';
import jellaiImage from '@/components/images/jellai.png'
import { useTranslation } from 'react-i18next'
import { Button, Modal, message } from 'antd'
import { PromptFormIndex } from './prompt-index'
import { LogoAI } from './chat'
import { useISDK, Message } from '@aigxion/isdk/react'
import { PromptForm } from './prompt-form'
import { ChatMessage } from './chat-message'
import { Separator } from './ui/separator'
import remarkGfm from 'remark-gfm'
import remarkMath from 'remark-math'
import { CodeBlock } from '@/components/ui/codeblock'
import { MemoizedReactMarkdown } from '@/components/markdown'
import { IconF, IconUser, IconW3GPT } from '@/components/ui/icons'
import { ChatMessageActions } from '@/components/chat-message-actions'
import { number } from 'zod'
import Link from 'next/link'
import { ChatRequest, FunctionCallHandler, FunctionCallHandlerV2 } from '@aigxion/isdk'
export interface ChatProps extends React.ComponentProps<'div'> {
initialMessages?: Message[]
id: string
position?: string
defaultOpen?: boolean
showLanding?: boolean
avatarUrl?: string | null | undefined
}
export function ChatModel({
id,
initialMessages,
className,
position,
defaultOpen = false,
showLanding = false,
avatarUrl
}: ChatProps) {
const router = useRouter()
const path = usePathname()
const { t, i18n } = useTranslation()
const [open, setOpen] = useState(defaultOpen);
const searchParams = useSearchParams()
let q = searchParams.get('q')
let f = searchParams.get('f')
console.log("-------", q, path)
const isChatPage = path.includes('chat')
const {
setIsGenerating,
setIsDeploying,
setDeployContractConfig,
verifyContractConfig,
lastDeploymentData,
setLastDeploymentData
} = useGlobalStore()
// const { chain } = useNetwork()
// const fallbackChainId = chain?.unsupported === false ? chain.id : 5001
// const activeChainId = chain?.id ?? fallbackChainId
// const { deploy } = useW3GPTDeploy({ chainId: activeChainId })
useEffect(() => {
let isLounted = true
async function verifyContract() {
}
verifyContract()
// try {
// let data = localStorage.getItem("UserData");
// if (data == null) return
// data = JSON.parse(data)
// console.log("UserData", data)
// } catch (error) {
// console.log(error)
// }
return () => {
isLounted = false
}
}, [])
useEffect(() => {
let isMounted = true
async function verifyContract() {
if (
!verifyContractConfig?.deployHash ||
lastDeploymentData?.verificationStatus === 'success'
) {
return
}
try {
const response = await fetch('/api/verify-contract', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(verifyContractConfig)
})
const data = await response.json()
if (typeof data === 'string' && data.startsWith('0x') && isMounted) {
toast.success('Contract verified successfully!')
lastDeploymentData &&
setLastDeploymentData({
...lastDeploymentData,
verificationStatus: 'success'
})
} else {
setTimeout(verifyContract, 15000) // Retry after 15 seconds
}
} catch (error) {
console.error('Verification failed', error)
setTimeout(verifyContract, 15000) // Retry after 15 seconds
}
}
verifyContract()
return () => {
isMounted = false
}
}, [lastDeploymentData, verifyContractConfig, setLastDeploymentData])
const functionCallHandler: FunctionCallHandler = async (
chatMessages,
functionCall
) => {
if (functionCall.name === 'deploy_contract') {
setIsDeploying(true)
const { chainId, contractName, sourceCode, constructorArgs } = JSON.parse(
functionCall.arguments || '{}'
)
// setDeployContractConfig({
// chainId: chainId ?? activeChainId,
// contractName,
// sourceCode,
// constructorArgs
// })
// const verifiedContractAddress = await deploy({
// chainId: chainId ?? activeChainId,
// contractName,
// sourceCode,
// constructorArgs
// })
// const functionResponse: ChatRequest = {
// messages: [
// ...chatMessages,
// {
// id: nanoid(),
// name: 'deploy_contract',
// role: 'function',
// content: JSON.stringify({ verifiedContractAddress })
// }
// ],
// functions: functionSchemas
// }
// return functionResponse
} else if (functionCall.name === 'text_to_image') {
const response = await fetch('/api/text-to-image', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ text: functionCall.arguments })
})
if (!response.ok) {
throw new Error(response.statusText)
}
const { imageUrl, metadataUrl } = await response.json()
const functionResponse: ChatRequest = {
messages: [
...chatMessages,
{
id: nanoid(),
name: 'text-to-image',
role: 'function',
content: JSON.stringify({ imageUrl, metadataUrl })
}
],
functions: functionSchemas
}
return functionResponse
}
}
// {"messageId": "sendMessage", "conversationId": "thread_s1Bn2vw0X8BbsQCz3WtpmFg6", "type": "function", "functionName": "ShowCryptoAddressReceive", "functionArgs": "{\"cryptocurrencyType\":\"BTC\"}"}
const functionCallHandlerV2: FunctionCallHandlerV2 = async (
functionCall
) => {
const functionname = functionCall?.functionName
message.warning(t("not_supported_call", { functionname }))
switch (functionname) {
case "CreateWalletWithTypes": // 创建钱包
break
case "ShowCryptoAddressReceive": // 收款
console.log("----ShowCryptoAddressReceive-----", functionCall)
break
case "LoginHelp": // 进行第三方登录
break;
case "ScanQrCode": // 扫描二维码
break;
case "ChangePaymentPin": // 修改交易密码
break;
case "CreatePaymentPin": // 设置交易密码
break;
case "CountCryptoKindsQuantity": // 查询钱包中代币种类数量
break;
case "InquireCryptoPriceFromExchange": // 查询代币当前价格
break;
case "ListHistoryTransactionsUsingDate": // 查询交易记录
break;
case "InqueryCryptoBalance": // 查询余额
break;
case "TransferCryptoFromTo": // 转账
break;
case "RecoverWallet": // 恢复钱包
break;
default:
console.log("----functionCallHandlerV2--default---", functionCall)
break;
}
}
// const isSocket = true
// const { messages, append, reload, stop, isLoading, input, setInput } =
// useChat({
// experimental_onFunctionCall: functionCallHandler,
// initialMessages,
// id,
// body: {
// id
// },
// onResponse(response) {
// setIsGenerating(true)
// if (!isChatPage) {
// router.prefetch(`${i18n.language}/chat/${id}`, {
// kind: PrefetchKind.FULL
// })
// }
// if (response.status === 401) {
// toast.error(response.statusText)
// }
// },
// onFinish() {
// setIsGenerating(false)
// if (!isChatPage) {
// history.pushState({}, '', `${i18n.language}/chat/${id}`)
// history.go(1)
// }
// }
// })
// const optins = !!q ? {
// // initialInput: `{
// // "method": "REQUEST",
// // "params":
// // [
// // "@account",
// // "@balance"
// // ],
// // "id": 12
// // }`
// initialInput: q
// } : {}
// const { messages, append, isLoading, input, setInput, } = useISDK('ws://116.213.39.234:8083/ws', optins);
const { messages, append, setMessages, reload, stop, isLoading, isSocket, input, setInput } =
useISDK({
api: `${process.env.NEXT_PUBLIC_CLIENT_BASE_WS}`,
experimental_onFunctionCall: functionCallHandler,
experimental_onFunctionCallV2: functionCallHandlerV2,
initialMessages,
id,
body: {
id
},
onResponse(response) {
setIsGenerating(true)
if (!isChatPage) {
// router.prefetch(`/${i18n.language}/chat/${id}`, {
router.prefetch(`/${path}`, {
kind: PrefetchKind.FULL
})
}
if (response.status === 401) {
toast.error(response.statusText)
}
},
onFinish() {
setIsGenerating(false)
if (!isChatPage) {
history.pushState({}, '', `/${path}`)
history.go(1)
}
}
})
useEffect(() => {
if (!isLoading && isSocket) {
console.log('q has changed:', q);
if (!!q) {
append(
{
id,
content: q,
role: 'user'
},
{ functions: functionSchemas }
)
setOpen(true)
}
}
// 这里可以执行您想要的任何操作
}, [q, isSocket]);
return (
<>
<div className={cn('inset-x-0 bg-gradient-to-b from-muted/10 from-10% to-muted/0.3 ', className)}>
<div className={cn('fixed top-0 lg:top-[0] left-0 w-full lg:w-3/5 py-[0rem] bg-[#fff] z-50', position)}>
{/* <Button type="primary" onClick={() => setOpen(true)}>
Open Modal of 1000px width
</Button> */}
<div className='flex flex-row items-center justify-start min-h-[4.5rem]'>
{
showLanding && !position &&
// <Image src={logoImage} height={40} alt="show" className="cursor-pointer m-auto" onClick={() => { }} />
<Link
href="/"
className='hidden lg:flex text-center m-auto h-[2rem] w-3/12'
>
<LogoAI
width='100%'
/>
</Link>
}
<div className=" space-y-4 py-[1rem] w-5/6 lg:w-5/6 xl:w-4/5 2xl:w-3/4 m-auto">
{!open && <PromptFormIndex
id={id}
onSubmit={async (value) => {
router.prefetch(`${path}?q=${value}`, {
kind: PrefetchKind.FULL
})
history.pushState({}, '', `${path}?q=${value}`)
history.go(1)
// console.log('--value----', value)
// await new Promise(r => setTimeout(r, 300));
// setOpen(true)
// await append(
// {
// id,
// content: value,
// role: 'user'
// },
// { functions: functionSchemas }
// )
}}
// input={input}
// setInput={setInput}
// isLoading={isLoading}
/>}
</div>
</div>
</div>
<Modal
// title="Modal 1000px width"
centered
open={open}
onOk={() => setOpen(false)}
onCancel={() => {
q = ""
// const url = `/chat/${id}`
router.prefetch(path, {
kind: PrefetchKind.FULL
})
history.pushState({}, '', path)
history.go(1)
setMessages([])
setOpen(false)
}}
footer=""
width={1000}
>
<div className="mx-auto sm:max-w-2xl sm:px-4 min-h-[300px] mb-[3rem] lg:mb-[5rem]">
<FadeIn >
{/* {showLanding && <Landing disableAnimations={isChatPage} />} */}
{showLanding && <h3 className='text-center text-2xl font-bold py-[1.2rem]'>{t('JellyAI')}</h3>}
</FadeIn>
{/* <ChatList messages={messages} avatarUrl={avatarUrl} /> */}
<div className="relative mx-auto max-w-2xl px-2">
{messages.map((message, index) => (
<div key={index}>
<ChatMessageISDK message={message} avatarUrl={avatarUrl} />
{index < messages.length - 1 && (
<Separator className="my-4 md:my-8" />
)}
</div>
))}
</div>
<ChatScrollAnchor trackVisibility={isLoading} />
{/* {!!messages && <ChatPanel
id={id}
// stop={stop}
isLoading={isLoading}
append={append}
// reload={reload}
messages={messages}
input={input}
setInput={setInput}
/>} */}
<div
className={cn(
"absolute inset-x-0 bottom-0 inset-x-0 bg-gradient-to-b from-muted/10 from-10% to-muted/0.3",
// messages.length > 1 ? 'fixed bottom-0 bg-[#f6f6f6] z-[999]' : "absolute bg-[#fff]"
)}
style={{
// top: messages.length > 1 ? 'auto' : "10px",
// backgroundColor: messages.length > 1 ? '#fff' : "",
}}
>
{/* <ButtonScrollToBottom /> */}
<div className="mx-auto sm:max-w-2xl sm:px-4 ">
{/* <div className="flex h-10 items-center justify-center">
{isLoading ? (
<Button
variant="outline"
onClick={() => stop()}
className="bg-background"
>
<IconSpinner className="mr-2 animate-spin" />
Stop generating
</Button>
) : (
messages?.length > 1 && (
<Button
variant="outline"
onClick={() => reload({
functions: functionSchemas
})}
className="bg-background"
>
<IconRefresh className="mr-2" />
Regenerate response
</Button>
)
)}
</div> */}
{/* <div className="space-y-4 border-t bg-background px-4 py-2 shadow-lg sm:rounded-t-xl sm:border md:py-4"> */}
<div className="space-y-4 px-4 py-2 ">
<PromptForm
onSubmit={async value => {
await new Promise(r => setTimeout(r, 300));
await append(
{
id,
content: value,
role: 'user'
},
{ functions: functionSchemas }
)
}}
input={input}
setInput={setInput}
isLoading={isLoading}
/>
</div>
</div>
</div>
</div>
</Modal>
</div>
</>
)
}
interface ChatMessageISDKProps {
message: Message
avatarUrl?: string | null | undefined
}
function ChatMessageISDK({
message,
avatarUrl,
...props
}: ChatMessageISDKProps) {
const [isExpanded, setIsExpanded] = useState(false)
const { t, i18n } = useTranslation()
const language = i18n.language
console.log("----ChatMessageISDK------", message)
const onExpandClick = () => setIsExpanded(!isExpanded)
if ((message.function_call && !isExpanded)) {
return (
<div
className="group relative mb-4 flex items-start md:-ml-12"
{...props}
>
<div className="flex size-8 shrink-0 select-none items-center justify-center rounded-md border bg-baclbtn text-primary-foreground shadow">
<IconF />
</div>
<div className="ml-4 flex-1 space-y-2 px-1">
<Button onClick={onExpandClick} >
Function Call
</Button>
{/* <ChatMessageActions message={message} onExpandClick={onExpandClick} /> */}
</div>
</div>
)
}
return (
<div
className={cn('group relative mb-4 flex items-start md:-ml-12')}
{...props}
>
<div
className={cn(
'relative flex size-10 shrink-0 select-none items-center justify-center overflow-hidden rounded-full border-[0.3rem] border-[#E7F0FF] bg-[#E7F0FF] '
// rounded-md border shadow
)}
>
{message.role === 'user' ? (
// {true ? (
avatarUrl ? (
<Image
className="rounded-md"
src={avatarUrl}
alt={'user avatar'}
fill={true}
sizes="32px"
/>
) : (
// <IconUser />
<span className='text-[#1A1A1A] text-[0.8rem] font-bold text-center '>{t('your')}</span>
)
) : message.function_call ? (
<div className="flex h-8 w-8 shrink-0 select-none items-center justify-center rounded-md border bg-baclbtn text-primary-foreground shadow">
<IconF />
</div>
) : (
// <IconW3GPT />
<figure className=' h-full ' >
<Image src={jellaiImage} sizes="32px" alt="Answer Avatar" fill={true} className="rounded-md" />
</figure>
)}
</div>
<div className="ml-4 flex-1 space-y-2 overflow-x-auto">
<MemoizedReactMarkdown
className="prose break-words dark:prose-invert prose-p:leading-relaxed prose-pre:p-0"
remarkPlugins={[remarkGfm, remarkMath]}
linkTarget={'_blank'}
components={{
p({ children }) {
return <p className="mb-2 last:mb-0">{children}</p>
},
code({ node, inline, className, children, ...props }) {
if (children.length) {
if (children[0] == '▍') {
return (
<span className="mt-1 animate-pulse cursor-default"></span>
)
}
children[0] = (children[0] as string).replace('`▍`', '▍')
}
const match = /language-(\w+)/.exec(className || '')
if (inline) {
return (
<code className={className} {...props}>
{children}
</code>
)
}
return (
<CodeBlock
key={Math.random()}
language={(match && match[1]) || ''}
value={String(children).replace(/\n$/, '')}
{...props}
/>
)
}
}}
>
{message.content === ''
? typeof message.function_call === 'string'
? message.function_call
: JSON.stringify(message.function_call)
: message.content ?? ''}
</MemoizedReactMarkdown>
<div className="flex flex-col justify-end">
<ChatMessageActions message={message} onExpandClick={onExpandClick} />
</div>
</div>
</div>
)
}