|
|
@@ -25,6 +25,42 @@ function http(plugin) {
|
|
|
timeout: DEFAULT_TIMEOUT_MS,
|
|
|
});
|
|
|
}
|
|
|
+function normalizeSourceFile(value) {
|
|
|
+ if (!value)
|
|
|
+ return undefined;
|
|
|
+ const trimmed = value.trim();
|
|
|
+ return trimmed.length > 0 ? trimmed : undefined;
|
|
|
+}
|
|
|
+function buildSourceQuery(sourceFile) {
|
|
|
+ return sourceFile
|
|
|
+ .replace(/\.pdf$/i, "")
|
|
|
+ .replace(/[-_]/g, " ")
|
|
|
+ .trim();
|
|
|
+}
|
|
|
+function filterBySource(results, sourceFile) {
|
|
|
+ return results.filter((r) => r?.metadata?.source_file && r.metadata.source_file === sourceFile);
|
|
|
+}
|
|
|
+function summarizeKnowledgeResults(results) {
|
|
|
+ const pages = results
|
|
|
+ .map((r) => r?.metadata?.page)
|
|
|
+ .filter((v) => typeof v === "number");
|
|
|
+ const chapters = results
|
|
|
+ .map((r) => r?.metadata?.chapter)
|
|
|
+ .filter((v) => typeof v === "number");
|
|
|
+ const created = results
|
|
|
+ .map((r) => r?.created_at)
|
|
|
+ .filter((v) => typeof v === "string");
|
|
|
+ return {
|
|
|
+ count: results.length,
|
|
|
+ pageRange: pages.length ? [Math.min(...pages), Math.max(...pages)] : null,
|
|
|
+ chapterRange: chapters.length
|
|
|
+ ? [Math.min(...chapters), Math.max(...chapters)]
|
|
|
+ : null,
|
|
|
+ createdAtRange: created.length
|
|
|
+ ? [created.sort()[0], created.sort()[created.length - 1]]
|
|
|
+ : null,
|
|
|
+ };
|
|
|
+}
|
|
|
export const activate = (plugin) => {
|
|
|
const client = http(plugin);
|
|
|
// write conversational memory
|
|
|
@@ -47,16 +83,86 @@ export const activate = (plugin) => {
|
|
|
return res.data;
|
|
|
};
|
|
|
// search knowledge base
|
|
|
- plugin.searchKnowledge = async ({ query, userId }) => {
|
|
|
+ plugin.searchKnowledge = async ({ query, userId, limit }) => {
|
|
|
const finalUserId = userId || getKnowledgeUserId(plugin);
|
|
|
if (!query)
|
|
|
throw new Error("Missing query for knowledge search");
|
|
|
const res = await client.post(`/knowledge/search`, {
|
|
|
query,
|
|
|
userId: finalUserId,
|
|
|
+ limit,
|
|
|
+ });
|
|
|
+ return res.data;
|
|
|
+ };
|
|
|
+ // list knowledge sources (books)
|
|
|
+ plugin.listKnowledgeSources = async ({ userId } = {}) => {
|
|
|
+ const finalUserId = userId || getKnowledgeUserId(plugin);
|
|
|
+ const res = await client.post(`/knowledge/sources`, {
|
|
|
+ user_id: finalUserId,
|
|
|
});
|
|
|
return res.data;
|
|
|
};
|
|
|
+ // describe a single book by source_file (metadata summary)
|
|
|
+ plugin.describeKnowledgeBook = async ({ sourceFile, userId }) => {
|
|
|
+ const finalUserId = userId || getKnowledgeUserId(plugin);
|
|
|
+ const normalized = normalizeSourceFile(sourceFile);
|
|
|
+ if (!normalized)
|
|
|
+ throw new Error("Missing sourceFile for knowledge describe");
|
|
|
+ const res = await client.post(`/knowledge/search`, {
|
|
|
+ query: buildSourceQuery(normalized),
|
|
|
+ userId: finalUserId,
|
|
|
+ limit: 200,
|
|
|
+ });
|
|
|
+ const filtered = filterBySource(res.data?.results || [], normalized);
|
|
|
+ const summary = summarizeKnowledgeResults(filtered);
|
|
|
+ return {
|
|
|
+ sourceFile: normalized,
|
|
|
+ summary,
|
|
|
+ sample: filtered.slice(0, 5),
|
|
|
+ hintQuery: buildSourceQuery(normalized),
|
|
|
+ };
|
|
|
+ };
|
|
|
+ // search within a single book (client-side filtered)
|
|
|
+ plugin.searchKnowledgeBook = async ({ query, sourceFile, userId, limit = 8 }) => {
|
|
|
+ const finalUserId = userId || getKnowledgeUserId(plugin);
|
|
|
+ const normalized = normalizeSourceFile(sourceFile);
|
|
|
+ if (!query)
|
|
|
+ throw new Error("Missing query for knowledge search");
|
|
|
+ if (!normalized)
|
|
|
+ throw new Error("Missing sourceFile for book search");
|
|
|
+ const res = await client.post(`/knowledge/search`, {
|
|
|
+ query,
|
|
|
+ userId: finalUserId,
|
|
|
+ limit: Math.max(limit, 20),
|
|
|
+ });
|
|
|
+ const filtered = filterBySource(res.data?.results || [], normalized);
|
|
|
+ return {
|
|
|
+ sourceFile: normalized,
|
|
|
+ results: filtered.slice(0, limit),
|
|
|
+ };
|
|
|
+ };
|
|
|
+ // search across a set of books
|
|
|
+ plugin.searchKnowledgeBooks = async ({ query, sourceFiles, userId, limit = 8 }) => {
|
|
|
+ const finalUserId = userId || getKnowledgeUserId(plugin);
|
|
|
+ if (!query)
|
|
|
+ throw new Error("Missing query for knowledge search");
|
|
|
+ if (!Array.isArray(sourceFiles) || sourceFiles.length === 0) {
|
|
|
+ throw new Error("Missing sourceFiles for multi-book search");
|
|
|
+ }
|
|
|
+ const res = await client.post(`/knowledge/search`, {
|
|
|
+ query,
|
|
|
+ userId: finalUserId,
|
|
|
+ limit: Math.max(limit * 3, 20),
|
|
|
+ });
|
|
|
+ const normalizedSources = sourceFiles
|
|
|
+ .map((s) => normalizeSourceFile(s))
|
|
|
+ .filter(Boolean);
|
|
|
+ const filtered = (res.data?.results || []).filter((r) => normalizedSources.includes(r?.metadata?.source_file));
|
|
|
+ return {
|
|
|
+ sourceFiles: normalizedSources,
|
|
|
+ results: filtered.slice(0, limit),
|
|
|
+ };
|
|
|
+ };
|
|
|
// write to knowledge base
|
|
|
plugin.writeKnowledge = async ({ text, userId }) => {
|
|
|
const finalUserId = userId || getKnowledgeUserId(plugin);
|