浏览器客户端是一个轻量级搜索运行时(<3KB gzipped),用于加载预构建的静态搜索索引文件,并使用关键字匹配和整数向量余弦相似度进行打分,无需 Web Worker、WASM 或神经网络模型权重。
软件包安装与导入
npm install docmd-search
import { load, search, isReady } from 'docmd-search/client';
<script src="https://unpkg.com/docmd-search/dist/client/index.js"></script>
<script>
const { load, search, isReady } = window.DocmdSearch;
</script>
基本使用示例
import { load, search } from 'docmd-search/client';
// 从预构建的 _docmd-search 目录初始化索引
await load('/assets/_docmd-search');
// 执行搜索查询
const results = search('deploy kubernetes', 10);
for (const result of results) {
console.log(`${result.chunk.file} → ${result.chunk.heading} (${result.score.toFixed(2)})`);
}
客户端 API 参考
load(basePath, onBatchLoaded?)
从 basePath 获取索引文件。读取 manifest.json,加载 batches/000.json 以便搜索立即就绪,并在后台异步流式传输剩余的索引批次。
function load(
basePath: string,
onBatchLoaded?: (loaded: number, total: number) => void
): Promise<void>
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
basePath |
string |
包含 manifest.json 和批次文件的相对或绝对 URL 路径 |
onBatchLoaded |
function |
每次批次加载完成时触发的回调函数 |
批次流式加载回调示例:
await load('/_docmd-search', (loaded, total) => {
const percent = Math.round((loaded / total) * 100);
console.log(`索引加载进度: ${percent}% (${loaded}/${total} 批次)`);
});
只要 batches/000.json 加载完成,搜索即可正常工作。onBatchLoaded 回调允许你在后台批次流式传输时更新 UI 中的加载指示器。
search(query, topK?)
使用混合打分对已加载的索引批次评估查询。
function search(query: string, topK?: number): SearchResult[]
参数:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
query |
string |
- | 搜索查询字符串 |
topK |
number |
10 |
返回的最大结果数 |
返回值: SearchResult[]
interface SearchResult {
score: number; // 归一化的相关性得分 (0.0 到 1.0)
chunk: {
file: string; // 相对源文档文件路径
heading?: string; // 标题章节上下文
text: string; // 文本块片段
range: [number, number]; // 原始源文件中的字节偏移范围
};
}
在 load() 完成之前运行 search() 会抛出错误。在搜索前使用 isReady() 检查索引是否已加载。
isReady()
如果至少已加载一个批次且搜索就绪,则返回 true。
function isReady(): boolean
getProgress()
返回当前批次加载进度。
function getProgress(): { loaded: number; total: number }
getChunkCount()
返回已加载到内存中的文档文本块总数。
function getChunkCount(): number
HTML UI 集成示例
一个完整的独立搜索栏集成示例:
<input type="text" id="search-input" placeholder="正在加载搜索索引..." disabled />
<div id="search-results"></div>
<script type="module">
import { load, search, isReady } from 'docmd-search/client';
const input = document.getElementById('search-input');
const resultsEl = document.getElementById('search-results');
// 加载静态搜索索引
await load('/assets/_docmd-search', (loaded, total) => {
input.placeholder = `正在加载索引... ${Math.round((loaded / total) * 100)}%`;
});
input.placeholder = '搜索文档...';
input.disabled = false;
input.focus();
// 防抖输入搜索
let timer;
input.addEventListener('input', () => {
clearTimeout(timer);
timer = setTimeout(() => {
if (!isReady()) return;
const results = search(input.value, 8);
resultsEl.innerHTML = results
.map(r => `
<a href="/${r.chunk.file.replace('.md', '/')}">
<strong>${r.chunk.heading || r.chunk.file}</strong>
<p>${r.chunk.text.slice(0, 120)}...</p>
</a>
`)
.join('');
}, 150);
});
</script>
混合打分系统
查询排序通过两阶段混合算法计算候选得分:
阶段 1:BM25 词条匹配
文档文本中的词条匹配按词条饱和度衰减打分:
$$ ext{keywordScore} = \sum rac{ ext{count}(t)}{ ext{count}(t) + 1.5}$$
阶段 2:向量余弦重排序
最高关键字匹配向量作为候选参考。计算候选向量与查询向量的余弦相似度,并将分数归一化到 $[0, 1]$:
$$ ext{normalisedKw} = rac{ ext{keywordScore}}{ ext{keywordScore} + 1}$$
$$ ext{finalScore} = ( ext{normalisedKw} imes 0.6) + ( ext{cosineSimilarity} imes 0.4)$$
索引版本兼容性
当缺少 manifest.json 时,客户端会自动检测旧版的单文件搜索索引模式 (search-index.json),保持与旧版 docmd 的完全向下兼容。