mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-08-29 04:26:38 +00:00
The VitePress config declared no position for the subsystem or other-interface sections, so `indexOf` returned -1 and sorted them ahead of every declared group: the reference landing page's own sidebar entry sat 1549px below the fold. Four subsystem pages also shared `order` values with pages in the same section, resolved only by sort stability and array concatenation order. Section placement and collapse move into the manifest as a per-locale declaration, and `sectionSpec` throws for an undeclared section instead of sorting it silently to the top. Subsystem pages are grouped by concern, the six topical groups collapse until one holds the page being read, and page order derives from array position. The projector drops the language-switcher line and repository badge the canonical pages carry for their GitHub readers. The navigation bar gains the DeepSeek wordmark, a release-stage tag, and a favicon; the sidebar scrollbar rests invisible and appears while scrolling. Subsystem pages carry a two-level outline, and the two plugin-development tracks now cross-link.
277 lines
10 KiB
TypeScript
277 lines
10 KiB
TypeScript
/** VitePress configuration for the locally projected documentation site. */
|
|
|
|
import { readFileSync } from 'node:fs'
|
|
import { resolve } from 'node:path'
|
|
import type { DefaultTheme, PageData } from 'vitepress'
|
|
import type { ViteDevServer } from 'vite'
|
|
import { withMermaid } from 'vitepress-plugin-mermaid'
|
|
import { docsPages, sectionSpec, type DocsLocale, type DocsPage } from '../docs.ts'
|
|
import { docsSourceFiles, projectDocs } from '../../scripts/project-doc-site.ts'
|
|
|
|
projectDocs()
|
|
|
|
function sidebar(locale: DocsLocale, collection: DocsPage['sidebar']): DefaultTheme.SidebarItem[] {
|
|
const pages = docsPages.filter(page => page.locale === locale && page.sidebar === collection)
|
|
const groups = new Map<string, DocsPage[]>()
|
|
for (const page of pages) {
|
|
const entries = groups.get(page.section) ?? []
|
|
entries.push(page)
|
|
groups.set(page.section, entries)
|
|
}
|
|
return [...groups.entries()]
|
|
.sort(([left], [right]) => sectionSpec(locale, left).index - sectionSpec(locale, right).index)
|
|
.map(([text, entries]) => {
|
|
const { collapsed } = sectionSpec(locale, text)
|
|
return {
|
|
text,
|
|
// A present `collapsed` is what makes the default theme render the
|
|
// group as collapsible at all, so an open group must omit the key.
|
|
...(collapsed === undefined ? {} : { collapsed }),
|
|
items: entries
|
|
.sort((left, right) => left.order - right.order)
|
|
.map(page => ({ text: page.label, link: `/${page.route.replace(/(?:index)?\.md$/, '')}` })),
|
|
}
|
|
})
|
|
}
|
|
|
|
function watchCanonicalDocs(server: ViteDevServer): void {
|
|
const sources = docsSourceFiles()
|
|
server.watcher.add(sources)
|
|
server.watcher.on('change', (changed) => {
|
|
if (!sources.includes(changed)) return
|
|
projectDocs()
|
|
})
|
|
}
|
|
|
|
function escapeVueInterpolation(html: string): string {
|
|
return html.replaceAll('{{', '{{').replaceAll('}}', '}}')
|
|
}
|
|
|
|
const sharedTheme: Pick<DefaultTheme.Config, 'search' | 'socialLinks' | 'editLink'> = {
|
|
search: {
|
|
provider: 'local',
|
|
options: {
|
|
locales: {
|
|
root: {
|
|
translations: {
|
|
button: {
|
|
buttonText: '搜索文档',
|
|
buttonAriaLabel: '搜索文档',
|
|
},
|
|
modal: {
|
|
displayDetails: '显示详细列表',
|
|
resetButtonTitle: '清除搜索',
|
|
backButtonTitle: '关闭搜索',
|
|
noResultsText: '未找到相关结果',
|
|
footer: {
|
|
selectText: '选择',
|
|
selectKeyAriaLabel: '回车键',
|
|
navigateText: '切换',
|
|
navigateUpKeyAriaLabel: '上方向键',
|
|
navigateDownKeyAriaLabel: '下方向键',
|
|
closeText: '关闭',
|
|
closeKeyAriaLabel: 'Esc 键',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
socialLinks: [
|
|
{ icon: 'github', link: 'https://github.com/deepseek-ai/deepseek-harness' },
|
|
],
|
|
editLink: {
|
|
pattern: ({ frontmatter }: PageData) => {
|
|
const data: unknown = frontmatter
|
|
const editSource: unknown = typeof data === 'object' && data !== null ? Reflect.get(data, 'editSource') : undefined
|
|
if (typeof editSource !== 'string') throw new Error('Projected documentation page has no editSource frontmatter.')
|
|
return `https://github.com/deepseek-ai/deepseek-harness/edit/master/${editSource}`
|
|
},
|
|
text: '在 GitHub 上编辑此页',
|
|
},
|
|
}
|
|
|
|
/** Site base path, carrying the leading and trailing slashes VitePress requires. */
|
|
const base = process.env.DOCS_BASE ?? '/'
|
|
|
|
/**
|
|
* The DeepSeek wordmark, inlined so its `currentColor` fills follow the active
|
|
* theme. An `<img>` would freeze the mark at the colors the file declares.
|
|
*/
|
|
const wordmark = readFileSync(resolve(import.meta.dirname, '../public/wordmark.svg'), 'utf8')
|
|
.trim()
|
|
.replace('<svg ', '<svg class="dsh-wordmark" ')
|
|
|
|
/**
|
|
* Styles the default theme does not provide, carried inline because the site
|
|
* runs the stock theme with no theme directory of its own.
|
|
*
|
|
* The navigation-bar lockup pairs with `siteTitle`. The scrollbar rules replace
|
|
* the sidebar's platform bar, which reserves 15px of a 265px column and draws a
|
|
* track the rest of the navigation has no border for; `scrollbarScript` supplies
|
|
* the marker that reveals the thumb. Chrome drops `::-webkit-scrollbar` once
|
|
* `scrollbar-width` is set to anything but `auto`, so the standard properties
|
|
* stay behind a query only Firefox answers.
|
|
*/
|
|
const siteStyle = `
|
|
.dsh-lockup { display: inline-flex; align-items: center; gap: 8px; min-width: 0; }
|
|
.dsh-wordmark { display: block; height: 22px; width: auto; color: var(--vp-c-text-1); }
|
|
.dsh-tag {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
border: 1px solid var(--vp-c-brand-soft);
|
|
border-radius: 999px;
|
|
padding: 1px 9px;
|
|
font-size: 12px;
|
|
font-weight: 500;
|
|
line-height: 18px;
|
|
white-space: nowrap;
|
|
color: var(--vp-c-brand-1);
|
|
}
|
|
|
|
.VPSidebar::-webkit-scrollbar { width: 6px; }
|
|
.VPSidebar::-webkit-scrollbar-track { background: transparent; }
|
|
.VPSidebar::-webkit-scrollbar-thumb {
|
|
background-color: transparent;
|
|
border-radius: 3px;
|
|
transition: background-color 0.3s;
|
|
}
|
|
.VPSidebar[data-scrolling]::-webkit-scrollbar-thumb { background-color: var(--vp-c-text-3); }
|
|
@supports not selector(::-webkit-scrollbar) {
|
|
.VPSidebar { scrollbar-width: thin; scrollbar-color: transparent transparent; }
|
|
.VPSidebar[data-scrolling] { scrollbar-color: var(--vp-c-text-3) transparent; }
|
|
}
|
|
`
|
|
|
|
/**
|
|
* Mark the sidebar while it scrolls, so its scrollbar rests invisible.
|
|
*
|
|
* A sized `::-webkit-scrollbar` opts the element out of the platform's
|
|
* self-hiding overlay bar, leaving one painted at all times; nothing in CSS
|
|
* reports that an element is scrolling. The listener captures instead of
|
|
* bubbling because scroll events do not bubble, and marks a `data-` attribute
|
|
* rather than a class because Vue rewrites `class` wholesale when it patches
|
|
* the element.
|
|
*/
|
|
const scrollbarScript = `
|
|
(() => {
|
|
let idle
|
|
addEventListener('scroll', (event) => {
|
|
const target = event.target
|
|
if (!(target instanceof Element) || !target.classList.contains('VPSidebar')) return
|
|
target.dataset.scrolling = ''
|
|
clearTimeout(idle)
|
|
idle = setTimeout(() => delete target.dataset.scrolling, 800)
|
|
}, true)
|
|
})()
|
|
`
|
|
|
|
/**
|
|
* Navigation-bar title: the DeepSeek wordmark and the release-stage tag.
|
|
* VitePress renders `siteTitle` as HTML.
|
|
*
|
|
* @param previewTag - Localized release-stage label.
|
|
* @returns Markup placed beside the navigation-bar home link.
|
|
*/
|
|
function siteTitle(previewTag: string): string {
|
|
return `<span class="dsh-lockup">${wordmark}<span class="dsh-tag">${previewTag}</span></span>`
|
|
}
|
|
|
|
export default withMermaid({
|
|
title: 'DeepSeek Harness',
|
|
description: '用于构建 Agent Harness 的插件化 SDK',
|
|
base,
|
|
head: [
|
|
// VitePress leaves head hrefs untouched, so the base belongs here explicitly.
|
|
['link', { rel: 'icon', type: 'image/svg+xml', href: `${base}favicon.svg` }],
|
|
['style', {}, siteStyle],
|
|
['script', {}, scrollbarScript],
|
|
],
|
|
cleanUrls: true,
|
|
srcDir: '.generated',
|
|
cacheDir: '.cache',
|
|
outDir: '.dist',
|
|
locales: {
|
|
root: {
|
|
label: '简体中文',
|
|
lang: 'zh-CN',
|
|
themeConfig: {
|
|
siteTitle: siteTitle('技术预览'),
|
|
nav: [
|
|
{ text: '入门', link: '/guide/', activeMatch: '^/guide/' },
|
|
{ text: '开发', link: '/develop/basic/', activeMatch: '^/develop/' },
|
|
{ text: '参考', link: '/reference/', activeMatch: '^/reference/' },
|
|
],
|
|
sidebar: {
|
|
'/guide/': sidebar('root', 'zh-guide'),
|
|
'/develop/': sidebar('root', 'zh-develop'),
|
|
'/reference/': sidebar('root', 'zh-reference'),
|
|
},
|
|
outline: { label: '本页目录' },
|
|
docFooter: { prev: '上一篇', next: '下一篇' },
|
|
darkModeSwitchLabel: '外观',
|
|
lightModeSwitchTitle: '切换到浅色主题',
|
|
darkModeSwitchTitle: '切换到深色主题',
|
|
sidebarMenuLabel: '菜单',
|
|
returnToTopLabel: '返回顶部',
|
|
langMenuLabel: '切换语言',
|
|
skipToContentLabel: '跳至内容',
|
|
},
|
|
},
|
|
en: {
|
|
label: 'English',
|
|
lang: 'en-US',
|
|
link: '/en/',
|
|
themeConfig: {
|
|
siteTitle: siteTitle('Preview'),
|
|
nav: [
|
|
{ text: 'Guide', link: '/en/guide/', activeMatch: '^/en/guide/' },
|
|
{ text: 'Develop', link: '/en/develop/basic/', activeMatch: '^/en/develop/' },
|
|
{ text: 'Reference', link: '/en/reference/', activeMatch: '^/en/reference/' },
|
|
],
|
|
sidebar: {
|
|
'/en/guide/': sidebar('en', 'en-guide'),
|
|
'/en/develop/': sidebar('en', 'en-develop'),
|
|
'/en/reference/': sidebar('en', 'en-reference'),
|
|
},
|
|
editLink: {
|
|
pattern: ({ frontmatter }: PageData) => {
|
|
const data: unknown = frontmatter
|
|
const editSource: unknown = typeof data === 'object' && data !== null ? Reflect.get(data, 'editSource') : undefined
|
|
if (typeof editSource !== 'string') throw new Error('Projected documentation page has no editSource frontmatter.')
|
|
return `https://github.com/deepseek-ai/deepseek-harness/edit/master/${editSource}`
|
|
},
|
|
text: 'Edit this page on GitHub',
|
|
},
|
|
outline: { label: 'On this page' },
|
|
docFooter: { prev: 'Previous', next: 'Next' },
|
|
},
|
|
},
|
|
},
|
|
vite: {
|
|
// `srcDir` puts the Vite root inside the disposable generated tree, whose
|
|
// own `public/` no tracked asset can live in.
|
|
publicDir: resolve(import.meta.dirname, '../public'),
|
|
plugins: [
|
|
{
|
|
name: 'deepseek-harness-doc-projector',
|
|
configureServer: watchCanonicalDocs,
|
|
},
|
|
],
|
|
},
|
|
markdown: {
|
|
config(md) {
|
|
const renderText = md.renderer.rules.text
|
|
const renderCode = md.renderer.rules.code_inline
|
|
if (renderText === undefined || renderCode === undefined) {
|
|
throw new Error('VitePress Markdown renderer is missing its text or inline-code rule.')
|
|
}
|
|
md.renderer.rules.text = (...args) => escapeVueInterpolation(renderText(...args))
|
|
md.renderer.rules.code_inline = (...args) => escapeVueInterpolation(renderCode(...args))
|
|
},
|
|
},
|
|
mermaid: {},
|
|
themeConfig: sharedTheme,
|
|
})
|