694 lines
20 KiB
TypeScript
694 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 { Dispatch, SetStateAction, 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
|
|
serial_no?: string
|
|
position?: string
|
|
className?: string
|
|
classNameModal?: string
|
|
defaultOpen?: boolean
|
|
open?: boolean
|
|
setOpen?: Dispatch<SetStateAction<boolean>>
|
|
showLanding?: boolean
|
|
avatarUrl?: string | null | undefined
|
|
}
|
|
|
|
export function ChatModel({
|
|
id,
|
|
initialMessages,
|
|
className,
|
|
classNameModal,
|
|
position,
|
|
defaultOpen = false,
|
|
showLanding = false,
|
|
open,
|
|
setOpen,
|
|
avatarUrl,
|
|
|
|
}: ChatProps) {
|
|
const router = useRouter()
|
|
const path = usePathname()
|
|
|
|
const { t, i18n } = useTranslation()
|
|
|
|
// const [open, setOpen] = useState(defaultOpen);
|
|
|
|
const defserialNo = process.env.NEXT_PUBLIC_STAFF_SERIAL_NO || "" //"zSv0TAQ7oaDvmmVENzasDBjZPQ0ffQkU"
|
|
|
|
const searchParams = useSearchParams()
|
|
let q = searchParams.get('q')
|
|
let f = searchParams.get('f')
|
|
let serial_no = searchParams.get('serial_no') || defserialNo
|
|
|
|
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 { messages, append, setMessages, reload, stop, isLoading, isSocket, input, setInput } =
|
|
useISDK({
|
|
api: `${process.env.NEXT_PUBLIC_CLIENT_BASE_WS}/${serial_no}`,
|
|
|
|
// // 用户名和密码
|
|
// username: 'JS8dw07Ld0hcCsVf',
|
|
// password: '8jfPM53Sq84Etvc3J98kqlxtFwqPOvVMQxlGmc3ud7sy10ch',
|
|
|
|
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',
|
|
action: "chat",
|
|
type: 'text',
|
|
},
|
|
{ functions: functionSchemas }
|
|
)
|
|
if (setOpen) {
|
|
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)}>
|
|
{/* <div className='flex flex-row items-center justify-start min-h-[4.5rem]'>
|
|
{
|
|
showLanding && !position &&
|
|
<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={() => {
|
|
if (setOpen) {
|
|
setOpen(false)
|
|
}
|
|
}}
|
|
onCancel={() => {
|
|
q = ""
|
|
// const url = `/chat/${id}`
|
|
router.prefetch(path, {
|
|
kind: PrefetchKind.FULL
|
|
})
|
|
|
|
history.pushState({}, '', path)
|
|
history.go(1)
|
|
|
|
setMessages([])
|
|
if (setOpen) {
|
|
setOpen(false)
|
|
}
|
|
}}
|
|
footer=""
|
|
// width={"50%"}
|
|
|
|
// wrapClassName={cn('min-w-[95%] lg:min-w-[700px] p-[20px]', classNameModal)}
|
|
className={cn('min-w-[95%] lg:min-w-[700px] p-[20px]', classNameModal)}
|
|
>
|
|
|
|
<div className="mx-auto sm:max-w-2xl min-h-[500px] mb-[3rem] lg:mb-[5rem] rounded bg-[#fff]">
|
|
|
|
<FadeIn >
|
|
{/* {showLanding && <Landing disableAnimations={isChatPage} />} */}
|
|
{showLanding && <h3 className='text-left sm:px-4 text-2xl font-bold py-[1.2rem]'>Describe your intention</h3>}
|
|
|
|
<div className='flex flex-row items-center justify-around bg-[#F5F5F5] text-[#666666] mb-[2rem]'>
|
|
<Link href="/xauth/login" >
|
|
<Button type="text" className="h-8 " >Login</Button>
|
|
</Link>
|
|
<Link href="/xauth/login">
|
|
<Button type="text" className="h-8" >Signup</Button>
|
|
</Link>
|
|
<Link href="/xauth/login">
|
|
<Button type="text" className="h-8" >Download</Button>
|
|
</Link>
|
|
</div>
|
|
</FadeIn>
|
|
|
|
{/* <ChatList messages={messages} avatarUrl={avatarUrl} /> */}
|
|
|
|
<div className="relative mx-auto px-2 sm:px-4">
|
|
{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',
|
|
action: "chat",
|
|
type: 'text',
|
|
},
|
|
{ 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 md:ml-0"
|
|
{...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 md:ml-0')}
|
|
{...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>
|
|
)
|
|
}
|