Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/api/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
31 changes: 31 additions & 0 deletions apps/api/src/routes/conversation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
23 changes: 20 additions & 3 deletions apps/api/src/services/conversationsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,23 @@ export class ConversationService {
sessionId: string,
userMessage: string,
): Promise<ConversationMessage> {
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);

Expand Down Expand Up @@ -205,7 +222,7 @@ export class ConversationService {
// `requirements`フェーズで、ユーザーの発言が6回に達し、かつ最近拒否されていない場合のみ移行を提案
if (
session.phase === "requirements" &&
userMessagesInPhase >= 5 &&
userMessagesInPhase >= 8 &&
!recentlyRejectedTransition
) {
console.log(
Expand Down Expand Up @@ -275,7 +292,7 @@ export class ConversationService {
}

// 要件定義書を生成するメソッド
private async generateRequirementsDocFromSession(
public async generateRequirementsDocFromSession(
sessionId: string,
): Promise<string> {
const session = await this.getSession(sessionId);
Expand Down Expand Up @@ -506,7 +523,7 @@ export class ConversationService {
}

// メッセージをDBに保存
private async saveMessage(
public async saveMessage(
sessionId: string,
role: "user" | "ai",
content: string,
Expand Down
12 changes: 9 additions & 3 deletions apps/api/src/services/prompts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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" の場合
**目的**: これまでの対話内容をまとめ、最終成果物の生成をユーザーに確認してもらうこと。
Expand Down
6 changes: 5 additions & 1 deletion apps/extension/src/components/ModelResponse.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
15 changes: 15 additions & 0 deletions apps/extension/src/hooks/api/generateRequirements.ts
Original file line number Diff line number Diff line change
@@ -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 };
};
17 changes: 17 additions & 0 deletions apps/extension/src/sidepanel/AppLayout.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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 (
<div className="flex h-full w-full flex-col overflow-hidden">
<header className="flex w-full items-center justify-between gap-3 bg-[#151b23] p-4 shadow-inner-bottom">
Expand All @@ -43,6 +58,8 @@ export const AppLayout = (props: AppLayoutProps) => {
<button
className="h-8 w-8 rounded-md border border-[#3d444d] duration-75 hover:bg-[#656c7626]"
type="button"
onClick={handleGenerateRequirements}
title="要件定義書を生成"
>
<IconClock />
</button>
Expand Down