@raingor/pi-web-switch 0.4.0 → 0.4.1

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.
@@ -1,18 +1,25 @@
1
- import { Outlet } from "react-router-dom";
1
+ import { Outlet, useLocation } from "react-router-dom";
2
2
  import { Sidebar } from "./Sidebar";
3
3
 
4
4
  export function AppShell() {
5
+ const location = useLocation();
6
+ const isFullHeightPage = location.pathname.startsWith("/chat");
7
+
5
8
  return (
6
9
  <div className="flex h-screen overflow-hidden">
7
10
  <Sidebar />
8
11
  <main
9
- className="flex-1 overflow-y-auto"
12
+ className={isFullHeightPage ? "flex-1 overflow-hidden" : "flex-1 overflow-y-auto"}
10
13
  style={{ backgroundColor: "var(--page-bg)" }}
11
14
  >
12
- <div className="mx-auto max-w-7xl px-8 py-8">
15
+ {isFullHeightPage ? (
13
16
  <Outlet />
14
- </div>
17
+ ) : (
18
+ <div className="mx-auto max-w-7xl px-8 py-8">
19
+ <Outlet />
20
+ </div>
21
+ )}
15
22
  </main>
16
23
  </div>
17
24
  );
18
- }
25
+ }
@@ -7,6 +7,7 @@ import {
7
7
  Globe,
8
8
  Plug,
9
9
  Users,
10
+ MessageSquare,
10
11
  } from "lucide-react";
11
12
  import { cn } from "@/lib/utils";
12
13
  import { useTranslation, LANGUAGES } from "@/lib/i18n";
@@ -14,6 +15,7 @@ import { useState } from "react";
14
15
 
15
16
  const navItems = [
16
17
  { to: "/", icon: LayoutDashboard, key: "nav.dashboard" },
18
+ { to: "/chat", icon: MessageSquare, key: "nav.chat" },
17
19
  { to: "/sessions", icon: History, key: "nav.sessions" },
18
20
  { to: "/memory", icon: Brain, key: "nav.memory" },
19
21
  { to: "/providers", icon: Plug, key: "nav.providers_models" },
@@ -73,19 +73,20 @@ function isValidHttpUrl(value: string): boolean {
73
73
  const DEFAULT_CONTEXT_WINDOW = 262144;
74
74
  const DEFAULT_MAX_TOKENS = 32768;
75
75
 
76
- // Sanitize to a config-safe id: lowercase letters, digits and hyphens only
76
+ // Sanitize to a config-safe id: letters (any script), digits and hyphens.
77
+ // pi shows this key verbatim in its model picker badge, so keep it readable.
77
78
  function sanitizeProviderId(name: string): string {
78
79
  return name
79
80
  .trim()
80
81
  .toLowerCase()
81
82
  .replace(/\s+/g, "-")
82
- .replace(/[^a-z0-9-]/g, "")
83
+ .replace(/[^\p{L}\p{N}-]/gu, "")
83
84
  .replace(/-+/g, "-")
84
85
  .replace(/^-|-$/g, "");
85
86
  }
86
87
 
87
- // Non-ASCII names (e.g. 中文) sanitize to "" — fall back to the endpoint hostname
88
- // so the provider still gets a valid id while keeping the original display name.
88
+ // Empty or symbol-only names fall back to the endpoint hostname so the
89
+ // provider still gets a valid id while keeping the original display name.
89
90
  function deriveProviderId(name: string, baseUrl: string): string {
90
91
  const fromName = sanitizeProviderId(name);
91
92
  if (fromName || !name.trim()) return fromName;
@@ -294,15 +295,19 @@ export function ProvidersModelsPage() {
294
295
  // builtin provider's model list so duplicates keep their models.
295
296
  const sourceProvider = allProviders.find((p) => p.id === id);
296
297
  const sourceModels = existing?.models ?? sourceProvider?.models ?? [];
298
+ const sourceName = sourceProvider?.name ?? id;
299
+ // Derive the new key from the display name so pi's model picker badge
300
+ // matches what the user sees in this UI.
301
+ const baseId = sanitizeProviderId(sourceName) || id;
297
302
  const suffix = "-copy";
298
- let newId = sanitizeProviderId(id + suffix);
303
+ let newId = sanitizeProviderId(baseId + suffix);
299
304
  // Ensure uniqueness against existing provider ids
300
305
  const taken = new Set(allProviders.map((p) => p.id));
301
306
  let i = 2;
302
- while (taken.has(newId)) newId = sanitizeProviderId(`${id}-copy${i++}`);
307
+ while (taken.has(newId)) newId = sanitizeProviderId(`${baseId}-copy${i++}`);
303
308
  // Copy config but clear apiKey; carry models + headers + overrides
304
309
  const cfg: CustomProviderConfig = {
305
- name: `${sourceProvider?.name ?? id} (copy)`,
310
+ name: `${sourceName} (copy)`,
306
311
  baseUrl: existing?.baseUrl ?? sourceProvider?.baseUrl,
307
312
  api: existing?.api ?? sourceProvider?.api,
308
313
  headers: existing?.headers,
@@ -430,6 +435,7 @@ export function ProvidersModelsPage() {
430
435
  provider={selected}
431
436
  onDelete={() => setDeleteConfirm(selected.id)}
432
437
  onDuplicate={() => handleDuplicateProvider(selected.id)}
438
+ onRenamed={(newId) => setSelectedId(newId)}
433
439
  />
434
440
  ) : (
435
441
  <div className="flex h-40 items-center justify-center text-sm text-gray-500">
@@ -597,7 +603,7 @@ function TestConnectionButton({ baseUrl, apiKey }: { baseUrl: string; apiKey?: s
597
603
 
598
604
  // ─── Provider Detail Panel ────────────────────────────────
599
605
 
600
- function ProviderDetail({ provider, onDelete, onDuplicate }: { provider: Provider; onDelete: () => void; onDuplicate: () => void }) {
606
+ function ProviderDetail({ provider, onDelete, onDuplicate, onRenamed }: { provider: Provider; onDelete: () => void; onDuplicate: () => void; onRenamed: (newId: string) => void }) {
601
607
  const { t } = useTranslation();
602
608
  const { currency } = useCurrency();
603
609
  const {
@@ -605,6 +611,7 @@ function ProviderDetail({ provider, onDelete, onDuplicate }: { provider: Provide
605
611
  settings,
606
612
  updateSettings,
607
613
  updateCustomProvider,
614
+ renameCustomProvider,
608
615
  setProviderAuth,
609
616
  removeProviderAuth,
610
617
  addModel,
@@ -858,13 +865,24 @@ function ProviderDetail({ provider, onDelete, onDuplicate }: { provider: Provide
858
865
  setSaveState("saving");
859
866
  let ok = true;
860
867
  if (isCustom) {
861
- ok = await updateCustomProvider(provider.id, {
868
+ const cfgPatch = {
862
869
  name: providerName || undefined,
863
870
  baseUrl: baseUrl || undefined,
864
871
  api,
865
872
  apiKey: apiKey || undefined,
866
873
  compat: { supportsDeveloperRole },
867
- });
874
+ };
875
+ // pi's model picker shows the provider key, not the display name, so
876
+ // rename the key too when the name changes (references get rewritten).
877
+ const nameChanged = providerName !== (provider.name ?? "");
878
+ const newId = nameChanged ? sanitizeProviderId(providerName) : "";
879
+ const taken = new Set(useConfigStore.getState().allProviders.map((p) => p.id));
880
+ if (newId && newId !== provider.id && !taken.has(newId)) {
881
+ ok = await renameCustomProvider(provider.id, newId, cfgPatch);
882
+ if (ok) onRenamed(newId);
883
+ } else {
884
+ ok = await updateCustomProvider(provider.id, cfgPatch);
885
+ }
868
886
  } else {
869
887
  // Builtin providers: baseUrl / api are persisted as a models.json
870
888
  // override (so the user can point them at a proxy/gateway), while the