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: [
|
head: [
|
||||||
{
|
{
|
||||||
tag: 'script',
|
tag: 'script',
|
||||||
content: `
|
attrs: { src: '/scripts/inject-navigation.js', defer: true },
|
||||||
// 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();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
`,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
tag: 'style',
|
tag: 'link',
|
||||||
content: `
|
attrs: { rel: 'stylesheet', href: '/styles/navigation.css' },
|
||||||
/* 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,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"openapi": "3.1.0",
|
"openapi": "3.1.0",
|
||||||
"info": {
|
"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.",
|
"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",
|
"version": "0.3.14",
|
||||||
"contact": {
|
"contact": {
|
||||||
@ -49,7 +49,9 @@
|
|||||||
"paths": {
|
"paths": {
|
||||||
"/v1/completions": {
|
"/v1/completions": {
|
||||||
"post": {
|
"post": {
|
||||||
"tags": ["Completions"],
|
"tags": [
|
||||||
|
"Completions"
|
||||||
|
],
|
||||||
"summary": "Create completion",
|
"summary": "Create completion",
|
||||||
"description": "Creates a completion for the provided prompt and parameters. This endpoint is compatible with OpenAI's completions API.",
|
"description": "Creates a completion for the provided prompt and parameters. This endpoint is compatible with OpenAI's completions API.",
|
||||||
"operationId": "create_completion",
|
"operationId": "create_completion",
|
||||||
@ -90,7 +92,11 @@
|
|||||||
"prompt": "# Python function to calculate fibonacci\ndef fibonacci(n):",
|
"prompt": "# Python function to calculate fibonacci\ndef fibonacci(n):",
|
||||||
"max_tokens": 200,
|
"max_tokens": 200,
|
||||||
"temperature": 0.3,
|
"temperature": 0.3,
|
||||||
"stop": ["\n\n", "def ", "class "]
|
"stop": [
|
||||||
|
"\n\n",
|
||||||
|
"def ",
|
||||||
|
"class "
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"streaming": {
|
"streaming": {
|
||||||
@ -151,7 +157,9 @@
|
|||||||
},
|
},
|
||||||
"/v1/chat/completions": {
|
"/v1/chat/completions": {
|
||||||
"post": {
|
"post": {
|
||||||
"tags": ["Chat"],
|
"tags": [
|
||||||
|
"Chat"
|
||||||
|
],
|
||||||
"summary": "Create chat completion",
|
"summary": "Create chat completion",
|
||||||
"description": "Creates a model response for the given chat conversation. This endpoint is compatible with OpenAI's chat completions API.",
|
"description": "Creates a model response for the given chat conversation. This endpoint is compatible with OpenAI's chat completions API.",
|
||||||
"operationId": "create_chat_completion",
|
"operationId": "create_chat_completion",
|
||||||
@ -311,7 +319,9 @@
|
|||||||
},
|
},
|
||||||
"/v1/models": {
|
"/v1/models": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": ["Models"],
|
"tags": [
|
||||||
|
"Models"
|
||||||
|
],
|
||||||
"summary": "List available models",
|
"summary": "List available models",
|
||||||
"description": "Lists the currently available models and provides basic information about each one such as the owner and availability.",
|
"description": "Lists the currently available models and provides basic information about each one such as the owner and availability.",
|
||||||
"operationId": "list_models",
|
"operationId": "list_models",
|
||||||
@ -360,7 +370,9 @@
|
|||||||
},
|
},
|
||||||
"/extras/tokenize": {
|
"/extras/tokenize": {
|
||||||
"post": {
|
"post": {
|
||||||
"tags": ["Extras"],
|
"tags": [
|
||||||
|
"Extras"
|
||||||
|
],
|
||||||
"summary": "Tokenize text",
|
"summary": "Tokenize text",
|
||||||
"description": "Convert text input into tokens using the model's tokenizer.",
|
"description": "Convert text input into tokens using the model's tokenizer.",
|
||||||
"operationId": "tokenize",
|
"operationId": "tokenize",
|
||||||
@ -387,7 +399,12 @@
|
|||||||
"$ref": "#/components/schemas/TokenizeResponse"
|
"$ref": "#/components/schemas/TokenizeResponse"
|
||||||
},
|
},
|
||||||
"example": {
|
"example": {
|
||||||
"tokens": [15339, 11, 1917, 0]
|
"tokens": [
|
||||||
|
15339,
|
||||||
|
11,
|
||||||
|
1917,
|
||||||
|
0
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -397,7 +414,9 @@
|
|||||||
},
|
},
|
||||||
"/extras/tokenize/count": {
|
"/extras/tokenize/count": {
|
||||||
"post": {
|
"post": {
|
||||||
"tags": ["Extras"],
|
"tags": [
|
||||||
|
"Extras"
|
||||||
|
],
|
||||||
"summary": "Count tokens",
|
"summary": "Count tokens",
|
||||||
"description": "Count the number of tokens in the provided text.",
|
"description": "Count the number of tokens in the provided text.",
|
||||||
"operationId": "count_tokens",
|
"operationId": "count_tokens",
|
||||||
@ -453,7 +472,9 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": ["input"]
|
"required": [
|
||||||
|
"input"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"TokenizeResponse": {
|
"TokenizeResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@ -466,7 +487,9 @@
|
|||||||
"description": "Array of token IDs"
|
"description": "Array of token IDs"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": ["tokens"]
|
"required": [
|
||||||
|
"tokens"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"TokenCountResponse": {
|
"TokenCountResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
@ -476,7 +499,9 @@
|
|||||||
"description": "Number of tokens"
|
"description": "Number of tokens"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": ["count"]
|
"required": [
|
||||||
|
"count"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"securitySchemes": {
|
"securitySchemes": {
|
||||||
@ -505,7 +530,10 @@
|
|||||||
"no_telemetry": true,
|
"no_telemetry": true,
|
||||||
"offline_capable": true
|
"offline_capable": true
|
||||||
},
|
},
|
||||||
"model_formats": ["GGUF", "GGML"],
|
"model_formats": [
|
||||||
|
"GGUF",
|
||||||
|
"GGML"
|
||||||
|
],
|
||||||
"default_settings": {
|
"default_settings": {
|
||||||
"context_length": 4096,
|
"context_length": 4096,
|
||||||
"batch_size": 512,
|
"batch_size": 512,
|
||||||
|
|||||||
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 @@
|
|||||||
---
|
<!DOCTYPE html>
|
||||||
// Redirect to the new API documentation landing page
|
<html lang="en">
|
||||||
return Astro.redirect('/api', 301);
|
<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