|
"use client" |
|
|
|
import * as React from "react" |
|
|
|
import { LoadingButton } from "@/components/Button/LoadingButton" |
|
import { |
|
setAgentConnected, |
|
setMobileActiveTab, |
|
setGlobalSettingsDialog, |
|
} from "@/store/reducers/global" |
|
import { |
|
useAppDispatch, |
|
useAppSelector, |
|
apiPing, |
|
apiStartService, |
|
apiStopService, |
|
MOBILE_ACTIVE_TAB_MAP, |
|
EMobileActiveTab, |
|
type StartRequestConfig, |
|
} from "@/common" |
|
import { toast } from "sonner" |
|
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs" |
|
import { cn } from "@/lib/utils" |
|
import SettingsDialog, { |
|
isCozeGraph, |
|
cozeSettingsFormSchema, |
|
isDifyGraph, |
|
difySettingsFormSchema, |
|
} from "@/components/Dialog/Settings" |
|
|
|
let intervalId: NodeJS.Timeout | null = null |
|
|
|
export default function Action(props: { className?: string }) { |
|
const { className } = props |
|
const dispatch = useAppDispatch() |
|
const agentConnected = useAppSelector((state) => state.global.agentConnected) |
|
const channel = useAppSelector((state) => state.global.options.channel) |
|
const userId = useAppSelector((state) => state.global.options.userId) |
|
const language = useAppSelector((state) => state.global.language) |
|
const voiceType = useAppSelector((state) => state.global.voiceType) |
|
const graphName = useAppSelector((state) => state.global.graphName) |
|
const agentSettings = useAppSelector((state) => state.global.agentSettings) |
|
const cozeSettings = useAppSelector((state) => state.global.cozeSettings) |
|
const difySettings = useAppSelector((state) => state.global.difySettings) |
|
const mobileActiveTab = useAppSelector( |
|
(state) => state.global.mobileActiveTab, |
|
) |
|
|
|
const [loading, setLoading] = React.useState(false) |
|
|
|
React.useEffect(() => { |
|
if (channel) { |
|
checkAgentConnected() |
|
} |
|
}, [channel]) |
|
|
|
const checkAgentConnected = async () => { |
|
const res: any = await apiPing(channel) |
|
if (res?.code == 0) { |
|
dispatch(setAgentConnected(true)) |
|
} |
|
} |
|
|
|
const onClickConnect = async () => { |
|
if (loading) { |
|
return |
|
} |
|
setLoading(true) |
|
try { |
|
if (agentConnected) { |
|
|
|
await apiStopService(channel) |
|
dispatch(setAgentConnected(false)) |
|
toast.success("Agent disconnected") |
|
stopPing() |
|
} else { |
|
|
|
|
|
const startServicePayload: StartRequestConfig = { |
|
channel, |
|
userId, |
|
graphName, |
|
language, |
|
voiceType, |
|
greeting: agentSettings.greeting, |
|
prompt: agentSettings.prompt, |
|
} |
|
|
|
if (isCozeGraph(graphName)) { |
|
|
|
const cozeSettingsResult = |
|
cozeSettingsFormSchema.safeParse(cozeSettings) |
|
if (!cozeSettingsResult.success) { |
|
dispatch( |
|
setGlobalSettingsDialog({ |
|
open: true, |
|
tab: "coze", |
|
}), |
|
) |
|
throw new Error( |
|
"Invalid Coze settings. Please check your settings.", |
|
) |
|
} |
|
startServicePayload.coze_token = cozeSettingsResult.data.token |
|
startServicePayload.coze_bot_id = cozeSettingsResult.data.bot_id |
|
startServicePayload.coze_base_url = cozeSettingsResult.data.base_url |
|
} else if (isDifyGraph(graphName)) { |
|
const difySettingsResult = difySettingsFormSchema.safeParse(difySettings) |
|
if (!difySettingsResult.success) { |
|
dispatch( |
|
setGlobalSettingsDialog({ |
|
open: true, |
|
tab: "dify", |
|
}), |
|
) |
|
throw new Error( |
|
"Invalid Dify settings. Please check your settings.", |
|
) |
|
} |
|
startServicePayload.dify_api_key = difySettingsResult.data.api_key |
|
} |
|
|
|
const res = await apiStartService(startServicePayload) |
|
const { code, msg } = res || {} |
|
if (code != 0) { |
|
if (code == "10001") { |
|
toast.error( |
|
"The number of users experiencing the program simultaneously has exceeded the limit. Please try again later.", |
|
) |
|
} else { |
|
toast.error(`code:${code},msg:${msg}`) |
|
} |
|
throw new Error(msg) |
|
} |
|
dispatch(setAgentConnected(true)) |
|
toast.success("Agent connected") |
|
startPing() |
|
} |
|
} catch (error) { |
|
console.error(error) |
|
toast.error("Failed to connect/disconnect agent", { |
|
description: (error as Error)?.message, |
|
}) |
|
} finally { |
|
setLoading(false) |
|
} |
|
} |
|
|
|
const startPing = () => { |
|
if (intervalId) { |
|
stopPing() |
|
} |
|
intervalId = setInterval(() => { |
|
apiPing(channel) |
|
}, 3000) |
|
} |
|
|
|
const stopPing = () => { |
|
if (intervalId) { |
|
clearInterval(intervalId) |
|
intervalId = null |
|
} |
|
} |
|
|
|
const onChangeMobileActiveTab = (tab: string) => { |
|
dispatch(setMobileActiveTab(tab as EMobileActiveTab)) |
|
} |
|
|
|
return ( |
|
<> |
|
{/* Action Bar */} |
|
<div |
|
className={cn( |
|
"mx-2 mt-2 flex items-center justify-between rounded-t-lg bg-[#181a1d] p-2 md:m-2 md:rounded-lg", |
|
className, |
|
)} |
|
> |
|
{/* -- Description Part */} |
|
<div className="hidden md:block"> |
|
<span className="text-sm font-bold"></span> |
|
<span className="ml-2 text-xs text-muted-foreground"> |
|
</span> |
|
</div> |
|
|
|
<Tabs |
|
defaultValue={mobileActiveTab} |
|
className="w-[400px] md:hidden" |
|
onValueChange={onChangeMobileActiveTab} |
|
> |
|
<TabsList> |
|
{Object.values(EMobileActiveTab).map((tab) => ( |
|
<TabsTrigger key={tab} value={tab} className="w-24 text-sm"> |
|
{MOBILE_ACTIVE_TAB_MAP[tab]} |
|
</TabsTrigger> |
|
))} |
|
</TabsList> |
|
</Tabs> |
|
|
|
{/* -- Action Button */} |
|
<div className="ml-auto flex items-center gap-2"> |
|
<SettingsDialog /> |
|
<LoadingButton |
|
onClick={onClickConnect} |
|
variant={!agentConnected ? "default" : "destructive"} |
|
size="sm" |
|
className="w-fit min-w-24" |
|
loading={loading} |
|
svgProps={{ className: "h-4 w-4 text-muted-foreground" }} |
|
> |
|
{loading |
|
? "Connecting" |
|
: !agentConnected |
|
? "Connect" |
|
: "Disconnect"} |
|
</LoadingButton> |
|
</div> |
|
</div> |
|
</> |
|
) |
|
} |
|
|