mirror of
https://github.com/eliasstepanik/core.git
synced 2026-01-24 06:18:32 +00:00
feat: entity attributes and timeframe filter in search
This commit is contained in:
parent
7124253981
commit
33eae2619a
@ -45,7 +45,7 @@ const EnvironmentSchema = z.object({
|
|||||||
AUTH_GOOGLE_CLIENT_ID: z.string().optional(),
|
AUTH_GOOGLE_CLIENT_ID: z.string().optional(),
|
||||||
AUTH_GOOGLE_CLIENT_SECRET: z.string().optional(),
|
AUTH_GOOGLE_CLIENT_SECRET: z.string().optional(),
|
||||||
|
|
||||||
ENABLE_EMAIL_LOGIN: z.coerce.boolean().default(false),
|
ENABLE_EMAIL_LOGIN: z.coerce.boolean().default(true),
|
||||||
|
|
||||||
//Redis
|
//Redis
|
||||||
REDIS_HOST: z.string().default("localhost"),
|
REDIS_HOST: z.string().default("localhost"),
|
||||||
|
|||||||
@ -6,7 +6,14 @@ import { json } from "@remix-run/node";
|
|||||||
export const SearchBodyRequest = z.object({
|
export const SearchBodyRequest = z.object({
|
||||||
query: z.string(),
|
query: z.string(),
|
||||||
spaceId: z.string().optional(),
|
spaceId: z.string().optional(),
|
||||||
sessionId: z.string().optional(),
|
startTime: z.string().optional(),
|
||||||
|
endTime: z.string().optional(),
|
||||||
|
limit: z.number().optional(),
|
||||||
|
maxBfsDepth: z.number().optional(),
|
||||||
|
includeInvalidated: z.boolean().optional(),
|
||||||
|
entityTypes: z.array(z.string()).optional(),
|
||||||
|
scoreThreshold: z.number().optional(),
|
||||||
|
minResults: z.number().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const searchService = new SearchService();
|
const searchService = new SearchService();
|
||||||
@ -23,6 +30,16 @@ const { action, loader } = createActionApiRoute(
|
|||||||
const results = await searchService.search(
|
const results = await searchService.search(
|
||||||
body.query,
|
body.query,
|
||||||
authentication.userId,
|
authentication.userId,
|
||||||
|
{
|
||||||
|
startTime: body.startTime ? new Date(body.startTime) : undefined,
|
||||||
|
endTime: body.endTime ? new Date(body.endTime) : undefined,
|
||||||
|
limit: body.limit,
|
||||||
|
maxBfsDepth: body.maxBfsDepth,
|
||||||
|
includeInvalidated: body.includeInvalidated,
|
||||||
|
entityTypes: body.entityTypes,
|
||||||
|
scoreThreshold: body.scoreThreshold,
|
||||||
|
minResults: body.minResults,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
return json(results);
|
return json(results);
|
||||||
},
|
},
|
||||||
|
|||||||
@ -7,7 +7,7 @@ export async function saveEntity(entity: EntityNode): Promise<string> {
|
|||||||
ON CREATE SET
|
ON CREATE SET
|
||||||
n.name = $name,
|
n.name = $name,
|
||||||
n.type = $type,
|
n.type = $type,
|
||||||
n.attributesJson = $attributesJson,
|
n.attributes = $attributes,
|
||||||
n.nameEmbedding = $nameEmbedding,
|
n.nameEmbedding = $nameEmbedding,
|
||||||
n.createdAt = $createdAt,
|
n.createdAt = $createdAt,
|
||||||
n.userId = $userId,
|
n.userId = $userId,
|
||||||
@ -15,7 +15,7 @@ export async function saveEntity(entity: EntityNode): Promise<string> {
|
|||||||
ON MATCH SET
|
ON MATCH SET
|
||||||
n.name = $name,
|
n.name = $name,
|
||||||
n.type = $type,
|
n.type = $type,
|
||||||
n.attributesJson = $attributesJson,
|
n.attributes = $attributes,
|
||||||
n.nameEmbedding = $nameEmbedding,
|
n.nameEmbedding = $nameEmbedding,
|
||||||
n.space = $space
|
n.space = $space
|
||||||
RETURN n.uuid as uuid
|
RETURN n.uuid as uuid
|
||||||
@ -25,7 +25,7 @@ export async function saveEntity(entity: EntityNode): Promise<string> {
|
|||||||
uuid: entity.uuid,
|
uuid: entity.uuid,
|
||||||
name: entity.name,
|
name: entity.name,
|
||||||
type: entity.type,
|
type: entity.type,
|
||||||
attributesJson: JSON.stringify(entity.attributes || {}),
|
attributes: JSON.stringify(entity.attributes || {}),
|
||||||
nameEmbedding: entity.nameEmbedding,
|
nameEmbedding: entity.nameEmbedding,
|
||||||
createdAt: entity.createdAt.toISOString(),
|
createdAt: entity.createdAt.toISOString(),
|
||||||
userId: entity.userId,
|
userId: entity.userId,
|
||||||
@ -50,7 +50,7 @@ export async function getEntity(uuid: string): Promise<EntityNode | null> {
|
|||||||
uuid: entity.uuid,
|
uuid: entity.uuid,
|
||||||
name: entity.name,
|
name: entity.name,
|
||||||
type: entity.type,
|
type: entity.type,
|
||||||
attributes: JSON.parse(entity.attributesJson || "{}"),
|
attributes: JSON.parse(entity.attributes || "{}"),
|
||||||
nameEmbedding: entity.nameEmbedding,
|
nameEmbedding: entity.nameEmbedding,
|
||||||
createdAt: new Date(entity.createdAt),
|
createdAt: new Date(entity.createdAt),
|
||||||
userId: entity.userId,
|
userId: entity.userId,
|
||||||
@ -81,7 +81,7 @@ export async function findSimilarEntities(params: {
|
|||||||
uuid: entity.uuid,
|
uuid: entity.uuid,
|
||||||
name: entity.name,
|
name: entity.name,
|
||||||
type: entity.type,
|
type: entity.type,
|
||||||
attributes: JSON.parse(entity.attributesJson || "{}"),
|
attributes: JSON.parse(entity.attributes || "{}"),
|
||||||
nameEmbedding: entity.nameEmbedding,
|
nameEmbedding: entity.nameEmbedding,
|
||||||
createdAt: new Date(entity.createdAt),
|
createdAt: new Date(entity.createdAt),
|
||||||
userId: entity.userId,
|
userId: entity.userId,
|
||||||
|
|||||||
@ -8,7 +8,7 @@ export async function saveEpisode(episode: EpisodicNode): Promise<string> {
|
|||||||
e.content = $content,
|
e.content = $content,
|
||||||
e.originalContent = $originalContent,
|
e.originalContent = $originalContent,
|
||||||
e.contentEmbedding = $contentEmbedding,
|
e.contentEmbedding = $contentEmbedding,
|
||||||
e.type = $type,
|
e.metadata = $metadata,
|
||||||
e.source = $source,
|
e.source = $source,
|
||||||
e.createdAt = $createdAt,
|
e.createdAt = $createdAt,
|
||||||
e.validAt = $validAt,
|
e.validAt = $validAt,
|
||||||
@ -20,7 +20,7 @@ export async function saveEpisode(episode: EpisodicNode): Promise<string> {
|
|||||||
e.content = $content,
|
e.content = $content,
|
||||||
e.contentEmbedding = $contentEmbedding,
|
e.contentEmbedding = $contentEmbedding,
|
||||||
e.originalContent = $originalContent,
|
e.originalContent = $originalContent,
|
||||||
e.type = $type,
|
e.metadata = $metadata,
|
||||||
e.source = $source,
|
e.source = $source,
|
||||||
e.validAt = $validAt,
|
e.validAt = $validAt,
|
||||||
e.labels = $labels,
|
e.labels = $labels,
|
||||||
@ -34,7 +34,7 @@ export async function saveEpisode(episode: EpisodicNode): Promise<string> {
|
|||||||
content: episode.content,
|
content: episode.content,
|
||||||
originalContent: episode.originalContent,
|
originalContent: episode.originalContent,
|
||||||
source: episode.source,
|
source: episode.source,
|
||||||
type: episode.type,
|
metadata: JSON.stringify(episode.metadata || {}),
|
||||||
userId: episode.userId || null,
|
userId: episode.userId || null,
|
||||||
labels: episode.labels || [],
|
labels: episode.labels || [],
|
||||||
createdAt: episode.createdAt.toISOString(),
|
createdAt: episode.createdAt.toISOString(),
|
||||||
@ -64,7 +64,7 @@ export async function getEpisode(uuid: string): Promise<EpisodicNode | null> {
|
|||||||
content: episode.content,
|
content: episode.content,
|
||||||
originalContent: episode.originalContent,
|
originalContent: episode.originalContent,
|
||||||
contentEmbedding: episode.contentEmbedding,
|
contentEmbedding: episode.contentEmbedding,
|
||||||
type: episode.type,
|
metadata: JSON.parse(episode.metadata || "{}"),
|
||||||
source: episode.source,
|
source: episode.source,
|
||||||
createdAt: new Date(episode.createdAt),
|
createdAt: new Date(episode.createdAt),
|
||||||
validAt: new Date(episode.validAt),
|
validAt: new Date(episode.validAt),
|
||||||
@ -118,7 +118,7 @@ export async function getRecentEpisodes(params: {
|
|||||||
content: episode.content,
|
content: episode.content,
|
||||||
originalContent: episode.originalContent,
|
originalContent: episode.originalContent,
|
||||||
contentEmbedding: episode.contentEmbedding,
|
contentEmbedding: episode.contentEmbedding,
|
||||||
type: episode.type,
|
metadata: JSON.parse(episode.metadata || "{}"),
|
||||||
source: episode.source,
|
source: episode.source,
|
||||||
createdAt: new Date(episode.createdAt),
|
createdAt: new Date(episode.createdAt),
|
||||||
validAt: new Date(episode.validAt),
|
validAt: new Date(episode.validAt),
|
||||||
|
|||||||
@ -21,7 +21,7 @@ export async function saveTriple(triple: Triple): Promise<string> {
|
|||||||
n.createdAt = $createdAt,
|
n.createdAt = $createdAt,
|
||||||
n.validAt = $validAt,
|
n.validAt = $validAt,
|
||||||
n.invalidAt = $invalidAt,
|
n.invalidAt = $invalidAt,
|
||||||
n.attributesJson = $attributesJson,
|
n.attributes = $attributes,
|
||||||
n.userId = $userId,
|
n.userId = $userId,
|
||||||
n.space = $space
|
n.space = $space
|
||||||
ON MATCH SET
|
ON MATCH SET
|
||||||
@ -29,7 +29,7 @@ export async function saveTriple(triple: Triple): Promise<string> {
|
|||||||
n.factEmbedding = $factEmbedding,
|
n.factEmbedding = $factEmbedding,
|
||||||
n.validAt = $validAt,
|
n.validAt = $validAt,
|
||||||
n.invalidAt = $invalidAt,
|
n.invalidAt = $invalidAt,
|
||||||
n.attributesJson = $attributesJson,
|
n.attributes = $attributes,
|
||||||
n.space = $space
|
n.space = $space
|
||||||
RETURN n.uuid as uuid
|
RETURN n.uuid as uuid
|
||||||
`;
|
`;
|
||||||
@ -43,7 +43,7 @@ export async function saveTriple(triple: Triple): Promise<string> {
|
|||||||
invalidAt: triple.statement.invalidAt
|
invalidAt: triple.statement.invalidAt
|
||||||
? triple.statement.invalidAt.toISOString()
|
? triple.statement.invalidAt.toISOString()
|
||||||
: null,
|
: null,
|
||||||
attributesJson: JSON.stringify(triple.statement.attributes || {}),
|
attributes: JSON.stringify(triple.statement.attributes || {}),
|
||||||
userId: triple.provenance.userId,
|
userId: triple.provenance.userId,
|
||||||
space: triple.statement.space || null,
|
space: triple.statement.space || null,
|
||||||
};
|
};
|
||||||
@ -273,7 +273,7 @@ export async function getTripleForStatement({
|
|||||||
content: episodeProps.content,
|
content: episodeProps.content,
|
||||||
originalContent: episodeProps.originalContent,
|
originalContent: episodeProps.originalContent,
|
||||||
source: episodeProps.source,
|
source: episodeProps.source,
|
||||||
type: episodeProps.type,
|
metadata: episodeProps.metadata,
|
||||||
createdAt: new Date(episodeProps.createdAt),
|
createdAt: new Date(episodeProps.createdAt),
|
||||||
validAt: new Date(episodeProps.validAt),
|
validAt: new Date(episodeProps.validAt),
|
||||||
contentEmbedding: episodeProps.contentEmbedding,
|
contentEmbedding: episodeProps.contentEmbedding,
|
||||||
|
|||||||
@ -11,7 +11,12 @@ import {
|
|||||||
} from "@core/types";
|
} from "@core/types";
|
||||||
import { logger } from "./logger.service";
|
import { logger } from "./logger.service";
|
||||||
import crypto from "crypto";
|
import crypto from "crypto";
|
||||||
import { dedupeNodes, extractMessage, extractText } from "./prompts/nodes";
|
import {
|
||||||
|
dedupeNodes,
|
||||||
|
extractAttributes,
|
||||||
|
extractMessage,
|
||||||
|
extractText,
|
||||||
|
} from "./prompts/nodes";
|
||||||
import {
|
import {
|
||||||
extractStatements,
|
extractStatements,
|
||||||
resolveStatementPrompt,
|
resolveStatementPrompt,
|
||||||
@ -31,7 +36,6 @@ import { normalizePrompt } from "./prompts";
|
|||||||
|
|
||||||
// Default number of previous episodes to retrieve for context
|
// Default number of previous episodes to retrieve for context
|
||||||
const DEFAULT_EPISODE_WINDOW = 5;
|
const DEFAULT_EPISODE_WINDOW = 5;
|
||||||
const RELEVANT_SCHEMA_LIMIT = 10;
|
|
||||||
|
|
||||||
export class KnowledgeGraphService {
|
export class KnowledgeGraphService {
|
||||||
async getEmbedding(text: string) {
|
async getEmbedding(text: string) {
|
||||||
@ -60,6 +64,7 @@ export class KnowledgeGraphService {
|
|||||||
limit: DEFAULT_EPISODE_WINDOW,
|
limit: DEFAULT_EPISODE_WINDOW,
|
||||||
userId: params.userId,
|
userId: params.userId,
|
||||||
source: params.source,
|
source: params.source,
|
||||||
|
sessionId: params.sessionId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const normalizedEpisodeBody = await this.normalizeEpisodeBody(
|
const normalizedEpisodeBody = await this.normalizeEpisodeBody(
|
||||||
@ -73,7 +78,7 @@ export class KnowledgeGraphService {
|
|||||||
content: normalizedEpisodeBody,
|
content: normalizedEpisodeBody,
|
||||||
originalContent: params.episodeBody,
|
originalContent: params.episodeBody,
|
||||||
source: params.source,
|
source: params.source,
|
||||||
type: params.type || EpisodeType.Text,
|
metadata: params.metadata || {},
|
||||||
createdAt: now,
|
createdAt: now,
|
||||||
validAt: new Date(params.referenceTime),
|
validAt: new Date(params.referenceTime),
|
||||||
labels: [],
|
labels: [],
|
||||||
@ -106,8 +111,27 @@ export class KnowledgeGraphService {
|
|||||||
const { resolvedStatements, invalidatedStatements } =
|
const { resolvedStatements, invalidatedStatements } =
|
||||||
await this.resolveStatements(resolvedTriples, episode);
|
await this.resolveStatements(resolvedTriples, episode);
|
||||||
|
|
||||||
|
// Step 7: ADd attributes to entity nodes
|
||||||
|
const updatedTriples = await this.addAttributesToEntities(
|
||||||
|
resolvedStatements,
|
||||||
|
episode,
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const triple of updatedTriples) {
|
||||||
|
const { subject, predicate, object, statement, provenance } = triple;
|
||||||
|
const safeTriple = {
|
||||||
|
subject: { ...subject, nameEmbedding: undefined },
|
||||||
|
predicate: { ...predicate, nameEmbedding: undefined },
|
||||||
|
object: { ...object, nameEmbedding: undefined },
|
||||||
|
statement: { ...statement, factEmbedding: undefined },
|
||||||
|
provenance,
|
||||||
|
};
|
||||||
|
console.log("Triple (no embedding):", JSON.stringify(safeTriple));
|
||||||
|
}
|
||||||
|
// console.log("Invalidated statements", invalidatedStatements);
|
||||||
|
|
||||||
// Save triples sequentially to avoid parallel processing issues
|
// Save triples sequentially to avoid parallel processing issues
|
||||||
for (const triple of resolvedStatements) {
|
for (const triple of updatedTriples) {
|
||||||
await saveTriple(triple);
|
await saveTriple(triple);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,10 +178,9 @@ export class KnowledgeGraphService {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Get the extract_json prompt from the prompt library
|
// Get the extract_json prompt from the prompt library
|
||||||
const messages =
|
const messages = episode.sessionId
|
||||||
episode.type === EpisodeType.Conversation
|
? extractMessage(context)
|
||||||
? extractMessage(context)
|
: extractText(context);
|
||||||
: extractText(context);
|
|
||||||
|
|
||||||
let responseText = "";
|
let responseText = "";
|
||||||
|
|
||||||
@ -668,6 +691,100 @@ export class KnowledgeGraphService {
|
|||||||
return { resolvedStatements, invalidatedStatements };
|
return { resolvedStatements, invalidatedStatements };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add attributes to entity nodes based on the resolved statements
|
||||||
|
*/
|
||||||
|
private async addAttributesToEntities(
|
||||||
|
triples: Triple[],
|
||||||
|
episode: EpisodicNode,
|
||||||
|
): Promise<Triple[]> {
|
||||||
|
// Collect all unique entities from the triples
|
||||||
|
const entityMap = new Map<string, EntityNode>();
|
||||||
|
|
||||||
|
// Add all subjects, predicates, and objects to the map
|
||||||
|
triples.forEach((triple) => {
|
||||||
|
if (triple.subject) {
|
||||||
|
entityMap.set(triple.subject.uuid, triple.subject);
|
||||||
|
}
|
||||||
|
if (triple.predicate) {
|
||||||
|
entityMap.set(triple.predicate.uuid, triple.predicate);
|
||||||
|
}
|
||||||
|
if (triple.object) {
|
||||||
|
entityMap.set(triple.object.uuid, triple.object);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Convert the map to an array of entities
|
||||||
|
const entities = Array.from(entityMap.values());
|
||||||
|
|
||||||
|
if (entities.length === 0) {
|
||||||
|
return triples; // No entities to process
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all app keys
|
||||||
|
const allAppEnumValues = Object.values(Apps);
|
||||||
|
|
||||||
|
// Get all node types with their attribute definitions
|
||||||
|
const entityTypes = getNodeTypes(allAppEnumValues);
|
||||||
|
|
||||||
|
// Prepare simplified context for the LLM
|
||||||
|
const context = {
|
||||||
|
episodeContent: episode.content,
|
||||||
|
entityTypes: entityTypes,
|
||||||
|
entities: entities.map((entity) => ({
|
||||||
|
uuid: entity.uuid,
|
||||||
|
name: entity.name,
|
||||||
|
type: entity.type,
|
||||||
|
currentAttributes: entity.attributes || {},
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log("entityTypes", JSON.stringify(entityTypes));
|
||||||
|
console.log("entities", JSON.stringify(context.entities));
|
||||||
|
|
||||||
|
// Create a prompt for the LLM to extract attributes
|
||||||
|
const messages = extractAttributes(context);
|
||||||
|
|
||||||
|
let responseText = "";
|
||||||
|
|
||||||
|
// Call the LLM to extract attributes
|
||||||
|
await makeModelCall(
|
||||||
|
false,
|
||||||
|
LLMModelEnum.GPT41,
|
||||||
|
messages as CoreMessage[],
|
||||||
|
(text) => {
|
||||||
|
responseText = text;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const outputMatch = responseText.match(/<output>([\s\S]*?)<\/output>/);
|
||||||
|
if (outputMatch && outputMatch[1]) {
|
||||||
|
responseText = outputMatch[1].trim();
|
||||||
|
}
|
||||||
|
// Parse the LLM response
|
||||||
|
const responseData = JSON.parse(responseText);
|
||||||
|
const updatedEntities = responseData.entities || [];
|
||||||
|
|
||||||
|
// Update entity attributes and save them
|
||||||
|
for (const updatedEntity of updatedEntities) {
|
||||||
|
const entity = entityMap.get(updatedEntity.uuid);
|
||||||
|
if (entity) {
|
||||||
|
// Merge the existing attributes with the new ones
|
||||||
|
entity.attributes = {
|
||||||
|
...updatedEntity.attributes,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(`Updated attributes for ${updatedEntities.length} entities`);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Error processing entity attributes", { error });
|
||||||
|
}
|
||||||
|
|
||||||
|
return triples;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Normalize an episode by extracting entities and creating nodes and statements
|
* Normalize an episode by extracting entities and creating nodes and statements
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -256,3 +256,56 @@ ${JSON.stringify(context.extracted_nodes, null, 2)}
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const extractAttributes = (
|
||||||
|
context: Record<string, any>,
|
||||||
|
): CoreMessage[] => {
|
||||||
|
const sysPrompt = `
|
||||||
|
You are an AI assistant that extracts and enhances entity attributes based on context.
|
||||||
|
Your task is to analyze entities and provide appropriate attribute values for each entity based on its type definition.
|
||||||
|
|
||||||
|
For each entity:
|
||||||
|
1. Look at its type and identify the required and optional attributes from the entity type definitions
|
||||||
|
2. Check if the entity already has values for these attributes
|
||||||
|
3. For missing attributes, extract appropriate values from the context if possible
|
||||||
|
4. For existing attributes, enhance or correct them if needed based on the context
|
||||||
|
5. Give empty attributes object ({}) when there are no attributes to update
|
||||||
|
6. Only include attributes that you're updating - don't repeat existing attributes that don't need changes
|
||||||
|
7. I'll merge your new attributes with the current attributes, so only provide values that should be added or modified
|
||||||
|
|
||||||
|
Provide your output in this structure:
|
||||||
|
<output>
|
||||||
|
{
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"uuid": "entity-uuid",
|
||||||
|
"attributes": {
|
||||||
|
"attributeName1": "value1",
|
||||||
|
"attributeName2": "value2",
|
||||||
|
...
|
||||||
|
}
|
||||||
|
},
|
||||||
|
...
|
||||||
|
]
|
||||||
|
}
|
||||||
|
</output>`;
|
||||||
|
|
||||||
|
const userPrompt = `
|
||||||
|
<ENTITY_TYPES>
|
||||||
|
${JSON.stringify(context.entityTypes, null, 2)}
|
||||||
|
</ENTITY_TYPES>
|
||||||
|
|
||||||
|
<ENTITIES>
|
||||||
|
${JSON.stringify(context.entities, null, 2)}
|
||||||
|
</ENTITIES>
|
||||||
|
|
||||||
|
<EPISODE_CONTENT>
|
||||||
|
${context.episodeContent}
|
||||||
|
</EPISODE_CONTENT>
|
||||||
|
|
||||||
|
Based on the above information, please extract and enhance attributes for each entity according to its type definition. Return only the uuid and updated attributes for each entity.`;
|
||||||
|
return [
|
||||||
|
{ role: "system", content: sysPrompt },
|
||||||
|
{ role: "user", content: userPrompt },
|
||||||
|
];
|
||||||
|
};
|
||||||
|
|||||||
@ -37,10 +37,13 @@ export class SearchService {
|
|||||||
options: SearchOptions = {},
|
options: SearchOptions = {},
|
||||||
): Promise<{ episodes: string[]; facts: string[] }> {
|
): Promise<{ episodes: string[]; facts: string[] }> {
|
||||||
// Default options
|
// Default options
|
||||||
|
|
||||||
const opts: Required<SearchOptions> = {
|
const opts: Required<SearchOptions> = {
|
||||||
limit: options.limit || 10,
|
limit: options.limit || 10,
|
||||||
maxBfsDepth: options.maxBfsDepth || 4,
|
maxBfsDepth: options.maxBfsDepth || 4,
|
||||||
validAt: options.validAt || new Date(),
|
validAt: options.validAt || new Date(),
|
||||||
|
startTime: options.startTime || null,
|
||||||
|
endTime: options.endTime || new Date(),
|
||||||
includeInvalidated: options.includeInvalidated || false,
|
includeInvalidated: options.includeInvalidated || false,
|
||||||
entityTypes: options.entityTypes || [],
|
entityTypes: options.entityTypes || [],
|
||||||
predicateTypes: options.predicateTypes || [],
|
predicateTypes: options.predicateTypes || [],
|
||||||
@ -213,6 +216,8 @@ export interface SearchOptions {
|
|||||||
limit?: number;
|
limit?: number;
|
||||||
maxBfsDepth?: number;
|
maxBfsDepth?: number;
|
||||||
validAt?: Date;
|
validAt?: Date;
|
||||||
|
startTime?: Date | null;
|
||||||
|
endTime?: Date;
|
||||||
includeInvalidated?: boolean;
|
includeInvalidated?: boolean;
|
||||||
entityTypes?: string[];
|
entityTypes?: string[];
|
||||||
predicateTypes?: string[];
|
predicateTypes?: string[];
|
||||||
|
|||||||
@ -16,14 +16,28 @@ export async function performBM25Search(
|
|||||||
// Sanitize the query for Lucene syntax
|
// Sanitize the query for Lucene syntax
|
||||||
const sanitizedQuery = sanitizeLuceneQuery(query);
|
const sanitizedQuery = sanitizeLuceneQuery(query);
|
||||||
|
|
||||||
|
// Build the WHERE clause based on timeframe options
|
||||||
|
let timeframeCondition = `
|
||||||
|
AND s.validAt <= $validAt
|
||||||
|
AND (s.invalidAt IS NULL OR s.invalidAt > $validAt)
|
||||||
|
`;
|
||||||
|
|
||||||
|
// If startTime is provided, add condition to filter by validAt >= startTime
|
||||||
|
if (options.startTime) {
|
||||||
|
timeframeCondition = `
|
||||||
|
AND s.validAt <= $validAt
|
||||||
|
AND (s.invalidAt IS NULL OR s.invalidAt > $validAt)
|
||||||
|
AND s.validAt >= $startTime
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
// Use Neo4j's built-in fulltext search capabilities
|
// Use Neo4j's built-in fulltext search capabilities
|
||||||
const cypher = `
|
const cypher = `
|
||||||
CALL db.index.fulltext.queryNodes("statement_fact_index", $query)
|
CALL db.index.fulltext.queryNodes("statement_fact_index", $query)
|
||||||
YIELD node AS s, score
|
YIELD node AS s, score
|
||||||
WHERE
|
WHERE
|
||||||
s.validAt <= $validAt
|
(s.userId = $userId)
|
||||||
AND (s.invalidAt IS NULL OR s.invalidAt > $validAt)
|
${timeframeCondition}
|
||||||
AND (s.userId = $userId)
|
|
||||||
RETURN s, score
|
RETURN s, score
|
||||||
ORDER BY score DESC
|
ORDER BY score DESC
|
||||||
`;
|
`;
|
||||||
@ -31,7 +45,8 @@ export async function performBM25Search(
|
|||||||
const params = {
|
const params = {
|
||||||
query: sanitizedQuery,
|
query: sanitizedQuery,
|
||||||
userId,
|
userId,
|
||||||
validAt: options.validAt.toISOString(),
|
validAt: options.endTime.toISOString(),
|
||||||
|
...(options.startTime && { startTime: options.startTime.toISOString() }),
|
||||||
};
|
};
|
||||||
|
|
||||||
const records = await runQuery(cypher, params);
|
const records = await runQuery(cypher, params);
|
||||||
@ -46,9 +61,9 @@ export async function performBM25Search(
|
|||||||
* Sanitize a query string for Lucene syntax
|
* Sanitize a query string for Lucene syntax
|
||||||
*/
|
*/
|
||||||
export function sanitizeLuceneQuery(query: string): string {
|
export function sanitizeLuceneQuery(query: string): string {
|
||||||
// Escape special characters: + - && || ! ( ) { } [ ] ^ " ~ * ? : \
|
// Escape special characters: + - && || ! ( ) { } [ ] ^ " ~ * ? : \ /
|
||||||
let sanitized = query.replace(
|
let sanitized = query.replace(
|
||||||
/[+\-&|!(){}[\]^"~*?:\\]/g,
|
/[+\-&|!(){}[\]^"~*?:\\\/]/g,
|
||||||
(match) => "\\" + match,
|
(match) => "\\" + match,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -71,16 +86,27 @@ export async function performVectorSearch(
|
|||||||
options: Required<SearchOptions>,
|
options: Required<SearchOptions>,
|
||||||
): Promise<StatementNode[]> {
|
): Promise<StatementNode[]> {
|
||||||
try {
|
try {
|
||||||
// 1. Generate embedding for the query
|
// Build the WHERE clause based on timeframe options
|
||||||
// const embedding = await this.getEmbedding(query);
|
let timeframeCondition = `
|
||||||
|
AND s.validAt <= $validAt
|
||||||
|
AND (s.invalidAt IS NULL OR s.invalidAt > $validAt)
|
||||||
|
`;
|
||||||
|
|
||||||
// 2. Search for similar statements using Neo4j vector search
|
// If startTime is provided, add condition to filter by validAt >= startTime
|
||||||
|
if (options.startTime) {
|
||||||
|
timeframeCondition = `
|
||||||
|
AND s.validAt <= $validAt
|
||||||
|
AND (s.invalidAt IS NULL OR s.invalidAt > $validAt)
|
||||||
|
AND s.validAt >= $startTime
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Search for similar statements using Neo4j vector search
|
||||||
const cypher = `
|
const cypher = `
|
||||||
MATCH (s:Statement)
|
MATCH (s:Statement)
|
||||||
WHERE
|
WHERE
|
||||||
s.validAt <= $validAt
|
(s.userId = $userId)
|
||||||
AND (s.invalidAt IS NULL OR s.invalidAt > $validAt)
|
${timeframeCondition}
|
||||||
AND (s.userId = $userId)
|
|
||||||
WITH s, vector.similarity.cosine(s.factEmbedding, $embedding) AS score
|
WITH s, vector.similarity.cosine(s.factEmbedding, $embedding) AS score
|
||||||
WHERE score > 0.7
|
WHERE score > 0.7
|
||||||
RETURN s, score
|
RETURN s, score
|
||||||
@ -90,7 +116,8 @@ export async function performVectorSearch(
|
|||||||
const params = {
|
const params = {
|
||||||
embedding: query,
|
embedding: query,
|
||||||
userId,
|
userId,
|
||||||
validAt: options.validAt.toISOString(),
|
validAt: options.endTime.toISOString(),
|
||||||
|
...(options.startTime && { startTime: options.startTime.toISOString() }),
|
||||||
};
|
};
|
||||||
|
|
||||||
const records = await runQuery(cypher, params);
|
const records = await runQuery(cypher, params);
|
||||||
@ -120,9 +147,10 @@ export async function performBfsSearch(
|
|||||||
const statements = await bfsTraversal(
|
const statements = await bfsTraversal(
|
||||||
entity.uuid,
|
entity.uuid,
|
||||||
options.maxBfsDepth,
|
options.maxBfsDepth,
|
||||||
options.validAt,
|
options.endTime,
|
||||||
userId,
|
userId,
|
||||||
options.includeInvalidated,
|
options.includeInvalidated,
|
||||||
|
options.startTime,
|
||||||
);
|
);
|
||||||
allStatements.push(...statements);
|
allStatements.push(...statements);
|
||||||
}
|
}
|
||||||
@ -143,17 +171,31 @@ export async function bfsTraversal(
|
|||||||
validAt: Date,
|
validAt: Date,
|
||||||
userId: string,
|
userId: string,
|
||||||
includeInvalidated: boolean,
|
includeInvalidated: boolean,
|
||||||
|
startTime: Date | null,
|
||||||
): Promise<StatementNode[]> {
|
): Promise<StatementNode[]> {
|
||||||
try {
|
try {
|
||||||
|
// Build the WHERE clause based on timeframe options
|
||||||
|
let timeframeCondition = `
|
||||||
|
AND s.validAt <= $validAt
|
||||||
|
AND (s.invalidAt IS NULL OR s.invalidAt > $validAt)
|
||||||
|
`;
|
||||||
|
|
||||||
|
// If startTime is provided, add condition to filter by validAt >= startTime
|
||||||
|
if (startTime) {
|
||||||
|
timeframeCondition = `
|
||||||
|
AND s.validAt <= $validAt
|
||||||
|
AND (s.invalidAt IS NULL OR s.invalidAt > $validAt)
|
||||||
|
AND s.validAt >= $startTime
|
||||||
|
`;
|
||||||
|
}
|
||||||
// Use Neo4j's built-in path finding capabilities for efficient BFS
|
// Use Neo4j's built-in path finding capabilities for efficient BFS
|
||||||
// This query implements BFS up to maxDepth and collects all statements along the way
|
// This query implements BFS up to maxDepth and collects all statements along the way
|
||||||
const cypher = `
|
const cypher = `
|
||||||
MATCH (e:Entity {uuid: $startEntityId})<-[:HAS_SUBJECT|HAS_OBJECT|HAS_PREDICATE]-(s:Statement)
|
MATCH (e:Entity {uuid: $startEntityId})<-[:HAS_SUBJECT|HAS_OBJECT|HAS_PREDICATE]-(s:Statement)
|
||||||
WHERE
|
WHERE
|
||||||
s.validAt <= $validAt
|
(s.userId = $userId)
|
||||||
AND (s.invalidAt IS NULL OR s.invalidAt > $validAt)
|
|
||||||
AND (s.userId = $userId)
|
|
||||||
AND ($includeInvalidated OR s.invalidAt IS NULL)
|
AND ($includeInvalidated OR s.invalidAt IS NULL)
|
||||||
|
${timeframeCondition}
|
||||||
RETURN s as statement
|
RETURN s as statement
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -163,6 +205,7 @@ export async function bfsTraversal(
|
|||||||
validAt: validAt.toISOString(),
|
validAt: validAt.toISOString(),
|
||||||
userId,
|
userId,
|
||||||
includeInvalidated,
|
includeInvalidated,
|
||||||
|
...(startTime && { startTime: startTime.toISOString() }),
|
||||||
};
|
};
|
||||||
|
|
||||||
const records = await runQuery(cypher, params);
|
const records = await runQuery(cypher, params);
|
||||||
|
|||||||
@ -10,31 +10,106 @@ export const AppNames = {
|
|||||||
[Apps.SOL]: "Sol",
|
[Apps.SOL]: "Sol",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
// Define attribute structure
|
||||||
|
export interface NodeAttribute {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
type?: "string" | "number" | "boolean" | "date" | "array";
|
||||||
|
required?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
// General node types that are common across all apps
|
// General node types that are common across all apps
|
||||||
export const GENERAL_NODE_TYPES = {
|
export const GENERAL_NODE_TYPES = {
|
||||||
PERSON: {
|
PERSON: {
|
||||||
name: "Person",
|
name: "Person",
|
||||||
description: "Represents an individual, like a team member or contact",
|
description: "Represents an individual, like a team member or contact",
|
||||||
|
attributes: [
|
||||||
|
{
|
||||||
|
name: "email",
|
||||||
|
description: "The email address of the person",
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "role",
|
||||||
|
description: "The role or position of the person",
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
APP: {
|
APP: {
|
||||||
name: "App",
|
name: "App",
|
||||||
description: "A software application or service that's integrated",
|
description: "A software application or service that's integrated",
|
||||||
|
attributes: [],
|
||||||
},
|
},
|
||||||
PLACE: {
|
PLACE: {
|
||||||
name: "Place",
|
name: "Place",
|
||||||
description: "A physical location like an office, meeting room, or city",
|
description: "A physical location like an office, meeting room, or city",
|
||||||
|
attributes: [
|
||||||
|
{
|
||||||
|
name: "address",
|
||||||
|
description: "The address of the location",
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "coordinates",
|
||||||
|
description: "Geographic coordinates of the location",
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
ORGANIZATION: {
|
ORGANIZATION: {
|
||||||
name: "Organization",
|
name: "Organization",
|
||||||
description: "A company, team, or any formal group of people",
|
description: "A company, team, or any formal group of people",
|
||||||
|
attributes: [
|
||||||
|
{
|
||||||
|
name: "industry",
|
||||||
|
description: "The industry the organization operates in",
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "size",
|
||||||
|
description: "The size of the organization",
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
EVENT: {
|
EVENT: {
|
||||||
name: "Event",
|
name: "Event",
|
||||||
description: "A meeting, deadline, or any time-based occurrence",
|
description: "A meeting, deadline, or any time-based occurrence",
|
||||||
|
attributes: [
|
||||||
|
{
|
||||||
|
name: "startTime",
|
||||||
|
description: "The start date and time of the event",
|
||||||
|
type: "date",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "endTime",
|
||||||
|
description: "The end date and time of the event",
|
||||||
|
type: "date",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "location",
|
||||||
|
description: "The location of the event",
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
ALIAS: {
|
ALIAS: {
|
||||||
name: "Alias",
|
name: "Alias",
|
||||||
description: "An alternative name or identifier for an entity",
|
description: "An alternative name or identifier for an entity",
|
||||||
|
attributes: [
|
||||||
|
{
|
||||||
|
name: "originalName",
|
||||||
|
description: "The original name this is an alias for",
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "context",
|
||||||
|
description: "The context in which this alias is used",
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
@ -45,70 +120,409 @@ export const APP_NODE_TYPES = {
|
|||||||
name: "Sol Task",
|
name: "Sol Task",
|
||||||
description:
|
description:
|
||||||
"An independent unit of work in Sol, such as a task, bug report, or feature request. Tasks can be associated with lists or linked as subtasks to other tasks.",
|
"An independent unit of work in Sol, such as a task, bug report, or feature request. Tasks can be associated with lists or linked as subtasks to other tasks.",
|
||||||
|
attributes: [
|
||||||
|
{
|
||||||
|
name: "taskId",
|
||||||
|
description: "Unique identifier for the task",
|
||||||
|
type: "string",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "title",
|
||||||
|
description: "The title of the task",
|
||||||
|
type: "string",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "description",
|
||||||
|
description: "The description of the task",
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "status",
|
||||||
|
description: "The current status of the task",
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "dueDate",
|
||||||
|
description: "The due date of the task",
|
||||||
|
type: "date",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "priority",
|
||||||
|
description: "The priority level of the task",
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
LIST: {
|
LIST: {
|
||||||
name: "Sol List",
|
name: "Sol List",
|
||||||
description:
|
description:
|
||||||
"A flexible container in Sol for organizing content such as tasks, text, or references. Lists are used for task tracking, information collections, or reference materials.",
|
"A flexible container in Sol for organizing content such as tasks, text, or references. Lists are used for task tracking, information collections, or reference materials.",
|
||||||
|
attributes: [
|
||||||
|
{
|
||||||
|
name: "listId",
|
||||||
|
description: "Unique identifier for the list",
|
||||||
|
type: "string",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "title",
|
||||||
|
description: "The title of the list",
|
||||||
|
type: "string",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "description",
|
||||||
|
description: "The description of the list",
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "itemCount",
|
||||||
|
description: "The number of items in the list",
|
||||||
|
type: "number",
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
PREFERENCE: {
|
PREFERENCE: {
|
||||||
name: "Sol Preference",
|
name: "Sol Preference",
|
||||||
description:
|
description:
|
||||||
"A user-stated intent, setting, or configuration in Sol, such as preferred formats, notification settings, timezones, or other customizations. Preferences reflect how a user wants the system to behave.",
|
"A user-stated intent, setting, or configuration in Sol, such as preferred formats, notification settings, timezones, or other customizations. Preferences reflect how a user wants the system to behave.",
|
||||||
|
attributes: [
|
||||||
|
{
|
||||||
|
name: "key",
|
||||||
|
description: "The preference key or name",
|
||||||
|
type: "string",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "value",
|
||||||
|
description: "The preference value",
|
||||||
|
type: "string",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
COMMAND: {
|
COMMAND: {
|
||||||
name: "Sol Command",
|
name: "Sol Command",
|
||||||
description:
|
description:
|
||||||
"A user-issued command or trigger phrase, often starting with '/' or '@', that directs the system or an app to perform a specific action. Commands should always be extracted as distinct, important user actions.",
|
"A user-issued command or trigger phrase, often starting with '/', that directs the system or an app to perform a specific action. Commands should always be extracted as distinct, important user actions.",
|
||||||
|
attributes: [
|
||||||
|
{
|
||||||
|
name: "commandId",
|
||||||
|
description: "Unique identifier for the command",
|
||||||
|
type: "string",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "commandName",
|
||||||
|
description: "The name of the command",
|
||||||
|
type: "string",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
AUTOMATION: {
|
AUTOMATION: {
|
||||||
name: "Sol Automation",
|
name: "Sol Automation",
|
||||||
description:
|
description:
|
||||||
"A workflow or rule in Sol that automatically performs actions based on specific conditions or triggers, such as recurring tasks, reminders, or integrations with other systems.",
|
"A workflow or rule in Sol that automatically performs actions based on specific conditions or triggers, such as recurring tasks, reminders, or integrations with other systems.",
|
||||||
|
attributes: [
|
||||||
|
{
|
||||||
|
name: "automationId",
|
||||||
|
description: "Unique identifier for the automation",
|
||||||
|
type: "string",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "trigger",
|
||||||
|
description: "The event that triggers this automation",
|
||||||
|
type: "string",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "action",
|
||||||
|
description: "The action performed by this automation",
|
||||||
|
type: "string",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
[Apps.LINEAR]: {
|
[Apps.LINEAR]: {
|
||||||
ISSUE: {
|
ISSUE: {
|
||||||
name: "Linear Issue",
|
name: "Linear Issue",
|
||||||
description: "A task, bug report, or feature request tracked in Linear",
|
description: "A task, bug report, or feature request tracked in Linear",
|
||||||
|
attributes: [
|
||||||
|
{
|
||||||
|
name: "issueId",
|
||||||
|
description: "Unique identifier for the issue",
|
||||||
|
type: "string",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "title",
|
||||||
|
description: "The title of the issue",
|
||||||
|
type: "string",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "status",
|
||||||
|
description: "The current status of the issue",
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "priority",
|
||||||
|
description: "The priority level of the issue",
|
||||||
|
type: "number",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "assignee",
|
||||||
|
description: "The person assigned to the issue",
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
PROJECT: {
|
PROJECT: {
|
||||||
name: "Linear Project",
|
name: "Linear Project",
|
||||||
description: "A collection of related issues and work items in Linear",
|
description: "A collection of related issues and work items in Linear",
|
||||||
|
attributes: [
|
||||||
|
{
|
||||||
|
name: "projectId",
|
||||||
|
description: "Unique identifier for the project",
|
||||||
|
type: "string",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "name",
|
||||||
|
description: "The name of the project",
|
||||||
|
type: "string",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "status",
|
||||||
|
description: "The current status of the project",
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "startDate",
|
||||||
|
description: "The start date of the project",
|
||||||
|
type: "date",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "targetDate",
|
||||||
|
description: "The target completion date of the project",
|
||||||
|
type: "date",
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
CYCLE: {
|
CYCLE: {
|
||||||
name: "Linear Cycle",
|
name: "Linear Cycle",
|
||||||
description: "A time-boxed iteration of work in Linear",
|
description: "A time-boxed iteration of work in Linear",
|
||||||
|
attributes: [
|
||||||
|
{
|
||||||
|
name: "cycleId",
|
||||||
|
description: "Unique identifier for the cycle",
|
||||||
|
type: "string",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "name",
|
||||||
|
description: "The name of the cycle",
|
||||||
|
type: "string",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "startDate",
|
||||||
|
description: "The start date of the cycle",
|
||||||
|
type: "date",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "endDate",
|
||||||
|
description: "The end date of the cycle",
|
||||||
|
type: "date",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
TEAM: {
|
TEAM: {
|
||||||
name: "Linear Team",
|
name: "Linear Team",
|
||||||
description: "A group of people working together in Linear",
|
description: "A group of people working together in Linear",
|
||||||
|
attributes: [
|
||||||
|
{
|
||||||
|
name: "teamId",
|
||||||
|
description: "Unique identifier for the team",
|
||||||
|
type: "string",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "name",
|
||||||
|
description: "The name of the team",
|
||||||
|
type: "string",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "key",
|
||||||
|
description: "The team's key or shorthand",
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "memberCount",
|
||||||
|
description: "Number of members in the team",
|
||||||
|
type: "number",
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
LABEL: {
|
LABEL: {
|
||||||
name: "Linear Label",
|
name: "Linear Label",
|
||||||
description: "A tag used to categorize and organize issues in Linear",
|
description: "A tag used to categorize and organize issues in Linear",
|
||||||
|
attributes: [
|
||||||
|
{
|
||||||
|
name: "labelId",
|
||||||
|
description: "Unique identifier for the label",
|
||||||
|
type: "string",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "name",
|
||||||
|
description: "The name of the label",
|
||||||
|
type: "string",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "color",
|
||||||
|
description: "The color of the label",
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
[Apps.SLACK]: {
|
[Apps.SLACK]: {
|
||||||
CHANNEL: {
|
CHANNEL: {
|
||||||
name: "Slack Channel",
|
name: "Slack Channel",
|
||||||
description: "A dedicated space for team communication in Slack",
|
description: "A dedicated space for team communication in Slack",
|
||||||
|
attributes: [
|
||||||
|
{
|
||||||
|
name: "channelId",
|
||||||
|
description: "Unique identifier for the channel",
|
||||||
|
type: "string",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "name",
|
||||||
|
description: "The name of the channel",
|
||||||
|
type: "string",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "isPrivate",
|
||||||
|
description: "Whether the channel is private",
|
||||||
|
type: "boolean",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "memberCount",
|
||||||
|
description: "The number of members in the channel",
|
||||||
|
type: "number",
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
THREAD: {
|
THREAD: {
|
||||||
name: "Slack Thread",
|
name: "Slack Thread",
|
||||||
description: "A focused conversation branch within a Slack channel",
|
description: "A focused conversation branch within a Slack channel",
|
||||||
|
attributes: [
|
||||||
|
{
|
||||||
|
name: "threadId",
|
||||||
|
description: "Unique identifier for the thread",
|
||||||
|
type: "string",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "parentMessageId",
|
||||||
|
description: "ID of the parent message",
|
||||||
|
type: "string",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "replyCount",
|
||||||
|
description: "Number of replies in the thread",
|
||||||
|
type: "number",
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
MESSAGE: {
|
MESSAGE: {
|
||||||
name: "Slack Message",
|
name: "Slack Message",
|
||||||
description: "A single communication sent in a Slack channel or thread",
|
description: "A single communication sent in a Slack channel or thread",
|
||||||
|
attributes: [
|
||||||
|
{
|
||||||
|
name: "messageId",
|
||||||
|
description: "Unique identifier for the message",
|
||||||
|
type: "string",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "content",
|
||||||
|
description: "The content of the message",
|
||||||
|
type: "string",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "timestamp",
|
||||||
|
description: "When the message was sent",
|
||||||
|
type: "date",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "reactions",
|
||||||
|
description: "Reactions to the message",
|
||||||
|
type: "array",
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
REACTION: {
|
REACTION: {
|
||||||
name: "Slack Reaction",
|
name: "Slack Reaction",
|
||||||
description: "An emoji response to a message in Slack",
|
description: "An emoji response to a message in Slack",
|
||||||
|
attributes: [
|
||||||
|
{
|
||||||
|
name: "emoji",
|
||||||
|
description: "The emoji used in the reaction",
|
||||||
|
type: "string",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "count",
|
||||||
|
description: "Number of users who reacted with this emoji",
|
||||||
|
type: "number",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
FILE: {
|
FILE: {
|
||||||
name: "Slack File",
|
name: "Slack File",
|
||||||
description: "A document, image or other file shared in Slack",
|
description: "A document, image or other file shared in Slack",
|
||||||
|
attributes: [
|
||||||
|
{
|
||||||
|
name: "fileId",
|
||||||
|
description: "Unique identifier for the file",
|
||||||
|
type: "string",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "name",
|
||||||
|
description: "The name of the file",
|
||||||
|
type: "string",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "type",
|
||||||
|
description: "The file type or format",
|
||||||
|
type: "string",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "size",
|
||||||
|
description: "The size of the file in bytes",
|
||||||
|
type: "number",
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
@ -161,3 +575,7 @@ export function getNodeTypesString(apps: Array<keyof typeof APP_NODE_TYPES>) {
|
|||||||
nodeTypesString += `App-specific Node Types:\n${appSpecificTypesString}`;
|
nodeTypesString += `App-specific Node Types:\n${appSpecificTypesString}`;
|
||||||
return nodeTypesString;
|
return nodeTypesString;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getNodeAttributesString(
|
||||||
|
apps: Array<keyof typeof APP_NODE_TYPES>,
|
||||||
|
) {}
|
||||||
|
|||||||
@ -12,7 +12,7 @@ export interface EpisodicNode {
|
|||||||
content: string;
|
content: string;
|
||||||
originalContent: string;
|
originalContent: string;
|
||||||
contentEmbedding?: number[];
|
contentEmbedding?: number[];
|
||||||
type: string;
|
metadata: Record<string, any>;
|
||||||
source: string;
|
source: string;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
validAt: Date;
|
validAt: Date;
|
||||||
@ -70,7 +70,7 @@ export type AddEpisodeParams = {
|
|||||||
name: string;
|
name: string;
|
||||||
episodeBody: string;
|
episodeBody: string;
|
||||||
referenceTime: Date;
|
referenceTime: Date;
|
||||||
type: EpisodeType;
|
metadata: Record<string, any>;
|
||||||
source: string;
|
source: string;
|
||||||
userId: string;
|
userId: string;
|
||||||
spaceId?: string;
|
spaceId?: string;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user