| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159 |
- import { useState, useRef } from 'react'
- import { useAppStore } from '@/stores/appStore'
- interface RecordButtonProps {
- onPress: () => void
- onRelease: () => void
- disabled?: boolean
- }
- export default function RecordButton({ onPress, onRelease, disabled = false }: RecordButtonProps) {
- const isRecording = useAppStore((state) => state.isRecording)
- const isPlaying = useAppStore((state) => state.isPlaying)
- const isConnected = useAppStore((state) => state.isConnected)
- const animationState = useAppStore((state) => state.animationState)
-
- // 本地状态:用于立即响应按钮按下/松开
- const [isPressed, setIsPressed] = useState(false)
- const isPressing = useRef(false)
- const handleMouseDown = (e?: React.MouseEvent) => {
- if (e) e.preventDefault()
- console.log('按钮按下 - state:', animationState, 'disabled:', disabled, 'isConnected:', isConnected)
-
- // 允许录音(只要不在播放中)
- if (!disabled && !isPlaying && isConnected) {
- console.log('触发onPress')
- setIsPressed(true)
- isPressing.current = true
- onPress()
- }
- }
- const handleMouseUp = () => {
- console.log('按钮松开 - isRecording:', isRecording, 'isPressing:', isPressing.current)
-
- // 立即更新本地状态
- setIsPressed(false)
-
- if (isPressing.current) {
- console.log('触发onRelease')
- isPressing.current = false
- onRelease()
- }
- }
- const handleTouchStart = (e: React.TouchEvent) => {
- e.preventDefault() // 防止触发鼠标事件和默认行为
- console.log('触摸开始 - state:', animationState)
-
- // 允许录音
- if (!disabled && !isPlaying && isConnected) {
- setIsPressed(true)
- isPressing.current = true
- onPress()
- }
- }
- const handleTouchEnd = (e: React.TouchEvent) => {
- e.preventDefault()
- console.log('触摸结束')
- setIsPressed(false)
- if (isPressing.current) {
- isPressing.current = false
- onRelease()
- }
- }
- const handleTouchCancel = (e: React.TouchEvent) => {
- e.preventDefault()
- console.log('触摸取消')
- setIsPressed(false)
- if (isPressing.current) {
- isPressing.current = false
- onRelease()
- }
- }
- // 阻止长按弹出右键菜单
- const handleContextMenu = (e: React.MouseEvent) => {
- e.preventDefault()
- console.log('阻止右键菜单')
- }
- const getButtonStyle = () => {
- if (disabled || !isConnected) {
- return 'bg-gray-400 cursor-not-allowed'
- }
- // 使用本地状态isPressed来立即响应
- if (isPressed || isRecording) {
- return 'bg-red-500 hover:bg-red-600 scale-110 shadow-2xl'
- }
- if (isPlaying) {
- return 'bg-purple-400 cursor-not-allowed'
- }
- return 'bg-blue-500 hover:bg-blue-600 active:scale-95'
- }
- const getButtonText = () => {
- if (!isConnected) return '连接中...'
- if (isPressed || isRecording) return '松开停止'
- if (animationState === 'PROCESSING') return 'AI思考中...'
- if (animationState === 'SPEAKING' || isPlaying) return 'AI回复中...'
- return '按住说话'
- }
- return (
- <div className="flex flex-col items-center space-y-4">
- {/* 录音按钮 */}
- <button
- className={`
- w-32 h-32 rounded-full text-white font-bold text-lg
- transition-all duration-200 transform
- flex items-center justify-center
- select-none
- ${getButtonStyle()}
- `}
- style={{
- WebkitTouchCallout: 'none',
- WebkitUserSelect: 'none',
- touchAction: 'none',
- }}
- onMouseDown={handleMouseDown}
- onMouseUp={handleMouseUp}
- onMouseLeave={handleMouseUp}
- onTouchStart={handleTouchStart}
- onTouchEnd={handleTouchEnd}
- onTouchCancel={handleTouchCancel}
- onContextMenu={handleContextMenu}
- disabled={disabled || !isConnected}
- >
- <div className="flex flex-col items-center">
- <span className="text-3xl mb-1">🎤</span>
- <span className="text-sm">{getButtonText()}</span>
- </div>
- </button>
- {/* 使用提示 */}
- <div className="text-center text-sm text-gray-600 max-w-xs">
- {!isConnected ? (
- <p className="text-orange-500">正在连接服务器...</p>
- ) : (
- <>
- <p className="mb-1">按住按钮开始说话</p>
- <p className="text-xs text-gray-500">松开按钮听取AI回复</p>
- </>
- )}
- </div>
- {/* 录音状态指示器 */}
- {(isPressed || isRecording) && (
- <div className="flex items-center space-x-2 text-red-500 animate-pulse">
- <div className="w-3 h-3 bg-red-500 rounded-full"></div>
- <span className="text-sm font-medium">录音中...</span>
- </div>
- )}
- </div>
- )
- }
|