Skip to content
Draft
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
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ npx prisma dev
ルートディレクトリに .env または .env.local という名前のファイルを作成し、以下の内容を記述
```dotenv
API_KEY=GeminiAPIキー
OPENROUTER_API_KEY=OpenRouterAPIキー
OPENROUTER_MODEL=foo;bar
BETTER_AUTH_URL=http://localhost:3000
DATABASE_URL="postgres://... (prisma devの出力)"
GOOGLE_CLIENT_ID=
Expand All @@ -26,7 +28,9 @@ GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
```

* `API_KEY` はGeminiのAPIキーを作成して設定します。未設定の場合チャットが使えません
* チャット用にGeminiのAPIキーまたはOpenRouterのAPIキーのいずれかが必要です。未設定の場合チャットが使えません
* OpenRouterを使う場合は使用するモデルをセミコロン区切りで `OPENROUTER_MODEL` に設定してください (エラー時に2番目以降にフォールバックします)
* 両方設定されている場合はOpenRouterが使われます
* `GITHUB_CLIENT_ID` `GITHUB_CLIENT_SECRET` はGitHub OAuthのクライアントIDとシークレットを設定します。未設定の場合「GitHubでログイン」が使えません。
作り方については https://www.better-auth.com/docs/authentication/github を参照
* `GOOGLE_CLIENT_ID` `GOOGLE_CLIENT_SECRET` はGoogle OAuthのクライアントIDとシークレットを設定します。未設定の場合「Googleでログイン」が使えません。
Expand Down
61 changes: 57 additions & 4 deletions app/actions/gemini.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,67 @@

import { GoogleGenAI } from "@google/genai";

export async function generateContent(prompt: string, systemInstruction?: string) {
export async function generateContent(
prompt: string,
systemInstruction?: string
): Promise<{ text: string }> {
const openRouterApiKey = process.env.OPENROUTER_API_KEY;
const openRouterModel = process.env.OPENROUTER_MODEL;

if (openRouterApiKey && openRouterModel) {
// Support semicolon-separated list of models for automatic fallback via
// OpenRouter's `models` array parameter.
const models = openRouterModel.split(";").map((m) => m.trim()).filter(Boolean);

const messages: { role: string; content: string }[] = [];
if (systemInstruction) {
messages.push({ role: "system", content: systemInstruction });
}
messages.push({ role: "user", content: prompt });

const response = await fetch("https://openrouter.ai/api/v1/chat/completions", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${openRouterApiKey}`,
},
body: JSON.stringify({ models, messages }),
});

if (!response.ok) {
const body = await response.text();
throw new Error(
`OpenRouter APIエラー: ${response.status} ${response.statusText} - ${body}`
);
}

const data = (await response.json()) as {
choices?: { message?: { content?: string | null } }[];
};
const text = data.choices?.[0]?.message?.content;
if (!text) {
throw new Error("OpenRouterからの応答が空でした");
}
return { text };
}

const params = {
model: "gemini-2.5-flash",
contents: prompt,
config: {
systemInstruction,
}
},
};

const ai = new GoogleGenAI({ apiKey: process.env.API_KEY! });

try {
return await ai.models.generateContent(params);
const result = await ai.models.generateContent(params);
const text = result.text;
if (!text) {
throw new Error("Geminiからの応答が空でした");
}
return { text };
} catch (e: unknown) {
if (String(e).includes("User location is not supported")) {
// For the new API, we can use httpOptions to set a custom baseUrl
Expand All @@ -24,7 +72,12 @@ export async function generateContent(prompt: string, systemInstruction?: string
baseUrl: "https://gemini-proxy.utcode.net",
},
});
return await aiWithProxy.models.generateContent(params);
const result = await aiWithProxy.models.generateContent(params);
const text = result.text;
if (!text) {
throw new Error("Geminiからの応答が空でした");
}
return { text };
} else {
throw e;
}
Expand Down
Loading