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

フロントエンドのアーキテクチャ設計詳解

🏗️ アーキテクチャの概要

ASR-LLM-TTS のフロントエンドは、モダンなイベント駆動 + リアクティブアーキテクチャを採用しています。React 19 と Next.js 15 で構築され、WebSocket でバックエンドとリアルタイムに通信することで、スムーズな音声対話体験を実現しています。

📐 設計原則

1. コンポーネント指向の設計

  • 単一責任:各コンポーネントは特定の機能に専念します。
  • 再利用性:複数のシーンで再利用できるよう設計します。
  • 型安全:TypeScript の型を完全にサポートします。
  • テストしやすさ:コンポーネントのインターフェースが明確で、ユニットテストが容易です。

2. 状態管理の原則

  • 状態の一元化:単一の状態ツリーでアプリケーションの状態を管理します。
  • 不変性:Reducer パターンで状態の不変性を保証します。
  • 予測可能性:状態変更の経路が明確で追跡可能です。
  • パフォーマンスの最適化:不要なコンポーネントの再レンダリングを減らします。

3. ユーザー体験の優先

  • ストリーミング体験:LLM の応答をリアルタイムにストリーミング表示します。
  • 視覚的フィードバック:3D による状態の可視化で直感的なフィードバックを提供します。
  • レスポンシブデザイン:さまざまなデバイスや画面に完全対応します。
  • アクセシビリティ:Web アクセシビリティ標準に準拠します。

🔄 主要なアーキテクチャ図

┌─────────────────────────────────────────────────────────────────┐
│                      Next.js 15 应用层                         │
├─────────────────────────────────────────────────────────────────┤
│                                                                 │
│  ┌─────────────────┐    ┌───────────────────────────────────┐   │
│  │    page.tsx     │◄──►│        AppStateProvider           │   │
│  │   (主页面)      │    │      (统一状态管理)              │   │
│  └─────────┬───────┘    └─────────────┬─────────────────────┘   │
│            │                          │                         │
│            ▼                          ▼                         │
│  ┌─────────────────┐         ┌─────────────────┐                │
│  │ ChatInterface   │◄────────│ WebSocket      │                │
│  │  (聊天界面)     │         │ Connection     │                │
│  └─────────────────┘         │ Management     │                │
│            │                  └─────────────────┘                │
│            ▼                          ▲                         │
│  ┌─────────────────┐                  │                         │
│  │     Sphere      │                  │                         │
│  │ (3D状态可视化)   │                  │                         │
│  └─────────────────┘                  │                         │
│            │                          │                         │
│  ┌─────────────────────────────────────┼─────────────────┐       │
│  │              UI组件层               │                 │       │
│  │  ┌─────────────┐  ┌─────────────┐  │  ┌─────────────┐ │       │
│  │  │  shadcn/ui  │  │ TailwindCSS │  │  │ Three.js    │ │       │
│  │  │   组件库    │  │   样式系统  │  │  │  3D引擎     │ │       │
│  │  └─────────────┘  └─────────────┘  │  └─────────────┘ │       │
│  └─────────────────────────────────────┼─────────────────┘       │
└─────────────────────────────────────────┼─────────────────────────┘
                                          ▼
                                ┌─────────────────┐
                                │    Backend      │
                                │  WebSocket API  │
                                └─────────────────┘

📱 コンポーネントアーキテクチャの階層

1. ページ層(Pages)

app/page.tsx:アプリケーションのメインページ

export default function Home() {
  const { t } = useI18n()
  const [skinTheme, setSkinTheme] = useState<"default" | "alternative" | "happy">("default")

  return (
    <>
      <DynamicHead />
      <main className="flex min-h-screen flex-col items-center justify-between">
        {/* 设置面板 */}
        <div className="absolute top-4 right-4 z-10">
          <LanguageSwitcher />
          {/* 皮肤切换 */}
        </div>

        {/* 主要内容区 */}
        <div className="w-full max-w-5xl flex flex-col md:flex-row gap-8">
          <Sphere skinTheme={skinTheme} />
          <ChatInterface />
        </div>
      </main>
    </>
  )
}

特長:

  • レイアウト管理:レスポンシブレイアウトでモバイルにも対応
  • テーマの切り替え:複数のスキンテーマに対応
  • 国際化:i18n システムを統合
  • SEO 最適化:動的な Head 管理

2. 状態管理層(State Management)

AppStateProvider:状態を一元管理するプロバイダー

interface AppStateContextType {
  // 状态
  state: AppState

  // 基础操作
  dispatch: React.Dispatch<AppAction>

  // 连接管理
  connectWebSocket: (url?: string) => void
  disconnectWebSocket: () => void
  sendMessage: (message: WebSocketMessage) => boolean

  // 消息管理
  addMessage: (message: Omit<Message, 'id' | 'timestamp'>) => string
  updateMessage: (id: string, updates: Partial<Message>) => void

  // UI状态管理
  setInputMode: (mode: InputMode) => void
  setListening: (isListening: boolean) => void

  // 工具调用管理
  startToolCall: (toolCall: ToolCall) => void
  endToolCall: (id: string, result: string) => void
}

状態の構造:

interface AppState {
  connection: {
    state: ConnectionState
    url: string
    error: string | null
    reconnectAttempts: number
  }
  conversation: {
    messages: Message[]
    currentInput: string
    currentTurnId: string | null
    isAssistantTyping: boolean
    isReceivingStream: boolean
  }
  ui: {
    inputMode: InputMode
    isListening: boolean
    asrResult: string
    isInputDisabled: boolean
  }
  system: {
    statuses: Record<string, SystemStatus>
    errors: ErrorInfo[]
    lastMessage: WebSocketMessage | null
  }
  tools: {
    activeCalls: Record<string, ToolCall>
  }
}

3. ビジネスコンポーネント層(Business Components)

ChatInterface - チャット画面コンポーネント

主な機能:

  • メッセージ表示:テキスト、ツール呼び出し、思考の連鎖の表示に対応
  • 入力処理:2 つの入力モード(キーボード/音声)
  • ストリーミング更新:LLM の応答をリアルタイムにストリーミング表示
  • ツール呼び出しの可視化:Function Calling の過程を表示

コンポーネントの構造:

export function ChatInterface() {
  const { state, setInput, sendUserInput, setInputMode, interruptTurn } = useAppState()

  // 消息处理
  const handleSendMessage = useCallback(() => {
    if (!currentInput.trim()) return
    sendUserInput()
  }, [currentInput, sendUserInput])

  // 模式切换
  const toggleInputMode = useCallback(() => {
    const newMode = inputMode === 'keyboard' ? 'voice' : 'keyboard'
    setInputMode(newMode)
  }, [inputMode, setInputMode])

  return (
    <div className="flex flex-col h-[600px] w-full max-w-2xl mx-auto border rounded-lg">
      {/* 消息区域 */}
      <ScrollArea className="flex-1 p-4">
        {messages.map((message) => (
          <MessageBubble key={message.id} message={message} />
        ))}
      </ScrollArea>

      {/* 输入区域 */}
      <div className="border-t p-4">
        <MessageInput />
      </div>
    </div>
  )
}

Sphere - 3D 状態可視化コンポーネント

技術的な実装:

  • Three.js によるレンダリング:WebGL のハードウェアアクセラレーション
  • カスタムシェーダー:GLSL の頂点シェーダーとフラグメントシェーダー
  • アニメーションシステム:状態に基づくアニメーションパラメータのマッピング
  • パフォーマンスの最適化:インテリジェントなフレームレート制御

アニメーション状態のマッピング:

const ANIMATION_PARAMS = {
  idle: {
    amplitude: 0.02,
    frequency: 0.3,
    color1: new THREE.Color("#1A1A1A"),
    color2: new THREE.Color("#333333"),
    timeIncrement: 0.05,
  },
  listening: {
    amplitude: 0.15,
    frequency: 2.0,
    color1: new THREE.Color("#00E0A0"),
    color2: new THREE.Color("#00C0E0"),
    timeIncrement: 0.05,
  },
  speaking: {
    amplitude: 0.18,
    frequency: 4.0,
    color1: new THREE.Color("#1E00FF"),
    color2: new THREE.Color("#9D00FF"),
    timeIncrement: 0.08,
  }
}

4. UI コンポーネント層(UI Components)

shadcn/ui ベースのコンポーネントシステム:

基本コンポーネント:

  • Button、Input、Textarea - フォームコンポーネント
  • Dialog、Toast、AlertDialog - インタラクションコンポーネント
  • ScrollArea、Card、Separator - レイアウトコンポーネント

カスタムコンポーネント:

  • LanguageSwitcher - 言語切り替え
  • MessageBubble - メッセージ吹き出し
  • ToolCallDisplay - ツール呼び出しの表示
  • ReasoningDisplay - 思考の連鎖の表示

🔄 データフローのアーキテクチャ

1. WebSocket のデータフロー

用户操作 → UI组件 → AppStateProvider → WebSocket发送
                                          ↓
后端响应 ← UI更新 ← 状态更新 ← 消息处理 ← WebSocket接收

メッセージ処理の流れ:

const handleWebSocketMessage = useCallback((message: WebSocketMessage) => {
  switch (message.type) {
    case 'LLM_CHUNK':
      // 流式内容更新
      dispatch({ type: 'STREAM_CHUNK', payload: message.payload })
      break

    case 'TOOL_CALL_START':
      // 工具调用开始
      dispatch({ type: 'TOOL_CALL_START', payload: { toolCall } })
      break

    case 'SYSTEM_STATUS':
      // 系统状态更新
      dispatch({ type: 'SYSTEM_STATUS_UPDATE', payload: message.payload })
      break
  }
}, [])

2. 状態更新の流れ

Reducer パターン:

function appStateReducer(state: AppState, action: AppAction): AppState {
  switch (action.type) {
    case 'MESSAGE_ADD':
      return {
        ...state,
        conversation: {
          ...state.conversation,
          messages: [...state.conversation.messages, action.payload.message]
        }
      }

    case 'STREAM_CHUNK':
      // 流式内容更新逻辑
      return updateStreamContent(state, action.payload)

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

    default:
      return state
  }
}

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

1. コンポーネントの最適化

React.memo の使用:

const MessageBubble = React.memo(({ message }: { message: Message }) => {
  // 只在message变化时重新渲染
  return <div className="message-bubble">{message.content}</div>
}, (prevProps, nextProps) => {
  return prevProps.message.id === nextProps.message.id &&
         prevProps.message.content === nextProps.message.content
})

useCallback による最適化:

const handleSendMessage = useCallback(() => {
  if (!currentInput.trim()) return
  sendUserInput()
}, [currentInput, sendUserInput]) // 依赖项最小化

2. 状態更新の最適化

一括更新:

// 避免多次setState导致的重复渲染
const updateMessageWithToolCalls = useCallback((messageId: string, toolCalls: ToolCall[]) => {
  dispatch({
    type: 'MESSAGE_BATCH_UPDATE',
    payload: {
      id: messageId,
      updates: {
        toolCalls,
        timestamp: Date.now()
      }
    }
  })
}, [])

インテリジェントな重複排除:

// 避免重复处理相同的LLM响应
if (!window.processedLLMResponses) {
  window.processedLLMResponses = new Set()
}

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

3. 3D レンダリングの最適化

フレームレート制御:

useFrame((state, delta) => {
  // 限制最大帧率避免过度消耗
  if (delta > 1/30) return

  // 更新动画参数
  meshRef.current.material.uniforms.time.value += params.timeIncrement
})

リソース管理:

useEffect(() => {
  return () => {
    // 清理Three.js资源
    geometry?.dispose()
    material?.dispose()
    renderer?.dispose()
  }
}, [])

🌍 レスポンシブデザインのアーキテクチャ

1. ブレークポイントシステム

TailwindCSS のブレークポイント:

const breakpoints = {
  sm: '640px',   // 手机横屏/小平板
  md: '768px',   // 平板
  lg: '1024px',  // 笔记本
  xl: '1280px',  // 桌面
  '2xl': '1536px' // 大屏桌面
}

2. レイアウトの適応

フレキシブルレイアウト:

<div className="w-full flex flex-col md:flex-row gap-8">
  <div className="w-full md:w-1/2 flex justify-center">
    <Sphere skinTheme={skinTheme} />
  </div>
  <div className="w-full md:w-1/2">
    <ChatInterface />
  </div>
</div>

モバイル向けの最適化:

const isMobile = useIsMobile()

return (
  <div className={cn(
    "chat-interface",
    isMobile ? "h-screen" : "h-[600px]"
  )}>
    {/* 移动端全屏显示,桌面端固定高度 */}
  </div>
)

🔒 エラー処理のアーキテクチャ

1. エラーバウンダリ

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props)
    this.state = { hasError: false }
  }

  static getDerivedStateFromError(error) {
    return { hasError: true }
  }

  componentDidCatch(error, errorInfo) {
    console.error('Error caught by boundary:', error, errorInfo)
  }

  render() {
    if (this.state.hasError) {
      return <ErrorFallback />
    }
    return this.props.children
  }
}

2. ネットワークエラーの処理

const connectWebSocket = useCallback((url?: string) => {
  try {
    const newSocket = new WebSocket(wsUrl)

    newSocket.onerror = (error) => {
      dispatch({
        type: 'CONNECTION_ERROR',
        payload: { error: "WebSocket连接错误" }
      })
    }

    newSocket.onclose = (event) => {
      if (event.code !== 1000) {
        // 非正常关闭,尝试重连
        scheduleReconnect()
      }
    }
  } catch (error) {
    dispatch({
      type: 'CONNECTION_ERROR',
      payload: { error: "创建WebSocket连接失败" }
    })
  }
}, [])

🔮 拡張性を考慮した設計

1. プラグイン化アーキテクチャへの備え

interface PluginInterface {
  name: string
  initialize: () => void
  cleanup: () => void
  handleMessage?: (message: WebSocketMessage) => void
}

class PluginManager {
  private plugins: PluginInterface[] = []

  register(plugin: PluginInterface) {
    this.plugins.push(plugin)
    plugin.initialize()
  }

  unregister(pluginName: string) {
    const plugin = this.plugins.find(p => p.name === pluginName)
    if (plugin) {
      plugin.cleanup()
      this.plugins = this.plugins.filter(p => p.name !== pluginName)
    }
  }
}

2. テーマシステムの拡張

interface ThemeConfig {
  colors: Record<string, string>
  animations: Record<string, AnimationParams>
  fonts: Record<string, string>
}

const registerTheme = (name: string, config: ThemeConfig) => {
  THEMES[name] = config
}

このアーキテクチャ設計により、フロントエンドシステムの高パフォーマンス、高い保守性、拡張のしやすさが確保され、音声対話アシスタントの確かな技術基盤となっています。