feat: sync open-source context and capsule removal
- remove legacy contexts surfaces from the app, MCP bridge, standalone server, and docs - keep getContext and retrieveQueryContext while aligning the simplified graph-only contract - harden dev startup migration behavior and disable the accidental chat surface in open source Generated with Codex
This commit is contained in:
@@ -463,7 +463,6 @@ export class ChunkService {
|
||||
|
||||
return result.rows;
|
||||
} catch (error) {
|
||||
sqlite.disableFtsTable('chunks', 'chunks_fts query failed during chunk search', error);
|
||||
console.warn('[ChunkSearch] FTS chunk search failed, falling back to LIKE:', error);
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -1,237 +0,0 @@
|
||||
import { getSQLiteClient } from './sqlite-client';
|
||||
import type { Context, ContextSummary, Node } from '@/types/database';
|
||||
import { nodeService } from './nodes';
|
||||
|
||||
type ContextRow = Context;
|
||||
export const MAX_CONTEXTS_PER_ACCOUNT = 10;
|
||||
|
||||
function normalizeContextName(name: string): string {
|
||||
return name.trim().replace(/\s+/g, ' ');
|
||||
}
|
||||
|
||||
function assertContextName(name: unknown): string {
|
||||
if (typeof name !== 'string') {
|
||||
throw new Error('Context name is required.');
|
||||
}
|
||||
const normalized = normalizeContextName(name);
|
||||
if (!normalized) {
|
||||
throw new Error('Context name is required.');
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function assertContextDescription(description: unknown): string {
|
||||
if (typeof description !== 'string') {
|
||||
throw new Error('Context description is required.');
|
||||
}
|
||||
const normalized = description.trim();
|
||||
if (!normalized) {
|
||||
throw new Error('Context description is required.');
|
||||
}
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function mapContextRow(row: ContextRow): Context {
|
||||
return {
|
||||
id: Number(row.id),
|
||||
name: row.name,
|
||||
description: row.description ?? null,
|
||||
icon: row.icon ?? null,
|
||||
created_at: row.created_at,
|
||||
updated_at: row.updated_at,
|
||||
};
|
||||
}
|
||||
|
||||
export class ContextService {
|
||||
async listContexts(): Promise<ContextSummary[]> {
|
||||
const sqlite = getSQLiteClient();
|
||||
const rows = sqlite.query<ContextSummary>(`
|
||||
SELECT c.id, c.name, c.description, c.icon, COUNT(n.id) as count
|
||||
FROM contexts c
|
||||
LEFT JOIN nodes n ON n.context_id = c.id
|
||||
GROUP BY c.id
|
||||
ORDER BY c.name COLLATE NOCASE ASC
|
||||
`).rows;
|
||||
|
||||
return rows.map((row) => ({
|
||||
id: Number(row.id),
|
||||
name: row.name,
|
||||
description: row.description ?? null,
|
||||
icon: row.icon ?? null,
|
||||
count: Number(row.count ?? 0),
|
||||
}));
|
||||
}
|
||||
|
||||
async getContextById(id: number): Promise<ContextSummary | null> {
|
||||
const sqlite = getSQLiteClient();
|
||||
const row = sqlite.query<ContextSummary>(`
|
||||
SELECT c.id, c.name, c.description, c.icon, COUNT(n.id) as count
|
||||
FROM contexts c
|
||||
LEFT JOIN nodes n ON n.context_id = c.id
|
||||
WHERE c.id = ?
|
||||
GROUP BY c.id
|
||||
`, [id]).rows[0];
|
||||
|
||||
if (!row) return null;
|
||||
|
||||
return {
|
||||
id: Number(row.id),
|
||||
name: row.name,
|
||||
description: row.description ?? null,
|
||||
icon: row.icon ?? null,
|
||||
count: Number(row.count ?? 0),
|
||||
};
|
||||
}
|
||||
|
||||
async getContextByName(name: string): Promise<Context | null> {
|
||||
const normalized = assertContextName(name);
|
||||
const sqlite = getSQLiteClient();
|
||||
const row = sqlite.query<ContextRow>(`
|
||||
SELECT id, name, description, icon, created_at, updated_at
|
||||
FROM contexts
|
||||
WHERE LOWER(TRIM(name)) = LOWER(TRIM(?))
|
||||
LIMIT 1
|
||||
`, [normalized]).rows[0];
|
||||
|
||||
return row ? mapContextRow(row) : null;
|
||||
}
|
||||
|
||||
async createContext(input: { name: string; description: string; icon?: string | null }): Promise<Context> {
|
||||
const name = assertContextName(input.name);
|
||||
const description = assertContextDescription(input.description);
|
||||
const icon = typeof input.icon === 'string' && input.icon.trim() ? input.icon.trim() : null;
|
||||
const sqlite = getSQLiteClient();
|
||||
const now = new Date().toISOString();
|
||||
|
||||
const existing = await this.getContextByName(name);
|
||||
if (existing) {
|
||||
throw new Error(`Context "${name}" already exists.`);
|
||||
}
|
||||
|
||||
const contextCountRow = sqlite.query<{ count: number }>(`
|
||||
SELECT COUNT(*) AS count
|
||||
FROM contexts
|
||||
`).rows[0];
|
||||
const existingCount = Number(contextCountRow?.count ?? 0);
|
||||
if (existingCount >= MAX_CONTEXTS_PER_ACCOUNT) {
|
||||
throw new Error(`Context limit reached. Maximum ${MAX_CONTEXTS_PER_ACCOUNT} contexts are allowed per account.`);
|
||||
}
|
||||
|
||||
const result = sqlite.prepare(`
|
||||
INSERT INTO contexts (name, description, icon, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
`).run(name, description, icon, now, now);
|
||||
|
||||
const created = await this.getContextById(Number(result.lastInsertRowid));
|
||||
if (!created) {
|
||||
throw new Error('Failed to create context.');
|
||||
}
|
||||
|
||||
return {
|
||||
...created,
|
||||
created_at: now,
|
||||
updated_at: now,
|
||||
};
|
||||
}
|
||||
|
||||
async updateContext(input: { id: number; name?: string; description?: string; icon?: string | null }): Promise<Context> {
|
||||
const sqlite = getSQLiteClient();
|
||||
const existing = sqlite.query<ContextRow>(`
|
||||
SELECT id, name, description, icon, created_at, updated_at
|
||||
FROM contexts
|
||||
WHERE id = ?
|
||||
`, [input.id]).rows[0];
|
||||
|
||||
if (!existing) {
|
||||
throw new Error(`Context ${input.id} not found.`);
|
||||
}
|
||||
|
||||
const nextName = input.name !== undefined ? assertContextName(input.name) : existing.name;
|
||||
const nextDescription = input.description !== undefined
|
||||
? assertContextDescription(input.description)
|
||||
: existing.description;
|
||||
const nextIcon = input.icon !== undefined
|
||||
? (typeof input.icon === 'string' && input.icon.trim() ? input.icon.trim() : null)
|
||||
: existing.icon;
|
||||
|
||||
const conflicting = sqlite.query<{ id: number }>(`
|
||||
SELECT id FROM contexts
|
||||
WHERE LOWER(TRIM(name)) = LOWER(TRIM(?))
|
||||
AND id != ?
|
||||
LIMIT 1
|
||||
`, [nextName, input.id]).rows[0];
|
||||
|
||||
if (conflicting) {
|
||||
throw new Error(`Context "${nextName}" already exists.`);
|
||||
}
|
||||
|
||||
const now = new Date().toISOString();
|
||||
sqlite.prepare(`
|
||||
UPDATE contexts
|
||||
SET name = ?, description = ?, icon = ?, updated_at = ?
|
||||
WHERE id = ?
|
||||
`).run(nextName, nextDescription, nextIcon, now, input.id);
|
||||
|
||||
return {
|
||||
id: input.id,
|
||||
name: nextName,
|
||||
description: nextDescription ?? null,
|
||||
icon: nextIcon ?? null,
|
||||
created_at: existing.created_at,
|
||||
updated_at: now,
|
||||
};
|
||||
}
|
||||
|
||||
async getNodesForContext(id: number): Promise<Node[]> {
|
||||
return nodeService.getNodes({ contextId: id, limit: 500 });
|
||||
}
|
||||
|
||||
async resolveContextId(input: { context_id?: number | null; context_name?: string | null }): Promise<number | null | undefined> {
|
||||
const hasContextId =
|
||||
Object.prototype.hasOwnProperty.call(input, 'context_id') &&
|
||||
input.context_id !== undefined;
|
||||
const hasContextName = typeof input.context_name === 'string' && input.context_name.trim().length > 0;
|
||||
|
||||
if (!hasContextId && !hasContextName) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (hasContextId && input.context_id === null) {
|
||||
if (hasContextName) {
|
||||
throw new Error('context_name cannot be combined with context_id: null.');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
let resolvedById: Context | null = null;
|
||||
if (hasContextId) {
|
||||
if (typeof input.context_id !== 'number' || !Number.isInteger(input.context_id) || input.context_id <= 0) {
|
||||
throw new Error('context_id must be a positive integer or null.');
|
||||
}
|
||||
const byId = await this.getContextById(input.context_id);
|
||||
if (!byId) {
|
||||
throw new Error(`Context ${input.context_id} not found.`);
|
||||
}
|
||||
resolvedById = {
|
||||
...byId,
|
||||
created_at: '',
|
||||
updated_at: '',
|
||||
};
|
||||
}
|
||||
|
||||
if (hasContextName) {
|
||||
const byName = await this.getContextByName(input.context_name!);
|
||||
if (!byName) {
|
||||
throw new Error(`Context "${input.context_name}" not found.`);
|
||||
}
|
||||
if (resolvedById && resolvedById.id !== byName.id) {
|
||||
throw new Error('context_id and context_name refer to different contexts.');
|
||||
}
|
||||
return byName.id;
|
||||
}
|
||||
|
||||
return resolvedById?.id;
|
||||
}
|
||||
}
|
||||
|
||||
export const contextService = new ContextService();
|
||||
@@ -4,7 +4,6 @@ import type { DatabaseIntegrityReport } from './sqlite-client';
|
||||
export { nodeService, NodeService } from './nodes';
|
||||
export { chunkService, ChunkService } from './chunks';
|
||||
export { edgeService, EdgeService } from './edges';
|
||||
export { contextService, ContextService } from './contextService';
|
||||
// export { HelperService } from './helpers'; // Removed - migrated to JSON-based service
|
||||
|
||||
// Types
|
||||
|
||||
@@ -2,12 +2,10 @@ import { getSQLiteClient } from './sqlite-client';
|
||||
import { Node, NodeFilters } from '@/types/database';
|
||||
import { eventBroadcaster } from '../events';
|
||||
import { EmbeddingService } from '@/services/embeddings';
|
||||
import { scoreNodeSearchMatch } from './searchRanking';
|
||||
import { getHighSignalSearchTerms, scoreNodeSearchMatch } from './searchRanking';
|
||||
import { buildCanonicalNodeMetadata, mergeNodeMetadata } from '@/services/nodes/metadata';
|
||||
|
||||
type NodeRow = Node & {
|
||||
context_json: string | null;
|
||||
};
|
||||
type NodeRow = Node;
|
||||
type NodeSearchRow = NodeRow & { rank?: number; similarity?: number };
|
||||
|
||||
function sanitizeFtsQuery(input: string): string {
|
||||
@@ -21,35 +19,7 @@ function sanitizeFtsQuery(input: string): string {
|
||||
}
|
||||
|
||||
function extractRelaxedSearchTerms(query: string): string[] {
|
||||
const stopWords = new Set([
|
||||
'a', 'an', 'and', 'are', 'as', 'at', 'be', 'by', 'can', 'do', 'find',
|
||||
'for', 'from', 'hello', 'i', 'in', 'is', 'it', 'me', 'my', 'of', 'on',
|
||||
'or', 'recent', 'stuff', 'term', 'that', 'the', 'this', 'to', 'with', 'you'
|
||||
]);
|
||||
|
||||
const rawTerms = query
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9\s]+/g, ' ')
|
||||
.split(/\s+/)
|
||||
.map(term => term.trim())
|
||||
.filter(Boolean);
|
||||
|
||||
const expanded = new Set<string>();
|
||||
|
||||
for (const term of rawTerms) {
|
||||
if (!stopWords.has(term) && term.length >= 3) {
|
||||
expanded.add(term);
|
||||
}
|
||||
|
||||
const alphaParts = term.replace(/\d+/g, ' ').split(/\s+/).filter(Boolean);
|
||||
for (const part of alphaParts) {
|
||||
if (!stopWords.has(part) && part.length >= 3) {
|
||||
expanded.add(part);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(expanded).slice(0, 8);
|
||||
return getHighSignalSearchTerms(query).slice(0, 8);
|
||||
}
|
||||
|
||||
function reciprocalRankFuse<T extends { id: number }>(
|
||||
@@ -90,7 +60,6 @@ export class NodeService {
|
||||
eventAfter,
|
||||
eventBefore,
|
||||
chunkStatus,
|
||||
contextId,
|
||||
} = filters;
|
||||
|
||||
if (search?.trim()) {
|
||||
@@ -112,8 +81,6 @@ export class NodeService {
|
||||
if (eventAfter) { query += ` AND n.event_date >= ?`; params.push(eventAfter); }
|
||||
if (eventBefore) { query += ` AND n.event_date < ?`; params.push(eventBefore); }
|
||||
if (chunkStatus) { query += ` AND n.chunk_status = ?`; params.push(chunkStatus); }
|
||||
if (contextId !== undefined) { query += ` AND n.context_id = ?`; params.push(contextId); }
|
||||
|
||||
const result = sqlite.query<{ total: number }>(query, params);
|
||||
return result.rows[0]?.total ?? 0;
|
||||
}
|
||||
@@ -131,7 +98,6 @@ export class NodeService {
|
||||
eventAfter,
|
||||
eventBefore,
|
||||
chunkStatus,
|
||||
contextId,
|
||||
} = filters;
|
||||
|
||||
if (search?.trim()) {
|
||||
@@ -143,14 +109,9 @@ export class NodeService {
|
||||
let query = `
|
||||
SELECT n.id, n.title, n.description, n.source, n.link, n.event_date, n.metadata,
|
||||
n.chunk_status, n.embedding_updated_at, n.embedding_text,
|
||||
n.created_at, n.updated_at, n.context_id,
|
||||
CASE
|
||||
WHEN c.id IS NULL THEN NULL
|
||||
ELSE json_object('id', c.id, 'name', c.name, 'description', c.description, 'icon', c.icon)
|
||||
END as context_json,
|
||||
n.created_at, n.updated_at,
|
||||
(SELECT COUNT(*) FROM edges WHERE from_node_id = n.id OR to_node_id = n.id) as edge_count
|
||||
FROM nodes n
|
||||
LEFT JOIN contexts c ON c.id = n.context_id
|
||||
WHERE 1=1
|
||||
`;
|
||||
const params: any[] = [];
|
||||
@@ -182,11 +143,6 @@ export class NodeService {
|
||||
query += ` AND n.chunk_status = ?`;
|
||||
params.push(chunkStatus);
|
||||
}
|
||||
if (contextId !== undefined) {
|
||||
query += ` AND n.context_id = ?`;
|
||||
params.push(contextId);
|
||||
}
|
||||
|
||||
// Sorting logic
|
||||
if (search) {
|
||||
// For search queries, prioritize by relevance: exact title → starts with → contains in title → description → source
|
||||
@@ -243,13 +199,8 @@ export class NodeService {
|
||||
const query = `
|
||||
SELECT n.id, n.title, n.description, n.source, n.link, n.event_date, n.metadata,
|
||||
n.chunk_status, n.embedding_updated_at, n.embedding_text,
|
||||
n.created_at, n.updated_at, n.context_id,
|
||||
CASE
|
||||
WHEN c.id IS NULL THEN NULL
|
||||
ELSE json_object('id', c.id, 'name', c.name, 'description', c.description, 'icon', c.icon)
|
||||
END as context_json
|
||||
n.created_at, n.updated_at
|
||||
FROM nodes n
|
||||
LEFT JOIN contexts c ON c.id = n.context_id
|
||||
WHERE n.id = ?
|
||||
`;
|
||||
const result = sqlite.query<NodeRow>(query, [id]);
|
||||
@@ -275,7 +226,6 @@ export class NodeService {
|
||||
event_date,
|
||||
chunk_status,
|
||||
metadata = {},
|
||||
context_id,
|
||||
} = nodeData;
|
||||
const canonicalMetadata = buildCanonicalNodeMetadata({ metadata });
|
||||
const now = new Date().toISOString();
|
||||
@@ -284,8 +234,8 @@ export class NodeService {
|
||||
const nodeId = sqlite.transaction(() => {
|
||||
// Insert node using prepare/run for lastInsertRowid access
|
||||
const nodeResult = sqlite.prepare(`
|
||||
INSERT INTO nodes (title, description, source, link, event_date, metadata, chunk_status, context_id, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
INSERT INTO nodes (title, description, source, link, event_date, metadata, chunk_status, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`).run(
|
||||
title,
|
||||
description ?? null,
|
||||
@@ -294,7 +244,6 @@ export class NodeService {
|
||||
event_date ?? null,
|
||||
JSON.stringify(canonicalMetadata),
|
||||
chunk_status ?? null,
|
||||
context_id ?? null,
|
||||
now,
|
||||
now
|
||||
);
|
||||
@@ -353,10 +302,6 @@ export class NodeService {
|
||||
if (source !== undefined) { setFields.push('source = ?'); params.push(source); }
|
||||
if (link !== undefined) { setFields.push('link = ?'); params.push(link); }
|
||||
if (event_date !== undefined) { setFields.push('event_date = ?'); params.push(event_date); }
|
||||
if (Object.prototype.hasOwnProperty.call(updates, 'context_id')) {
|
||||
setFields.push('context_id = ?');
|
||||
params.push(updates.context_id ?? null);
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(updates, 'chunk_status')) {
|
||||
setFields.push('chunk_status = ?');
|
||||
params.push(updates.chunk_status ?? null);
|
||||
@@ -419,11 +364,9 @@ export class NodeService {
|
||||
}
|
||||
|
||||
private mapNodeRow(row: NodeRow): Node {
|
||||
const { context_json, ...baseRow } = row;
|
||||
return {
|
||||
...baseRow,
|
||||
metadata: baseRow.metadata ? (typeof baseRow.metadata === 'string' ? JSON.parse(baseRow.metadata) : baseRow.metadata) : null,
|
||||
context: context_json ? JSON.parse(context_json) : null,
|
||||
...row,
|
||||
metadata: row.metadata ? (typeof row.metadata === 'string' ? JSON.parse(row.metadata) : row.metadata) : null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -433,7 +376,6 @@ export class NodeService {
|
||||
createdBefore,
|
||||
eventAfter,
|
||||
eventBefore,
|
||||
contextId,
|
||||
} = filters;
|
||||
|
||||
const clauses: string[] = [];
|
||||
@@ -443,8 +385,6 @@ export class NodeService {
|
||||
if (createdBefore) { clauses.push(`${alias}.created_at < ?`); params.push(createdBefore); }
|
||||
if (eventAfter) { clauses.push(`${alias}.event_date >= ?`); params.push(eventAfter); }
|
||||
if (eventBefore) { clauses.push(`${alias}.event_date < ?`); params.push(eventBefore); }
|
||||
if (contextId !== undefined) { clauses.push(`${alias}.context_id = ?`); params.push(contextId); }
|
||||
|
||||
return { clauses, params };
|
||||
}
|
||||
|
||||
@@ -517,7 +457,8 @@ export class NodeService {
|
||||
|
||||
return Number(result.rows[0]?.total ?? 0);
|
||||
} catch (error) {
|
||||
sqlite.disableFtsTable('nodes', 'nodes_fts query failed during count search', error);
|
||||
sqlite.getIntegrityReport(true);
|
||||
console.warn('[NodeSearch] FTS count failed, falling back to LIKE count:', error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -546,6 +487,7 @@ export class NodeService {
|
||||
): NodeSearchRow[] {
|
||||
const ftsQuery = sanitizeFtsQuery(search);
|
||||
if (!ftsQuery) return [];
|
||||
|
||||
if (!sqlite.canUseFtsTable('nodes')) return [];
|
||||
|
||||
const { clauses, params } = this.buildNodeFilterClauses(filters);
|
||||
@@ -561,15 +503,10 @@ export class NodeService {
|
||||
)
|
||||
SELECT n.id, n.title, n.description, n.source, n.link, n.event_date, n.metadata,
|
||||
n.chunk_status, n.embedding_updated_at, n.embedding_text,
|
||||
n.created_at, n.updated_at, n.context_id,
|
||||
CASE
|
||||
WHEN c.id IS NULL THEN NULL
|
||||
ELSE json_object('id', c.id, 'name', c.name, 'description', c.description, 'icon', c.icon)
|
||||
END as context_json,
|
||||
n.created_at, n.updated_at,
|
||||
fm.rank
|
||||
FROM fts_matches fm
|
||||
JOIN nodes n ON n.id = fm.rowid
|
||||
LEFT JOIN contexts c ON c.id = n.context_id
|
||||
${whereClauses}
|
||||
ORDER BY fm.rank
|
||||
LIMIT ?
|
||||
@@ -577,7 +514,8 @@ export class NodeService {
|
||||
|
||||
return result.rows;
|
||||
} catch (error) {
|
||||
sqlite.disableFtsTable('nodes', 'nodes_fts query failed during node search', error);
|
||||
sqlite.getIntegrityReport(true);
|
||||
console.warn('[NodeSearch] FTS search failed, falling back to LIKE:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -593,13 +531,8 @@ export class NodeService {
|
||||
let query = `
|
||||
SELECT n.id, n.title, n.description, n.source, n.link, n.event_date, n.metadata,
|
||||
n.chunk_status, n.embedding_updated_at, n.embedding_text,
|
||||
n.created_at, n.updated_at, n.context_id,
|
||||
CASE
|
||||
WHEN c.id IS NULL THEN NULL
|
||||
ELSE json_object('id', c.id, 'name', c.name, 'description', c.description, 'icon', c.icon)
|
||||
END as context_json
|
||||
n.created_at, n.updated_at
|
||||
FROM nodes n
|
||||
LEFT JOIN contexts c ON c.id = n.context_id
|
||||
WHERE 1=1
|
||||
`;
|
||||
const queryParams = [...params];
|
||||
@@ -641,13 +574,8 @@ export class NodeService {
|
||||
let query = `
|
||||
SELECT n.id, n.title, n.description, n.source, n.link, n.event_date, n.metadata,
|
||||
n.chunk_status, n.embedding_updated_at, n.embedding_text,
|
||||
n.created_at, n.updated_at, n.context_id,
|
||||
CASE
|
||||
WHEN c.id IS NULL THEN NULL
|
||||
ELSE json_object('id', c.id, 'name', c.name, 'description', c.description, 'icon', c.icon)
|
||||
END as context_json
|
||||
n.created_at, n.updated_at
|
||||
FROM nodes n
|
||||
LEFT JOIN contexts c ON c.id = n.context_id
|
||||
WHERE 1=1
|
||||
`;
|
||||
const queryParams = [...params];
|
||||
@@ -718,15 +646,10 @@ export class NodeService {
|
||||
)
|
||||
SELECT n.id, n.title, n.description, n.source, n.link, n.event_date, n.metadata,
|
||||
n.chunk_status, n.embedding_updated_at, n.embedding_text,
|
||||
n.created_at, n.updated_at, n.context_id,
|
||||
CASE
|
||||
WHEN c.id IS NULL THEN NULL
|
||||
ELSE json_object('id', c.id, 'name', c.name, 'description', c.description, 'icon', c.icon)
|
||||
END as context_json,
|
||||
n.created_at, n.updated_at,
|
||||
(1.0 / (1.0 + vm.distance)) AS similarity
|
||||
FROM vector_matches vm
|
||||
JOIN nodes n ON n.id = vm.node_id
|
||||
LEFT JOIN contexts c ON c.id = n.context_id
|
||||
${whereClauses}
|
||||
ORDER BY vm.distance
|
||||
LIMIT ?
|
||||
@@ -769,6 +692,14 @@ export class NodeService {
|
||||
return updatedNodes;
|
||||
}
|
||||
|
||||
async getAllDimensions(): Promise<string[]> {
|
||||
return [];
|
||||
}
|
||||
|
||||
async getDimensionStats(): Promise<{dimension: string, count: number}[]> {
|
||||
return [];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Export singleton instance
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user