separated scripts inside config file and fixed nav bar
This commit is contained in:
parent
9e8dc9f84a
commit
aea474bf57
@ -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' },
|
||||
},
|
||||
],
|
||||
|
||||
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
119
website/public/scripts/inject-navigation.js
Normal file
119
website/public/scripts/inject-navigation.js
Normal file
@ -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 })
|
||||
})()
|
||||
48
website/public/styles/navigation.css
Normal file
48
website/public/styles/navigation.css
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,22 @@
|
||||
---
|
||||
// Redirect to the new API documentation landing page
|
||||
return Astro.redirect('/api', 301);
|
||||
---
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Redirecting to API Documentation | Jan</title>
|
||||
<meta http-equiv="refresh" content="0; url=/api" />
|
||||
<link rel="canonical" href="/api" />
|
||||
</head>
|
||||
<body>
|
||||
<div style="display: flex; align-items: center; justify-content: center; min-height: 100vh; font-family: system-ui, sans-serif;">
|
||||
<div style="text-align: center;">
|
||||
<h1>Redirecting...</h1>
|
||||
<p>If you are not redirected automatically, <a href="/api">click here to go to the API Documentation</a>.</p>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
// Fallback redirect in case meta refresh doesn't work
|
||||
window.location.href = '/api';
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user