diff --git a/demo/next-env.d.ts b/demo/next-env.d.ts index 4f11a03d..40c3d680 100644 --- a/demo/next-env.d.ts +++ b/demo/next-env.d.ts @@ -2,4 +2,4 @@ /// // NOTE: This file should not be edited -// see https://nextjs.org/docs/basic-features/typescript for more information. +// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information. diff --git a/demo/package.json b/demo/package.json index e8e63ef2..caa99567 100644 --- a/demo/package.json +++ b/demo/package.json @@ -6,16 +6,18 @@ "node": ">=20" }, "scripts": { - "dev": "next dev", + "dev": "next dev --turbopack", "build": "next build", "start": "next start", "lint": "next lint", "proto": "pbjs -t json-module -w commonjs -o src/protobuf/SttMessage.js src/protobuf/SttMessage.proto" }, "dependencies": { - "@ant-design/icons": "^5.3.7", + "@hookform/resolvers": "^3.9.1", "@radix-ui/react-avatar": "^1.1.1", + "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-icons": "^1.3.0", + "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-popover": "^1.1.2", "@radix-ui/react-select": "^2.1.2", "@radix-ui/react-slot": "^1.1.0", @@ -24,22 +26,23 @@ "@reduxjs/toolkit": "^2.2.3", "agora-rtc-sdk-ng": "^4.21.0", "agora-rtm": "^2.2.0", - "antd": "^5.21.4", "axios": "^1.7.7", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "lucide-react": "^0.453.0", - "next": "14.2.4", + "next": "^15.0.2", "next-themes": "^0.3.0", "protobufjs": "^7.2.5", "react": "^18", "react-colorful": "^5.6.1", "react-dom": "^18", + "react-hook-form": "^7.53.1", "react-redux": "^9.1.0", "redux": "^5.0.1", "sonner": "^1.5.0", "tailwind-merge": "^2.5.4", - "tailwindcss-animate": "^1.0.7" + "tailwindcss-animate": "^1.0.7", + "zod": "^3.23.8" }, "devDependencies": { "@minko-fe/postcss-pxtoviewport": "^1.3.2", @@ -50,7 +53,7 @@ "@types/react-redux": "^7.1.22", "autoprefixer": "^10.4.20", "eslint": "^8", - "eslint-config-next": "14.2.4", + "eslint-config-next": "^15.0.2", "postcss": "^8.4.47", "prettier": "^3.3.3", "prettier-plugin-tailwindcss": "^0.6.8", diff --git a/demo/src/app/home/page.tsx b/demo/src/app/home/page.tsx index 9599af16..2d51c29e 100644 --- a/demo/src/app/home/page.tsx +++ b/demo/src/app/home/page.tsx @@ -8,7 +8,6 @@ import Action from "@/components/Layout/Action" // import RTCCard from "@/components/Dynamic/RTCCard" // import ChatCard from "@/components/Chat/ChatCard" import { cn } from "@/lib/utils" -import FormModal from "@/components/settings" const DynamicRTCCard = dynamic(() => import("@/components/Dynamic/RTCCard"), { ssr: false, @@ -46,7 +45,6 @@ export default function Home() { )} /> - ) diff --git a/demo/src/app/index.module.scss b/demo/src/app/index.module.css similarity index 60% rename from demo/src/app/index.module.scss rename to demo/src/app/index.module.css index d55e6d9a..408f2d7b 100644 --- a/demo/src/app/index.module.scss +++ b/demo/src/app/index.module.css @@ -1,20 +1,4 @@ -@function multiple-box-shadow($n, $width, $height) { - $value: "#{random() * $width} #{random() * $height} #FFF"; - - @for $i from 2 through $n { - $value: "#{$value}, #{random() * $width} #{random() * $height} #FFF"; - } - - @return unquote($value); -} - @media (max-width: 1400px) { - $width: 1500px; - $height: 1500px; - $shadows-small: multiple-box-shadow(700, $width, $height); - $shadows-medium: multiple-box-shadow(200, $width, $height); - $shadows-big: multiple-box-shadow(100, $width, $height); - .login { position: absolute; left: 0; @@ -31,14 +15,18 @@ width: 1px; height: 1px; background: transparent; - box-shadow: $shadows-small; + box-shadow: + 145px 234px #fff, + 876px 543px #fff; animation: animStar 50s linear infinite; } .starts2 { width: 2px; height: 2px; - box-shadow: $shadows-medium; + box-shadow: + 445px 234px #fff, + 276px 943px #fff; animation: animStar 100s linear infinite; } @@ -46,18 +34,14 @@ width: 3px; height: 3px; background: transparent; - box-shadow: $shadows-big; + box-shadow: + 745px 834px #fff, + 176px 243px #fff; animation: animStar 150s linear infinite; } } @media (min-width: 1400px) { - $width: 150vw; - $height: 150vh; - $shadows-small: multiple-box-shadow(700, $width, $height); - $shadows-medium: multiple-box-shadow(200, $width, $height); - $shadows-big: multiple-box-shadow(100, $width, $height); - .login { position: absolute; left: 0; @@ -74,14 +58,18 @@ width: 1px; height: 1px; background: transparent; - box-shadow: $shadows-small; + box-shadow: + 45vw 34vh #fff, + 76vw 43vh #fff; animation: animStar 50s linear infinite; } .starts2 { width: 2px; height: 2px; - box-shadow: $shadows-medium; + box-shadow: + 145vw 134vh #fff, + 76vw 143vh #fff; animation: animStar 100s linear infinite; } @@ -89,7 +77,9 @@ width: 3px; height: 3px; background: transparent; - box-shadow: $shadows-big; + box-shadow: + 45vw 134vh #fff, + 176vw 43vh #fff; animation: animStar 150s linear infinite; } } diff --git a/demo/src/app/layout.tsx b/demo/src/app/layout.tsx index 122b68b6..c9fec642 100644 --- a/demo/src/app/layout.tsx +++ b/demo/src/app/layout.tsx @@ -1,4 +1,3 @@ -import { ConfigProvider } from "antd" import { StoreProvider } from "@/store" import type { Metadata, Viewport } from "next" import "./global.css" @@ -31,7 +30,7 @@ export default function RootLayout({ return ( - - {children} - + > */} + {children} + {/* */} diff --git a/demo/src/app/page.tsx b/demo/src/app/page.tsx index 771958bf..1fcb2f08 100644 --- a/demo/src/app/page.tsx +++ b/demo/src/app/page.tsx @@ -1,8 +1,7 @@ -import LoginCard from "@/components/loginCard" -import styles from "./index.module.scss" +import LoginCard from "@/components/Card/Login" +import styles from "./index.module.css" export default function Login() { - return (
@@ -10,5 +9,5 @@ export default function Login() {
- ); + ) } diff --git a/demo/src/common/hooks.ts b/demo/src/common/hooks.ts index 9759fa29..8bb87c53 100644 --- a/demo/src/common/hooks.ts +++ b/demo/src/common/hooks.ts @@ -5,9 +5,6 @@ import { normalizeFrequencies } from "./utils" import { useState, useEffect, useMemo, useRef } from "react" import type { AppDispatch, AppStore, RootState } from "../store" import { useDispatch, useSelector, useStore } from "react-redux" -import { Grid } from "antd" - -const { useBreakpoint } = Grid; export const useAppDispatch = useDispatch.withTypes() export const useAppSelector = useSelector.withTypes() @@ -17,60 +14,60 @@ export const useMultibandTrackVolume = ( track?: IMicrophoneAudioTrack | MediaStreamTrack, bands: number = 5, loPass: number = 100, - hiPass: number = 600 + hiPass: number = 600, ) => { - const [frequencyBands, setFrequencyBands] = useState([]); + const [frequencyBands, setFrequencyBands] = useState([]) useEffect(() => { if (!track) { return setFrequencyBands(new Array(bands).fill(new Float32Array(0))) } - const ctx = new AudioContext(); - let finTrack = track instanceof MediaStreamTrack ? track : track.getMediaStreamTrack() - const mediaStream = new MediaStream([finTrack]); - const source = ctx.createMediaStreamSource(mediaStream); - const analyser = ctx.createAnalyser(); + const ctx = new AudioContext() + let finTrack = + track instanceof MediaStreamTrack ? track : track.getMediaStreamTrack() + const mediaStream = new MediaStream([finTrack]) + const source = ctx.createMediaStreamSource(mediaStream) + const analyser = ctx.createAnalyser() analyser.fftSize = 2048 - source.connect(analyser); + source.connect(analyser) - const bufferLength = analyser.frequencyBinCount; - const dataArray = new Float32Array(bufferLength); + const bufferLength = analyser.frequencyBinCount + const dataArray = new Float32Array(bufferLength) const updateVolume = () => { - analyser.getFloatFrequencyData(dataArray); - let frequencies: Float32Array = new Float32Array(dataArray.length); + analyser.getFloatFrequencyData(dataArray) + let frequencies: Float32Array = new Float32Array(dataArray.length) for (let i = 0; i < dataArray.length; i++) { - frequencies[i] = dataArray[i]; + frequencies[i] = dataArray[i] } - frequencies = frequencies.slice(loPass, hiPass); + frequencies = frequencies.slice(loPass, hiPass) - const normalizedFrequencies = normalizeFrequencies(frequencies); - const chunkSize = Math.ceil(normalizedFrequencies.length / bands); - const chunks: Float32Array[] = []; + const normalizedFrequencies = normalizeFrequencies(frequencies) + const chunkSize = Math.ceil(normalizedFrequencies.length / bands) + const chunks: Float32Array[] = [] for (let i = 0; i < bands; i++) { chunks.push( - normalizedFrequencies.slice(i * chunkSize, (i + 1) * chunkSize) - ); + normalizedFrequencies.slice(i * chunkSize, (i + 1) * chunkSize), + ) } - setFrequencyBands(chunks); - }; + setFrequencyBands(chunks) + } - const interval = setInterval(updateVolume, 10); + const interval = setInterval(updateVolume, 10) return () => { - source.disconnect(); - clearInterval(interval); - }; - }, [track, loPass, hiPass, bands]); + source.disconnect() + clearInterval(interval) + } + }, [track, loPass, hiPass, bands]) - return frequencyBands; -}; + return frequencyBands +} export const useAutoScroll = (ref: React.RefObject) => { - const callback: MutationCallback = (mutationList, observer) => { mutationList.forEach((mutation) => { switch (mutation.type) { @@ -78,54 +75,52 @@ export const useAutoScroll = (ref: React.RefObject) => { if (!ref.current) { return } - ref.current.scrollTop = ref.current.scrollHeight; - break; + ref.current.scrollTop = ref.current.scrollHeight + break } }) } useEffect(() => { if (!ref.current) { - return; + return } - const observer = new MutationObserver(callback); + const observer = new MutationObserver(callback) observer.observe(ref.current, { childList: true, - subtree: true - }); + subtree: true, + }) return () => { - observer.disconnect(); - }; - }, [ref]); + observer.disconnect() + } + }, [ref]) } -export const useSmallScreen = () => { - const screens = useBreakpoint(); +// export const useSmallScreen = () => { +// const screens = useBreakpoint(); - const xs = useMemo(() => { - return !screens.sm && screens.xs - }, [screens]) +// const xs = useMemo(() => { +// return !screens.sm && screens.xs +// }, [screens]) - const sm = useMemo(() => { - return !screens.md && screens.sm - }, [screens]) +// const sm = useMemo(() => { +// return !screens.md && screens.sm +// }, [screens]) - return { - xs, - sm, - isSmallScreen: xs || sm - } -} +// return { +// xs, +// sm, +// isSmallScreen: xs || sm +// } +// } export const usePrevious = (value: any) => { - const ref = useRef(); + const ref = useRef() useEffect(() => { - ref.current = value; - }, [value]); - - return ref.current; -}; - + ref.current = value + }, [value]) + return ref.current +} diff --git a/demo/src/components/Agent/Camera.tsx b/demo/src/components/Agent/Camera.tsx index 436a0135..68ead80b 100644 --- a/demo/src/components/Agent/Camera.tsx +++ b/demo/src/components/Agent/Camera.tsx @@ -5,7 +5,7 @@ import * as React from "react" import { CamIconByStatus } from "@/components/Icon" import AgoraRTC, { ICameraVideoTrack } from "agora-rtc-sdk-ng" // import { LocalStreamPlayer } from "../streamPlayer" -import { useSmallScreen } from "@/common" +// import { useSmallScreen } from "@/common" import { CommonDeviceWrapper, TDeviceSelectItem, diff --git a/demo/src/components/loginCard/index.tsx b/demo/src/components/Card/Login.tsx similarity index 100% rename from demo/src/components/loginCard/index.tsx rename to demo/src/components/Card/Login.tsx diff --git a/demo/src/components/Chat/ChatCard.tsx b/demo/src/components/Chat/ChatCard.tsx index 5f374096..cdf93d1e 100644 --- a/demo/src/components/Chat/ChatCard.tsx +++ b/demo/src/components/Chat/ChatCard.tsx @@ -3,7 +3,7 @@ import * as React from "react" import { cn } from "@/lib/utils" import { LanguageSelect, GraphSelect } from "@/components/Chat/ChatCfgSelect" -import PdfSelect from "@/components/pdfSelect" +import PdfSelect from "@/components/Chat/PdfSelect" import { useAppDispatch, useAppSelector, isRagGraph } from "@/common" import { setRtmConnected, addChatItem } from "@/store/reducers/global" import MessageList from "@/components/Chat/MessageList" diff --git a/demo/src/components/Chat/PdfSelect.tsx b/demo/src/components/Chat/PdfSelect.tsx new file mode 100644 index 00000000..c9959351 --- /dev/null +++ b/demo/src/components/Chat/PdfSelect.tsx @@ -0,0 +1,191 @@ +"use client" + +import * as React from "react" +import { Button } from "@/components/ui/button" +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog" +import { Input } from "@/components/ui/input" +import { Label } from "@/components/ui/label" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" +import { FileTextIcon } from "lucide-react" + +import { OptionType, IPdfData } from "@/types" +import { + apiGetDocumentList, + apiUpdateDocument, + useAppSelector, + genUUID, +} from "@/common" +import { toast } from "sonner" + +export default function PdfSelect() { + const options = useAppSelector((state) => state.global.options) + const { channel, userId } = options + const [pdfOptions, setPdfOptions] = React.useState([]) + const [selectedPdf, setSelectedPdf] = React.useState("") + const agentConnected = useAppSelector((state) => state.global.agentConnected) + + React.useEffect(() => { + if (agentConnected) { + getPDFOptions() + } + }, [agentConnected]) + + const getPDFOptions = async () => { + const res = await apiGetDocumentList() + setPdfOptions( + res.data.map((item: any) => { + return { + value: item.collection, + label: item.file_name, + } + }), + ) + setSelectedPdf("") + } + + const onUploadSuccess = (data: IPdfData) => { + setPdfOptions([ + ...pdfOptions, + { + value: data.collection, + label: data.fileName, + }, + ]) + setSelectedPdf(data.collection) + } + + const onSelectPdf = async (val: string) => { + const item = pdfOptions.find((item) => item.value === val) + if (!item) { + // return message.error("Please select a PDF file") + return + } + setSelectedPdf(val) + await apiUpdateDocument({ + collection: val, + fileName: item.label, + channel, + }) + } + + return ( + <> + + + + + + + Upload & Select PDF + + +
+ +
+
+
+ + ) +} + +export function UploadPdf({ + onSuccess, +}: { + onSuccess?: (data: IPdfData) => void +}) { + const agentConnected = useAppSelector((state) => state.global.agentConnected) + const options = useAppSelector((state) => state.global.options) + const { channel, userId } = options + const [uploading, setUploading] = React.useState(false) + + const handleUpload = async (e: React.ChangeEvent) => { + if (!agentConnected) { + toast.error("Please connect to agent first") + return + } + + const file = e.target.files?.[0] + if (!file) return + + setUploading(true) + + const formData = new FormData() + formData.append("file", file) + formData.append("channel_name", channel) + formData.append("uid", String(userId)) + formData.append("request_id", genUUID()) + + try { + const response = await fetch("/api/vector/document/upload", { + method: "POST", + body: formData, + }) + const data = await response.json() + + if (data.code === "0") { + toast.success(`Upload ${file.name} success`) + const { collection, file_name } = data.data + onSuccess?.({ + fileName: file_name, + collection, + }) + } else { + toast.info(data.msg) + } + } catch (err) { + toast.error(`Upload ${file.name} failed`) + } finally { + setUploading(false) + } + } + + return ( +
+ +
+ ) +} diff --git a/demo/src/components/Dialog/Settings.tsx b/demo/src/components/Dialog/Settings.tsx new file mode 100644 index 00000000..78c02249 --- /dev/null +++ b/demo/src/components/Dialog/Settings.tsx @@ -0,0 +1,116 @@ +"use client" + +import * as React from "react" +import { Button } from "@/components/ui/button" +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog" +import { Input } from "@/components/ui/input" +import { Label } from "@/components/ui/label" +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form" +import { Textarea } from "@/components/ui/textarea" +import { SettingsIcon } from "lucide-react" +import { zodResolver } from "@hookform/resolvers/zod" +import { useForm } from "react-hook-form" +import { z } from "zod" + +import { useAppDispatch, useAppSelector } from "@/common" +import { setAgentSettings } from "@/store/reducers/global" + +const formSchema = z.object({ + greeting: z.string(), + prompt: z.string(), +}) + +export default function SettingsDialog() { + const [open, setOpen] = React.useState(false) + + const dispatch = useAppDispatch() + const agentSettings = useAppSelector((state) => state.global.agentSettings) + + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + greeting: agentSettings.greeting, + prompt: agentSettings.prompt, + }, + }) + + function onSubmit(values: z.infer) { + console.log("Form Values:", values) + dispatch(setAgentSettings(values)) + setOpen(false) + } + + return ( + + + + + + + Settings + + +
+ + ( + + Greeting + +