diff --git a/public/icons/receive-time.png b/public/icons/receive-time.png new file mode 100644 index 0000000..8dd2f82 Binary files /dev/null and b/public/icons/receive-time.png differ diff --git a/public/icons/receive.png b/public/icons/receive.png new file mode 100644 index 0000000..7c3d231 Binary files /dev/null and b/public/icons/receive.png differ diff --git a/src/app/MainArea/Antechamber/index.tsx b/src/app/MainArea/Antechamber/index.tsx index 72c5648..36c99db 100644 --- a/src/app/MainArea/Antechamber/index.tsx +++ b/src/app/MainArea/Antechamber/index.tsx @@ -5,45 +5,45 @@ import AntechamberScore from "@/components/AntechamberScore"; import { useContext, useEffect, useState } from "react"; import { RealtimeClientContext } from "@/components/Provider/RealtimeClientProvider"; import { useSearchParams } from "react-router-dom"; -import { fetchUserToken } from "@/apis/user"; +import { fetchUserToken } from "@/apis/user"; import { useToast } from "@/hooks/use-toast"; import { useAbortController } from "@/hooks/useAbortController"; import AntechamberFile from "@/components/AntechamberFile"; import AntechamberReport from "@/components/AntechamberReport"; import AntechamberWishList from "@/components/AntechamberWishList"; import { ReportContext } from "@/components/Provider/ReportResolveProvider"; +import ReceiveTime from "/icons/receive-time.png"; +import { ReceiveDialog } from "@/components/ReceiveDialog"; export default function Antechamber() { - - const { handleConnect} = useContext(RealtimeClientContext); + const { handleConnect } = useContext(RealtimeClientContext); const { hasHandledReport } = useContext(ReportContext); - const [searchParams] = useSearchParams(); - const [disable,setDisable] = useState(true); - const [isLoading,setIsLoading] = useState(false); + const [disable, setDisable] = useState(true); + const [isLoading, setIsLoading] = useState(false); + + const token = searchParams.get("token") || ""; - const token = searchParams.get("token") || ''; - const { toast } = useToast(); const { getSignal } = useAbortController(); const getUserToken = async () => { try { - const { result, message } = await fetchUserToken({ - options: { + const { result, message } = await fetchUserToken({ + options: { signal: getSignal(), headers: { - "Authorization": `Bearer ${encodeURIComponent(token)}` - } - } + Authorization: `Bearer ${encodeURIComponent(token)}`, + }, + }, }); if (message) { console.log(message); } else { - const _result = result as {isExpired:boolean;msg:string}; + const _result = result as { isExpired: boolean; msg: string }; setDisable(!_result.isExpired); - if(!_result.isExpired &&_result.msg){ + if (!_result.isExpired && _result.msg) { toast({ title: _result.msg, description: "请重新登录", @@ -51,42 +51,60 @@ export default function Antechamber() { } } } catch (error: any) { - if (error.name !== 'AbortError') { - console.error('获取用户令牌失败:', error); + if (error.name !== "AbortError") { + console.error("获取用户令牌失败:", error); } } }; - useEffect(() => { getUserToken(); }, [token]); - - const toRoom = (params:{initMessage?:string,fileUrl?:string}) => { - if(!hasHandledReport && (disable || isLoading)){ + const toRoom = (params: { initMessage?: string; fileUrl?: string }) => { + if (!hasHandledReport && (disable || isLoading)) { return; } - setIsLoading(true) + setIsLoading(true); handleConnect(params).then(() => { setIsLoading(false); }); }; - + + const [isReceiveDialogOpen, setIsReceiveDialogOpen] = useState(true); + return (
- + toRoom({})} /> - { - isLoading ?
- loading - 加载中 -
: <> - } + receive-item setIsReceiveDialogOpen(true)} + /> + {isLoading ? ( +
+
+ loading + 加载中 +
+
+ ) : ( + <> + )} +
); } diff --git a/src/app/MainArea/index.tsx b/src/app/MainArea/index.tsx index d86f75d..08c39b0 100644 --- a/src/app/MainArea/index.tsx +++ b/src/app/MainArea/index.tsx @@ -8,6 +8,7 @@ import { import { ReportProvider } from "@/components/Provider/ReportResolveProvider"; // import { RealtimeUtils } from "@coze/realtime-api"; import { useLocation } from "react-router-dom"; +import { CountdownProvider } from "@/components/Provider/CountdownProvider"; function MainContent() { const { isConnected, handleDisconnect } = useContext(RealtimeClientContext); @@ -28,9 +29,14 @@ function MainContent() { }, [location.pathname]); return ( - - {isConnected ? : } - + + + <> + {/* {isConnected ? : } */} + {isConnected ? : } + + + ); } diff --git a/src/components/AntechamberHeader/index.tsx b/src/components/AntechamberHeader/index.tsx index eba34b6..ed8ade8 100644 --- a/src/components/AntechamberHeader/index.tsx +++ b/src/components/AntechamberHeader/index.tsx @@ -8,7 +8,7 @@ import styles from "./index.module.css"; import { fetchQuestions } from "@/apis/questions"; import { useAbortController } from "@/hooks/useAbortController"; import { RealtimeClientContext } from "../Provider/RealtimeClientProvider"; - +import Countdown from "../Countdown"; export default function HeaderGroup() { const [isRotating, setIsRotating] = useState(false); @@ -69,6 +69,7 @@ export default function HeaderGroup() { return (
+
hello
Hey,我是您的六纬AI小助手
diff --git a/src/components/Countdown/index.tsx b/src/components/Countdown/index.tsx new file mode 100644 index 0000000..decaa37 --- /dev/null +++ b/src/components/Countdown/index.tsx @@ -0,0 +1,45 @@ +import { useEffect } from 'react'; +import { useCountdown } from '../Provider/CountdownProvider'; + +/** + * 将秒数转换为"分钟秒"格式 + * @param totalSeconds 总秒数 + * @returns 格式化后的字符串,如"10分00秒" + */ +const formatTime = (totalSeconds: number): string => { + const minutes = Math.floor(totalSeconds / 60); + const seconds = totalSeconds % 60; + + // 确保秒数显示为两位数 + const formattedSeconds = seconds < 10 ? `0${seconds}` : `${seconds}`; + + return `${minutes}分${formattedSeconds}秒`; +}; + +interface CountdownProps { + +} + +export default function Countdown({ }: CountdownProps) { + const { countdown, setCountdown } = useCountdown(); + + useEffect(() => { + // 只有当秒数大于0时才启动倒计时 + if (countdown <= 0) return; + + const timer = setInterval(() => { + setCountdown(countdown - 1) + }, 1000); + + + // 清理定时器 + return () => clearInterval(timer); + }, [countdown]); + + return ( +
+
+
剩余可用:{formatTime(countdown)}
+
+ ); +} \ No newline at end of file diff --git a/src/components/Provider/CountdownProvider.tsx b/src/components/Provider/CountdownProvider.tsx new file mode 100644 index 0000000..d696a71 --- /dev/null +++ b/src/components/Provider/CountdownProvider.tsx @@ -0,0 +1,25 @@ +import { createContext, useContext, useState } from "react" + +export const CountdownContext = createContext<{ + countdown: number; + setCountdown: (countdown: number) => void; +}>({ + countdown: 0, + setCountdown: () => {} +}) + +export const useCountdown = () => { + const ctx = useContext(CountdownContext); + if (!ctx) throw new Error("useCountdown 必须在 CountdownProvider 内部使用"); + return ctx; +} + +export const CountdownProvider = ({ children }: { children: React.ReactNode }) => { + const [countdown, setCountdown] = useState(0); + + return ( + + {children} + + ) +} \ No newline at end of file diff --git a/src/components/ReceiveDialog/index.css b/src/components/ReceiveDialog/index.css new file mode 100644 index 0000000..5310d3b --- /dev/null +++ b/src/components/ReceiveDialog/index.css @@ -0,0 +1,3 @@ +.custom-bg { + background: linear-gradient(0deg, #FFFFFF 30%,transparent 100%); +} \ No newline at end of file diff --git a/src/components/ReceiveDialog/index.tsx b/src/components/ReceiveDialog/index.tsx new file mode 100644 index 0000000..e59db59 --- /dev/null +++ b/src/components/ReceiveDialog/index.tsx @@ -0,0 +1,56 @@ +import { + Dialog, + DialogContent, + DialogTitle, + DialogDescription, + DialogClose +} from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { X } from "lucide-react"; +import "./index.css"; + +export const ReceiveDialog = ({ + isOpen = false, + onOpenChange, + onConfirm +}: { + isOpen?: boolean; + onOpenChange?: (open: boolean) => void; + onConfirm?: () => void; +}) => { + return ( + + + 接收新消息 + 您有一条新的消息,是否立即查看? + + + onOpenChange?.(false)}>关闭 + +
+ 接收 +
+

AI通话时长送你啦~

+

志愿怎么选?分数能上哪?六维AI填报师来帮你解答~

+
+ + +
+
+
+
+
+ ); +}; \ No newline at end of file diff --git a/src/components/RoomConversation/index.tsx b/src/components/RoomConversation/index.tsx index 8ebc554..f2923bd 100644 --- a/src/components/RoomConversation/index.tsx +++ b/src/components/RoomConversation/index.tsx @@ -96,6 +96,10 @@ export default function RoomConversation() {
AI也会犯错,请考虑核实重要信息。
+
+ 您的剩余时间不足3分钟,请续费畅聊生涯~ + +
); } diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx new file mode 100644 index 0000000..d40c864 --- /dev/null +++ b/src/components/ui/dialog.tsx @@ -0,0 +1,119 @@ +import * as React from "react" +import * as DialogPrimitive from "@radix-ui/react-dialog" +import { cn } from "@/lib/utils" +import { Cross2Icon } from "@radix-ui/react-icons" + +const Dialog = DialogPrimitive.Root + +const DialogTrigger = DialogPrimitive.Trigger + +const DialogPortal = DialogPrimitive.Portal + +const DialogClose = DialogPrimitive.Close + +const DialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName + +const DialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + {children} + + + Close + + + +)) +DialogContent.displayName = DialogPrimitive.Content.displayName + +const DialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DialogHeader.displayName = "DialogHeader" + +const DialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +DialogFooter.displayName = "DialogFooter" + +const DialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogTitle.displayName = DialogPrimitive.Title.displayName + +const DialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +DialogDescription.displayName = DialogPrimitive.Description.displayName + +export { + Dialog, + DialogPortal, + DialogOverlay, + DialogTrigger, + DialogClose, + DialogContent, + DialogHeader, + DialogFooter, + DialogTitle, + DialogDescription, +}