解決策提案アプリ
Solution Plan Generator

AI解決策
解決策プランを提案
あなたの悩みを入力すると、AIが期間別のタスクリスト形式で具体的な解決策を提案します。
技術解説
1. Google Gemini APIの利用
このアプリでは、Google Gemini APIを使って、ユーザーの悩みに対する解決策を生成しています。APIルート(Next.js API Routes)を使って、サーバーサイドでAPIを呼び出しています。
// APIルートでGoogle Gemini APIを呼び出す
const apiUrl = `https://generativelanguage.googleapis.com/v1/models/${modelName}:generateContent?key=${apiKey}`;
const response = await fetch(apiUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
contents: [{
parts: [{ text: prompt }]
}]
})
});
const data = await response.json();
const text = data.candidates?.[0]?.content?.parts?.[0]?.text;- Next.js API Routes:
app/apiディレクトリにファイルを配置すると、自動的にAPIエンドポイントとして機能します。サーバーサイドで実行されるため、APIキーなどの秘密情報を安全に扱えます。 - 環境変数:
process.env.GEMINI_API_KEYでAPIキーを取得しています。APIキーは公開リポジトリに含めず、環境変数で管理するのがベストプラクティスです。 - JSON形式のレスポンス: AIからの応答をJSON形式で受け取り、期間別タスクリストとして構造化しています。
2. 状態管理(useState)
フォームの入力値、解決策プラン、ローディング状態、エラー状態などを管理するために、ReactのuseStateフックを使っています。
// 複数の状態を管理
const [problem, setProblem] = useState("");
const [solutionPlan, setSolutionPlan] = useState<SolutionPlan | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
// 状態を更新
setLoading(true); // ローディング開始
setSolutionPlan(data.plan); // プランを設定
setError("エラーメッセージ"); // エラーを設定- useState: コンポーネント内で状態を管理するためのフックです。
[値, 更新関数]の形式で返され、更新関数を呼ぶと画面が自動的に更新されます。 - 型定義:
useState<SolutionPlan | null>(null)のように型を指定することで、TypeScriptが型チェックを行い、エラーを防げます。
3. 期間別タスクリストの実装
AIから受け取ったタスクリストを期間別にグループ化して表示しています。
// 期間別にタスクをグループ化
const tasksByPeriod = solutionPlan
? solutionPlan.tasks.reduce((acc, task) => {
if (!acc[task.period]) {
acc[task.period] = [];
}
acc[task.period].push(task);
return acc;
}, {} as Record<string, TaskItem[]>)
: {};
// 期間別に表示
{Object.entries(tasksByPeriod).map(([period, tasks]) => (
<div key={period}>
<h4>{period}</h4>
{tasks.map((task) => (
<div key={task.id}>{task.content}</div>
))}
</div>
))}- reduce(): 配列をオブジェクトに変換するために使っています。期間をキーとして、その期間のタスクを配列として格納します。
- Object.entries(): オブジェクトを
[キー, 値]の配列に変換して、map()で各期間を順番に処理しています。
4. チェックボックスの状態管理
タスクの完了状態をチェックボックスで管理し、完了したタスクは視覚的に区別できるようにしています。
// タスクの完了状態を切り替え
const toggleTask = (taskId: string) => {
if (!solutionPlan) return;
setSolutionPlan({
...solutionPlan,
tasks: solutionPlan.tasks.map((task) =>
task.id === taskId
? { ...task, completed: !task.completed }
: task
),
});
};
// チェックボックス
<input
type="checkbox"
checked={task.completed}
onChange={() => toggleTask(task.id)}
/>- スプレッド構文(...):
{...solutionPlan}で既存のオブジェクトを展開し、tasksプロパティだけを更新した新しいオブジェクトを作成します。 - map(): タスク配列を順番に処理し、該当するタスクの
completedプロパティだけを反転させます。 - 条件付きスタイル: 完了したタスクには
completedクラスを追加し、スタイルで打ち消し線や背景色を変更しています。
5. 優先度と難易度によるソート
AIから受け取ったタスクを、優先度が高く、達成しやすい順に並べ替えています。
// 優先度と難易度でソート
const tasks = solutionPlan.tasks
.map((task, index) => ({
...task,
id: `task-${index}`,
completed: false,
}))
.sort((a, b) => {
// 優先度が高い順、同じ優先度なら難易度が低い順(簡単な順)
if (a.priority !== b.priority) {
return a.priority - b.priority;
}
return a.difficulty - b.difficulty;
});- sort(): 配列を並べ替えるメソッドです。比較関数を渡すことで、カスタムの並べ替えルールを定義できます。
- 優先度と難易度: 優先度(1-5、1が最高)と難易度(1-5、1が最も簡単)の2つの基準でソートしています。
6. エラーハンドリングとローディング状態
API呼び出し中はローディング表示を出し、エラーが発生した場合はユーザーに分かりやすいメッセージを表示しています。
// エラーハンドリング
try {
setLoading(true);
setError(null);
const response = await fetch("/api/solution-steps", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ problem, triedActions }),
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error || "エラーが発生しました");
}
const data = await response.json();
setSolutionPlan(data.plan);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}- try-catch-finally:
tryブロックでエラーが発生する可能性のある処理を囲み、catchブロックでエラーを処理します。finallyブロックは、成功・失敗に関わらず必ず実行されます。 - ローディング状態: API呼び出し中は
loadingをtrueに設定し、ユーザーに処理中であることを伝えます。
