@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.
- package/package.json +5 -1
- package/public/sw.js +28 -5
- package/server/agent-session-manager.ts +827 -0
- package/server/chat-api-plugin.ts +488 -0
- package/server/pi-reader.ts +242 -1
- package/src/App.tsx +2 -0
- package/src/components/chat/ChatInput.tsx +863 -0
- package/src/components/chat/ChatPage.tsx +617 -0
- package/src/components/chat/ChatWindow.tsx +338 -0
- package/src/components/chat/MessageView.tsx +595 -0
- package/src/components/dashboard/DashboardPage.tsx +45 -5
- package/src/components/layout/AppShell.tsx +12 -5
- package/src/components/layout/Sidebar.tsx +2 -0
- package/src/components/providers/ProvidersModelsPage.tsx +28 -10
- package/src/components/sessions/SessionsPage.tsx +466 -148
- package/src/hooks/useAgentSession.ts +1104 -0
- package/src/index.css +24 -0
- package/src/lib/translations/en.ts +69 -0
- package/src/lib/translations/ja.ts +69 -0
- package/src/lib/translations/zh-CN.ts +69 -0
- package/src/lib/translations/zh-TW.ts +69 -0
- package/src/main.tsx +4 -2
- package/src/store/config-store.ts +41 -0
- package/src/types/chat.ts +217 -0
- package/vite.config.ts +141 -0
|
@@ -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
|
-
|
|
15
|
+
{isFullHeightPage ? (
|
|
13
16
|
<Outlet />
|
|
14
|
-
|
|
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:
|
|
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(/[
|
|
83
|
+
.replace(/[^\p{L}\p{N}-]/gu, "")
|
|
83
84
|
.replace(/-+/g, "-")
|
|
84
85
|
.replace(/^-|-$/g, "");
|
|
85
86
|
}
|
|
86
87
|
|
|
87
|
-
//
|
|
88
|
-
//
|
|
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(
|
|
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(`${
|
|
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: `${
|
|
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
|
-
|
|
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
|