diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts index 222d306..f2cb6e5 100644 --- a/apps/api/src/index.ts +++ b/apps/api/src/index.ts @@ -62,7 +62,7 @@ app.use("*", async (c, next) => { // GitHub認証関連のルートを登録 app.route("/github", githubRouter); -app.route("/", conversations); +app.route("/conversation", conversations); app.route("/policies", policies); diff --git a/apps/api/src/routes/conversation.ts b/apps/api/src/routes/conversation.ts index 168ca55..e5311cb 100644 --- a/apps/api/src/routes/conversation.ts +++ b/apps/api/src/routes/conversation.ts @@ -101,6 +101,37 @@ conversations.get("/:sessionId/issues", async (c) => { } }); +conversations.post("/:sessionId/generate-requirements", async (c) => { + const userId = c.get("userId"); + if (!userId) return c.json({ error: "Unauthorized" }, 401); + + const { sessionId } = c.req.param(); + const svc = new ConversationService(c.env.GEMINI_API_KEY ?? "", c.env.DB); + + // 所有者チェック + const owner = await svc.getSessionOwner(sessionId); + if (owner !== userId) return c.json({ error: "Forbidden" }, 403); + + try { + const requirementsDoc = + await svc.generateRequirementsDocFromSession(sessionId); + const response = `これまでの対話を基に、要件定義書を生成しました。\n\n${requirementsDoc}`; + + // AIメッセージとしてDBに保存 + const aiMessage = await svc.saveMessage(sessionId, "ai", response); + return c.json({ success: true, data: aiMessage }); + } catch (error) { + console.error("Failed to generate requirements document:", error); + return c.json( + { + error: "Failed to generate requirements document", + details: error instanceof Error ? error.message : "Unknown error", + }, + 500, + ); + } +}); + conversations.post("/auth/logout", async (c) => { const userId = c.get("userId"); const GITHUB_CLIENT_ID = c.env.GITHUB_CLIENT_ID; diff --git a/apps/api/src/services/conversationsService.ts b/apps/api/src/services/conversationsService.ts index 41c1c8a..48667d8 100644 --- a/apps/api/src/services/conversationsService.ts +++ b/apps/api/src/services/conversationsService.ts @@ -57,6 +57,23 @@ export class ConversationService { sessionId: string, userMessage: string, ): Promise { + if (userMessage === "要件定義書を生成") { + try { + const requirementsDoc = + await this.generateRequirementsDocFromSession(sessionId); + const response = + "これまでの対話を基に、要件定義書を生成しました。\n\n" + + requirementsDoc; + return this.saveMessage(sessionId, "ai", response); + } catch (error) { + console.error("要件定義書生成中にエラー:", error); + return this.saveMessage( + sessionId, + "ai", + "申し訳ありません、要件定義書の生成中にエラーが発生しました。もう少し対話を進めてから再試行してください。", + ); + } + } // 1. ユーザーメッセージをDBに保存 await this.saveMessage(sessionId, "user", userMessage); @@ -205,7 +222,7 @@ export class ConversationService { // `requirements`フェーズで、ユーザーの発言が6回に達し、かつ最近拒否されていない場合のみ移行を提案 if ( session.phase === "requirements" && - userMessagesInPhase >= 5 && + userMessagesInPhase >= 8 && !recentlyRejectedTransition ) { console.log( @@ -275,7 +292,7 @@ export class ConversationService { } // 要件定義書を生成するメソッド - private async generateRequirementsDocFromSession( + public async generateRequirementsDocFromSession( sessionId: string, ): Promise { const session = await this.getSession(sessionId); @@ -506,7 +523,7 @@ export class ConversationService { } // メッセージをDBに保存 - private async saveMessage( + public async saveMessage( sessionId: string, role: "user" | "ai", content: string, diff --git a/apps/api/src/services/prompts.ts b/apps/api/src/services/prompts.ts index c6888e4..e4bc7c5 100644 --- a/apps/api/src/services/prompts.ts +++ b/apps/api/src/services/prompts.ts @@ -55,11 +55,17 @@ export const PROMPT_TEMPLATE = ` 3. 上記**3つのの3つの必須項目について、それぞれ**1つずつ**主要な回答をユーザーから引き出しなさい。**、それ以上深掘りせず、速やかに'[TRANSITION_SUGGESTION]'を出力しなさい。 ### フェーズが "requirements" の場合 -**目的**: アイデアを実現するために必要な主要機能やインターフェースを洗い出すこと。 +**目的**: アイデアを実現するために必要な機能要件・非機能要件を洗い出すこと。 +**深掘りポイント**: 以下の3つのポイントのみに絞り、各ポイント最大2-3回の質問で完了させる。 +1. 主要機能の洗い出し(最低限必要な機能) +2. ユーザーインターフェース(どんな画面や操作が必要か) +3. 非機能要件(技術スタック、セキュリティ、UXなど) + **行動**: 1. もし前のフェーズから移行した直後であれば、「ありがとうございます。では、このアプリに必要な機能を一緒に考えていきましょう。まずは思いつくままに、どんな機能がほしいかリストアップしてもらえますか?」という文章が自動で送信される。 -2. ユーザーから提示された機能について、「主要機能とスコープ」「利用者との接点(インターフェース)」「外部環境と依存関係」の3つの必須項目について、それぞれ**1つずつ**主要な回答をユーザーから引き出しなさい。 -3. 上記3つの必須項目について、**3つすべて**主要な回答をユーザーから引き出しなさい。 +2. 対話履歴を参考に、上記3つのポイントのうち、まだ十分に深掘りできていない項目について、具体的な質問を生成しなさい。 +3. 各ポイントについて2-3回質問したら、そのポイントは完了とみなし、次のポイントに進むか、3つすべて完了していれば'[TRANSITION_SUGGESTION]'とだけ返しなさい。 +4. 現在どのポイントについて質問しているかを「主要機能について教えてください」のように明示しなさい。 ### フェーズが "tasks" の場合 **目的**: これまでの対話内容をまとめ、最終成果物の生成をユーザーに確認してもらうこと。 diff --git a/apps/extension/src/components/ModelResponse.tsx b/apps/extension/src/components/ModelResponse.tsx index d734bbf..3192a98 100644 --- a/apps/extension/src/components/ModelResponse.tsx +++ b/apps/extension/src/components/ModelResponse.tsx @@ -54,7 +54,11 @@ export const ModelResponse = memo((props: ModelResponseProps) => { ); } catch (error) { console.error("Issue登録に失敗しました:", error); - alert("Issue登録に失敗しました。もう一度お試しください。"); + let errorMessage = "Issue登録に失敗しました。"; + if (error instanceof Error) { + errorMessage = `Issue登録に失敗しました。\n詳細: ${error.message}`; + } + alert(`${errorMessage}\n\nもう一度お試しください。`); } finally { setIsCreatingIssues(false); setShowRepositorySelection(false); diff --git a/apps/extension/src/hooks/api/generateRequirements.ts b/apps/extension/src/hooks/api/generateRequirements.ts new file mode 100644 index 0000000..4a23ff8 --- /dev/null +++ b/apps/extension/src/hooks/api/generateRequirements.ts @@ -0,0 +1,15 @@ +import { client } from "@/utils/client"; +import { useCallback } from "react"; + +export const useGenerateRequirements = () => { + const generateRequirements = useCallback(async (sessionId: string) => { + console.log("API呼び出し: sessionId =", sessionId); + const response = await client.post( + `conversation/${sessionId}/generate-requirements`, + ); + console.log("API応答:", response); + return (response as { data: { success: boolean; data: any } }).data; + }, []); + + return { generateRequirements }; +}; diff --git a/apps/extension/src/sidepanel/AppLayout.tsx b/apps/extension/src/sidepanel/AppLayout.tsx index 92d8527..8fa6f96 100644 --- a/apps/extension/src/sidepanel/AppLayout.tsx +++ b/apps/extension/src/sidepanel/AppLayout.tsx @@ -1,3 +1,4 @@ +import { useChatStore } from "@/store/chatStore"; import { DropDownMenu } from "@/components/DropDownMenu"; import { IconClock } from "@/components/ui/IconClock"; import { IconPlus } from "@/components/ui/IconPlus"; @@ -29,6 +30,20 @@ export const AppLayout = (props: AppLayoutProps) => { setIsDropdownOpen((prev) => !prev); }, []); + const sessionId = useChatStore((s) => s.sessionId); + const sendMessage = useChatStore((s) => s.sendMessage); + + const handleGenerateRequirements = async () => { + console.log("要件定義書生成ボタンがクリックされました"); + console.log("現在のセッションID", sessionId); + if (!sessionId) { + alert("セッションが見つかりません。会話を開始してください。"); + return; + } + + sendMessage("要件定義書を生成"); + }; + return (
@@ -43,6 +58,8 @@ export const AppLayout = (props: AppLayoutProps) => {