フロントエンドのアーキテクチャ設計詳解
🏗️ アーキテクチャの概要
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
}
このアーキテクチャ設計により、フロントエンドシステムの高パフォーマンス、高い保守性、拡張のしやすさが確保され、音声対話アシスタントの確かな技術基盤となっています。
ドキュメント