2.2. フロントエンド仕様
React (Vite) で構築されたシングルページアプリケーション (SPA)。ユーザーインターフェースの提供とバックエンドAPIとの通信を担います。
コンポーネント図
| コンポーネント | 説明 |
|---|---|
| Pages | 各画面(ページ)を構成するコンポーネント。 |
| Components | ボタンやナビゲーションバーなど、再利用可能なUI部品。 |
| Stores | Zustandを使用してアプリケーション全体の状態を管理するストア。 |
| Services | APIクライアントなど、外部との通信処理を責務とするモジュール。 |
画面一覧
| 画面 | パス | 認証 | 説明 |
|---|---|---|---|
| HomePage | / | 不要 | アプリケーションのトップページ。ログインや新規登録への導線。 |
| LoginPage | /login | 不要 | ログインフォーム画面。 |
| RegisterPage | /register | 不要 | 新規ユーザー登録フォーム画面。 |
| NetworkChatPage | /chat | 必要 | グラフ可視化とチャットUIを統合したメイン画面。 |
状態管理とAPI連携 (Zustand & Axios)
状態管理にはZustandを使用し、機能ごとにストアを分割します。API通信にはaxiosをベースとしたクライアント (services/api.js) を用い、各ストアのアクション内から呼び出します。APIクライアントは全てのリクエストに認証トークンを付与し、トークン失効時はログインページへリダイレクトします。
authStore
ユーザーの認証状態とアクセストークンを管理します。
- 状態:
isAuthenticated,user,token - 主要なアクションと連携API:
login(username, password):POST /auth/tokenを呼び出し、成功時にトークンを状態とlocalStorageに保存する。register(username, password):POST /auth/registerを呼び出す。logout(): トークンを削除し、状態をリセットする。
networkStore
表示中のグラフデータ(ノード、エッジ、レイアウト情報)の状態を管理します。このストアは、バックエンドから受け取った最新のグラフデータを保持し、可視化コンポーネントに提供する責務を持ちます。
- 状態:
networkId,nodes,edges(ノードの座標、スタイル、エッジのスタイルなど、すべてのレンダリングデータを含む) - 主要なアクションと連携API:
fetchNetwork(networkId):GET /network/{networkId}/visdataを呼び出し、visual_stylesテーブルから最終レンダリングデータを取得する。uploadNetwork(file):POST /network/uploadを呼び出し、新規グラフをアップロードする。setNetworkData(visData):chatStoreのアクション経由で取得した、更新後の最終レンダリングデータで状態を上書きする。
chatStore
会話の履歴やメッセージ一覧を管理します。グラフに対する操作は、すべてこのストアのsendMessageアクションが起点となります。
- 状態:
conversationId,messages,isLoading - 主要なアクションと連携API:
fetchHistory(conversationId):GET /conversations/{id}/messagesを呼び出し、messages状態を更新する。sendMessage(messageContent): ユーザーの自然言語による指示(計算、可視化、レイアウト変更など)をバックエンドのPOST /chat/processへ送信する。バックエンドでの処理の結果、グラフが更新された場合は、networkStoreの状態も更新されます。
コード例: sendMessage アクションの実装イメージ
// chatStore.js (Zustand)
import { create } from 'zustand';
import { api } from '../services/api'; // axios instance
import { useNetworkStore } from './networkStore';
export const useChatStore = create((set, get) => ({
messages: [],
isLoading: false,
sendMessage: async (messageContent) => {
const conversationId = get().conversationId;
set({ isLoading: true });
try {
// ユーザーのメッセージをUIに即時反映
const userMessage = { role: 'user', content: messageContent };
set((state) => ({ messages: [...state.messages, userMessage] }));
// バックエンドに処理をリクエスト
const response = await api.post('/chat/process', {
conversation_id: conversationId,
message: userMessage,
});
// アシスタントの返信をUIに反映
const assistantMessage = response.data.message;
set((state) => ({ messages: [...state.messages, assistantMessage] }));
// グラフが更新された場合、networkStoreのアクションを呼び出す (new_vis_dataは最終レンダリングデータ)
if (response.data.graph_updated) {
useNetworkStore.getState().setNetworkData(response.data.new_vis_data);
}
} catch (error) {
console.error("Failed to send message:", error);
// エラー処理: ユーザーへの通知、ログ記録、必要に応じたリトライなどを実装
} finally {
set({ isLoading: false });
}
},
}));