feat: Initial clean release of CHM
一些检查失败了
automerge / automerge (push) Has been cancelled
react-native-android-build-apk / AssembleRelease (push) Has been cancelled
Build macOS M1 / Main (push) Has been cancelled
Joplin Continuous Integration / Main (macos-15-intel) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-11-vs2026-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-2025) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04-arm) (push) Has been cancelled
Joplin UI tests / Main (ubuntu-22.04) (push) Has been cancelled
Joplin UI tests / Main (windows-2025) (push) Has been cancelled
Joplin UI tests / Mobile (push) Has been cancelled
Close stale issues / ProcessStaleIssues (push) Has been cancelled
一些检查失败了
automerge / automerge (push) Has been cancelled
react-native-android-build-apk / AssembleRelease (push) Has been cancelled
Build macOS M1 / Main (push) Has been cancelled
Joplin Continuous Integration / Main (macos-15-intel) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / Main (ubuntu-22.04-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-11-vs2026-arm) (push) Has been cancelled
Joplin Continuous Integration / Main (windows-2025) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04) (push) Has been cancelled
Joplin Continuous Integration / ServerDockerImage (ubuntu-22.04-arm) (push) Has been cancelled
Joplin UI tests / Main (ubuntu-22.04) (push) Has been cancelled
Joplin UI tests / Main (windows-2025) (push) Has been cancelled
Joplin UI tests / Mobile (push) Has been cancelled
Close stale issues / ProcessStaleIssues (push) Has been cancelled
这个提交包含在:
@@ -0,0 +1,86 @@
|
||||
import { _ } from '../../../../locale';
|
||||
import Note from '../../../../models/Note';
|
||||
import SearchService from '../../../ai/SearchService';
|
||||
import { ToolError } from '../types';
|
||||
import buildTool from '../utils/buildTool';
|
||||
|
||||
interface Input {
|
||||
query?: string;
|
||||
notebook_id?: string;
|
||||
tag_id?: string;
|
||||
relevance?: 'strict' | 'normal' | 'loose';
|
||||
}
|
||||
|
||||
const tool = buildTool({
|
||||
id: 'semantic_search_notes',
|
||||
userDescription: (input: Input) => _('Searched for: %s', input.query || _('(empty)')),
|
||||
description: [
|
||||
'Semantic search across notes using the local embeddings index.',
|
||||
'Use this when the user asks by meaning rather than exact words — for example "the note about pet sitters for my dog" rather than "pet sitter".',
|
||||
'Falls back with a clear error if AI embeddings are not enabled in Settings → AI.',
|
||||
'',
|
||||
'Results are ranked chunks (not whole notes), each with the source note id, the chunk text that matched, and a similarity score. Use read_note on the note id to get full context, or read_notes for several notes at once.',
|
||||
].join('\n'),
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
query: { type: 'string', description: 'Free-text query expressing what to find.' },
|
||||
notebook_id: { type: 'string', description: 'Optional: limit search to a single notebook.' },
|
||||
tag_id: { type: 'string', description: 'Optional: limit search to notes with this tag.' },
|
||||
relevance: {
|
||||
type: 'string',
|
||||
enum: ['strict', 'normal', 'loose'],
|
||||
description: 'How strict to be about similarity. "strict" returns fewer high-confidence chunks; "loose" returns more candidates.',
|
||||
default: 'normal',
|
||||
},
|
||||
},
|
||||
required: ['query'],
|
||||
},
|
||||
handler: async (input: Input) => {
|
||||
if (!input.query || !input.query.trim()) throw new ToolError('Missing "query" parameter');
|
||||
if (input.notebook_id && input.tag_id) throw new ToolError('Pass either "notebook_id" or "tag_id", not both');
|
||||
|
||||
const scope = input.notebook_id
|
||||
? { type: 'folder' as const, folderId: input.notebook_id }
|
||||
: input.tag_id
|
||||
? { type: 'tag' as const, tagId: input.tag_id }
|
||||
: undefined;
|
||||
|
||||
let hits;
|
||||
try {
|
||||
hits = await SearchService.instance().search({
|
||||
query: { text: input.query },
|
||||
scope,
|
||||
relevance: input.relevance ?? 'normal',
|
||||
});
|
||||
} catch (error) {
|
||||
// SearchService throws when no embedding provider is active — that's
|
||||
// a configuration mistake the LLM should report to the user, not an
|
||||
// internal bug.
|
||||
const message = error instanceof Error ? error.message : 'Semantic search failed';
|
||||
throw new ToolError(message);
|
||||
}
|
||||
|
||||
const noteIds = Array.from(new Set(hits.map(h => h.noteId)));
|
||||
const notes = noteIds.length
|
||||
? await Note.byIds(noteIds, { fields: ['id', 'title', 'parent_id'] })
|
||||
: [];
|
||||
const noteById = new Map(notes.map(n => [n.id, n]));
|
||||
|
||||
const results = hits.map(h => {
|
||||
const note = noteById.get(h.noteId);
|
||||
return {
|
||||
note_id: h.noteId,
|
||||
title: note?.title ?? null,
|
||||
notebook_id: note?.parent_id ?? null,
|
||||
chunk_index: h.chunkIndex,
|
||||
chunk_text: h.chunkText,
|
||||
score: Math.round(h.score * 1000) / 1000,
|
||||
};
|
||||
});
|
||||
|
||||
return { results, total: results.length };
|
||||
},
|
||||
});
|
||||
|
||||
export default tool;
|
||||
在新工单中引用
屏蔽一个用户