HumansXドキュメントセンター 公式サイトへ ↗

状態管理の詳細

状態管理アーキテクチャの概要

フロントエンドでは Reducer + Context パターンで状態管理を行い、イミュータブルな状態更新と型安全な設計を組み合わせることで、予測可能かつ高性能な状態管理システムを実現しています。

️ 状態アーキテクチャの設計

状態ツリーの構造

interface AppState {
  // 连接状态
  connection: {
    state: ConnectionState
    url: string
    error: string | null
    reconnectAttempts: number
    socket: WebSocket | null
  }

  // 对话状态
  conversation: {
    messages: Message[]
    currentInput: string
    currentTurnId: string | null
    isAssistantTyping: boolean
    isReceivingStream: boolean
    hasToolCallsInCurrentTurn: boolean
  }

  // UI状态
  ui: {
    inputMode: InputMode
    isListening: boolean
    asrResult: string
    isInputDisabled: boolean
  }

  // 系统状态
  system: {
    statuses: Record<string, SystemStatus>
    errors: ErrorInfo[]
    lastMessage: WebSocketMessage | null
  }

  // 工具状态
  tools: {
    activeCalls: Record<string, ToolCall>
  }
}

Reducer パターンの実装

function appStateReducer(state: AppState, action: AppAction): AppState {
  switch (action.type) {
    // 连接管理
    case 'CONNECTION_CONNECTING':
      return {
        ...state,
        connection: {
          ...state.connection,
          state: 'connecting',
          url: action.payload.url,
          error: null
        }
      }

    case 'CONNECTION_CONNECTED':
      return {
        ...state,
        connection: {
          ...state.connection,
          state: 'connected',
          socket: action.payload.socket,
          error: null
        }
      }

    // 消息管理
    case 'MESSAGE_ADD':
      return {
        ...state,
        conversation: {
          ...state.conversation,
          messages: [...state.conversation.messages, action.payload.message]
        }
      }

    case 'MESSAGE_UPDATE':
      return {
        ...state,
        conversation: {
          ...state.conversation,
          messages: state.conversation.messages.map(msg =>
            msg.id === action.payload.id
              ? { ...msg, ...action.payload.updates }
              : msg
          )
        }
      }

    // 流式响应
    case 'STREAM_CHUNK':
      return updateStreamContent(state, action.payload)

    // 工具调用
    case 'TOOL_CALL_START':
      return {
        ...state,
        tools: {
          ...state.tools,
          activeCalls: {
            ...state.tools.activeCalls,
            [action.payload.toolCall.id]: action.payload.toolCall
          }
        },
        conversation: {
          ...state.conversation,
          hasToolCallsInCurrentTurn: true
        }
      }

    default:
      return state
  }
}

コア機能の実装

メッセージのストリーミング更新

function updateStreamContent(state: AppState, payload: { content: string; isFirst?: boolean }): AppState {
  const { content, isFirst } = payload

  if (!state.conversation.currentTurnId) {
    return state
  }

  const messageIndex = state.conversation.messages.findIndex(
    msg => msg.id === state.conversation.currentTurnId
  )

  if (messageIndex === -1) {
    return state
  }

  const currentMessage = state.conversation.messages[messageIndex]
  const newContent = isFirst ? content : (currentMessage.content || '') + content

  const updatedMessages = [...state.conversation.messages]
  updatedMessages[messageIndex] = {
    ...currentMessage,
    content: newContent
  }

  return {
    ...state,
    conversation: {
      ...state.conversation,
      messages: updatedMessages,
      isReceivingStream: true
    }
  }
}

インテリジェントな重複排除の仕組み

// 全局去重状态
declare global {
  interface Window {
    processedLLMResponses?: Set<string>
  }
}

// LLM_FINAL_RESPONSE去重处理
case 'LLM_FINAL_RESPONSE':
  const responseContent = message.payload.content || ''
  const currentTurnId = stateRef.current.conversation.currentTurnId
  const responseKey = `${currentTurnId}_${responseContent.length}_${responseContent.slice(-20)}`

  if (!window.processedLLMResponses) {
    window.processedLLMResponses = new Set()
  }

  if (window.processedLLMResponses.has(responseKey)) {
    return state // 跳过重复响应
  }

  window.processedLLMResponses.add(responseKey)

  // 继续处理逻辑...

⚡ パフォーマンス最適化戦略

1. 不要な再レンダリングの削減

// 使用React.memo优化组件
const MessageBubble = React.memo(({ message }: { message: Message }) => {
  return <div>{message.content}</div>
}, (prevProps, nextProps) => {
  // 自定义比较函数
  return (
    prevProps.message.id === nextProps.message.id &&
    prevProps.message.content === nextProps.message.content &&
    prevProps.message.toolCalls?.length === nextProps.message.toolCalls?.length
  )
})

// 使用useCallback稳定引用
const handleSendMessage = useCallback(() => {
  sendUserInput()
}, [sendUserInput])

2. 状態セレクターの最適化

// 自定义选择器Hook
function useMessages() {
  const { state } = useAppState()
  return useMemo(() => state.conversation.messages, [state.conversation.messages])
}

function useCurrentInput() {
  const { state } = useAppState()
  return useMemo(() => state.conversation.currentInput, [state.conversation.currentInput])
}

// 避免整个状态对象的依赖
function useConnectionStatus() {
  const { state } = useAppState()
  return useMemo(() => ({
    isConnected: state.connection.state === 'connected',
    error: state.connection.error,
    reconnectAttempts: state.connection.reconnectAttempts
  }), [state.connection.state, state.connection.error, state.connection.reconnectAttempts])
}

3. 状態の一括更新

// 批量更新多个状态字段
case 'TURN_COMPLETE_BATCH':
  return {
    ...state,
    conversation: {
      ...state.conversation,
      isAssistantTyping: false,
      isReceivingStream: false,
      currentTurnId: null
    },
    tools: {
      ...state.tools,
      activeCalls: {} // 清空活跃工具调用
    }
  }

型安全性

Action 型の定義

type AppAction =
  // 连接相关
  | { type: 'CONNECTION_CONNECTING'; payload: { url: string } }
  | { type: 'CONNECTION_CONNECTED'; payload: { socket: WebSocket } }
  | { type: 'CONNECTION_DISCONNECTED'; payload: {} }
  | { type: 'CONNECTION_ERROR'; payload: { error: string } }

  // 消息相关
  | { type: 'MESSAGE_ADD'; payload: { message: Message } }
  | { type: 'MESSAGE_UPDATE'; payload: { id: string; updates: Partial<Message> } }
  | { type: 'MESSAGE_CLEAR_ALL'; payload: {} }

  // 流式响应
  | { type: 'STREAM_START'; payload: { turnId: string } }
  | { type: 'STREAM_CHUNK'; payload: { content: string; isFirst?: boolean } }
  | { type: 'STREAM_END'; payload: { finalContent?: string } }

  // 工具调用
  | { type: 'TOOL_CALL_START'; payload: { toolCall: ToolCall } }
  | { type: 'TOOL_CALL_UPDATE'; payload: { id: string; updates: Partial<ToolCall> } }
  | { type: 'TOOL_CALL_END'; payload: { id: string; result: string } }
  | { type: 'TOOL_CALL_ERROR'; payload: { id: string; error: string } }

状態更新のヘルパー関数

// 类型安全的状态更新辅助函数
function updateMessage(state: AppState, messageId: string, updates: Partial<Message>): AppState {
  const messageIndex = state.conversation.messages.findIndex(msg => msg.id === messageId)

  if (messageIndex === -1) {
    console.warn(`Message with id ${messageId} not found`)
    return state
  }

  const updatedMessages = [...state.conversation.messages]
  updatedMessages[messageIndex] = {
    ...updatedMessages[messageIndex],
    ...updates
  }

  return {
    ...state,
    conversation: {
      ...state.conversation,
      messages: updatedMessages
    }
  }
}

function addMessage(state: AppState, message: Message): AppState {
  return {
    ...state,
    conversation: {
      ...state.conversation,
      messages: [...state.conversation.messages, message]
    }
  }
}

状態のテスト

ユニットテスト

import { appStateReducer, initialAppState } from '@/lib/app-state-manager'

describe('App State Reducer', () => {
  it('should add message correctly', () => {
    const message: Message = {
      id: 'test-1',
      role: 'user',
      content: 'Hello',
      timestamp: Date.now()
    }

    const action = { type: 'MESSAGE_ADD', payload: { message } }
    const newState = appStateReducer(initialAppState, action)

    expect(newState.conversation.messages).toHaveLength(1)
    expect(newState.conversation.messages[0]).toEqual(message)
  })

  it('should update connection state', () => {
    const action = { type: 'CONNECTION_CONNECTING', payload: { url: 'ws://test' } }
    const newState = appStateReducer(initialAppState, action)

    expect(newState.connection.state).toBe('connecting')
    expect(newState.connection.url).toBe('ws://test')
    expect(newState.connection.error).toBeNull()
  })
})

統合テスト

import { render, screen, fireEvent } from '@testing-library/react'
import { AppStateProvider } from '@/components/app-state-provider'
import { ChatInterface } from '@/components/chat-interface'

function TestWrapper({ children }: { children: React.ReactNode }) {
  return (
    <AppStateProvider>
      {children}
    </AppStateProvider>
  )
}

describe('State Integration', () => {
  it('should handle message sending flow', async () => {
    render(
      <TestWrapper>
        <ChatInterface />
      </TestWrapper>
    )

    const input = screen.getByPlaceholderText('输入你的问题...')
    const sendButton = screen.getByRole('button', { name: /发送/i })

    fireEvent.change(input, { target: { value: 'Test message' } })
    fireEvent.click(sendButton)

    expect(await screen.findByText('Test message')).toBeInTheDocument()
    expect(input).toHaveValue('')
  })
})

デバッグツール

Redux DevTools との統合

// 开发模式下启用Redux DevTools
function createAppStateProvider() {
  const [state, dispatch] = useReducer(
    appStateReducer,
    initialAppState,
    (initial) => {
      // Redux DevTools支持
      if (process.env.NODE_ENV === 'development' && typeof window !== 'undefined') {
        const devTools = (window as any).__REDUX_DEVTOOLS_EXTENSION__
        if (devTools) {
          devTools.connect({
            name: 'ASR-LLM-TTS Frontend',
            trace: true
          })
        }
      }
      return initial
    }
  )

  // 开发模式下记录所有状态变更
  useEffect(() => {
    if (process.env.NODE_ENV === 'development') {
      console.log('State updated:', state)
    }
  }, [state])

  return { state, dispatch }
}

状態スナップショットツール

// 状态快照和恢复工具
function useStateSnapshot() {
  const { state, dispatch } = useAppState()

  const createSnapshot = useCallback(() => {
    const snapshot = {
      timestamp: Date.now(),
      state: JSON.parse(JSON.stringify(state))
    }
    localStorage.setItem('app-state-snapshot', JSON.stringify(snapshot))
    return snapshot
  }, [state])

  const restoreSnapshot = useCallback(() => {
    const saved = localStorage.getItem('app-state-snapshot')
    if (saved) {
      const snapshot = JSON.parse(saved)
      dispatch({ type: 'STATE_RESTORE', payload: snapshot.state })
    }
  }, [dispatch])

  return { createSnapshot, restoreSnapshot }
}

この状態管理システムは、フロントエンドアプリケーションに安定的で予測可能かつ高性能なデータフロー管理を提供し、複雑なインタラクションのシーンでも状態の一貫性と良好なユーザー体験を確保します。