diff --git a/package-lock.json b/package-lock.json index bb7c602..0d5ba3a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.0", "dependencies": { "fuse.js": "^7.0.0", + "marked": "^17.0.1", "mixpanel-browser": "^2.73.0", "svelte-typewriter": "^3.2.3" }, @@ -1306,6 +1307,18 @@ "@jridgewell/sourcemap-codec": "^1.4.15" } }, + "node_modules/marked": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/marked/-/marked-17.0.1.tgz", + "integrity": "sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, "node_modules/mdn-data": { "version": "2.0.30", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", diff --git a/package.json b/package.json index 6f26624..74dd7d8 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ }, "dependencies": { "fuse.js": "^7.0.0", + "marked": "^17.0.1", "mixpanel-browser": "^2.73.0", "svelte-typewriter": "^3.2.3" } diff --git a/public/_redirects b/public/_redirects new file mode 100644 index 0000000..4e31746 --- /dev/null +++ b/public/_redirects @@ -0,0 +1,2 @@ +/* /index.html 200 + diff --git a/src/Cookbook.svelte b/src/Cookbook.svelte new file mode 100644 index 0000000..57153d8 --- /dev/null +++ b/src/Cookbook.svelte @@ -0,0 +1,310 @@ + + +
+ {#if loading} +
+
+

Loading cookbook guides...

+
+ {:else if error} +
+

Error Loading Guides

+

{error}

+ +
+ {:else} + +
+

AI Coding Tool Guides

+

+ Learn how to integrate LiteLLM with popular AI coding tools +

+
+ + {#if guides.length === 0} +
+

No guides available at the moment.

+
+ {:else} +
+ {#each guides as guide} +
openGuide(guide)} on:keydown={(e) => e.key === 'Enter' && openGuide(guide)} role="button" tabindex="0"> +

{guide.title}

+

{guide.description}

+ +
+ {/each} +
+ {/if} + {/if} +
+ + + diff --git a/src/Main.svelte b/src/Main.svelte index c9b4b78..b8e01ba 100644 --- a/src/Main.svelte +++ b/src/Main.svelte @@ -2,16 +2,34 @@ import { onMount } from "svelte"; import App from "./App.svelte"; import Providers from "./Providers.svelte"; + import Cookbook from "./Cookbook.svelte"; import RequestForm from "./RequestForm.svelte"; import { initAnalytics, trackPageView, trackTabChange } from "./analytics"; - let activeTab: "models" | "providers" = "models"; + let activeTab: "models" | "providers" | "cookbook" = "models"; let mobileMenuOpen = false; let requestForm: RequestForm; const GITHUB_URL = "https://github.com/BerriAI/litellm"; const DOCS_URL = "https://docs.litellm.ai"; + // Map URL paths to tab names + function getTabFromPath(path: string): "models" | "providers" | "cookbook" { + if (path === "/providers" || path === "/providers/") { + return "providers"; + } else if (path === "/cookbook" || path === "/cookbook/") { + return "cookbook"; + } + return "models"; + } + + // Get path from tab name + function getPathFromTab(tab: "models" | "providers" | "cookbook"): string { + if (tab === "providers") return "/providers"; + if (tab === "cookbook") return "/cookbook"; + return "/"; + } + function toggleMobileMenu() { mobileMenuOpen = !mobileMenuOpen; } @@ -20,10 +38,22 @@ mobileMenuOpen = false; } - function selectTab(tab: "models" | "providers") { + function selectTab(tab: "models" | "providers" | "cookbook", updateUrl = true) { activeTab = tab; closeMobileMenu(); trackTabChange(tab); + + // Update URL without page reload + if (updateUrl) { + const path = getPathFromTab(tab); + window.history.pushState({ tab }, "", path); + } + } + + // Handle browser back/forward buttons + function handlePopState(event: PopStateEvent) { + const tab = event.state?.tab || getTabFromPath(window.location.pathname); + selectTab(tab, false); } const PROVIDERS_URL = "https://raw.githubusercontent.com/BerriAI/litellm/main/provider_endpoints_support.json"; const MODELS_URL = "https://raw.githubusercontent.com/BerriAI/litellm/main/model_prices_and_context_window.json"; @@ -39,6 +69,16 @@ initAnalytics(); trackPageView('Home'); + // Set initial tab based on URL + const initialTab = getTabFromPath(window.location.pathname); + activeTab = initialTab; + + // Set initial history state + window.history.replaceState({ tab: initialTab }, "", window.location.pathname); + + // Listen for browser back/forward navigation + window.addEventListener("popstate", handlePopState); + // Check if URL has ?request=true to auto-open the form const urlParams = new URLSearchParams(window.location.search); if (urlParams.get('request') === 'true') { @@ -82,6 +122,11 @@ console.error("Failed to load statistics:", error); statsLoading = false; } + + // Cleanup event listener on component destroy + return () => { + window.removeEventListener("popstate", handlePopState); + }; }); @@ -109,7 +154,14 @@ class:active={activeTab === "providers"} on:click={() => selectTab("providers")} > - AI Gateway - Endpoints & Providers + Endpoints & Providers + +