Skip to content

Commit 5d748c5

Browse files
committed
Add Gemini option
1 parent 5bcf868 commit 5d748c5

File tree

5 files changed

+63
-6
lines changed

5 files changed

+63
-6
lines changed

src/ifcchat/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
IfcOpenShell AI Assistant
22
=========================
33

4-
A web-based client-side (pyodide + OpenAI, Anthropic, or OpenRouter API) model interrogation and generation API based on: ifcedit, ifcquery and ifcmcp (ifcopenshell-mcp) packaged in a HTML+JS application.
4+
A web-based client-side (pyodide + OpenAI, Anthropic, Gemini, or OpenRouter API) model interrogation and generation API based on: ifcedit, ifcquery and ifcmcp (ifcopenshell-mcp) packaged in a HTML+JS application.
55

66
### Setup instructions
77

src/ifcchat/api_openai.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1+
function getChatCompletionsUrl(baseURL) {
2+
const root = (baseURL || "https://api.openai.com/v1").replace(/\/+$/, "");
3+
return `${root}/chat/completions`;
4+
}
15

2-
export async function chat({ apiKey, model, messages, tools }) {
3-
const res = await fetch("https://api.openai.com/v1/chat/completions", {
6+
export async function chat({ apiKey, baseURL, model, messages, tools }) {
7+
const res = await fetch(getChatCompletionsUrl(baseURL), {
48
method: "POST",
59
headers: {
610
"Content-Type": "application/json",

src/ifcchat/api_openrouter.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1+
function getChatCompletionsUrl(baseURL) {
2+
const root = (baseURL || "https://openrouter.ai/api/v1").replace(/\/+$/, "");
3+
return `${root}/chat/completions`;
4+
}
15

2-
export async function chat({ apiKey, model, messages, tools }) {
3-
const res = await fetch("https://openrouter.ai/api/v1/chat/completions", {
6+
export async function chat({ apiKey, baseURL, model, messages, tools }) {
7+
const res = await fetch(getChatCompletionsUrl(baseURL), {
48
method: "POST",
59
headers: {
610
"Content-Type": "application/json",

src/ifcchat/app.js

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ const PROVIDERS = {
88
api: openaiApi,
99
apiKeyLabel: "OpenAI API key",
1010
apiKeyPlaceholder: "sk-...",
11+
baseUrlLabel: "Base URL",
12+
baseUrlPlaceholder: "https://api.openai.com/v1",
13+
baseUrlDefault: "https://api.openai.com/v1",
1114
models: [
1215
{
1316
value: "gpt-5",
@@ -38,10 +41,35 @@ const PROVIDERS = {
3841
},
3942
],
4043
},
44+
gemini: {
45+
api: openaiApi,
46+
apiKeyLabel: "Gemini API key",
47+
apiKeyPlaceholder: "AIza...",
48+
baseUrlLabel: "Base URL",
49+
baseUrlPlaceholder: "https://generativelanguage.googleapis.com/v1beta/openai/",
50+
baseUrlDefault: "https://generativelanguage.googleapis.com/v1beta/openai/",
51+
models: [
52+
{
53+
value: "gemini-3-flash-preview",
54+
label: "gemini-3-flash-preview"
55+
},
56+
{
57+
value: "gemini-2.5-flash",
58+
label: "gemini-2.5-flash"
59+
},
60+
{
61+
value: "gemini-2.5-pro",
62+
label: "gemini-2.5-pro"
63+
},
64+
],
65+
},
4166
openrouter: {
4267
api: openrouterApi,
4368
apiKeyLabel: "OpenRouter API key",
4469
apiKeyPlaceholder: "sk-or-v1-...",
70+
baseUrlLabel: "Base URL",
71+
baseUrlPlaceholder: "https://openrouter.ai/api/v1",
72+
baseUrlDefault: "https://openrouter.ai/api/v1",
4573
models: [
4674
{
4775
value: "openai/gpt-oss-20b",
@@ -79,6 +107,9 @@ const sendBtn = $("send");
79107
const inputEl = $("input");
80108
const apiKeyEl = $("apiKey");
81109
const apiKeyLabelEl = $("apiKeyLabel");
110+
const baseUrlRowEl = $("baseUrlRow");
111+
const baseUrlLabelEl = $("baseUrlLabel");
112+
const baseUrlEl = $("baseUrl");
82113
const modelEl = $("model");
83114
const providerEl = $("provider");
84115
const ifcFileEl = $("ifcFile");
@@ -89,6 +120,15 @@ function onProviderChange() {
89120
const provider = PROVIDERS[providerEl.value];
90121
apiKeyLabelEl.innerHTML = `${provider.apiKeyLabel}<span class="small">stored in browser memory; only sent to provider servers</span>`;
91122
apiKeyEl.placeholder = provider.apiKeyPlaceholder;
123+
baseUrlRowEl.hidden = !provider.baseUrlDefault;
124+
if (provider.baseUrlDefault) {
125+
baseUrlLabelEl.innerHTML = `${provider.baseUrlLabel}<span class="small">override the API endpoint for OpenAI-compatible providers</span>`;
126+
baseUrlEl.placeholder = provider.baseUrlPlaceholder;
127+
baseUrlEl.value = provider.baseUrlDefault;
128+
} else {
129+
baseUrlEl.value = "";
130+
baseUrlEl.placeholder = "";
131+
}
92132
modelEl.innerHTML = provider.models.map(m => `<option value="${m.value}">${m.label}</option>`).join("");
93133
}
94134

@@ -251,13 +291,16 @@ async function runAgentTurn(userText) {
251291
const apiKey = apiKeyEl.value.trim();
252292
if (!apiKey) throw new Error("Missing API key");
253293

254-
const { chat } = PROVIDERS[providerEl.value].api;
294+
const provider = PROVIDERS[providerEl.value];
295+
const { chat } = provider.api;
296+
const baseURL = provider.baseUrlDefault ? baseUrlEl.value.trim() : undefined;
255297

256298
messages.push({ role: "user", content: userText });
257299

258300
for (let i = 0; i < 64; i++) {
259301
const response = await chat({
260302
apiKey,
303+
baseURL,
261304
model: modelEl.value,
262305
messages: [{ role: "system", content: SYSTEM_INSTRUCTIONS }, ...messages],
263306
tools,

src/ifcchat/index.html

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
<select id="provider" style="width: 100%;">
1818
<option value="openai">OpenAI</option>
1919
<option value="anthropic">Anthropic (Claude)</option>
20+
<option value="gemini">Gemini</option>
2021
<option value="openrouter">OpenRouter</option>
2122
</select>
2223
</div>
@@ -31,6 +32,11 @@
3132
<input id="apiKey" type="password" placeholder="sk-..." autocomplete="off" style="width: 100%;" />
3233
</div>
3334

35+
<div class="row" id="baseUrlRow">
36+
<label id="baseUrlLabel">Base URL<span class="small">override the API endpoint for OpenAI-compatible providers</span></label>
37+
<input id="baseUrl" type="text" placeholder="https://api.openai.com/v1" autocomplete="off" style="width: 100%;" />
38+
</div>
39+
3440
<hr />
3541

3642
<div class="row" style="margin-bottom: 10px;">

0 commit comments

Comments
 (0)