feat: AI对话期间静止打断
parent
ca04c56af2
commit
16a39a87cb
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1,2 @@
|
||||||
|
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/index-CR5a_mms.js","assets/.pnpm-CAIuqsZ0.js","assets/use-toast-DO4tfD4I.js","assets/index-PFueeGmc.css"])))=>i.map(i=>d[i]);
|
||||||
|
import{_ as t}from"./index-DOYUXUr3.js";import{j as r,r as a}from"./.pnpm-CAIuqsZ0.js";const o=a.lazy(()=>t(()=>import("./index-CR5a_mms.js"),__vite__mapDeps([0,1,2,3])));function i(){return r.jsx("div",{className:"h-full bg-[#F4F6FA]",children:r.jsx(o,{})})}export{i as default};
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/index-DQMJjaAj.js","assets/.pnpm-BDdfG1pO.js","assets/index-PFueeGmc.css"])))=>i.map(i=>d[i]);
|
|
||||||
import{_ as t}from"./index-j0VtgfAk.js";import{j as r,r as a}from"./.pnpm-BDdfG1pO.js";const o=a.lazy(()=>t(()=>import("./index-DQMJjaAj.js"),__vite__mapDeps([0,1,2])));function i(){return r.jsx("div",{className:"h-full bg-[#F4F6FA]",children:r.jsx(o,{})})}export{i as default};
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
import{t as b,b as j,P as y,r as o,j as e,d,e as N,T as n,D as c,C as u,f as T,V as l,A as p,O as R}from"./.pnpm-CAIuqsZ0.js";import{u as V}from"./use-toast-DO4tfD4I.js";function r(...t){return b(j(t))}const C=y,f=o.forwardRef(({className:t,...a},s)=>e.jsx(l,{ref:s,className:r("fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]",t),...a}));f.displayName=l.displayName;const k=N("group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",{variants:{variant:{default:"border bg-background text-foreground",destructive:"destructive group border-destructive bg-destructive text-destructive-foreground"}},defaultVariants:{variant:"default"}}),m=o.forwardRef(({className:t,variant:a,...s},i)=>e.jsx(d,{ref:i,className:r(k({variant:a}),t),...s}));m.displayName=d.displayName;const A=o.forwardRef(({className:t,...a},s)=>e.jsx(p,{ref:s,className:r("inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium transition-colors hover:bg-secondary focus:outline-none focus:ring-1 focus:ring-ring disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive",t),...a}));A.displayName=p.displayName;const x=o.forwardRef(({className:t,...a},s)=>e.jsx(u,{ref:s,className:r("absolute right-1 top-1 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-1 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600",t),"toast-close":"",...a,children:e.jsx(T,{className:"h-4 w-4"})}));x.displayName=u.displayName;const v=o.forwardRef(({className:t,...a},s)=>e.jsx(n,{ref:s,className:r("text-sm font-semibold [&+div]:text-xs",t),...a}));v.displayName=n.displayName;const g=o.forwardRef(({className:t,...a},s)=>e.jsx(c,{ref:s,className:r("text-sm opacity-90",t),...a}));g.displayName=c.displayName;function D(){const{toasts:t}=V();return e.jsxs(C,{children:[t.map(function({id:a,title:s,description:i,action:h,...w}){return e.jsxs(m,{...w,children:[e.jsxs("div",{className:"grid gap-1",children:[s&&e.jsx(v,{children:s}),i&&e.jsx(g,{children:i})]}),h,e.jsx(x,{})]},a)}),e.jsx(f,{})]})}function L(){return e.jsx(e.Fragment,{children:e.jsxs("div",{className:r("h-screen bg-background font-sans antialiased"),children:[e.jsx(R,{}),e.jsx(D,{})]})})}export{L as default};
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
import{t as s,b as n,j as t,O as e}from"./.pnpm-BDdfG1pO.js";function r(...a){return s(n(a))}function c(){return t.jsx(t.Fragment,{children:t.jsx("div",{className:r("h-dvh bg-background font-sans antialiased"),children:t.jsx(e,{})})})}export{c as default};
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -1,2 +1,2 @@
|
||||||
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/App-Dh3vQfIK.js","assets/.pnpm-BDdfG1pO.js","assets/MainLayout-BWiadrJR.js"])))=>i.map(i=>d[i]);
|
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/App-BkgWMpGx.js","assets/.pnpm-CAIuqsZ0.js","assets/MainLayout-BMCWJnk-.js","assets/use-toast-DO4tfD4I.js"])))=>i.map(i=>d[i]);
|
||||||
var v=Object.defineProperty;var x=(s,t,n)=>t in s?v(s,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):s[t]=n;var m=(s,t,n)=>x(s,typeof t!="symbol"?t+"":t,n);import{r as d,c as P,j as a,a as _,R as L}from"./.pnpm-BDdfG1pO.js";(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const e of document.querySelectorAll('link[rel="modulepreload"]'))f(e);new MutationObserver(e=>{for(const o of e)if(o.type==="childList")for(const r of o.addedNodes)r.tagName==="LINK"&&r.rel==="modulepreload"&&f(r)}).observe(document,{childList:!0,subtree:!0});function n(e){const o={};return e.integrity&&(o.integrity=e.integrity),e.referrerPolicy&&(o.referrerPolicy=e.referrerPolicy),e.crossOrigin==="use-credentials"?o.credentials="include":e.crossOrigin==="anonymous"?o.credentials="omit":o.credentials="same-origin",o}function f(e){if(e.ep)return;e.ep=!0;const o=n(e);fetch(e.href,o)}})();const j="modulepreload",O=function(s){return"/"+s},h={},p=function(t,n,f){let e=Promise.resolve();if(n&&n.length>0){document.getElementsByTagName("link");const r=document.querySelector("meta[property=csp-nonce]"),i=(r==null?void 0:r.nonce)||(r==null?void 0:r.getAttribute("nonce"));e=Promise.allSettled(n.map(c=>{if(c=O(c),c in h)return;h[c]=!0;const u=c.endsWith(".css"),E=u?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${c}"]${E}`))return;const l=document.createElement("link");if(l.rel=u?"stylesheet":j,u||(l.as="script"),l.crossOrigin="",l.href=c,i&&l.setAttribute("nonce",i),document.head.appendChild(l),u)return new Promise((y,g)=>{l.addEventListener("load",y),l.addEventListener("error",()=>g(new Error(`Unable to preload CSS for ${c}`)))})}))}function o(r){const i=new Event("vite:preloadError",{cancelable:!0});if(i.payload=r,window.dispatchEvent(i),!i.defaultPrevented)throw r}return e.then(r=>{for(const i of r||[])i.status==="rejected"&&o(i.reason);return t().catch(o)})},R=d.lazy(()=>p(()=>import("./App-Dh3vQfIK.js"),__vite__mapDeps([0,1]))),S=d.lazy(()=>p(()=>import("./MainLayout-BWiadrJR.js"),__vite__mapDeps([2,1])));class w extends d.Component{constructor(){super(...arguments);m(this,"state",{hasError:!1})}static getDerivedStateFromError(n){return{hasError:!0}}render(){return this.state.hasError?a.jsx("h1",{children:"出错了,请稍后再试。"}):this.props.children}}const b=P([{path:"/",element:a.jsx(S,{}),errorElement:a.jsx(w,{children:a.jsx("div",{className:"flex-auto flex flex-col p-6",children:"出错了,请稍后再试。"})}),children:[{path:"/",element:a.jsx(R,{})}]}]);_(document.getElementById("root")).render(a.jsx(d.StrictMode,{children:a.jsx(L,{router:b})}));export{p as _};
|
var v=Object.defineProperty;var x=(s,t,n)=>t in s?v(s,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):s[t]=n;var m=(s,t,n)=>x(s,typeof t!="symbol"?t+"":t,n);import{r as d,c as P,j as a,a as _,R as L}from"./.pnpm-CAIuqsZ0.js";(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const e of document.querySelectorAll('link[rel="modulepreload"]'))f(e);new MutationObserver(e=>{for(const o of e)if(o.type==="childList")for(const r of o.addedNodes)r.tagName==="LINK"&&r.rel==="modulepreload"&&f(r)}).observe(document,{childList:!0,subtree:!0});function n(e){const o={};return e.integrity&&(o.integrity=e.integrity),e.referrerPolicy&&(o.referrerPolicy=e.referrerPolicy),e.crossOrigin==="use-credentials"?o.credentials="include":e.crossOrigin==="anonymous"?o.credentials="omit":o.credentials="same-origin",o}function f(e){if(e.ep)return;e.ep=!0;const o=n(e);fetch(e.href,o)}})();const j="modulepreload",O=function(s){return"/"+s},h={},p=function(t,n,f){let e=Promise.resolve();if(n&&n.length>0){document.getElementsByTagName("link");const r=document.querySelector("meta[property=csp-nonce]"),i=(r==null?void 0:r.nonce)||(r==null?void 0:r.getAttribute("nonce"));e=Promise.allSettled(n.map(c=>{if(c=O(c),c in h)return;h[c]=!0;const u=c.endsWith(".css"),E=u?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${c}"]${E}`))return;const l=document.createElement("link");if(l.rel=u?"stylesheet":j,u||(l.as="script"),l.crossOrigin="",l.href=c,i&&l.setAttribute("nonce",i),document.head.appendChild(l),u)return new Promise((y,g)=>{l.addEventListener("load",y),l.addEventListener("error",()=>g(new Error(`Unable to preload CSS for ${c}`)))})}))}function o(r){const i=new Event("vite:preloadError",{cancelable:!0});if(i.payload=r,window.dispatchEvent(i),!i.defaultPrevented)throw r}return e.then(r=>{for(const i of r||[])i.status==="rejected"&&o(i.reason);return t().catch(o)})},R=d.lazy(()=>p(()=>import("./App-BkgWMpGx.js"),__vite__mapDeps([0,1]))),S=d.lazy(()=>p(()=>import("./MainLayout-BMCWJnk-.js"),__vite__mapDeps([2,1,3])));class w extends d.Component{constructor(){super(...arguments);m(this,"state",{hasError:!1})}static getDerivedStateFromError(n){return{hasError:!0}}render(){return this.state.hasError?a.jsx("h1",{children:"出错了,请稍后再试。"}):this.props.children}}const b=P([{path:"/",element:a.jsx(S,{}),errorElement:a.jsx(w,{children:a.jsx("div",{className:"flex-auto flex flex-col p-6",children:"出错了,请稍后再试。"})}),children:[{path:"/",element:a.jsx(R,{})}]}]);_(document.getElementById("root")).render(a.jsx(d.StrictMode,{children:a.jsx(L,{router:b})}));export{p as _};
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -0,0 +1 @@
|
||||||
|
import{r as c}from"./.pnpm-CAIuqsZ0.js";const d=1,p=1e6;let i=0;function A(){return i=(i+1)%Number.MAX_SAFE_INTEGER,i.toString()}const a=new Map,S=t=>{if(a.has(t))return;const s=setTimeout(()=>{a.delete(t),n({type:"REMOVE_TOAST",toastId:t})},p);a.set(t,s)},f=(t,s)=>{switch(s.type){case"ADD_TOAST":return{...t,toasts:[s.toast,...t.toasts].slice(0,d)};case"UPDATE_TOAST":return{...t,toasts:t.toasts.map(e=>e.id===s.toast.id?{...e,...s.toast}:e)};case"DISMISS_TOAST":{const{toastId:e}=s;return e?S(e):t.toasts.forEach(o=>{S(o.id)}),{...t,toasts:t.toasts.map(o=>o.id===e||e===void 0?{...o,open:!1}:o)}}case"REMOVE_TOAST":return s.toastId===void 0?{...t,toasts:[]}:{...t,toasts:t.toasts.filter(e=>e.id!==s.toastId)}}},r=[];let T={toasts:[]};function n(t){T=f(T,t),r.forEach(s=>{s(T)})}function E({...t}){const s=A(),e=u=>n({type:"UPDATE_TOAST",toast:{...u,id:s}}),o=()=>n({type:"DISMISS_TOAST",toastId:s});return n({type:"ADD_TOAST",toast:{...t,id:s,open:!0,onOpenChange:u=>{u||o()}}}),{id:s,dismiss:o,update:e}}function _(){const[t,s]=c.useState(T);return c.useEffect(()=>(r.push(s),()=>{const e=r.indexOf(s);e>-1&&r.splice(e,1)}),[t]),{...t,toast:E,dismiss:e=>n({type:"DISMISS_TOAST",toastId:e})}}export{_ as u};
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
|
|
@ -18,14 +18,14 @@
|
||||||
|
|
||||||
<link rel="icon" href="https://api.static.ycymedu.com/src/images/home/app-logo.svg" />
|
<link rel="icon" href="https://api.static.ycymedu.com/src/images/home/app-logo.svg" />
|
||||||
<meta name="author" content="HideInMatrix" />
|
<meta name="author" content="HideInMatrix" />
|
||||||
<!-- <script src="https://unpkg.com/vconsole@latest/dist/vconsole.min.js"></script>
|
<script src="https://unpkg.com/vconsole@latest/dist/vconsole.min.js"></script>
|
||||||
<script>
|
<script>
|
||||||
// VConsole will be exported to `window.VConsole` by default.
|
// VConsole will be exported to `window.VConsole` by default.
|
||||||
var vConsole = new window.VConsole();
|
var vConsole = new window.VConsole();
|
||||||
</script> -->
|
</script>
|
||||||
<script type="module" crossorigin src="/assets/index-j0VtgfAk.js"></script>
|
<script type="module" crossorigin src="/assets/index-DOYUXUr3.js"></script>
|
||||||
<link rel="modulepreload" crossorigin href="/assets/.pnpm-BDdfG1pO.js">
|
<link rel="modulepreload" crossorigin href="/assets/.pnpm-CAIuqsZ0.js">
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-ChXXIJVe.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-DPqYJA2j.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,11 @@
|
||||||
"lucide-react": "^0.437.0",
|
"lucide-react": "^0.437.0",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
|
"react-markdown": "^10.1.0",
|
||||||
"react-router-dom": "^6.26.1",
|
"react-router-dom": "^6.26.1",
|
||||||
"react-sortablejs": "^6.1.4",
|
"react-sortablejs": "^6.1.4",
|
||||||
|
"react-syntax-highlighter": "^15.6.1",
|
||||||
|
"remark-gfm": "^4.0.1",
|
||||||
"sortablejs": "^1.15.3",
|
"sortablejs": "^1.15.3",
|
||||||
"tailwind-merge": "^2.5.2",
|
"tailwind-merge": "^2.5.2",
|
||||||
"tailwindcss-animate": "^1.0.7",
|
"tailwindcss-animate": "^1.0.7",
|
||||||
|
|
|
||||||
1040
pnpm-lock.yaml
1040
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
|
|
@ -2,18 +2,38 @@ import { getRequest } from "@/lib/customFetch";
|
||||||
|
|
||||||
export const fetchUserToken = async ({
|
export const fetchUserToken = async ({
|
||||||
options,
|
options,
|
||||||
}: {
|
}: {
|
||||||
options?: { signal?: AbortSignal,headers?:Record<string,string> };
|
options?: { signal?: AbortSignal; headers?: Record<string, string> };
|
||||||
}) => {
|
}) => {
|
||||||
const response = await getRequest(
|
const response = await getRequest(
|
||||||
"https://api.v3.ycymedu.com/api/sysOnlineUser/hasitexpired",
|
"https://api.v3.ycymedu.com/api/sysOnlineUser/hasitexpired",
|
||||||
{},
|
{},
|
||||||
options
|
options
|
||||||
);
|
);
|
||||||
|
|
||||||
if(response.code === 200){
|
if (response.code === 200) {
|
||||||
return {result:response.result}
|
return { result: response.result };
|
||||||
}else{
|
} else {
|
||||||
return {result:[],message:response.message}
|
return { result: [], message: response.message };
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const fetchReport = async ({
|
||||||
|
params,
|
||||||
|
options,
|
||||||
|
}: {
|
||||||
|
params: { Type: string; Id: string };
|
||||||
|
options?: { signal?: AbortSignal; headers?: Record<string, string> };
|
||||||
|
}) => {
|
||||||
|
const response = await getRequest(
|
||||||
|
"https://api.v3.ycymedu.com/api/busScale/GetBusAIReportKeyWord",
|
||||||
|
params,
|
||||||
|
options
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.code === 200) {
|
||||||
|
return { result: response.result };
|
||||||
|
} else {
|
||||||
|
return { result: [], message: response.message };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -5,44 +5,96 @@ import AntechamberScore from "@/components/AntechamberScore";
|
||||||
import { useContext, useEffect, useState } from "react";
|
import { useContext, useEffect, useState } from "react";
|
||||||
import { RealtimeClientContext } from "@/components/Provider/RealtimeClientProvider";
|
import { RealtimeClientContext } from "@/components/Provider/RealtimeClientProvider";
|
||||||
import { useSearchParams } from "react-router-dom";
|
import { useSearchParams } from "react-router-dom";
|
||||||
import { fetchUserToken } from "@/apis/user";
|
import { fetchReport, fetchUserToken } from "@/apis/user";
|
||||||
|
import { useToast } from "@/hooks/use-toast";
|
||||||
|
import { useAbortController } from "@/hooks/useAbortController";
|
||||||
|
import { ReportContext } from "@/components/Provider/ReportResolveProvider";
|
||||||
|
|
||||||
export default function Antechamber() {
|
export default function Antechamber() {
|
||||||
|
|
||||||
const { handleConnect } = useContext(RealtimeClientContext);
|
const { handleConnect } = useContext(RealtimeClientContext);
|
||||||
|
const { setHasHandledReport,hasHandledReport } = useContext(ReportContext);
|
||||||
|
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const [disable,setDisable] = useState(true);
|
const [disable,setDisable] = useState(true);
|
||||||
|
|
||||||
const token = searchParams.get("token") || '';
|
const token = searchParams.get("token") || '';
|
||||||
|
const reportId = searchParams.get("reportId") || '';
|
||||||
|
const reportType = searchParams.get("reportType") || '';
|
||||||
|
|
||||||
const getUserToken = async ({ signal }: { signal: AbortSignal }) => {
|
const { toast } = useToast();
|
||||||
const { result, message } = await fetchUserToken({ options: { signal,headers:{"Authorization":`Bearer ${token}`} } });
|
const { getSignal } = useAbortController();
|
||||||
|
|
||||||
|
const getUserToken = async () => {
|
||||||
|
try {
|
||||||
|
const { result, message } = await fetchUserToken({
|
||||||
|
options: {
|
||||||
|
signal: getSignal(),
|
||||||
|
headers: {"Authorization":`Bearer ${token}`}
|
||||||
|
}
|
||||||
|
});
|
||||||
if (message) {
|
if (message) {
|
||||||
console.log(message);
|
console.log(message);
|
||||||
} else {
|
} else {
|
||||||
const _result = result as {isExpired:boolean};
|
const _result = result as {isExpired:boolean;msg:string};
|
||||||
setDisable(!_result.isExpired);
|
setDisable(!_result.isExpired);
|
||||||
|
if(!_result.isExpired &&_result.msg){
|
||||||
|
toast({
|
||||||
|
title: _result.msg,
|
||||||
|
description: "请重新登录",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error.name !== 'AbortError') {
|
||||||
|
console.error('获取用户令牌失败:', error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(()=>{
|
const getReport = async () => {
|
||||||
const controller = new AbortController();
|
try {
|
||||||
const { signal } = controller;
|
const { result, message } = await fetchReport({
|
||||||
getUserToken({ signal });
|
params:{Type:reportType,Id:reportId},
|
||||||
},[token])
|
options: {
|
||||||
|
signal: getSignal(),
|
||||||
|
headers: {"Authorization":`Bearer ${token}`}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (message) {
|
||||||
|
console.log(message);
|
||||||
|
} else {
|
||||||
|
handleConnect(result as string);
|
||||||
|
setHasHandledReport(true)
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
if (error.name !== 'AbortError') {
|
||||||
|
console.error('获取报告失败:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const toRoom = () => {
|
useEffect(() => {
|
||||||
// if(disable){
|
getUserToken();
|
||||||
// return;
|
}, [token]);
|
||||||
// }
|
|
||||||
handleConnect();
|
useEffect(() => {
|
||||||
|
if(reportId && reportType && !hasHandledReport){
|
||||||
|
getReport();
|
||||||
|
}
|
||||||
|
}, [reportId, reportType,hasHandledReport]);
|
||||||
|
|
||||||
|
const toRoom = (initMessage?:string) => {
|
||||||
|
if(disable){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handleConnect(initMessage);
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center h-full">
|
<div className="flex flex-col items-center h-full">
|
||||||
<AntechamberHeader toRoom={toRoom} />
|
<AntechamberHeader toRoom={toRoom} />
|
||||||
<AntechamberScore toRoom={toRoom} />
|
<AntechamberScore toRoom={toRoom} />
|
||||||
<InvokeButton disable={disable} onClick={toRoom} />
|
<InvokeButton disable={disable} onClick={() => toRoom()} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import RoomController from "@/components/RoomController";
|
||||||
|
|
||||||
export default function Room() {
|
export default function Room() {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-full">
|
<div className="flex flex-col max-h-full h-full">
|
||||||
<RoomConversation />
|
<RoomConversation />
|
||||||
<RoomController />
|
<RoomController />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,32 @@
|
||||||
import Room from "./Room";
|
import Room from "./Room";
|
||||||
import Antechamber from "./Antechamber";
|
import Antechamber from "./Antechamber";
|
||||||
import { useContext } from "react";
|
import { useContext, useEffect } from "react";
|
||||||
import {
|
import {
|
||||||
RealtimeClientContext,
|
RealtimeClientContext,
|
||||||
RealtimeClientProvider,
|
RealtimeClientProvider,
|
||||||
} from "@/components/Provider/RealtimeClientProvider";
|
} from "@/components/Provider/RealtimeClientProvider";
|
||||||
|
import { ReportProvider } from "@/components/Provider/ReportResolveProvider";
|
||||||
|
import { RealtimeUtils } from "@coze/realtime-api";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function MainContent() {
|
function MainContent() {
|
||||||
const { isConnected } = useContext(RealtimeClientContext);
|
const { isConnected } = useContext(RealtimeClientContext);
|
||||||
return isConnected ? <Room /> : <Antechamber />;
|
|
||||||
|
const handlePromise = async() => {
|
||||||
|
await RealtimeUtils.checkDevicePermission(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
handlePromise();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ReportProvider>
|
||||||
|
{isConnected ? <Room /> : <Antechamber />}
|
||||||
|
</ReportProvider>
|
||||||
|
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function MainArea() {
|
export default function MainArea() {
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,14 @@
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
import { Outlet } from "react-router-dom";
|
import { Outlet } from "react-router-dom";
|
||||||
|
import { Toaster } from "@/components/ui/toaster"
|
||||||
|
|
||||||
export default function LocaleLayout() {
|
export default function LocaleLayout() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={cn("h-dvh bg-background font-sans antialiased")}>
|
<div className={cn("h-screen bg-background font-sans antialiased")}>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
|
<Toaster />
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { useState, useEffect, useContext } from "react";
|
import { useState, useEffect } from "react";
|
||||||
|
|
||||||
import HelloGIF from "/icons/hello.gif";
|
import HelloGIF from "/icons/hello.gif";
|
||||||
import WhatsThing from "/icons/whatsThing.png";
|
import WhatsThing from "/icons/whatsThing.png";
|
||||||
|
|
@ -6,10 +6,10 @@ import CircleIcon from "/icons/circle.png";
|
||||||
import RightIcon from "/icons/right.png";
|
import RightIcon from "/icons/right.png";
|
||||||
import styles from "./index.module.css";
|
import styles from "./index.module.css";
|
||||||
import { fetchQuestions } from "@/apis/questions";
|
import { fetchQuestions } from "@/apis/questions";
|
||||||
import { RealtimeClientContext } from "../Provider/RealtimeClientProvider";
|
import { useAbortController } from "@/hooks/useAbortController";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
toRoom: () =>void;
|
toRoom: (initMessage?:string) =>void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function HeaderGroup({ toRoom }: Props) {
|
export default function HeaderGroup({ toRoom }: Props) {
|
||||||
|
|
@ -17,7 +17,7 @@ export default function HeaderGroup({ toRoom }: Props) {
|
||||||
const [displayQuestions, setDisplayQuestions] = useState<string[]>([]);
|
const [displayQuestions, setDisplayQuestions] = useState<string[]>([]);
|
||||||
const [allQuestions, setAllQuestions] = useState<string[]>([]);
|
const [allQuestions, setAllQuestions] = useState<string[]>([]);
|
||||||
|
|
||||||
const { setInitMessage } = useContext(RealtimeClientContext);
|
const { getSignal } = useAbortController();
|
||||||
|
|
||||||
// 随机获取4个问题的函数
|
// 随机获取4个问题的函数
|
||||||
const getRandomQuestions = () => {
|
const getRandomQuestions = () => {
|
||||||
|
|
@ -33,8 +33,8 @@ export default function HeaderGroup({ toRoom }: Props) {
|
||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getQuestion = async ({ signal }: { signal: AbortSignal }) => {
|
const getQuestion = async () => {
|
||||||
const { result, message } = await fetchQuestions({ options: { signal } });
|
const { result, message } = await fetchQuestions({ options: { signal:getSignal() } });
|
||||||
if (message) {
|
if (message) {
|
||||||
console.log(message);
|
console.log(message);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -48,9 +48,7 @@ export default function HeaderGroup({ toRoom }: Props) {
|
||||||
|
|
||||||
// 组件初始化时获取随机问题
|
// 组件初始化时获取随机问题
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const controller = new AbortController();
|
getQuestion();
|
||||||
const { signal } = controller;
|
|
||||||
getQuestion({ signal });
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
|
|
@ -66,8 +64,7 @@ export default function HeaderGroup({ toRoom }: Props) {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleQuestion = async (question: string) => {
|
const handleQuestion = async (question: string) => {
|
||||||
setInitMessage(question);
|
toRoom(question);
|
||||||
await toRoom();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,9 @@ import { useSearchParams } from 'react-router-dom';
|
||||||
import MyInputIcon from '/icons/myInput.png';
|
import MyInputIcon from '/icons/myInput.png';
|
||||||
import RightBlueIcon from '/icons/rightBlue.png';
|
import RightBlueIcon from '/icons/rightBlue.png';
|
||||||
import style from './index.module.css';
|
import style from './index.module.css';
|
||||||
import { RealtimeClientContext } from '../Provider/RealtimeClientProvider';
|
|
||||||
import { useContext } from 'react';
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
toRoom: () => void;
|
toRoom: (initMessage?:string) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function MyInput({ toRoom }: Props) {
|
export default function MyInput({ toRoom }: Props) {
|
||||||
|
|
@ -19,11 +17,9 @@ export default function MyInput({ toRoom }: Props) {
|
||||||
const subjectGroup = searchParams.get('subjectGroup') || '物/化/史'
|
const subjectGroup = searchParams.get('subjectGroup') || '物/化/史'
|
||||||
const expectedScore = searchParams.get('expectedScore') || 500
|
const expectedScore = searchParams.get('expectedScore') || 500
|
||||||
|
|
||||||
const {setInitMessage} = useContext(RealtimeClientContext)
|
|
||||||
|
|
||||||
const handleQuestion = async () => {
|
const handleQuestion = async () => {
|
||||||
toRoom();
|
toRoom(`我的高考地点在${provinceName},我选择的科目是${subjectGroup},我的高考分数为${expectedScore}分。我适合哪些学校和专业`);
|
||||||
setInitMessage(`我的高考地点在${provinceName},我选择的科目是${subjectGroup},我的高考分数为${expectedScore}分。我适合哪些学校和专业`);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ import {
|
||||||
ReactNode,
|
ReactNode,
|
||||||
useCallback,
|
useCallback,
|
||||||
useContext,
|
useContext,
|
||||||
useEffect,
|
|
||||||
useRef,
|
useRef,
|
||||||
useState,
|
useState,
|
||||||
} from "react";
|
} from "react";
|
||||||
|
|
@ -33,13 +32,12 @@ export const RealtimeClientContext = createContext<{
|
||||||
messageList: { content: string; role: RoleType }[];
|
messageList: { content: string; role: RoleType }[];
|
||||||
isAiTalking: boolean;
|
isAiTalking: boolean;
|
||||||
roomInfo: RoomInfo | null;
|
roomInfo: RoomInfo | null;
|
||||||
initClient: () => void;
|
initClient: (initMessage?:string) => void;
|
||||||
handleConnect: () => void;
|
handleConnect: (initMessage?:string) => Promise<void>;
|
||||||
handleInterrupt: () => void;
|
handleInterrupt: () => void;
|
||||||
handleDisconnect: () => void;
|
handleDisconnect: () => void;
|
||||||
toggleMicrophone: () => void;
|
toggleMicrophone: () => void;
|
||||||
sendUserMessageWithText: (message: string) => void;
|
|
||||||
setInitMessage: (message: string) => void;
|
|
||||||
}>({
|
}>({
|
||||||
client: null,
|
client: null,
|
||||||
isConnecting: false,
|
isConnecting: false,
|
||||||
|
|
@ -50,12 +48,11 @@ export const RealtimeClientContext = createContext<{
|
||||||
isAiTalking: false,
|
isAiTalking: false,
|
||||||
roomInfo: null,
|
roomInfo: null,
|
||||||
initClient: () => {},
|
initClient: () => {},
|
||||||
handleConnect: () => {},
|
handleConnect: () => Promise.resolve(),
|
||||||
handleInterrupt: () => {},
|
handleInterrupt: () => {},
|
||||||
handleDisconnect: () => {},
|
handleDisconnect: () => {},
|
||||||
toggleMicrophone: () => {},
|
toggleMicrophone: () => {},
|
||||||
sendUserMessageWithText: () => {},
|
|
||||||
setInitMessage: () => {},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 添加自定义hook
|
// 添加自定义hook
|
||||||
|
|
@ -89,27 +86,38 @@ export const RealtimeClientProvider = ({
|
||||||
// 是否已连接
|
// 是否已连接
|
||||||
const [isConnected, setIsConnected] = useState(false);
|
const [isConnected, setIsConnected] = useState(false);
|
||||||
// 是否开启麦克风
|
// 是否开启麦克风
|
||||||
const [audioEnabled, setAudioEnabled] = useState(false);
|
const [audioEnabled, setAudioEnabled] = useState(true);
|
||||||
// 是否支持视频
|
// 是否支持视频
|
||||||
const [isSupportVideo] = useState(false);
|
const [isSupportVideo] = useState(false);
|
||||||
// 是否正在说话
|
// 是否正在说话
|
||||||
const [isAiTalking, setIsAiTalking] = useState(false);
|
const [isAiTalking, setIsAiTalking] = useState(false);
|
||||||
|
|
||||||
const [initMessage, setInitMessage] = useState("");
|
|
||||||
const [isClientInitialized, setIsClientInitialized] = useState(false);
|
|
||||||
const [roomInfo, setRoomInfo] = useState<RoomInfo | null>(null);
|
const [roomInfo, setRoomInfo] = useState<RoomInfo | null>(null);
|
||||||
|
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
const initClient = async () => {
|
const initClient = async (_initMessage?:string) => {
|
||||||
const permission = await RealtimeUtils.checkDevicePermission(false);
|
const permission = await RealtimeUtils.checkDevicePermission(false);
|
||||||
|
const device = await RealtimeUtils.getAudioDevices();
|
||||||
|
|
||||||
if (!permission.audio) {
|
if (!permission.audio) {
|
||||||
toast({
|
toast({
|
||||||
title: "连接错误",
|
title: "连接错误",
|
||||||
description: "需要麦克风访问权限",
|
description: "需要麦克风访问权限",
|
||||||
});
|
});
|
||||||
throw new Error("需要麦克风访问权限");
|
throw new Error("需要麦克风访问权限");
|
||||||
}else{
|
}
|
||||||
|
|
||||||
|
if (device.audioInputs.length === 0) {
|
||||||
|
toast({
|
||||||
|
title: "连接错误",
|
||||||
|
description: "没有麦克风设备",
|
||||||
|
});
|
||||||
|
throw new Error("没有麦克风设备");
|
||||||
|
}
|
||||||
|
|
||||||
const client = new RealtimeClient({
|
const client = new RealtimeClient({
|
||||||
accessToken: token,
|
accessToken: token,
|
||||||
botId: botId,
|
botId: botId,
|
||||||
|
|
@ -119,27 +127,21 @@ export const RealtimeClientProvider = ({
|
||||||
});
|
});
|
||||||
|
|
||||||
clientRef.current = client;
|
clientRef.current = client;
|
||||||
|
|
||||||
|
|
||||||
setupEventListeners(client);
|
setupEventListeners(client);
|
||||||
setIsClientInitialized(true);
|
setupMessageEventListeners(client,_initMessage ?? '');
|
||||||
}
|
setupInitMessageEventListener(client,_initMessage)
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (clientRef.current) {
|
|
||||||
setupMessageEventListeners(clientRef.current);
|
|
||||||
if (initMessage) {
|
|
||||||
setupInitMessageEventListener(clientRef.current);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [initMessage, isClientInitialized]);
|
|
||||||
|
|
||||||
const handleConnect = async () => {
|
const handleConnect = async (initMessage?:string) => {
|
||||||
try {
|
try {
|
||||||
if (!clientRef.current) {
|
if (!clientRef.current) {
|
||||||
await initClient();
|
await initClient(initMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
await clientRef.current?.connect();
|
await clientRef.current?.connect();
|
||||||
|
await toggleMicrophone();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
if (error instanceof RealtimeAPIError) {
|
if (error instanceof RealtimeAPIError) {
|
||||||
|
|
@ -170,12 +172,13 @@ export const RealtimeClientProvider = ({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDisconnect = () => {
|
const handleDisconnect = async() => {
|
||||||
try {
|
try {
|
||||||
// 关闭客户的时候清除一些信息
|
// 关闭客户的时候清除一些信息
|
||||||
setIsAiTalking(false);
|
setIsAiTalking(false);
|
||||||
setIsClientInitialized(false);
|
|
||||||
setMessageList([]);
|
setMessageList([]);
|
||||||
|
await clientRef.current?.setAudioEnable(false);
|
||||||
|
setAudioEnabled(false);
|
||||||
|
|
||||||
clientRef.current?.disconnect();
|
clientRef.current?.disconnect();
|
||||||
clientRef.current?.clearEventHandlers();
|
clientRef.current?.clearEventHandlers();
|
||||||
|
|
@ -195,16 +198,36 @@ export const RealtimeClientProvider = ({
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const setupInitMessageEventListener = (client: RealtimeClient) => {
|
|
||||||
client.on(EventNames.ALL_SERVER, (eventName, _event: any) => {
|
const setupInitMessageEventListener = useCallback((client: RealtimeClient,_initMessage?:string) => {
|
||||||
|
client.on(EventNames.ALL_SERVER, async(eventName, _event: any) => {
|
||||||
if (eventName === "server.session.created") {
|
if (eventName === "server.session.created") {
|
||||||
|
await client.sendMessage({
|
||||||
|
id:'',
|
||||||
|
"event_type":"session.update",
|
||||||
|
data:{
|
||||||
|
chat_config:{
|
||||||
|
allow_voice_interrupt:false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if(eventName === "server.bot.join" && _initMessage){
|
||||||
// 这里需要加个 server. 前缀
|
// 这里需要加个 server. 前缀
|
||||||
sendUserMessageWithText(initMessage);
|
await clientRef.current?.sendMessage({
|
||||||
|
id: "",
|
||||||
|
event_type: "conversation.message.create",
|
||||||
|
data: {
|
||||||
|
role: "user",
|
||||||
|
content_type: "text",
|
||||||
|
content: _initMessage,
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
},[clientRef.current]);
|
||||||
|
|
||||||
const setupMessageEventListeners = (client: RealtimeClient) => {
|
const setupMessageEventListeners = (client: RealtimeClient,_initMessage:string) => {
|
||||||
let lastEvent: any;
|
let lastEvent: any;
|
||||||
client.on(EventNames.ALL, (_eventName, event: any) => {
|
client.on(EventNames.ALL, (_eventName, event: any) => {
|
||||||
// AI智能体设置
|
// AI智能体设置
|
||||||
|
|
@ -233,7 +256,7 @@ export const RealtimeClientProvider = ({
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加AI的欢迎语
|
// 添加AI的欢迎语
|
||||||
if (initMessage === "" && event.event_type === "conversation.created") {
|
if (_initMessage === "" && event.event_type === "conversation.created") {
|
||||||
return [
|
return [
|
||||||
...prev,
|
...prev,
|
||||||
{ content: event.data.prologue, role: RoleType.Assistant },
|
{ content: event.data.prologue, role: RoleType.Assistant },
|
||||||
|
|
@ -260,16 +283,18 @@ export const RealtimeClientProvider = ({
|
||||||
const setupEventListeners = useCallback(
|
const setupEventListeners = useCallback(
|
||||||
(client: RealtimeClient) => {
|
(client: RealtimeClient) => {
|
||||||
// 监听 AI 开始说话事件
|
// 监听 AI 开始说话事件
|
||||||
client.on(EventNames.AUDIO_AGENT_SPEECH_STARTED, () => {
|
client.on(EventNames.AUDIO_AGENT_SPEECH_STARTED, async() => {
|
||||||
// console.log("AI开始说话");
|
// console.log("AI开始说话");
|
||||||
setIsAiTalking(true);
|
setIsAiTalking(true);
|
||||||
|
await clientRef.current?.setAudioEnable(false);
|
||||||
setAudioEnabled(false);
|
setAudioEnabled(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 监听 AI 结束说话事件
|
// 监听 AI 结束说话事件
|
||||||
client.on(EventNames.AUDIO_AGENT_SPEECH_STOPPED, () => {
|
client.on(EventNames.AUDIO_AGENT_SPEECH_STOPPED, async() => {
|
||||||
// console.log("AI结束说话");
|
// console.log("AI结束说话");
|
||||||
setIsAiTalking(false);
|
setIsAiTalking(false);
|
||||||
|
await clientRef.current?.setAudioEnable(true);
|
||||||
setAudioEnabled(true);
|
setAudioEnabled(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -286,25 +311,25 @@ export const RealtimeClientProvider = ({
|
||||||
setIsConnected(true);
|
setIsConnected(true);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[clientRef.current, initMessage]
|
[clientRef.current]
|
||||||
);
|
);
|
||||||
|
|
||||||
// 发送信息
|
// 发送信息
|
||||||
const sendUserMessageWithText = async (message: string) => {
|
// const sendUserMessageWithText = async (message: string) => {
|
||||||
try {
|
// try {
|
||||||
await clientRef.current?.sendMessage({
|
// await clientRef.current?.sendMessage({
|
||||||
id: "",
|
// id: "",
|
||||||
event_type: "conversation.message.create",
|
// event_type: "conversation.message.create",
|
||||||
data: {
|
// data: {
|
||||||
role: "user",
|
// role: "user",
|
||||||
content_type: "text",
|
// content_type: "text",
|
||||||
content: message,
|
// content: message,
|
||||||
},
|
// },
|
||||||
});
|
// });
|
||||||
} catch (error) {
|
// } catch (error) {
|
||||||
console.error("发送消息失败:" + error);
|
// console.error("发送消息失败:" + error);
|
||||||
}
|
// }
|
||||||
};
|
// };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<RealtimeClientContext.Provider
|
<RealtimeClientContext.Provider
|
||||||
|
|
@ -322,8 +347,7 @@ export const RealtimeClientProvider = ({
|
||||||
handleInterrupt,
|
handleInterrupt,
|
||||||
handleDisconnect,
|
handleDisconnect,
|
||||||
toggleMicrophone,
|
toggleMicrophone,
|
||||||
sendUserMessageWithText,
|
|
||||||
setInitMessage,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
import { createContext, useState } from "react";
|
||||||
|
|
||||||
|
export const ReportContext = createContext<{
|
||||||
|
hasHandledReport: boolean;
|
||||||
|
setHasHandledReport: (value: boolean) => void;
|
||||||
|
}>({
|
||||||
|
hasHandledReport: false,
|
||||||
|
setHasHandledReport: () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
export const ReportProvider = ({ children }: { children: React.ReactNode }) => {
|
||||||
|
const [hasHandledReport, setHasHandledReport] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ReportContext.Provider value={{ hasHandledReport, setHasHandledReport }}>
|
||||||
|
{children}
|
||||||
|
</ReportContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
@ -28,7 +28,7 @@ function AudioController({className, ...rest}: React.HTMLAttributes<HTMLDivEleme
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`${className} flex items-center justify-center bg-white pb-[20px] pt-[10px] gap-[10px]`} {...rest}>
|
<div className={`flex items-center justify-center bg-white pb-[20px] pt-[10px] gap-[10px]`} {...rest}>
|
||||||
<div className={`${style.microphoneWrapper}`} onClick={handleDisconnect}>
|
<div className={`${style.microphoneWrapper}`} onClick={handleDisconnect}>
|
||||||
<img src={HandleOffIcon} alt="handoff" />
|
<img src={HandleOffIcon} alt="handoff" />
|
||||||
<div>挂断</div>
|
<div>挂断</div>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
import { useRef, useEffect, useContext } from "react";
|
import { useRef, useEffect, useContext } from "react";
|
||||||
import { RealtimeClientContext } from "../Provider/RealtimeClientProvider";
|
import { RealtimeClientContext } from "../Provider/RealtimeClientProvider";
|
||||||
|
import ReactMarkdown from 'react-markdown'
|
||||||
|
import gfm from 'remark-gfm'
|
||||||
import { RoleType } from "@coze/api";
|
import { RoleType } from "@coze/api";
|
||||||
|
|
||||||
export default function RoomConversation() {
|
export default function RoomConversation() {
|
||||||
|
|
@ -16,7 +18,14 @@ export default function RoomConversation() {
|
||||||
}, [messageList]);
|
}, [messageList]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex-1 flex flex-col h-full">
|
<div className="flex-1 flex flex-col overflow-y-auto">
|
||||||
|
<div className="w-full min-h-[120px] h-[120px]">
|
||||||
|
<div className="relative h-full">
|
||||||
|
<img src="/icons/hello.gif" alt="" className="absolute top-0 h-[97px] left-[50%] translate-x-[-50%]"/>
|
||||||
|
<img src="/icons/conversation-bg.png" alt="background" className='w-[222px] h-[49px] absolute bottom-0 left-[50%] translate-x-[-50%]'/>
|
||||||
|
<div className="text-black text-[14px] absolute bottom-[22px] left-[50%] translate-x-[-50%] z-[10]">Hey,我是您的六纬AI填报师</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div className="flex-1 overflow-y-auto p-4 space-y-4">
|
<div className="flex-1 overflow-y-auto p-4 space-y-4">
|
||||||
{messageList.map((message: any, index: number) => (
|
{messageList.map((message: any, index: number) => (
|
||||||
<div
|
<div
|
||||||
|
|
@ -34,7 +43,7 @@ export default function RoomConversation() {
|
||||||
: "bg-blue-500 text-white rounded-tr-none"
|
: "bg-blue-500 text-white rounded-tr-none"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{message.content}
|
<ReactMarkdown remarkPlugins={[gfm]}>{message.content}</ReactMarkdown>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
import { useEffect, useRef } from 'react';
|
||||||
|
|
||||||
|
export function useAbortController() {
|
||||||
|
const controllerRef = useRef<AbortController | null>(null);
|
||||||
|
|
||||||
|
// 获取AbortSignal实例
|
||||||
|
const getSignal = () => {
|
||||||
|
if (!controllerRef.current) {
|
||||||
|
controllerRef.current = new AbortController();
|
||||||
|
}
|
||||||
|
return controllerRef.current.signal;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 中止所有请求
|
||||||
|
const abortAll = () => {
|
||||||
|
if (controllerRef.current) {
|
||||||
|
controllerRef.current.abort();
|
||||||
|
controllerRef.current = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 创建新的Controller,并中止之前的请求
|
||||||
|
const recreate = () => {
|
||||||
|
abortAll();
|
||||||
|
controllerRef.current = new AbortController();
|
||||||
|
return controllerRef.current.signal;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 组件卸载时自动中止所有请求
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
abortAll();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return {
|
||||||
|
getSignal,
|
||||||
|
abortAll,
|
||||||
|
recreate
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -10,6 +10,7 @@ export const useSignalRConnection = (params: {
|
||||||
const connectionRef = useRef<signalR.HubConnection | null>(null);
|
const connectionRef = useRef<signalR.HubConnection | null>(null);
|
||||||
const { handleDisconnect } = useRealtimeClient();
|
const { handleDisconnect } = useRealtimeClient();
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
const timerRef = useRef<NodeJS.Timeout | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!params.access_token || !params.roomId) {
|
if (!params.access_token || !params.roomId) {
|
||||||
|
|
@ -51,7 +52,7 @@ export const useSignalRConnection = (params: {
|
||||||
.start()
|
.start()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
console.log("SignalR连接已建立");
|
console.log("SignalR连接已建立");
|
||||||
setInterval(() => {
|
timerRef.current = setInterval(() => {
|
||||||
connection.invoke("Ping");
|
connection.invoke("Ping");
|
||||||
}, 5000);
|
}, 5000);
|
||||||
})
|
})
|
||||||
|
|
@ -64,6 +65,7 @@ export const useSignalRConnection = (params: {
|
||||||
connectionRef.current
|
connectionRef.current
|
||||||
.stop()
|
.stop()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
clearInterval(timerRef.current as NodeJS.Timeout);
|
||||||
console.log("SignalR连接已关闭");
|
console.log("SignalR连接已关闭");
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue