pi-custom-provider-model 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -5
- package/package.json +1 -1
- package/src/service.ts +8 -2
- package/web/app.js +26 -7
- package/web/index.html +3 -1
- package/web/style.css +5 -3
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
A small, local browser panel for configuring custom providers in [Pi](https://pi.dev). English and Indonesian UI. Built against the official **Pi 0.85.1** public extension and SDK APIs.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Published on npm as [`pi-custom-provider-model`](https://www.npmjs.com/package/pi-custom-provider-model). Development uses a local path install; tagged releases are published through GitHub Actions.
|
|
6
6
|
|
|
7
7
|
## What works
|
|
8
8
|
|
|
@@ -17,11 +17,23 @@ A small, local browser panel for configuring custom providers in [Pi](https://pi
|
|
|
17
17
|
- Preserve existing unknown settings/model overrides and JSON comments when editing; detect stale writes and retain the last 10 `models.json` backups.
|
|
18
18
|
- Show credential source without returning saved keys or headers to the browser.
|
|
19
19
|
|
|
20
|
-
##
|
|
20
|
+
## Install
|
|
21
21
|
|
|
22
|
-
Requires Node **22.19+** and Pi **0.85.1+**. Compatibility is currently tested with 0.85.1 on Windows/Edge
|
|
22
|
+
Requires Node **22.19+** and Pi **0.85.1+**. Compatibility is currently tested with 0.85.1 on Windows/Edge.
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
For normal use:
|
|
25
|
+
|
|
26
|
+
```powershell
|
|
27
|
+
pi install npm:pi-custom-provider-model
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
To update later:
|
|
31
|
+
|
|
32
|
+
```powershell
|
|
33
|
+
pi update npm:pi-custom-provider-model
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
For a one-off local development run from this project directory:
|
|
25
37
|
|
|
26
38
|
```powershell
|
|
27
39
|
npm install --ignore-scripts
|
|
@@ -168,7 +180,7 @@ npm run pack:check
|
|
|
168
180
|
|
|
169
181
|
Tests use isolated temporary directories and mock endpoints. Real gateway compatibility and RAM footprint have not been benchmarked yet.
|
|
170
182
|
|
|
171
|
-
See [`docs/design.md`](docs/design.md) for the version-pinned official documentation and
|
|
183
|
+
See [`docs/design.md`](docs/design.md) for the version-pinned official documentation and release design. Tagged releases are published to npm as `pi-custom-provider-model` through GitHub Actions.
|
|
172
184
|
|
|
173
185
|
## License
|
|
174
186
|
|
package/package.json
CHANGED
package/src/service.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
1
2
|
import { join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
2
4
|
import { ModelRuntime, readStoredCredential, SettingsManager, VERSION } from "@earendil-works/pi-coding-agent";
|
|
3
5
|
import { ConfigStore, parseDocument, patch, readText } from "./storage.ts";
|
|
4
6
|
import { discover, endpointUrls, requestHeaders } from "./discovery.ts";
|
|
@@ -6,6 +8,8 @@ import { CAPABILITIES, probeModel, type Capability } from "./probes.ts";
|
|
|
6
8
|
import { LimitCatalog, LIMIT_FIELDS } from "./limits.ts";
|
|
7
9
|
import { APIS, AppError, mergeCompat, validateDraft, validateId, type JsonObject, type ModelInput, type ProviderDraft } from "./types.ts";
|
|
8
10
|
|
|
11
|
+
const MANAGER_VERSION = JSON.parse(readFileSync(fileURLToPath(new URL("../package.json", import.meta.url)), "utf8")).version as string;
|
|
12
|
+
|
|
9
13
|
function visibleCompat(entry: JsonObject): ModelInput["compat"] {
|
|
10
14
|
return typeof entry.compat?.supportsDeveloperRole === "boolean" ? { supportsDeveloperRole: entry.compat.supportsDeveloperRole } : undefined;
|
|
11
15
|
}
|
|
@@ -50,7 +54,7 @@ export class ProviderService {
|
|
|
50
54
|
};
|
|
51
55
|
});
|
|
52
56
|
return {
|
|
53
|
-
dir: this.dir, piVersion: VERSION, revision: current.revision, providers,
|
|
57
|
+
dir: this.dir, piVersion: VERSION, managerVersion: MANAGER_VERSION, revision: current.revision, providers,
|
|
54
58
|
defaultProvider: settings.defaultProvider ?? "", defaultModel: settings.defaultModel ?? "",
|
|
55
59
|
reservedIds: [...this.reserved],
|
|
56
60
|
};
|
|
@@ -175,7 +179,9 @@ export class ProviderService {
|
|
|
175
179
|
const current = await this.store.read();
|
|
176
180
|
this.assertEditable(id, current.data.providers[id]);
|
|
177
181
|
const settings = parseDocument(await readText(join(this.dir, "settings.json")));
|
|
178
|
-
if (settings.defaultProvider === id)
|
|
182
|
+
if (settings.defaultProvider === id) {
|
|
183
|
+
throw new AppError("This provider is Pi's default and cannot be deleted yet. Open another provider, choose Set default on one of its models, then try again.");
|
|
184
|
+
}
|
|
179
185
|
await this.store.update(expected, (text) => patch(text, ["providers", id], undefined));
|
|
180
186
|
await this.onSaved?.();
|
|
181
187
|
return { ok: true, message: "Provider removed. Its credentials remain in auth.json; remove them separately if needed.", state: await this.state() };
|
package/web/app.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
const $ = (id) => document.getElementById(id);
|
|
2
2
|
const translations = {
|
|
3
3
|
id: {
|
|
4
|
-
local:"Workspace lokal",providers:"Provider",addProvider:"+ Tambah provider",configuration:"KONFIGURASI",docs:"Dokumentasi resmi Pi ↗",connections:"KONEKSI",subtitle:"Endpoint dan model pilihanmu. Konfigurasi asli Pi.",refresh:"Muat ulang konfigurasi",connection:"Koneksi",native:"Format Pi",readonly:"Provider ini dikelola Pi atau integrasi lain. Buat provider custom untuk mengedit di sini.",providerId:"ID provider",idHelp:"Nama unik di Pi, misalnya my-gateway.",protocol:"Protokol API",protocolHelp:"Pilih protokol yang didukung endpoint.",requestPreview:"PREVIEW REQUEST",credentialSource:"SUMBER KREDENSIAL",removeKey:"Hapus key tersimpan",bearer:"Tambahkan header Authorization: Bearer secara eksplisit (Pi authHeader)",advanced:"Field lanjutan dan header custom yang sudah ada dipertahankan. Override per model dapat mengubah request efektif.",testConnection:"Tes koneksi",fetchModels:"Tarik model ↗",testHint:"Memeriksa daftar model. Tanpa request generasi.",models:"Model",addModel:"+ Model manual",modelHint:"Pilih model untuk disimpan. ID yang terdaftar belum menjamin dukungan chat, vision, atau tools.",selectAll:"Pilih yang terlihat",modelId:"ID model",context:"Konteks",input:"Input",actions:"Aksi",noModels:"Hubungkan model pertamamu",noModelsHint:"Tarik model dari endpoint atau tambahkan ID secara manual.",saveHelp:"Model → models.json · Key → auth.json",deleteProvider:"Hapus provider",save:"Simpan provider",footnote:"Konfigurasi disimpan lokal. Buka /model di Pi untuk memilih model. Default berlaku saat Pi dibuka kembali.",editModel:"Pengaturan model",displayName:"Nama tampilan (opsional)",maxOutput:"Maksimal token output",vision:"Input gambar",reasoning:"Dukungan reasoning",defaultText:"Default Pi (teks)",defaultOff:"Default Pi (nonaktif)",textOnly:"Teks saja",textImage:"Teks + gambar",off:"Nonaktif",on:"Aktif",metadataHelp:"Biarkan nilai yang belum diketahui memakai default Pi. Mengaktifkan opsi tidak menambahkan kemampuan pada model upstream.",applyModel:"Terapkan pengaturan",newProvider:"Provider baru",selected:"dipilih",edit:"Edit",test:"Tes",setDefault:"Jadikan default",default:"Default",remove:"Hapus",unsaved:"Ada perubahan yang belum disimpan. Lanjutkan?",keyHelp:"Kosongkan untuk mempertahankan key. Key baru disimpan di auth.json.",openaiHelp:"URL asli Pi. Biasanya diakhiri /v1; SDK menambahkan /chat/completions atau /responses.",anthropicHelp:"URL asli Pi. Biasanya tanpa /v1 di akhir; SDK menambahkan /v1/messages. Prefix gateway seperti /anthropic tetap dipakai.",busy:"Sedang memproses…",saved:"Provider tersimpan. Buka /model di Pi untuk memilihnya.",confirmDelete:"Hapus konfigurasi provider ini? Kredensialnya tetap tersimpan.",confirmKey:"Hapus key provider dari auth.json? Sumber key lain tetap dapat dipakai Pi.",confirmTest:"Kirim prompt singkat ke model ini? Tes memakai token, tanpa mengirim file atau percakapan proyek.",defaultSaved:"Default tersimpan untuk sesi Pi berikutnya.",fetchDone:"Daftar model diterima",allFetched:"Semua halaman yang dilaporkan endpoint telah diambil.",removeModel:"Hapus model ini dari pilihan? Perubahan berlaku setelah disimpan.",missingToken:"Buka manager dari link yang ditampilkan Pi.",search:"Cari ID model…",noProviders:"Belum ada provider custom.",saveFirst:"Simpan provider dahulu.",duplicate:"ID model sudah ada.",fallback:"default Pi",keyRemoved:"Key di auth.json dihapus.",working:"Bekerja",connectionOk:"Endpoint daftar model merespons.",refreshDone:"Konfigurasi dimuat ulang.",defaultHelp:"Berlaku saat Pi dibuka kembali.",partial:"Sebagian hasil",matches:"terlihat",
|
|
4
|
+
local:"Workspace lokal",providers:"Provider",addProvider:"+ Tambah provider",configuration:"KONFIGURASI",docs:"Dokumentasi resmi Pi ↗",connections:"KONEKSI",subtitle:"Endpoint dan model pilihanmu. Konfigurasi asli Pi.",refresh:"Muat ulang konfigurasi",connection:"Koneksi",native:"Format Pi",readonly:"Provider ini dikelola Pi atau integrasi lain. Buat provider custom untuk mengedit di sini.",defaultProviderBadge:"Default Pi",defaultProviderNote:"Provider ini adalah default Pi dan belum dapat dihapus. Untuk menghapusnya, buka provider lain lalu pilih ‘Jadikan default’ pada salah satu modelnya.",defaultActionHelp:"‘Jadikan default’ pada Aksi model menetapkan model dan provider tersebut sebagai default Pi untuk pembukaan berikutnya.",providerId:"ID provider",idHelp:"Nama unik di Pi, misalnya my-gateway.",protocol:"Protokol API",protocolHelp:"Pilih protokol yang didukung endpoint.",requestPreview:"PREVIEW REQUEST",credentialSource:"SUMBER KREDENSIAL",removeKey:"Hapus key tersimpan",bearer:"Tambahkan header Authorization: Bearer secara eksplisit (Pi authHeader)",advanced:"Field lanjutan dan header custom yang sudah ada dipertahankan. Override per model dapat mengubah request efektif.",testConnection:"Tes koneksi",fetchModels:"Tarik model ↗",testHint:"Memeriksa daftar model. Tanpa request generasi.",models:"Model",addModel:"+ Model manual",modelHint:"Pilih model untuk disimpan. ID yang terdaftar belum menjamin dukungan chat, vision, atau tools.",selectAll:"Pilih yang terlihat",modelId:"ID model",context:"Konteks",input:"Input",actions:"Aksi",noModels:"Hubungkan model pertamamu",noModelsHint:"Tarik model dari endpoint atau tambahkan ID secara manual.",saveHelp:"Model → models.json · Key → auth.json",deleteProvider:"Hapus provider",save:"Simpan provider",footnote:"Konfigurasi disimpan lokal. Buka /model di Pi untuk memilih model. Default berlaku saat Pi dibuka kembali.",editModel:"Pengaturan model",displayName:"Nama tampilan (opsional)",maxOutput:"Maksimal token output",vision:"Input gambar",reasoning:"Dukungan reasoning",defaultText:"Default Pi (teks)",defaultOff:"Default Pi (nonaktif)",textOnly:"Teks saja",textImage:"Teks + gambar",off:"Nonaktif",on:"Aktif",metadataHelp:"Biarkan nilai yang belum diketahui memakai default Pi. Mengaktifkan opsi tidak menambahkan kemampuan pada model upstream.",applyModel:"Terapkan pengaturan",newProvider:"Provider baru",selected:"dipilih",edit:"Edit",test:"Tes",setDefault:"Jadikan default",default:"Default",remove:"Hapus",unsaved:"Ada perubahan yang belum disimpan. Lanjutkan?",keyHelp:"Kosongkan untuk mempertahankan key. Key baru disimpan di auth.json.",openaiHelp:"URL asli Pi. Biasanya diakhiri /v1; SDK menambahkan /chat/completions atau /responses.",anthropicHelp:"URL asli Pi. Biasanya tanpa /v1 di akhir; SDK menambahkan /v1/messages. Prefix gateway seperti /anthropic tetap dipakai.",busy:"Sedang memproses…",saved:"Provider tersimpan. Buka /model di Pi untuk memilihnya.",confirmDelete:"Hapus konfigurasi provider ini? Kredensialnya tetap tersimpan.",confirmKey:"Hapus key provider dari auth.json? Sumber key lain tetap dapat dipakai Pi.",confirmTest:"Kirim prompt singkat ke model ini? Tes memakai token, tanpa mengirim file atau percakapan proyek.",defaultSaved:"Default tersimpan untuk sesi Pi berikutnya.",fetchDone:"Daftar model diterima",allFetched:"Semua halaman yang dilaporkan endpoint telah diambil.",removeModel:"Hapus model ini dari pilihan? Perubahan berlaku setelah disimpan.",missingToken:"Buka manager dari link yang ditampilkan Pi.",search:"Cari ID model…",noProviders:"Belum ada provider custom.",saveFirst:"Simpan provider dahulu.",duplicate:"ID model sudah ada.",fallback:"default Pi",keyRemoved:"Key di auth.json dihapus.",working:"Bekerja",connectionOk:"Endpoint daftar model merespons.",refreshDone:"Konfigurasi dimuat ulang.",defaultHelp:"Berlaku saat Pi dibuka kembali.",partial:"Sebagian hasil",matches:"terlihat",
|
|
5
5
|
},
|
|
6
6
|
en: {
|
|
7
|
-
newProvider:"New provider",selected:"selected",edit:"Edit",test:"Test",setDefault:"Set default",default:"Default",remove:"Remove",unsaved:"You have unsaved changes. Continue?",keyHelp:"Leave blank to keep the current key. New keys are stored in auth.json.",openaiHelp:"Pi-native URL. Usually ends in /v1; the SDK appends /chat/completions or /responses.",anthropicHelp:"Pi-native URL. Usually no trailing /v1; the SDK appends /v1/messages. Gateway prefixes such as /anthropic are preserved.",busy:"Working…",saved:"Provider saved. Open /model in Pi to select it.",confirmDelete:"Delete this provider configuration? Its credentials will be kept.",confirmKey:"Remove this provider's key from auth.json? Pi may still use other configured key sources.",confirmTest:"Send a short prompt to this model? This uses tokens, but sends no project files or chat history.",defaultSaved:"Default saved for new Pi launches.",fetchDone:"Model list received",allFetched:"All pages reported by the endpoint have been fetched.",removeModel:"Remove this model from the selection? Changes take effect after saving.",missingToken:"Open the manager using the link printed by Pi.",search:"Search model IDs…",noProviders:"No custom providers yet.",saveFirst:"Save the provider first.",duplicate:"This model ID already exists.",fallback:"Pi default",keyRemoved:"Saved key removed from auth.json.",working:"Working",connectionOk:"Model-list endpoint responded.",refreshDone:"Configuration reloaded.",defaultHelp:"Applies to new Pi launches.",partial:"Partial results",matches:"visible",
|
|
7
|
+
newProvider:"New provider",selected:"selected",edit:"Edit",test:"Test",setDefault:"Set default",default:"Default",defaultProviderBadge:"Pi default",defaultProviderNote:"This provider is Pi's default and cannot be deleted yet. To delete it, open another provider and choose ‘Set default’ on one of its models.",defaultActionHelp:"‘Set default’ in a model's Actions makes that model and provider Pi's default for new launches.",remove:"Remove",unsaved:"You have unsaved changes. Continue?",keyHelp:"Leave blank to keep the current key. New keys are stored in auth.json.",openaiHelp:"Pi-native URL. Usually ends in /v1; the SDK appends /chat/completions or /responses.",anthropicHelp:"Pi-native URL. Usually no trailing /v1; the SDK appends /v1/messages. Gateway prefixes such as /anthropic are preserved.",busy:"Working…",saved:"Provider saved. Open /model in Pi to select it.",confirmDelete:"Delete this provider configuration? Its credentials will be kept.",confirmKey:"Remove this provider's key from auth.json? Pi may still use other configured key sources.",confirmTest:"Send a short prompt to this model? This uses tokens, but sends no project files or chat history.",defaultSaved:"Default saved for new Pi launches.",fetchDone:"Model list received",allFetched:"All pages reported by the endpoint have been fetched.",removeModel:"Remove this model from the selection? Changes take effect after saving.",missingToken:"Open the manager using the link printed by Pi.",search:"Search model IDs…",noProviders:"No custom providers yet.",saveFirst:"Save the provider first.",duplicate:"This model ID already exists.",fallback:"Pi default",keyRemoved:"Saved key removed from auth.json.",working:"Working",connectionOk:"Model-list endpoint responded.",refreshDone:"Configuration reloaded.",defaultHelp:"Applies to new Pi launches.",partial:"Partial results",matches:"visible",
|
|
8
8
|
},
|
|
9
9
|
};
|
|
10
10
|
const original = new Map([...document.querySelectorAll("[data-i18n]")].map((el) => [el, el.textContent]));
|
|
@@ -118,7 +118,7 @@ function translate() {
|
|
|
118
118
|
$("editor-title").textContent = state.id || t("newProvider");
|
|
119
119
|
if (noticeTranslation) $("notice").textContent = t(noticeTranslation);
|
|
120
120
|
if (editingLimits) renderLimitEditor();
|
|
121
|
-
renderProviders(); renderModels();
|
|
121
|
+
renderProviders(); renderDefaultProviderNote(); renderModels();
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
async function api(path, data, signal) {
|
|
@@ -173,13 +173,25 @@ function renderProviders() {
|
|
|
173
173
|
button.className = state.id === provider.id ? "active" : "";
|
|
174
174
|
const title = document.createElement("strong"); title.textContent = provider.id;
|
|
175
175
|
const detail = document.createElement("small"); detail.textContent = `${provider.models.length} models · ${provider.api || "Pi managed"}`;
|
|
176
|
-
button.append(title
|
|
176
|
+
button.append(title);
|
|
177
|
+
if (provider.id === state.config?.defaultProvider) {
|
|
178
|
+
const badge = document.createElement("span"); badge.className = "provider-default-badge"; badge.textContent = t("defaultProviderBadge"); button.append(badge);
|
|
179
|
+
}
|
|
180
|
+
button.append(detail);
|
|
177
181
|
button.onclick = () => { if (!state.dirty || confirm(t("unsaved"))) loadProvider(provider.id); };
|
|
178
182
|
list.append(button);
|
|
179
183
|
}
|
|
180
184
|
setDisabled();
|
|
181
185
|
}
|
|
182
186
|
|
|
187
|
+
function renderDefaultProviderNote() {
|
|
188
|
+
const isDefault = !!state.id && state.id === state.config?.defaultProvider;
|
|
189
|
+
const note = $("default-provider-note");
|
|
190
|
+
note.hidden = !isDefault;
|
|
191
|
+
note.textContent = isDefault ? t("defaultProviderNote") : "";
|
|
192
|
+
$("delete-provider").classList.toggle("delete-default-provider", isDefault);
|
|
193
|
+
}
|
|
194
|
+
|
|
183
195
|
function loadProvider(id = null, preserveResults = false) {
|
|
184
196
|
const previous = preserveResults ? new Map(state.models.map((model) => [model.id, model])) : new Map();
|
|
185
197
|
if (!preserveResults) state.results.clear();
|
|
@@ -205,6 +217,7 @@ function loadProvider(id = null, preserveResults = false) {
|
|
|
205
217
|
$("readonly-note").hidden = !state.readOnly;
|
|
206
218
|
$("advanced-note").hidden = !provider?.hasHiddenSettings;
|
|
207
219
|
$("delete-provider").hidden = !provider;
|
|
220
|
+
renderDefaultProviderNote();
|
|
208
221
|
$("model-search").value = "";
|
|
209
222
|
$("diagnostics").hidden = true;
|
|
210
223
|
translate(); preview();
|
|
@@ -556,7 +569,7 @@ async function testModel(model, checks) {
|
|
|
556
569
|
}
|
|
557
570
|
async function setDefault(model) {
|
|
558
571
|
if (!state.id || state.dirty || !model.selected) { notify(t("saveFirst"), "warning"); return; }
|
|
559
|
-
await operation(async () => { const result = await api("default", { id: state.id, modelId: model.id }); state.config = result.state; renderModels(); notify(t("defaultSaved")); });
|
|
572
|
+
await operation(async () => { const result = await api("default", { id: state.id, modelId: model.id }); state.config = result.state; renderProviders(); renderDefaultProviderNote(); renderModels(); notify(t("defaultSaved")); });
|
|
560
573
|
}
|
|
561
574
|
$("provider-form").onsubmit = (event) => {
|
|
562
575
|
event.preventDefault();
|
|
@@ -566,7 +579,13 @@ $("provider-form").onsubmit = (event) => {
|
|
|
566
579
|
});
|
|
567
580
|
};
|
|
568
581
|
$("delete-provider").onclick = () => {
|
|
569
|
-
if (!state.id
|
|
582
|
+
if (!state.id) return;
|
|
583
|
+
if (state.id === state.config?.defaultProvider) {
|
|
584
|
+
notify(t("defaultProviderNote"), "warning");
|
|
585
|
+
$("default-provider-note").scrollIntoView({ behavior: "smooth", block: "center" });
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
if (!confirm(t("confirmDelete"))) return;
|
|
570
589
|
operation(async () => { const result = await api("delete", { id: state.id, revision: state.config.revision }); state.config = result.state; loadProvider(); notify(result.message); });
|
|
571
590
|
};
|
|
572
591
|
$("remove-key").onclick = () => {
|
|
@@ -584,6 +603,6 @@ translate();
|
|
|
584
603
|
if (!token) notify(t("missingToken"), "error");
|
|
585
604
|
else operation(async () => {
|
|
586
605
|
state.config = await api("state");
|
|
587
|
-
$("config-path").textContent = state.config.dir; $("version").textContent = `Pi ${state.config.piVersion} · Manager
|
|
606
|
+
$("config-path").textContent = state.config.dir; $("version").textContent = `Pi ${state.config.piVersion} · Manager ${state.config.managerVersion}`;
|
|
588
607
|
loadProvider(state.config.providers.find((p) => !p.readOnly)?.id || null);
|
|
589
608
|
});
|
package/web/index.html
CHANGED
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
<section class="card">
|
|
28
28
|
<div class="section-heading"><div><span class="step">01</span><h2 data-i18n="connection">Connection</h2></div><span class="tag" data-i18n="native">Pi native</span></div>
|
|
29
29
|
<div id="readonly-note" class="info" hidden data-i18n="readonly">This provider is managed by Pi or another integration. Create a custom provider to edit here.</div>
|
|
30
|
+
<div id="default-provider-note" class="warning" role="status" hidden></div>
|
|
30
31
|
<fieldset id="connection-fields">
|
|
31
32
|
<div class="grid two">
|
|
32
33
|
<label><span data-i18n="providerId">Provider ID</span><input id="provider-id" required maxlength="80" placeholder="my-gateway" autocomplete="off" spellcheck="false"><small data-i18n="idHelp">A unique name used by Pi, e.g. my-gateway.</small></label>
|
|
@@ -48,13 +49,14 @@
|
|
|
48
49
|
<section class="card">
|
|
49
50
|
<div class="section-heading"><div><span class="step">02</span><h2 data-i18n="models">Models</h2><span id="model-count" class="count">0</span></div><div class="action-row"><button id="fill-limits" type="button" class="quiet" data-i18n="fillLimits">Fill missing limits</button><button id="add-model" type="button" class="quiet" data-i18n="addModel">+ Manual model</button></div></div>
|
|
50
51
|
<p class="hint" data-i18n="modelHint">Select models to save. A listed ID does not guarantee chat, vision or tool support.</p>
|
|
52
|
+
<p class="hint" data-i18n="defaultActionHelp">“Set default” in a model's Actions makes that model and provider Pi's default for new launches.</p>
|
|
51
53
|
<p class="hint" data-i18n="limitsHelp">Context/output defaults come from endpoint metadata, then exact, unambiguous matches in Pi's bundled catalog. Gateway limits can differ. Existing values are kept; save the provider to persist filled defaults.</p>
|
|
52
54
|
<p class="hint" data-i18n="probeHelp">Test chat sends one short request. Check capabilities runs chat, a two-turn test tool, reasoning and a generated image (up to 5 requests). Uses API tokens: up to 256 output tokens per request, 2,048 for reasoning. Results appear below each model.</p>
|
|
53
55
|
<div class="model-toolbar"><input id="model-search" type="search" placeholder="Search model IDs…" aria-label="Search models"><label class="checkbox-line"><input id="select-all" type="checkbox"><span data-i18n="selectAll">Select visible</span></label><span id="selected-count" class="hint">0 selected</span></div>
|
|
54
56
|
<div class="table-scroll"><table><thead><tr><th class="check-col"></th><th data-i18n="modelId">Model ID</th><th data-i18n="tokenLimits">Token limits</th><th data-i18n="input">Input</th><th data-i18n="actions">Actions</th></tr></thead><tbody id="models-body"></tbody></table></div>
|
|
55
57
|
<div id="empty-models" class="empty"><span class="empty-icon">◇</span><h3 data-i18n="noModels">Connect your first model</h3><p data-i18n="noModelsHint">Fetch models from your endpoint, or add a model ID manually.</p></div>
|
|
56
58
|
</section>
|
|
57
|
-
<div class="save-bar"><div><strong id="save-summary"></strong><small data-i18n="saveHelp">Models → models.json · Key → auth.json</small></div><div class="action-row"><button id="delete-provider" type="button" class="text-danger" hidden data-i18n="deleteProvider">Delete provider</button><button id="save-provider" type="submit" class="primary" data-i18n="save">Save provider</button></div></div>
|
|
59
|
+
<div class="save-bar"><div><strong id="save-summary"></strong><small data-i18n="saveHelp">Models → models.json · Key → auth.json</small></div><div class="action-row"><button id="delete-provider" type="button" class="text-danger" hidden aria-describedby="default-provider-note" data-i18n="deleteProvider">Delete provider</button><button id="save-provider" type="submit" class="primary" data-i18n="save">Save provider</button></div></div>
|
|
58
60
|
</form>
|
|
59
61
|
<p class="footnote" data-i18n="footnote">Configuration is saved locally. Open /model in Pi to select a saved model. “Set default” applies to new Pi launches.</p>
|
|
60
62
|
</main>
|
package/web/style.css
CHANGED
|
@@ -93,6 +93,7 @@ nav button { text-align: left; border-color: transparent; background: transparen
|
|
|
93
93
|
nav button.active { background: var(--soft); color: var(--accent); border-color: #c9e0d8; }
|
|
94
94
|
nav button strong { display: block; overflow-wrap: anywhere; }
|
|
95
95
|
nav button small { display: block; color: var(--muted); margin-top: 6px; overflow-wrap: anywhere; line-height: 1.6; }
|
|
96
|
+
.provider-default-badge { display: inline-block; margin-top: 8px; border-radius: 6px; background: #e4f4eb; color: #21634e; padding: 4px 9px; font-weight: 600; }
|
|
96
97
|
|
|
97
98
|
.sidebar-footer {
|
|
98
99
|
margin-top: auto;
|
|
@@ -214,7 +215,7 @@ td button { padding: 9px 10px; }
|
|
|
214
215
|
.save-bar small { display: block; color: var(--muted); margin-top: 8px; }
|
|
215
216
|
.footnote { color: var(--muted); line-height: 1.7; margin-top: 24px; }
|
|
216
217
|
|
|
217
|
-
.diagnostics, #notice, .info {
|
|
218
|
+
.diagnostics, #notice, .info, .warning {
|
|
218
219
|
padding: 16px 18px;
|
|
219
220
|
border-radius: 8px;
|
|
220
221
|
line-height: 1.7;
|
|
@@ -226,8 +227,9 @@ td button { padding: 9px 10px; }
|
|
|
226
227
|
}
|
|
227
228
|
|
|
228
229
|
.diagnostics.error, #notice.error { background: #fff1ef; color: #9f3e3e; }
|
|
229
|
-
#notice, .info { margin: 0 0 24px; }
|
|
230
|
-
.diagnostics.warning, #notice.warning { background: #fff6df; color: #
|
|
230
|
+
#notice, .info, .warning { margin: 0 0 24px; }
|
|
231
|
+
.diagnostics.warning, #notice.warning, .warning { background: #fff6df; color: #765718; }
|
|
232
|
+
.delete-default-provider { border: 1px solid #e8caca; padding: 8px 12px; }
|
|
231
233
|
|
|
232
234
|
dialog {
|
|
233
235
|
border: 1px solid var(--line);
|