index.jsx 7.75 KB
Newer Older
xuchaozou's avatar
xuchaozou committed
1 2
import style from './style.less'
import { withRouter } from '@wisdom-utils/runtime'
xuchaozou's avatar
xuchaozou committed
3
import { Tooltip, Typography } from 'antd'
4
import { Fragment, useCallback, useContext, useEffect, useRef, useState } from 'react'
xuchaozou's avatar
xuchaozou committed
5
import { event } from '@wisdom-utils/utils'
xuchaozou's avatar
xuchaozou committed
6
import moment from 'moment'
xuchaozou's avatar
xuchaozou committed
7
import { Directive, AllDirectiveTypes, CloseSessionDirective , PandaSpeechSynthesisUtterance} from '../../core'
8 9
import context from '../../config'
import SparkDirective from '../../core/directive/SparkDirective'
xuchaozou's avatar
xuchaozou committed
10

xuchaozou's avatar
xuchaozou committed
11

xuchaozou's avatar
xuchaozou committed
12
const Dialog = props => {
xuchaozou's avatar
xuchaozou committed
13 14 15 16
  const { globalConfig, setGlobalConfig } = useContext(context)
  const { pandaRecordWebSocketRef, close } = props
  const ulRef = useRef(null)
  const directiveRef = useRef(null)
xuchaozou's avatar
xuchaozou committed
17
  const closeDirectiveRef = useRef(null)
xuchaozou's avatar
xuchaozou committed
18 19 20
  const toolTipRef = useRef(null)
  const timeRef = useRef(null)
  const isDebug = globalConfig.get("isDebug")
xuchaozou's avatar
xuchaozou committed
21
  const currentDirectiveRef = useRef(null)
xuchaozou's avatar
xuchaozou committed
22
  const pandaSpeechSynthesisUtteranceRef = useRef(null)
xuchaozou's avatar
xuchaozou committed
23 24
  //是否开启系统语音
  const isOpenSystemSound = globalConfig.getIn(['key', 'isOpenSystemSound'])
xuchaozou's avatar
xuchaozou committed
25
  const defaultEmptyMsg = "亲,我暂时还未理解你的意思,请换一种方式表达或者描述更加具体"
26

xuchaozou's avatar
xuchaozou committed
27 28 29 30 31
  const [data, setData] = useState(() => [{
    role: "system",
    content: `嗨,您好!猜测您可能问以下的问题\n ${globalConfig.get('guideTemplateData').toJS().map((i, index) => `${index + 1}.${i}。`).join("\n")}`,
    time: new moment().format("HH:mm:ss"),
  }])
32

邹绪超's avatar
邹绪超 committed
33

xuchaozou's avatar
xuchaozou committed
34 35 36 37 38 39 40 41
  useEffect(() => {
    startTime()
    if (!ulRef.current.lastChild) return
    ulRef.current.lastChild.scrollIntoView({
      block: 'end',
      behavior: 'smooth'
    })
  }, [data])
邹绪超's avatar
邹绪超 committed
42

xuchaozou's avatar
xuchaozou committed
43 44 45 46 47 48
  useEffect(() => {
    directiveRef.current = new Directive({
      params: {
        sparkModelConfig: globalConfig.getIn(['key']).shortSound
      }
    })
xuchaozou's avatar
xuchaozou committed
49 50 51
    closeDirectiveRef.current = new Directive({
      directiveTypes: [CloseSessionDirective]
    })
xuchaozou's avatar
xuchaozou committed
52
    pandaSpeechSynthesisUtteranceRef.current = new PandaSpeechSynthesisUtterance()
xuchaozou's avatar
xuchaozou committed
53 54
    if (globalConfig.get('key').isOpenSparkModel) {
      directiveRef.current.directiveTypes.push(SparkDirective)
xuchaozou's avatar
xuchaozou committed
55
      directiveRef.current._directiveTypes.push(SparkDirective)
xuchaozou's avatar
xuchaozou committed
56 57 58 59 60 61
    }
    event.on("aiSound:finish", finish)
    event.on("aiSound:frame", frame)
    return () => {
      event.off("aiSound:finish", finish)
      event.off("aiSound:frame", frame)
xuchaozou's avatar
xuchaozou committed
62 63 64 65
      if(pandaSpeechSynthesisUtteranceRef.current) {
        pandaSpeechSynthesisUtteranceRef.current.clear()
        pandaSpeechSynthesisUtteranceRef.current = null
      }
xuchaozou's avatar
xuchaozou committed
66 67 68
      destroyTime()
    }
  }, [])
xuchaozou's avatar
xuchaozou committed
69

xuchaozou's avatar
xuchaozou committed
70 71
  const finish = useCallback(async ({ resultText }) => {
    if (!resultText) return
邹绪超's avatar
邹绪超 committed
72 73
    if(currentDirectiveRef.current) {
      return
xuchaozou's avatar
xuchaozou committed
74
    }
邹绪超's avatar
邹绪超 committed
75
    pandaSpeechSynthesisUtteranceRef.current?.clear()
xuchaozou's avatar
xuchaozou committed
76 77 78 79
    const directive = directiveRef.current.parse({
      text: resultText
    })
    if (directive) {
xuchaozou's avatar
xuchaozou committed
80
      currentDirectiveRef.current = directive
xuchaozou's avatar
xuchaozou committed
81 82 83 84 85 86 87
      sendContent({
        role: "system",
        content: "系统正在思考中,请稍后..."
      })
      let resultMsg = ""
      await directive.excute({
        sendMsg: msg => {
xuchaozou's avatar
xuchaozou committed
88
          isOpenSystemSound && pandaSpeechSynthesisUtteranceRef.current.push(msg)
xuchaozou's avatar
xuchaozou committed
89 90 91 92
          resultMsg += msg
          updateLastData({
            content: resultMsg
          })
xuchaozou's avatar
xuchaozou committed
93
        }
xuchaozou's avatar
xuchaozou committed
94
      })
xuchaozou's avatar
xuchaozou committed
95 96 97 98
      if(isOpenSystemSound) {
        destroyTime()
        await pandaSpeechSynthesisUtteranceRef.current.speakEnd()
        startTime()
xuchaozou's avatar
xuchaozou committed
99 100
      }
      currentDirectiveRef.current = null
xuchaozou's avatar
xuchaozou committed
101 102
      return
    }
xuchaozou's avatar
xuchaozou committed
103

xuchaozou's avatar
xuchaozou committed
104 105
    sendContent({
      role: "system",
xuchaozou's avatar
xuchaozou committed
106
      content: defaultEmptyMsg
xuchaozou's avatar
xuchaozou committed
107
    })
xuchaozou's avatar
xuchaozou committed
108 109 110 111
    if(isOpenSystemSound) {
      pandaSpeechSynthesisUtteranceRef.current.push(defaultEmptyMsg)
      await pandaSpeechSynthesisUtteranceRef.current.speakEnd()
    }
xuchaozou's avatar
xuchaozou committed
112
  }, [])
xuchaozou's avatar
xuchaozou committed
113

xuchaozou's avatar
xuchaozou committed
114 115 116
  const frame = useCallback((data) => {
    const { resultText, resultTextTemp } = data
    if (!resultTextTemp) return
xuchaozou's avatar
xuchaozou committed
117 118 119 120 121 122 123
    const hasCloseSessionDirective = closeDirectiveRef.current.parse({
      text: resultTextTemp
    })
    if (hasCloseSessionDirective) {
      closeSession({
        resultText: resultTextTemp
      })
邹绪超's avatar
邹绪超 committed
124 125
    } else {
      !currentDirectiveRef.current && updateUserData({ resultTextTemp })
xuchaozou's avatar
xuchaozou committed
126 127 128 129 130 131
    }
  }, [])

  const closeSession = ({ resultText }) => {
    if (currentDirectiveRef.current) {
      currentDirectiveRef.current.closeResponse()
邹绪超's avatar
邹绪超 committed
132 133 134
      pandaSpeechSynthesisUtteranceRef.current?.clear()
      closeSessionReocrder()
      currentDirectiveRef.current = null
xuchaozou's avatar
xuchaozou committed
135 136
    } else {
      if (!isDebug) {
邹绪超's avatar
邹绪超 committed
137
        closeSessionReocrder()
xuchaozou's avatar
xuchaozou committed
138 139 140 141 142 143 144
        event.emit("aiSound:finish", {
          resultText
        })
      }
    }
  }

邹绪超's avatar
邹绪超 committed
145 146
  const closeSessionReocrder = () => {
    pandaRecordWebSocketRef.current?.wsClose?.()
xuchaozou's avatar
xuchaozou committed
147 148
  }

xuchaozou's avatar
xuchaozou committed
149
  const updateUserData = ({ resultTextTemp }) => {
xuchaozou's avatar
xuchaozou committed
150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
    setData(data => {
      const length = data.length
      const lastData = data.at(-1)
      if (length == 0 || lastData.role == "system") {
        return data.concat([{
          role: "user",
          time: new moment().format("HH:mm:ss"),
          content: resultTextTemp
        }])
      } else {
        return data.map((data, index) => {
          if (index < length - 1) {
            return {
              ...data
            }
          } else {
            return {
              ...data,
              content: resultTextTemp
xuchaozou's avatar
xuchaozou committed
169
            }
xuchaozou's avatar
xuchaozou committed
170
          }
xuchaozou's avatar
xuchaozou committed
171
        })
xuchaozou's avatar
xuchaozou committed
172 173
      }
    })
xuchaozou's avatar
xuchaozou committed
174
  }
xuchaozou's avatar
xuchaozou committed
175

xuchaozou's avatar
xuchaozou committed
176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
  const updateLastData = ({ content, ...params }) => {
    setData(data => data.map((item, index) => {
      if (index < data.length - 1) {
        return {
          ...item
        }
      } else {
        return {
          ...item,
          content,
          ...params,
        }
      }
    }))
  }
xuchaozou's avatar
xuchaozou committed
191

xuchaozou's avatar
xuchaozou committed
192 193 194 195 196 197 198 199
  const sendContent = ({ role = "system", content, ...params }) => {
    setData(i => i.concat([{
      role,
      time: new moment().format("HH:mm:ss"),
      content,
      ...params
    }]))
  }
200

xuchaozou's avatar
xuchaozou committed
201 202 203 204 205 206
  const startTime = () => {
    destroyTime()
    timeRef.current = setTimeout(() => {
      close && close()
    }, globalConfig.getIn(['closePandaAiTime']))
  }
邹绪超's avatar
邹绪超 committed
207

xuchaozou's avatar
xuchaozou committed
208 209 210 211
  const destroyTime = () => {
    if (timeRef.current) {
      clearTimeout(timeRef.current)
      timeRef.current = null
邹绪超's avatar
邹绪超 committed
212
    }
xuchaozou's avatar
xuchaozou committed
213
  }
xuchaozou's avatar
xuchaozou committed
214

xuchaozou's avatar
xuchaozou committed
215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231
  return (<div className={style.container}>
    <div className={style.pandaIcon}></div>
    <div className={style.main}>
      <div className={style.header}>
        <p>熊猫智能语音库</p>
        <div className={style.help}>
          <Tooltip
            placement="bottomRight"
            title={
              <div className={style.toolTipContent}>
                <p className={style.toolTipTitle}>支持以下语音指令集</p>
                <div className={style.toolTipWrapper}>
                  <ul>
                    {
                      AllDirectiveTypes.filter(item => !!item.name).map((item, index) => <li key={index}>{index + 1}.{item.name}</li>)
                    }
                  </ul>
xuchaozou's avatar
xuchaozou committed
232
                </div>
xuchaozou's avatar
xuchaozou committed
233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258
              </div>
            }
            getPopupContainer={() => toolTipRef.current}
            arrowPointAtCenter={true}
          >
            <span className={style.helpIcon} ref={toolTipRef}></span>
          </Tooltip>
        </div>
      </div>
      <div className={style.content}>
        <div className={style.display}>
          <ul className={style.ul} ref={ulRef}>
            {
              data.map((item, index) => <Fragment key={index}>
                {
                  item.interval ? <li className={style.interval}>{item.time}</li> : null
                }
                <li data-role={item.role} key={index} className={style.list}>
                  <span className={style.avater}></span>
                  <div className={style.aiContent}>
                    <p className={style.aiText}> {item.content}</p>
                  </div>
                </li>
              </Fragment>)
            }
          </ul>
xuchaozou's avatar
xuchaozou committed
259
        </div>
xuchaozou's avatar
xuchaozou committed
260 261 262
      </div>
    </div>
  </div>)
xuchaozou's avatar
xuchaozou committed
263 264 265
}

export default withRouter(Dialog)