From 228af61155e581a2b12909850940e98ec0dbec2f Mon Sep 17 00:00:00 2001
From: Lee Jackson <130007945+Imagineer99@users.noreply.github.com>
Date: Wed, 8 Apr 2026 12:07:18 +0100
Subject: [PATCH] feat: frontend implementation for Access Endpoint (#4914)
Co-authored-by: Roland Tannous <115670425+rolandtannous@users.noreply.github.com>
---
.../frontend/src/features/chat/chat-page.tsx | 173 ++++++++++++++++++
1 file changed, 173 insertions(+)
diff --git a/studio/frontend/src/features/chat/chat-page.tsx b/studio/frontend/src/features/chat/chat-page.tsx
index cf1ba11d7b..5d1c31d9e6 100644
--- a/studio/frontend/src/features/chat/chat-page.tsx
+++ b/studio/frontend/src/features/chat/chat-page.tsx
@@ -8,6 +8,14 @@ import {
} from "@/components/assistant-ui/model-selector";
import { Thread } from "@/components/assistant-ui/thread";
import { Button } from "@/components/ui/button";
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle,
+} from "@/components/ui/dialog";
+import { Input } from "@/components/ui/input";
import {
Sheet,
SheetContent,
@@ -33,6 +41,7 @@ import {
Settings04Icon,
} from "@hugeicons/core-free-icons";
import { HugeiconsIcon } from "@hugeicons/react";
+import { code as codePlugin } from "@streamdown/code";
import {
type CSSProperties,
type ReactElement,
@@ -44,6 +53,7 @@ import {
useRef,
useState,
} from "react";
+import { Streamdown } from "streamdown";
import { toast } from "sonner";
import { listLocalModels } from "./api/chat-api";
import { ChatSettingsPanel } from "./chat-settings-sheet";
@@ -73,6 +83,42 @@ type LoraCandidate = {
updatedAt?: number;
};
+const ENDPOINT_BASE_URL_FALLBACK = "http://127.0.0.1:8001/v1";
+const SHIKI_THEME = ["github-light", "github-dark"] as ["github-light", "github-dark"];
+
+function buildDefaultEndpointBaseUrl(): string {
+ if (typeof window === "undefined") {
+ return ENDPOINT_BASE_URL_FALLBACK;
+ }
+ return `${window.location.origin}/v1`;
+}
+
+function HighlightedSnippet({
+ language,
+ source,
+}: {
+ language: "python" | "bash";
+ source: string;
+}) {
+ const markdown = useMemo(
+ () => `\`\`\`${language}\n${source}\n\`\`\``,
+ [language, source],
+ );
+
+ return (
+
+
+ {markdown}
+
+
+ );
+}
+
function normalizeModelRef(value: string | null | undefined): string {
return value?.trim().toLowerCase() ?? "";
}
@@ -497,6 +543,11 @@ export function ChatPage(): ReactElement {
// explicitly sets a nonce in handleNewThread.
const [view, setView] = useState(getInitialSingleChatView);
const [settingsOpen, setSettingsOpen] = useState(false);
+ const [endpointDialogOpen, setEndpointDialogOpen] = useState(false);
+ const [endpointBaseUrl, setEndpointBaseUrl] = useState(
+ buildDefaultEndpointBaseUrl,
+ );
+ const [endpointApiKey, setEndpointApiKey] = useState("sk-no-key-required");
const [modelSelectorOpen, setModelSelectorOpen] = useState(false);
const [modelSelectorLocked, setModelSelectorLocked] = useState(false);
const [sidebarOpen, setSidebarOpen] = useState(true);
@@ -537,6 +588,47 @@ export function ChatPage(): ReactElement {
const canCompare = useMemo(() => {
return Boolean(inferenceParams.checkpoint);
}, [inferenceParams.checkpoint]);
+ const modelAlias = inferenceParams.checkpoint || "unsloth/your-model-alias";
+ const endpointPythonSnippet = useMemo(
+ () => `from openai import OpenAI
+
+client = OpenAI(
+ base_url="${endpointBaseUrl}",
+ api_key="${endpointApiKey}",
+)
+
+completion = client.chat.completions.create(
+ model="${modelAlias}",
+ messages=[{"role": "user", "content": "What is 2+2?"}],
+)
+
+print(completion.choices[0].message.content)`,
+ [endpointApiKey, endpointBaseUrl, modelAlias],
+ );
+ const endpointCurlSnippet = useMemo(
+ () => `curl ${endpointBaseUrl}/chat/completions \\
+ -H "Content-Type: application/json" \\
+ -H "Authorization: Bearer ${endpointApiKey}" \\
+ -d '{
+ "model": "${modelAlias}",
+ "messages": [{"role": "user", "content": "What is 2+2?"}]
+ }'`,
+ [endpointApiKey, endpointBaseUrl, modelAlias],
+ );
+
+ useEffect(() => {
+ if (typeof window === "undefined") return;
+ setEndpointBaseUrl((prev) =>
+ prev === ENDPOINT_BASE_URL_FALLBACK ? buildDefaultEndpointBaseUrl() : prev,
+ );
+ }, []);
+
+ useEffect(() => {
+ if (!endpointDialogOpen) return;
+ setEndpointBaseUrl((prev) =>
+ prev.trim().length === 0 ? buildDefaultEndpointBaseUrl() : prev,
+ );
+ }, [endpointDialogOpen]);
const handleCheckpointChange = useCallback(
(
@@ -641,6 +733,13 @@ export function ChatPage(): ReactElement {
const openSettings = useCallback(() => setSettingsOpen(true), []);
const closeSettings = useCallback(() => setSettingsOpen(false), []);
const openSidebar = useCallback(() => setSidebarOpen(true), []);
+ const handleOpenEndpointDialog = useCallback(() => {
+ if (!inferenceParams.checkpoint) {
+ toast.message("Load a model first to access endpoint details.");
+ return;
+ }
+ setEndpointDialogOpen(true);
+ }, [inferenceParams.checkpoint]);
const enterCompare = useCallback(() => {
setViewBeforeCompare((prev) => prev ?? view);
@@ -932,6 +1031,17 @@ export function ChatPage(): ReactElement {
completionTokens={contextUsage.completionTokens}
/>
) : null}
+ {inferenceParams.checkpoint ? (
+
+ ) : null}