diff --git a/website/astro.config.mjs b/website/astro.config.mjs index f7df88133..922f1a860 100644 --- a/website/astro.config.mjs +++ b/website/astro.config.mjs @@ -30,151 +30,11 @@ export default defineConfig({ head: [ { tag: 'script', - content: ` - // Navigation configuration for Jan docs - const JAN_NAV_CONFIG = { - // Product navigation links - easy to extend for multiple products - links: [ - { - href: '/', - text: 'Docs', - isActive: (path) => path === '/' || (path.startsWith('/') && !path.startsWith('/api')) - }, - { - href: '/api', - text: 'API Reference', - isActive: (path) => path.startsWith('/api') - } - ], - - // Pages that have their own navigation (don't inject nav) - excludePaths: ['/api-reference/', '/api/'] - }; - - document.addEventListener('DOMContentLoaded', function() { - // Update logo link - const logoLink = document.querySelector('a[href="/"]'); - if (logoLink && logoLink.getAttribute('href') === '/') { - logoLink.href = 'https://jan.ai'; - } - - // Add navigation to regular docs pages - // Add navigation to docs pages with retry logic - function addNavigation(retries = 0) { - const currentPath = window.location.pathname; - - // Skip if page has its own navigation - const shouldSkipNav = JAN_NAV_CONFIG.excludePaths.some( - path => currentPath.startsWith(path) - ); - if (shouldSkipNav) return; - - const header = document.querySelector('.header'); - const siteTitle = document.querySelector('.site-title'); - - if (header && siteTitle && !document.querySelector('.custom-nav-links')) { - // Find the right container for nav links - const searchContainer = header.querySelector('[class*="search"]')?.parentElement; - const targetContainer = searchContainer || header.querySelector('.sl-flex') || header; - - if (targetContainer) { - // Create navigation container - const nav = document.createElement('nav'); - nav.className = 'custom-nav-links'; - nav.setAttribute('aria-label', 'Product Navigation'); - - // Create links from configuration - JAN_NAV_CONFIG.links.forEach(link => { - const a = document.createElement('a'); - a.href = link.href; - a.textContent = link.text; - a.className = 'nav-link'; - - // Set active state - if (link.isActive(currentPath)) { - a.setAttribute('aria-current', 'page'); - } - - nav.appendChild(a); - }); - - // Insert navigation in the optimal position - if (searchContainer) { - targetContainer.insertBefore(nav, searchContainer); - } else { - targetContainer.appendChild(nav); - } - } else if (retries < 3) { - // Retry if container not found yet - setTimeout(() => addNavigation(retries + 1), 200); - } - } else if (retries < 3) { - // Retry if header/title not found yet - setTimeout(() => addNavigation(retries + 1), 200); - } - } - - // Start navigation injection - if (document.readyState === 'loading') { - setTimeout(() => addNavigation(), 100); - } else { - addNavigation(); - } - }); - `, + attrs: { src: '/scripts/inject-navigation.js', defer: true }, }, { - tag: 'style', - content: ` - /* Navigation links for regular docs pages */ - .custom-nav-links { - display: inline-flex; - align-items: center; - gap: 0.5rem; - margin: 0 1rem; - } - - .custom-nav-links .nav-link { - display: inline-flex; - align-items: center; - padding: 0.5rem 0.875rem; - border-radius: 0.375rem; - color: var(--sl-color-gray-2); - text-decoration: none; - font-weight: 500; - font-size: 0.875rem; - transition: all 0.2s ease; - white-space: nowrap; - } - - .custom-nav-links .nav-link:hover { - color: var(--sl-color-text); - background: var(--sl-color-gray-6); - } - - .custom-nav-links .nav-link[aria-current="page"] { - color: var(--sl-color-text); - background: var(--sl-color-gray-6); - } - - /* Responsive design */ - @media (max-width: 768px) { - .custom-nav-links { - display: none; - } - } - - @media (min-width: 768px) and (max-width: 1024px) { - .custom-nav-links { - margin: 0 0.5rem; - } - - .custom-nav-links .nav-link { - padding: 0.375rem 0.625rem; - font-size: 0.8125rem; - } - } - `, + tag: 'link', + attrs: { rel: 'stylesheet', href: '/styles/navigation.css' }, }, ], diff --git a/website/public/openapi/openapi.json b/website/public/openapi/openapi.json index 976199abf..cf4ecd136 100644 --- a/website/public/openapi/openapi.json +++ b/website/public/openapi/openapi.json @@ -1,7 +1,7 @@ { "openapi": "3.1.0", "info": { - "title": "👋Jan API", + "title": "Jan API", "description": "OpenAI-compatible API for local inference with Jan. Run AI models locally with complete privacy using llama.cpp's high-performance inference engine. Supports GGUF models with CPU and GPU acceleration. No authentication required for local usage.", "version": "0.3.14", "contact": { @@ -49,7 +49,9 @@ "paths": { "/v1/completions": { "post": { - "tags": ["Completions"], + "tags": [ + "Completions" + ], "summary": "Create completion", "description": "Creates a completion for the provided prompt and parameters. This endpoint is compatible with OpenAI's completions API.", "operationId": "create_completion", @@ -90,7 +92,11 @@ "prompt": "# Python function to calculate fibonacci\ndef fibonacci(n):", "max_tokens": 200, "temperature": 0.3, - "stop": ["\n\n", "def ", "class "] + "stop": [ + "\n\n", + "def ", + "class " + ] } }, "streaming": { @@ -151,7 +157,9 @@ }, "/v1/chat/completions": { "post": { - "tags": ["Chat"], + "tags": [ + "Chat" + ], "summary": "Create chat completion", "description": "Creates a model response for the given chat conversation. This endpoint is compatible with OpenAI's chat completions API.", "operationId": "create_chat_completion", @@ -311,7 +319,9 @@ }, "/v1/models": { "get": { - "tags": ["Models"], + "tags": [ + "Models" + ], "summary": "List available models", "description": "Lists the currently available models and provides basic information about each one such as the owner and availability.", "operationId": "list_models", @@ -360,7 +370,9 @@ }, "/extras/tokenize": { "post": { - "tags": ["Extras"], + "tags": [ + "Extras" + ], "summary": "Tokenize text", "description": "Convert text input into tokens using the model's tokenizer.", "operationId": "tokenize", @@ -387,7 +399,12 @@ "$ref": "#/components/schemas/TokenizeResponse" }, "example": { - "tokens": [15339, 11, 1917, 0] + "tokens": [ + 15339, + 11, + 1917, + 0 + ] } } } @@ -397,7 +414,9 @@ }, "/extras/tokenize/count": { "post": { - "tags": ["Extras"], + "tags": [ + "Extras" + ], "summary": "Count tokens", "description": "Count the number of tokens in the provided text.", "operationId": "count_tokens", @@ -453,7 +472,9 @@ ] } }, - "required": ["input"] + "required": [ + "input" + ] }, "TokenizeResponse": { "type": "object", @@ -466,7 +487,9 @@ "description": "Array of token IDs" } }, - "required": ["tokens"] + "required": [ + "tokens" + ] }, "TokenCountResponse": { "type": "object", @@ -476,7 +499,9 @@ "description": "Number of tokens" } }, - "required": ["count"] + "required": [ + "count" + ] } }, "securitySchemes": { @@ -505,11 +530,14 @@ "no_telemetry": true, "offline_capable": true }, - "model_formats": ["GGUF", "GGML"], + "model_formats": [ + "GGUF", + "GGML" + ], "default_settings": { "context_length": 4096, "batch_size": 512, "threads": "auto" } } -} +} \ No newline at end of file diff --git a/website/public/scripts/inject-navigation.js b/website/public/scripts/inject-navigation.js new file mode 100644 index 000000000..823d82773 --- /dev/null +++ b/website/public/scripts/inject-navigation.js @@ -0,0 +1,119 @@ +// Navigation injection script for Jan documentation +// This script adds navigation links to regular docs pages (not API reference pages) + +;(function () { + // Navigation configuration for Jan docs + const JAN_NAV_CONFIG = { + // Product navigation links - easy to extend for multiple products + links: [ + { + href: '/', + text: 'Docs', + isActive: (path) => + path === '/' || (path.startsWith('/') && !path.startsWith('/api')), + }, + { + href: '/api', + text: 'API Reference', + isActive: (path) => path.startsWith('/api'), + }, + ], + + // Pages that have their own navigation (don't inject nav) + excludePaths: ['/api-reference/', '/api/'], + } + + // Add navigation to docs pages with retry logic + function addNavigation(retries = 0) { + const currentPath = window.location.pathname + + // Skip if page has its own navigation + const shouldSkipNav = JAN_NAV_CONFIG.excludePaths.some((path) => + currentPath.startsWith(path) + ) + if (shouldSkipNav) return + + const header = document.querySelector('.header') + const siteTitle = document.querySelector('.site-title') + const existingNav = document.querySelector('.custom-nav-links') + + if (header && siteTitle && !existingNav) { + // Find the right container for nav links + const searchElement = header.querySelector('[class*="search"]') + const flexContainer = header.querySelector('.sl-flex') + const targetContainer = flexContainer || header + + if (targetContainer) { + // Create navigation container + const nav = document.createElement('nav') + nav.className = 'custom-nav-links' + nav.setAttribute('aria-label', 'Product Navigation') + + // Create links from configuration + JAN_NAV_CONFIG.links.forEach((link) => { + const a = document.createElement('a') + a.href = link.href + a.textContent = link.text + a.className = 'nav-link' + + // Set active state + if (link.isActive(currentPath)) { + a.setAttribute('aria-current', 'page') + } + + nav.appendChild(a) + }) + + // Insert navigation safely + if (searchElement && targetContainer.contains(searchElement)) { + targetContainer.insertBefore(nav, searchElement) + } else { + // Find site title and insert after it + if (siteTitle && targetContainer.contains(siteTitle)) { + siteTitle.insertAdjacentElement('afterend', nav) + } else { + targetContainer.appendChild(nav) + } + } + } else if (retries < 5) { + setTimeout(() => addNavigation(retries + 1), 500) + } + } else if (retries < 5) { + setTimeout(() => addNavigation(retries + 1), 500) + } + } + + // Initialize navigation injection + function initNavigation() { + // Update logo link to jan.ai + const logoLink = document.querySelector('a[href="/"]') + if (logoLink && logoLink.getAttribute('href') === '/') { + logoLink.href = 'https://jan.ai' + } + + // Start navigation injection + if (document.readyState === 'loading') { + setTimeout(() => addNavigation(), 1000) + } else { + addNavigation() + } + } + + // Run when DOM is ready + if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', initNavigation) + } else { + initNavigation() + } + + // Handle page navigation in SPA-like environments + let lastUrl = location.href + new MutationObserver(() => { + const url = location.href + if (url !== lastUrl) { + lastUrl = url + // Re-run navigation injection after navigation + setTimeout(() => addNavigation(), 100) + } + }).observe(document, { subtree: true, childList: true }) +})() diff --git a/website/public/styles/navigation.css b/website/public/styles/navigation.css new file mode 100644 index 000000000..00ff6694e --- /dev/null +++ b/website/public/styles/navigation.css @@ -0,0 +1,48 @@ +/* Navigation links for regular docs pages */ +.custom-nav-links { + display: inline-flex; + align-items: center; + gap: 0.5rem; + margin: 0 1rem; +} + +.custom-nav-links .nav-link { + display: inline-flex; + align-items: center; + padding: 0.5rem 0.875rem; + border-radius: 0.375rem; + color: var(--sl-color-gray-2); + text-decoration: none; + font-weight: 500; + font-size: 0.875rem; + transition: all 0.2s ease; + white-space: nowrap; +} + +.custom-nav-links .nav-link:hover { + color: var(--sl-color-text); + background: var(--sl-color-gray-6); +} + +.custom-nav-links .nav-link[aria-current="page"] { + color: var(--sl-color-text); + background: var(--sl-color-gray-6); +} + +/* Responsive design */ +@media (max-width: 768px) { + .custom-nav-links { + display: none; + } +} + +@media (min-width: 768px) and (max-width: 1024px) { + .custom-nav-links { + margin: 0 0.5rem; + } + + .custom-nav-links .nav-link { + padding: 0.375rem 0.625rem; + font-size: 0.8125rem; + } +} diff --git a/website/src/pages/api-reference.astro b/website/src/pages/api-reference.astro index a3cb2c70a..81e7c3f87 100644 --- a/website/src/pages/api-reference.astro +++ b/website/src/pages/api-reference.astro @@ -1,4 +1,22 @@ ---- -// Redirect to the new API documentation landing page -return Astro.redirect('/api', 301); ---- + + +
+ + +If you are not redirected automatically, click here to go to the API Documentation.
+