mirror of
https://github.com/eliasstepanik/core.git
synced 2026-01-11 18:28:29 +00:00
Fix: activity flow and invalidation in knowledge graph
This commit is contained in:
parent
e82e7c1db2
commit
aa7ae14f95
@ -9,7 +9,9 @@ import {
|
|||||||
import { openai } from "@ai-sdk/openai";
|
import { openai } from "@ai-sdk/openai";
|
||||||
import { logger } from "~/services/logger.service";
|
import { logger } from "~/services/logger.service";
|
||||||
import { env } from "~/env.server";
|
import { env } from "~/env.server";
|
||||||
import { createOllama } from "ollama-ai-provider";
|
import { createOllama, type OllamaProvider } from "ollama-ai-provider";
|
||||||
|
import { anthropic } from "@ai-sdk/anthropic";
|
||||||
|
import { google } from "@ai-sdk/google";
|
||||||
|
|
||||||
export async function makeModelCall(
|
export async function makeModelCall(
|
||||||
stream: boolean,
|
stream: boolean,
|
||||||
@ -19,44 +21,41 @@ export async function makeModelCall(
|
|||||||
) {
|
) {
|
||||||
let modelInstance;
|
let modelInstance;
|
||||||
const model = env.MODEL;
|
const model = env.MODEL;
|
||||||
let finalModel: string = "unknown";
|
const ollamaUrl = process.env.OLLAMA_URL;
|
||||||
// const ollamaUrl = process.env.OLLAMA_URL;
|
let ollama: OllamaProvider | undefined;
|
||||||
const ollamaUrl = undefined;
|
|
||||||
|
|
||||||
if (ollamaUrl) {
|
if (ollamaUrl) {
|
||||||
const ollama = createOllama({
|
ollama = createOllama({
|
||||||
baseURL: ollamaUrl,
|
baseURL: ollamaUrl,
|
||||||
});
|
});
|
||||||
modelInstance = ollama(model);
|
}
|
||||||
} else {
|
|
||||||
switch (model) {
|
|
||||||
case LLMModelEnum.GPT35TURBO:
|
|
||||||
case LLMModelEnum.GPT4TURBO:
|
|
||||||
case LLMModelEnum.GPT4O:
|
|
||||||
case LLMModelEnum.GPT41:
|
|
||||||
case LLMModelEnum.GPT41MINI:
|
|
||||||
case LLMModelEnum.GPT41NANO:
|
|
||||||
finalModel = LLMMappings[model];
|
|
||||||
modelInstance = openai(finalModel, { ...options });
|
|
||||||
break;
|
|
||||||
|
|
||||||
case LLMModelEnum.CLAUDEOPUS:
|
switch (model) {
|
||||||
case LLMModelEnum.CLAUDESONNET:
|
case "gpt-4.1-2025-04-14":
|
||||||
case LLMModelEnum.CLAUDEHAIKU:
|
case "gpt-4.1-mini-2025-04-14":
|
||||||
finalModel = LLMMappings[model];
|
case "gpt-4.1-nano-2025-04-14":
|
||||||
break;
|
modelInstance = openai(model, { ...options });
|
||||||
|
break;
|
||||||
|
|
||||||
case LLMModelEnum.GEMINI25FLASH:
|
case "claude-3-7-sonnet-20250219":
|
||||||
case LLMModelEnum.GEMINI25PRO:
|
case "claude-3-opus-20240229":
|
||||||
case LLMModelEnum.GEMINI20FLASH:
|
case "claude-3-5-haiku-20241022":
|
||||||
case LLMModelEnum.GEMINI20FLASHLITE:
|
modelInstance = anthropic(model, { ...options });
|
||||||
finalModel = LLMMappings[model];
|
break;
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
case "gemini-2.5-flash-preview-04-17":
|
||||||
logger.warn(`Unsupported model type: ${model}`);
|
case "gemini-2.5-pro-preview-03-25":
|
||||||
break;
|
case "gemini-2.0-flash":
|
||||||
}
|
case "gemini-2.0-flash-lite":
|
||||||
|
modelInstance = google(model, { ...options });
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
if (ollama) {
|
||||||
|
modelInstance = ollama(model);
|
||||||
|
}
|
||||||
|
logger.warn(`Unsupported model type: ${model}`);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stream) {
|
if (stream) {
|
||||||
@ -64,7 +63,7 @@ export async function makeModelCall(
|
|||||||
model: modelInstance as LanguageModelV1,
|
model: modelInstance as LanguageModelV1,
|
||||||
messages,
|
messages,
|
||||||
onFinish: async ({ text }) => {
|
onFinish: async ({ text }) => {
|
||||||
onFinish(text, finalModel);
|
onFinish(text, model);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -74,7 +73,7 @@ export async function makeModelCall(
|
|||||||
messages,
|
messages,
|
||||||
});
|
});
|
||||||
|
|
||||||
onFinish(text, finalModel);
|
onFinish(text, model);
|
||||||
|
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -56,12 +56,6 @@ const { action, loader } = createActionApiRoute(
|
|||||||
episodeBody: body.text,
|
episodeBody: body.text,
|
||||||
referenceTime: new Date().toISOString(),
|
referenceTime: new Date().toISOString(),
|
||||||
source: body.source,
|
source: body.source,
|
||||||
metadata: {
|
|
||||||
activityId: activity.id,
|
|
||||||
integrationAccountId: body.integrationAccountId || "",
|
|
||||||
taskId: body.taskId || "",
|
|
||||||
type: "activity",
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const queueResponse = await addToQueue(
|
const queueResponse = await addToQueue(
|
||||||
|
|||||||
@ -309,3 +309,32 @@ export async function getRelatedEpisodesEntities(params: {
|
|||||||
})
|
})
|
||||||
.filter((entity): entity is EntityNode => entity !== null);
|
.filter((entity): entity is EntityNode => entity !== null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getEpisodeStatements(params: {
|
||||||
|
episodeUuid: string;
|
||||||
|
userId: string;
|
||||||
|
}) {
|
||||||
|
const query = `
|
||||||
|
MATCH (episode:Episode {uuid: $episodeUuid, userId: $userId})-[:HAS_PROVENANCE]->(stmt:Statement)
|
||||||
|
RETURN stmt
|
||||||
|
`;
|
||||||
|
|
||||||
|
const result = await runQuery(query, {
|
||||||
|
episodeUuid: params.episodeUuid,
|
||||||
|
userId: params.userId,
|
||||||
|
});
|
||||||
|
|
||||||
|
return result.map((record) => {
|
||||||
|
const stmt = record.get("stmt").properties;
|
||||||
|
return {
|
||||||
|
uuid: stmt.uuid,
|
||||||
|
fact: stmt.fact,
|
||||||
|
factEmbedding: stmt.factEmbedding,
|
||||||
|
createdAt: new Date(stmt.createdAt),
|
||||||
|
validAt: new Date(stmt.validAt),
|
||||||
|
invalidAt: stmt.invalidAt ? new Date(stmt.invalidAt) : null,
|
||||||
|
attributes: stmt.attributesJson ? JSON.parse(stmt.attributesJson) : {},
|
||||||
|
userId: stmt.userId,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import {
|
|||||||
resolveStatementPrompt,
|
resolveStatementPrompt,
|
||||||
} from "./prompts/statements";
|
} from "./prompts/statements";
|
||||||
import {
|
import {
|
||||||
|
getEpisodeStatements,
|
||||||
getRecentEpisodes,
|
getRecentEpisodes,
|
||||||
getRelatedEpisodesEntities,
|
getRelatedEpisodesEntities,
|
||||||
searchEpisodesByEmbedding,
|
searchEpisodesByEmbedding,
|
||||||
@ -122,13 +123,15 @@ export class KnowledgeGraphService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Step 3.2: Handle preset type logic - expand entities for statement extraction
|
// Step 3.2: Handle preset type logic - expand entities for statement extraction
|
||||||
const entitiesForStatementExtraction =
|
const categorizedEntities = await this.expandEntitiesForStatements(
|
||||||
await this.expandEntitiesForStatements(extractedNodes, episode);
|
extractedNodes,
|
||||||
|
episode,
|
||||||
|
);
|
||||||
|
|
||||||
// Step 4: Statement Extraction - Extract statements (triples) instead of direct edges
|
// Step 4: Statement Extrraction - Extract statements (triples) instead of direct edges
|
||||||
const extractedStatements = await this.extractStatements(
|
const extractedStatements = await this.extractStatements(
|
||||||
episode,
|
episode,
|
||||||
entitiesForStatementExtraction,
|
categorizedEntities,
|
||||||
previousEpisodes,
|
previousEpisodes,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -141,7 +144,11 @@ export class KnowledgeGraphService {
|
|||||||
|
|
||||||
// Step 6: Statement Resolution - Resolve statements and detect contradictions
|
// Step 6: Statement Resolution - Resolve statements and detect contradictions
|
||||||
const { resolvedStatements, invalidatedStatements } =
|
const { resolvedStatements, invalidatedStatements } =
|
||||||
await this.resolveStatements(resolvedTriples, episode);
|
await this.resolveStatements(
|
||||||
|
resolvedTriples,
|
||||||
|
episode,
|
||||||
|
previousEpisodes,
|
||||||
|
);
|
||||||
|
|
||||||
// Step 7: ADd attributes to entity nodes
|
// Step 7: ADd attributes to entity nodes
|
||||||
const updatedTriples = await this.addAttributesToEntities(
|
const updatedTriples = await this.addAttributesToEntities(
|
||||||
@ -261,7 +268,10 @@ export class KnowledgeGraphService {
|
|||||||
*/
|
*/
|
||||||
private async extractStatements(
|
private async extractStatements(
|
||||||
episode: EpisodicNode,
|
episode: EpisodicNode,
|
||||||
extractedEntities: EntityNode[],
|
categorizedEntities: {
|
||||||
|
primary: EntityNode[];
|
||||||
|
expanded: EntityNode[];
|
||||||
|
},
|
||||||
previousEpisodes: EpisodicNode[],
|
previousEpisodes: EpisodicNode[],
|
||||||
): Promise<Triple[]> {
|
): Promise<Triple[]> {
|
||||||
// Use the prompt library to get the appropriate prompts
|
// Use the prompt library to get the appropriate prompts
|
||||||
@ -271,10 +281,16 @@ export class KnowledgeGraphService {
|
|||||||
content: ep.content,
|
content: ep.content,
|
||||||
createdAt: ep.createdAt.toISOString(),
|
createdAt: ep.createdAt.toISOString(),
|
||||||
})),
|
})),
|
||||||
entities: extractedEntities.map((node) => ({
|
entities: {
|
||||||
name: node.name,
|
primary: categorizedEntities.primary.map((node) => ({
|
||||||
type: node.type,
|
name: node.name,
|
||||||
})),
|
type: node.type,
|
||||||
|
})),
|
||||||
|
expanded: categorizedEntities.expanded.map((node) => ({
|
||||||
|
name: node.name,
|
||||||
|
type: node.type,
|
||||||
|
})),
|
||||||
|
},
|
||||||
referenceTime: episode.validAt.toISOString(),
|
referenceTime: episode.validAt.toISOString(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -319,17 +335,23 @@ export class KnowledgeGraphService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Combine primary and expanded entities for entity matching
|
||||||
|
const allEntities = [
|
||||||
|
...categorizedEntities.primary,
|
||||||
|
...categorizedEntities.expanded,
|
||||||
|
];
|
||||||
|
|
||||||
// Convert extracted triples to Triple objects with Statement nodes
|
// Convert extracted triples to Triple objects with Statement nodes
|
||||||
const triples = await Promise.all(
|
const triples = await Promise.all(
|
||||||
extractedTriples.map(async (triple: ExtractedTripleData) => {
|
extractedTriples.map(async (triple: ExtractedTripleData) => {
|
||||||
// Find the subject and object nodes by matching both name and type
|
// Find the subject and object nodes by matching both name and type
|
||||||
const subjectNode = extractedEntities.find(
|
const subjectNode = allEntities.find(
|
||||||
(node) =>
|
(node) =>
|
||||||
node.name.toLowerCase() === triple.source.toLowerCase() &&
|
node.name.toLowerCase() === triple.source.toLowerCase() &&
|
||||||
node.type.toLowerCase() === triple.sourceType.toLowerCase(),
|
node.type.toLowerCase() === triple.sourceType.toLowerCase(),
|
||||||
);
|
);
|
||||||
|
|
||||||
const objectNode = extractedEntities.find(
|
const objectNode = allEntities.find(
|
||||||
(node) =>
|
(node) =>
|
||||||
node.name.toLowerCase() === triple.target.toLowerCase() &&
|
node.name.toLowerCase() === triple.target.toLowerCase() &&
|
||||||
node.type.toLowerCase() === triple.targetType.toLowerCase(),
|
node.type.toLowerCase() === triple.targetType.toLowerCase(),
|
||||||
@ -373,9 +395,12 @@ export class KnowledgeGraphService {
|
|||||||
private async expandEntitiesForStatements(
|
private async expandEntitiesForStatements(
|
||||||
extractedNodes: EntityNode[],
|
extractedNodes: EntityNode[],
|
||||||
episode: EpisodicNode,
|
episode: EpisodicNode,
|
||||||
): Promise<EntityNode[]> {
|
): Promise<{
|
||||||
|
primary: EntityNode[];
|
||||||
|
expanded: EntityNode[];
|
||||||
|
}> {
|
||||||
const allAppEnumValues = Object.values(Apps);
|
const allAppEnumValues = Object.values(Apps);
|
||||||
const expandedEntities = [...extractedNodes];
|
const expandedEntities: EntityNode[] = [];
|
||||||
|
|
||||||
// For each extracted entity, check if we need to add existing preset entities
|
// For each extracted entity, check if we need to add existing preset entities
|
||||||
for (const entity of extractedNodes) {
|
for (const entity of extractedNodes) {
|
||||||
@ -385,7 +410,7 @@ export class KnowledgeGraphService {
|
|||||||
const similarEntities = await findSimilarEntities({
|
const similarEntities = await findSimilarEntities({
|
||||||
queryEmbedding: entity.nameEmbedding,
|
queryEmbedding: entity.nameEmbedding,
|
||||||
limit: 5,
|
limit: 5,
|
||||||
threshold: 0.7,
|
threshold: 0.8,
|
||||||
userId: episode.userId,
|
userId: episode.userId,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -405,7 +430,27 @@ export class KnowledgeGraphService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return expandedEntities;
|
// Deduplicate by name AND type combination
|
||||||
|
const deduplicateEntities = (entities: EntityNode[]) => {
|
||||||
|
const seen = new Map<string, EntityNode>();
|
||||||
|
return entities.filter((entity) => {
|
||||||
|
const key = `${entity.name.toLowerCase()}_${entity.type.toLowerCase()}`;
|
||||||
|
if (seen.has(key)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
seen.set(key, entity);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
primary: deduplicateEntities(extractedNodes),
|
||||||
|
expanded: deduplicateEntities(
|
||||||
|
expandedEntities.filter(
|
||||||
|
(e) => !extractedNodes.some((primary) => primary.uuid === e.uuid),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -436,9 +481,6 @@ export class KnowledgeGraphService {
|
|||||||
|
|
||||||
if (newIsPreset && !existingIsPreset) {
|
if (newIsPreset && !existingIsPreset) {
|
||||||
// New is preset, existing is custom - evolve existing entity to preset type
|
// New is preset, existing is custom - evolve existing entity to preset type
|
||||||
console.log(
|
|
||||||
`Evolving entity: ${existingEntity.name} from ${existingEntity.type} to ${newEntity.type}`,
|
|
||||||
);
|
|
||||||
existingEntityIds.push(existingEntity.uuid);
|
existingEntityIds.push(existingEntity.uuid);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -665,6 +707,7 @@ export class KnowledgeGraphService {
|
|||||||
private async resolveStatements(
|
private async resolveStatements(
|
||||||
triples: Triple[],
|
triples: Triple[],
|
||||||
episode: EpisodicNode,
|
episode: EpisodicNode,
|
||||||
|
previousEpisodes: EpisodicNode[],
|
||||||
): Promise<{
|
): Promise<{
|
||||||
resolvedStatements: Triple[];
|
resolvedStatements: Triple[];
|
||||||
invalidatedStatements: string[];
|
invalidatedStatements: string[];
|
||||||
@ -704,7 +747,7 @@ export class KnowledgeGraphService {
|
|||||||
// Phase 2: Find semantically similar statements
|
// Phase 2: Find semantically similar statements
|
||||||
const semanticMatches = await findSimilarStatements({
|
const semanticMatches = await findSimilarStatements({
|
||||||
factEmbedding: triple.statement.factEmbedding,
|
factEmbedding: triple.statement.factEmbedding,
|
||||||
threshold: 0.85,
|
threshold: 0.7,
|
||||||
excludeIds: checkedStatementIds,
|
excludeIds: checkedStatementIds,
|
||||||
userId: triple.provenance.userId,
|
userId: triple.provenance.userId,
|
||||||
});
|
});
|
||||||
@ -713,10 +756,35 @@ export class KnowledgeGraphService {
|
|||||||
potentialMatches.push(...semanticMatches);
|
potentialMatches.push(...semanticMatches);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Phase 3: Check related memories for contradictory statements
|
||||||
|
const previousEpisodesStatements: StatementNode[] = [];
|
||||||
|
|
||||||
|
await Promise.all(
|
||||||
|
previousEpisodes.map(async (episode) => {
|
||||||
|
const statements = await getEpisodeStatements({
|
||||||
|
episodeUuid: episode.uuid,
|
||||||
|
userId: episode.userId,
|
||||||
|
});
|
||||||
|
previousEpisodesStatements.push(...statements);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (previousEpisodesStatements && previousEpisodesStatements.length > 0) {
|
||||||
|
// Filter out facts we've already checked
|
||||||
|
const newRelatedFacts = previousEpisodesStatements
|
||||||
|
.flat()
|
||||||
|
.filter((fact) => !checkedStatementIds.includes(fact.uuid));
|
||||||
|
|
||||||
|
if (newRelatedFacts.length > 0) {
|
||||||
|
potentialMatches.push(...newRelatedFacts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (potentialMatches.length > 0) {
|
if (potentialMatches.length > 0) {
|
||||||
logger.info(
|
logger.info(
|
||||||
`Found ${potentialMatches.length} potential matches for: ${triple.statement.fact}`,
|
`Found ${potentialMatches.length} potential matches for: ${triple.statement.fact}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
allPotentialMatches.set(triple.statement.uuid, potentialMatches);
|
allPotentialMatches.set(triple.statement.uuid, potentialMatches);
|
||||||
|
|
||||||
// Get full triple information for each potential match
|
// Get full triple information for each potential match
|
||||||
@ -947,9 +1015,12 @@ export class KnowledgeGraphService {
|
|||||||
}
|
}
|
||||||
const entityTypes = getNodeTypesString(appEnumValues);
|
const entityTypes = getNodeTypesString(appEnumValues);
|
||||||
const relatedMemories = await this.getRelatedMemories(episodeBody, userId);
|
const relatedMemories = await this.getRelatedMemories(episodeBody, userId);
|
||||||
|
|
||||||
// Fetch ingestion rules for this source
|
// Fetch ingestion rules for this source
|
||||||
const ingestionRules = await this.getIngestionRulesForSource(source, userId);
|
const ingestionRules = await this.getIngestionRulesForSource(
|
||||||
|
source,
|
||||||
|
userId,
|
||||||
|
);
|
||||||
|
|
||||||
const context = {
|
const context = {
|
||||||
episodeContent: episodeBody,
|
episodeContent: episodeBody,
|
||||||
|
|||||||
@ -21,6 +21,17 @@ CRITICAL REQUIREMENT:
|
|||||||
- DO NOT create, invent, or modify any entity names.
|
- DO NOT create, invent, or modify any entity names.
|
||||||
- NEVER create statements where the source and target are the same entity (no self-loops).
|
- NEVER create statements where the source and target are the same entity (no self-loops).
|
||||||
|
|
||||||
|
ENTITY PRIORITIZATION:
|
||||||
|
- **PRIMARY ENTITIES**: Directly extracted from the current episode - these are your main focus
|
||||||
|
- **EXPANDED ENTITIES**: From related contexts - only use if they're explicitly mentioned or contextually relevant
|
||||||
|
|
||||||
|
RELATIONSHIP FORMATION RULES:
|
||||||
|
1. **PRIMARY-PRIMARY**: Always consider relationships between primary entities
|
||||||
|
2. **PRIMARY-EXPANDED**: Only if the expanded entity is mentioned in the episode content
|
||||||
|
3. **EXPANDED-EXPANDED**: Avoid unless there's explicit connection in the episode
|
||||||
|
|
||||||
|
FOCUS: Create relationships that ADD VALUE to understanding the current episode, not just because entities are available.
|
||||||
|
|
||||||
## PRIMARY MISSION: EXTRACT NEW RELATIONSHIPS
|
## PRIMARY MISSION: EXTRACT NEW RELATIONSHIPS
|
||||||
Focus on extracting factual statements that ADD NEW VALUE to the knowledge graph:
|
Focus on extracting factual statements that ADD NEW VALUE to the knowledge graph:
|
||||||
- **PRIORITIZE**: New relationships not already captured in previous episodes
|
- **PRIORITIZE**: New relationships not already captured in previous episodes
|
||||||
@ -136,7 +147,13 @@ ${JSON.stringify(context.previousEpisodes, null, 2)}
|
|||||||
</PREVIOUS_EPISODES>
|
</PREVIOUS_EPISODES>
|
||||||
|
|
||||||
<AVAILABLE_ENTITIES>
|
<AVAILABLE_ENTITIES>
|
||||||
${JSON.stringify(context.entities, null, 2)}
|
<PRIMARY_ENTITIES>
|
||||||
|
${JSON.stringify(context.entities.primary, null, 2)}
|
||||||
|
</PRIMARY_ENTITIES>
|
||||||
|
|
||||||
|
<EXPANDED_ENTITIES>
|
||||||
|
${JSON.stringify(context.entities.expanded, null, 2)}
|
||||||
|
</EXPANDED_ENTITIES>
|
||||||
</AVAILABLE_ENTITIES>
|
</AVAILABLE_ENTITIES>
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -126,7 +126,6 @@ const executeCLICommand = async (
|
|||||||
args.push("--config", JSON.stringify(config || {}));
|
args.push("--config", JSON.stringify(config || {}));
|
||||||
args.push("--state", JSON.stringify(state || {}));
|
args.push("--state", JSON.stringify(state || {}));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unsupported event type: ${eventType}`);
|
throw new Error(`Unsupported event type: ${eventType}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { PrismaClient } from "@prisma/client";
|
import { Activity, PrismaClient } from "@prisma/client";
|
||||||
import { type Message } from "@core/types";
|
import { type Message } from "@core/types";
|
||||||
|
import { addToQueue } from "~/lib/ingest.server";
|
||||||
|
|
||||||
const prisma = new PrismaClient();
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
@ -111,20 +112,42 @@ export const createActivities = async ({
|
|||||||
where: {
|
where: {
|
||||||
id: integrationAccountId,
|
id: integrationAccountId,
|
||||||
},
|
},
|
||||||
|
include: {
|
||||||
|
integrationDefinition: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!integrationAccount) {
|
if (!integrationAccount) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return await prisma.activity.createMany({
|
return await Promise.all(
|
||||||
data: messages.map((message) => {
|
messages.map(async (message) => {
|
||||||
|
const activity = await prisma.activity.create({
|
||||||
|
data: {
|
||||||
|
text: message.data.text,
|
||||||
|
sourceURL: message.data.sourceURL,
|
||||||
|
integrationAccountId,
|
||||||
|
workspaceId: integrationAccount?.workspaceId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const ingestData = {
|
||||||
|
episodeBody: message.data.text,
|
||||||
|
referenceTime: new Date().toISOString(),
|
||||||
|
source: integrationAccount?.integrationDefinition.slug,
|
||||||
|
};
|
||||||
|
|
||||||
|
const queueResponse = await addToQueue(
|
||||||
|
ingestData,
|
||||||
|
integrationAccount?.integratedById,
|
||||||
|
activity.id,
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
text: message.data.text,
|
activityId: activity.id,
|
||||||
sourceURL: message.data.sourceURL,
|
queueId: queueResponse.id,
|
||||||
integrationAccountId,
|
|
||||||
workspaceId: integrationAccount?.workspaceId,
|
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
});
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -256,6 +256,19 @@ export const APP_NODE_TYPES = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
STATUS: {
|
||||||
|
name: "Linear Status",
|
||||||
|
description:
|
||||||
|
"A status or state of a Linear issue (e.g., Todo, In Progress, Done, Completed)",
|
||||||
|
attributes: [
|
||||||
|
{
|
||||||
|
name: "statusName",
|
||||||
|
description: "The name of the status",
|
||||||
|
type: "string",
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
[Apps.SLACK]: {
|
[Apps.SLACK]: {
|
||||||
CHANNEL: {
|
CHANNEL: {
|
||||||
|
|||||||
@ -28,7 +28,7 @@ function createActivityMessage(params: LinearActivityCreateParams) {
|
|||||||
/**
|
/**
|
||||||
* Fetches user information from Linear
|
* Fetches user information from Linear
|
||||||
*/
|
*/
|
||||||
async function fetchUserInfo(accessToken: string) {
|
async function fetchUserInfo(apiKey: string) {
|
||||||
try {
|
try {
|
||||||
const query = `
|
const query = `
|
||||||
query {
|
query {
|
||||||
@ -46,7 +46,7 @@ async function fetchUserInfo(accessToken: string) {
|
|||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
Authorization: accessToken,
|
Authorization: apiKey,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -60,7 +60,7 @@ async function fetchUserInfo(accessToken: string) {
|
|||||||
/**
|
/**
|
||||||
* Fetches recent issues relevant to the user (created, assigned, or subscribed)
|
* Fetches recent issues relevant to the user (created, assigned, or subscribed)
|
||||||
*/
|
*/
|
||||||
async function fetchRecentIssues(accessToken: string, lastSyncTime: string) {
|
async function fetchRecentIssues(apiKey: string, lastSyncTime: string) {
|
||||||
try {
|
try {
|
||||||
const query = `
|
const query = `
|
||||||
query RecentIssues($lastSyncTime: DateTimeOrDuration) {
|
query RecentIssues($lastSyncTime: DateTimeOrDuration) {
|
||||||
@ -138,7 +138,7 @@ async function fetchRecentIssues(accessToken: string, lastSyncTime: string) {
|
|||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
Authorization: accessToken,
|
Authorization: apiKey,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -152,7 +152,7 @@ async function fetchRecentIssues(accessToken: string, lastSyncTime: string) {
|
|||||||
/**
|
/**
|
||||||
* Fetches recent comments on issues relevant to the user
|
* Fetches recent comments on issues relevant to the user
|
||||||
*/
|
*/
|
||||||
async function fetchRecentComments(accessToken: string, lastSyncTime: string) {
|
async function fetchRecentComments(apiKey: string, lastSyncTime: string) {
|
||||||
try {
|
try {
|
||||||
const query = `
|
const query = `
|
||||||
query RecentComments($lastSyncTime: DateTimeOrDuration) {
|
query RecentComments($lastSyncTime: DateTimeOrDuration) {
|
||||||
@ -216,7 +216,7 @@ async function fetchRecentComments(accessToken: string, lastSyncTime: string) {
|
|||||||
{
|
{
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
Authorization: accessToken,
|
Authorization: apiKey,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -444,7 +444,7 @@ export async function handleSchedule(config: any, state: any) {
|
|||||||
const integrationConfiguration = config;
|
const integrationConfiguration = config;
|
||||||
|
|
||||||
// Check if we have a valid access token
|
// Check if we have a valid access token
|
||||||
if (!integrationConfiguration?.accessToken) {
|
if (!integrationConfiguration?.apiKey) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -458,7 +458,7 @@ export async function handleSchedule(config: any, state: any) {
|
|||||||
// Fetch user info to identify activities relevant to them
|
// Fetch user info to identify activities relevant to them
|
||||||
let user;
|
let user;
|
||||||
try {
|
try {
|
||||||
user = await fetchUserInfo(integrationConfiguration.accessToken);
|
user = await fetchUserInfo(integrationConfiguration.apiKey);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@ -472,7 +472,7 @@ export async function handleSchedule(config: any, state: any) {
|
|||||||
|
|
||||||
// Process all issue activities (created, assigned, updated, etc.)
|
// Process all issue activities (created, assigned, updated, etc.)
|
||||||
try {
|
try {
|
||||||
const issues = await fetchRecentIssues(integrationConfiguration.accessToken, lastIssuesSync);
|
const issues = await fetchRecentIssues(integrationConfiguration.apiKey, lastIssuesSync);
|
||||||
if (issues && issues.nodes) {
|
if (issues && issues.nodes) {
|
||||||
const issueActivities = await processIssueActivities(issues.nodes, user.id);
|
const issueActivities = await processIssueActivities(issues.nodes, user.id);
|
||||||
messages.push(...issueActivities);
|
messages.push(...issueActivities);
|
||||||
@ -483,10 +483,7 @@ export async function handleSchedule(config: any, state: any) {
|
|||||||
|
|
||||||
// Process all comment activities
|
// Process all comment activities
|
||||||
try {
|
try {
|
||||||
const comments = await fetchRecentComments(
|
const comments = await fetchRecentComments(integrationConfiguration.apiKey, lastCommentsSync);
|
||||||
integrationConfiguration.accessToken,
|
|
||||||
lastCommentsSync,
|
|
||||||
);
|
|
||||||
if (comments && comments.nodes) {
|
if (comments && comments.nodes) {
|
||||||
const commentActivities = await processCommentActivities(comments.nodes, user.id, user);
|
const commentActivities = await processCommentActivities(comments.nodes, user.id, user);
|
||||||
messages.push(...commentActivities);
|
messages.push(...commentActivities);
|
||||||
|
|||||||
97
pnpm-lock.yaml
generated
97
pnpm-lock.yaml
generated
@ -257,7 +257,7 @@ importers:
|
|||||||
version: link:../../packages/emails
|
version: link:../../packages/emails
|
||||||
exa-js:
|
exa-js:
|
||||||
specifier: ^1.8.20
|
specifier: ^1.8.20
|
||||||
version: 1.8.20(encoding@0.1.13)(ws@8.17.1)
|
version: 1.8.20(encoding@0.1.13)
|
||||||
execa:
|
execa:
|
||||||
specifier: ^9.6.0
|
specifier: ^9.6.0
|
||||||
version: 9.6.0
|
version: 9.6.0
|
||||||
@ -381,7 +381,7 @@ importers:
|
|||||||
devDependencies:
|
devDependencies:
|
||||||
'@remix-run/dev':
|
'@remix-run/dev':
|
||||||
specifier: 2.16.7
|
specifier: 2.16.7
|
||||||
version: 2.16.7(@remix-run/react@2.16.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3))(@remix-run/serve@2.16.7(typescript@5.8.3))(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(typescript@5.8.3)(vite@6.3.5(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0))(yaml@2.8.0)
|
version: 2.16.7(@remix-run/react@2.16.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3))(@remix-run/serve@2.16.7(typescript@5.8.3))(@types/node@24.0.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(typescript@5.8.3)(vite@6.3.5(@types/node@24.0.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0))(yaml@2.8.0)
|
||||||
'@remix-run/eslint-config':
|
'@remix-run/eslint-config':
|
||||||
specifier: 2.16.7
|
specifier: 2.16.7
|
||||||
version: 2.16.7(eslint@8.57.1)(react@18.3.1)(typescript@5.8.3)
|
version: 2.16.7(eslint@8.57.1)(react@18.3.1)(typescript@5.8.3)
|
||||||
@ -396,7 +396,7 @@ importers:
|
|||||||
version: 0.5.16(tailwindcss@4.1.7)
|
version: 0.5.16(tailwindcss@4.1.7)
|
||||||
'@tailwindcss/vite':
|
'@tailwindcss/vite':
|
||||||
specifier: ^4.1.7
|
specifier: ^4.1.7
|
||||||
version: 4.1.9(vite@6.3.5(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0))
|
version: 4.1.9(vite@6.3.5(@types/node@24.0.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0))
|
||||||
'@trigger.dev/build':
|
'@trigger.dev/build':
|
||||||
specifier: ^4.0.0-v4-beta.22
|
specifier: ^4.0.0-v4-beta.22
|
||||||
version: 4.0.0-v4-beta.22(typescript@5.8.3)
|
version: 4.0.0-v4-beta.22(typescript@5.8.3)
|
||||||
@ -492,10 +492,10 @@ importers:
|
|||||||
version: 5.8.3
|
version: 5.8.3
|
||||||
vite:
|
vite:
|
||||||
specifier: ^6.0.0
|
specifier: ^6.0.0
|
||||||
version: 6.3.5(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0)
|
version: 6.3.5(@types/node@24.0.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0)
|
||||||
vite-tsconfig-paths:
|
vite-tsconfig-paths:
|
||||||
specifier: ^4.2.1
|
specifier: ^4.2.1
|
||||||
version: 4.3.2(typescript@5.8.3)(vite@6.3.5(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0))
|
version: 4.3.2(typescript@5.8.3)(vite@6.3.5(@types/node@24.0.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0))
|
||||||
|
|
||||||
packages/database:
|
packages/database:
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -598,7 +598,7 @@ importers:
|
|||||||
version: 20.19.7
|
version: 20.19.7
|
||||||
tsup:
|
tsup:
|
||||||
specifier: ^8.0.1
|
specifier: ^8.0.1
|
||||||
version: 8.5.0(@swc/core@1.3.101(@swc/helpers@0.5.17))(jiti@2.4.2)(postcss@8.5.5)(typescript@5.8.3)(yaml@2.8.0)
|
version: 8.5.0(@swc/core@1.3.101)(jiti@2.4.2)(postcss@8.5.5)(typescript@5.8.3)(yaml@2.8.0)
|
||||||
typescript:
|
typescript:
|
||||||
specifier: ^5.0.0
|
specifier: ^5.0.0
|
||||||
version: 5.8.3
|
version: 5.8.3
|
||||||
@ -635,7 +635,7 @@ importers:
|
|||||||
version: 6.0.1
|
version: 6.0.1
|
||||||
tsup:
|
tsup:
|
||||||
specifier: ^8.0.1
|
specifier: ^8.0.1
|
||||||
version: 8.5.0(@swc/core@1.3.101(@swc/helpers@0.5.17))(jiti@2.4.2)(postcss@8.5.5)(typescript@5.8.3)(yaml@2.8.0)
|
version: 8.5.0(@swc/core@1.3.101)(jiti@2.4.2)(postcss@8.5.5)(typescript@5.8.3)(yaml@2.8.0)
|
||||||
typescript:
|
typescript:
|
||||||
specifier: ^5.3.0
|
specifier: ^5.3.0
|
||||||
version: 5.8.3
|
version: 5.8.3
|
||||||
@ -12052,7 +12052,7 @@ snapshots:
|
|||||||
react-dom: 18.3.1(react@18.3.1)
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 18.2.47
|
'@types/react': 18.2.47
|
||||||
'@types/react-dom': 18.3.7(@types/react@18.2.69)
|
'@types/react-dom': 18.3.7(@types/react@18.2.47)
|
||||||
|
|
||||||
'@radix-ui/react-arrow@1.1.7(@types/react-dom@18.3.7(@types/react@18.2.69))(@types/react@18.2.69)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
'@radix-ui/react-arrow@1.1.7(@types/react-dom@18.3.7(@types/react@18.2.69))(@types/react@18.2.69)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -12106,7 +12106,7 @@ snapshots:
|
|||||||
react-dom: 18.3.1(react@18.3.1)
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 18.2.47
|
'@types/react': 18.2.47
|
||||||
'@types/react-dom': 18.3.7(@types/react@18.2.69)
|
'@types/react-dom': 18.3.7(@types/react@18.2.47)
|
||||||
|
|
||||||
'@radix-ui/react-collapsible@1.1.11(@types/react-dom@18.3.7(@types/react@18.2.69))(@types/react@18.2.69)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
'@radix-ui/react-collapsible@1.1.11(@types/react-dom@18.3.7(@types/react@18.2.69))(@types/react@18.2.69)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -12134,7 +12134,7 @@ snapshots:
|
|||||||
react-dom: 18.3.1(react@18.3.1)
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 18.2.47
|
'@types/react': 18.2.47
|
||||||
'@types/react-dom': 18.3.7(@types/react@18.2.69)
|
'@types/react-dom': 18.3.7(@types/react@18.2.47)
|
||||||
|
|
||||||
'@radix-ui/react-collection@1.1.7(@types/react-dom@18.3.7(@types/react@18.2.69))(@types/react@18.2.69)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
'@radix-ui/react-collection@1.1.7(@types/react-dom@18.3.7(@types/react@18.2.69))(@types/react@18.2.69)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -12267,7 +12267,7 @@ snapshots:
|
|||||||
react-dom: 18.3.1(react@18.3.1)
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 18.2.47
|
'@types/react': 18.2.47
|
||||||
'@types/react-dom': 18.3.7(@types/react@18.2.69)
|
'@types/react-dom': 18.3.7(@types/react@18.2.47)
|
||||||
|
|
||||||
'@radix-ui/react-dismissable-layer@1.1.10(@types/react-dom@18.3.7(@types/react@18.2.69))(@types/react@18.2.69)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
'@radix-ui/react-dismissable-layer@1.1.10(@types/react-dom@18.3.7(@types/react@18.2.69))(@types/react@18.2.69)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -12332,7 +12332,7 @@ snapshots:
|
|||||||
react-dom: 18.3.1(react@18.3.1)
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 18.2.47
|
'@types/react': 18.2.47
|
||||||
'@types/react-dom': 18.3.7(@types/react@18.2.69)
|
'@types/react-dom': 18.3.7(@types/react@18.2.47)
|
||||||
|
|
||||||
'@radix-ui/react-focus-scope@1.1.7(@types/react-dom@18.3.7(@types/react@18.2.69))(@types/react@18.2.69)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
'@radix-ui/react-focus-scope@1.1.7(@types/react-dom@18.3.7(@types/react@18.2.69))(@types/react@18.2.69)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -12425,7 +12425,7 @@ snapshots:
|
|||||||
react-remove-scroll: 2.5.7(@types/react@18.2.47)(react@18.3.1)
|
react-remove-scroll: 2.5.7(@types/react@18.2.47)(react@18.3.1)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 18.2.47
|
'@types/react': 18.2.47
|
||||||
'@types/react-dom': 18.3.7(@types/react@18.2.69)
|
'@types/react-dom': 18.3.7(@types/react@18.2.47)
|
||||||
|
|
||||||
'@radix-ui/react-popover@1.1.14(@types/react-dom@18.3.7(@types/react@18.2.69))(@types/react@18.2.69)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
'@radix-ui/react-popover@1.1.14(@types/react-dom@18.3.7(@types/react@18.2.69))(@types/react@18.2.69)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -12466,7 +12466,7 @@ snapshots:
|
|||||||
react-dom: 18.3.1(react@18.3.1)
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 18.2.47
|
'@types/react': 18.2.47
|
||||||
'@types/react-dom': 18.3.7(@types/react@18.2.69)
|
'@types/react-dom': 18.3.7(@types/react@18.2.47)
|
||||||
|
|
||||||
'@radix-ui/react-popper@1.2.7(@types/react-dom@18.3.7(@types/react@18.2.69))(@types/react@18.2.69)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
'@radix-ui/react-popper@1.2.7(@types/react-dom@18.3.7(@types/react@18.2.69))(@types/react@18.2.69)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -12501,7 +12501,7 @@ snapshots:
|
|||||||
react-dom: 18.3.1(react@18.3.1)
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 18.2.47
|
'@types/react': 18.2.47
|
||||||
'@types/react-dom': 18.3.7(@types/react@18.2.69)
|
'@types/react-dom': 18.3.7(@types/react@18.2.47)
|
||||||
|
|
||||||
'@radix-ui/react-portal@1.1.9(@types/react-dom@18.3.7(@types/react@18.2.69))(@types/react@18.2.69)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
'@radix-ui/react-portal@1.1.9(@types/react-dom@18.3.7(@types/react@18.2.69))(@types/react@18.2.69)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -12529,7 +12529,7 @@ snapshots:
|
|||||||
react-dom: 18.3.1(react@18.3.1)
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 18.2.47
|
'@types/react': 18.2.47
|
||||||
'@types/react-dom': 18.3.7(@types/react@18.2.69)
|
'@types/react-dom': 18.3.7(@types/react@18.2.47)
|
||||||
|
|
||||||
'@radix-ui/react-presence@1.1.4(@types/react-dom@18.3.7(@types/react@18.2.69))(@types/react@18.2.69)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
'@radix-ui/react-presence@1.1.4(@types/react-dom@18.3.7(@types/react@18.2.69))(@types/react@18.2.69)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -12555,7 +12555,7 @@ snapshots:
|
|||||||
react-dom: 18.3.1(react@18.3.1)
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 18.2.47
|
'@types/react': 18.2.47
|
||||||
'@types/react-dom': 18.3.7(@types/react@18.2.69)
|
'@types/react-dom': 18.3.7(@types/react@18.2.47)
|
||||||
|
|
||||||
'@radix-ui/react-primitive@2.1.3(@types/react-dom@18.3.7(@types/react@18.2.69))(@types/react@18.2.69)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
'@radix-ui/react-primitive@2.1.3(@types/react-dom@18.3.7(@types/react@18.2.69))(@types/react@18.2.69)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -12581,7 +12581,7 @@ snapshots:
|
|||||||
react-dom: 18.3.1(react@18.3.1)
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 18.2.47
|
'@types/react': 18.2.47
|
||||||
'@types/react-dom': 18.3.7(@types/react@18.2.69)
|
'@types/react-dom': 18.3.7(@types/react@18.2.47)
|
||||||
|
|
||||||
'@radix-ui/react-roving-focus@1.1.10(@types/react-dom@18.3.7(@types/react@18.2.69))(@types/react@18.2.69)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
'@radix-ui/react-roving-focus@1.1.10(@types/react-dom@18.3.7(@types/react@18.2.69))(@types/react@18.2.69)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -12747,7 +12747,7 @@ snapshots:
|
|||||||
react-dom: 18.3.1(react@18.3.1)
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 18.2.47
|
'@types/react': 18.2.47
|
||||||
'@types/react-dom': 18.3.7(@types/react@18.2.69)
|
'@types/react-dom': 18.3.7(@types/react@18.2.47)
|
||||||
|
|
||||||
'@radix-ui/react-toggle@1.1.0(@types/react-dom@18.3.7(@types/react@18.2.47))(@types/react@18.2.47)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
'@radix-ui/react-toggle@1.1.0(@types/react-dom@18.3.7(@types/react@18.2.47))(@types/react@18.2.47)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -12758,7 +12758,7 @@ snapshots:
|
|||||||
react-dom: 18.3.1(react@18.3.1)
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 18.2.47
|
'@types/react': 18.2.47
|
||||||
'@types/react-dom': 18.3.7(@types/react@18.2.69)
|
'@types/react-dom': 18.3.7(@types/react@18.2.47)
|
||||||
|
|
||||||
'@radix-ui/react-tooltip@1.1.1(@types/react-dom@18.3.7(@types/react@18.2.47))(@types/react@18.2.47)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
'@radix-ui/react-tooltip@1.1.1(@types/react-dom@18.3.7(@types/react@18.2.47))(@types/react@18.2.47)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -12778,7 +12778,7 @@ snapshots:
|
|||||||
react-dom: 18.3.1(react@18.3.1)
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 18.2.47
|
'@types/react': 18.2.47
|
||||||
'@types/react-dom': 18.3.7(@types/react@18.2.69)
|
'@types/react-dom': 18.3.7(@types/react@18.2.47)
|
||||||
|
|
||||||
'@radix-ui/react-tooltip@1.2.7(@types/react-dom@18.3.7(@types/react@18.2.69))(@types/react@18.2.69)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
'@radix-ui/react-tooltip@1.2.7(@types/react-dom@18.3.7(@types/react@18.2.69))(@types/react@18.2.69)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -12930,7 +12930,7 @@ snapshots:
|
|||||||
react-dom: 18.3.1(react@18.3.1)
|
react-dom: 18.3.1(react@18.3.1)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 18.2.47
|
'@types/react': 18.2.47
|
||||||
'@types/react-dom': 18.3.7(@types/react@18.2.69)
|
'@types/react-dom': 18.3.7(@types/react@18.2.47)
|
||||||
|
|
||||||
'@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@18.3.7(@types/react@18.2.69))(@types/react@18.2.69)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
'@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@18.3.7(@types/react@18.2.69))(@types/react@18.2.69)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -13082,7 +13082,7 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- encoding
|
- encoding
|
||||||
|
|
||||||
'@remix-run/dev@2.16.7(@remix-run/react@2.16.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3))(@remix-run/serve@2.16.7(typescript@5.8.3))(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(typescript@5.8.3)(vite@6.3.5(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0))(yaml@2.8.0)':
|
'@remix-run/dev@2.16.7(@remix-run/react@2.16.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.8.3))(@remix-run/serve@2.16.7(typescript@5.8.3))(@types/node@24.0.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(typescript@5.8.3)(vite@6.3.5(@types/node@24.0.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0))(yaml@2.8.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.27.4
|
'@babel/core': 7.27.4
|
||||||
'@babel/generator': 7.27.5
|
'@babel/generator': 7.27.5
|
||||||
@ -13099,7 +13099,7 @@ snapshots:
|
|||||||
'@remix-run/router': 1.23.0
|
'@remix-run/router': 1.23.0
|
||||||
'@remix-run/server-runtime': 2.16.7(typescript@5.8.3)
|
'@remix-run/server-runtime': 2.16.7(typescript@5.8.3)
|
||||||
'@types/mdx': 2.0.13
|
'@types/mdx': 2.0.13
|
||||||
'@vanilla-extract/integration': 6.5.0(@types/node@22.16.0)(lightningcss@1.30.1)(terser@5.42.0)
|
'@vanilla-extract/integration': 6.5.0(@types/node@24.0.0)(lightningcss@1.30.1)(terser@5.42.0)
|
||||||
arg: 5.0.2
|
arg: 5.0.2
|
||||||
cacache: 17.1.4
|
cacache: 17.1.4
|
||||||
chalk: 4.1.2
|
chalk: 4.1.2
|
||||||
@ -13139,12 +13139,12 @@ snapshots:
|
|||||||
tar-fs: 2.1.3
|
tar-fs: 2.1.3
|
||||||
tsconfig-paths: 4.2.0
|
tsconfig-paths: 4.2.0
|
||||||
valibot: 0.41.0(typescript@5.8.3)
|
valibot: 0.41.0(typescript@5.8.3)
|
||||||
vite-node: 3.2.3(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0)
|
vite-node: 3.2.3(@types/node@24.0.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0)
|
||||||
ws: 7.5.10
|
ws: 7.5.10
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@remix-run/serve': 2.16.7(typescript@5.8.3)
|
'@remix-run/serve': 2.16.7(typescript@5.8.3)
|
||||||
typescript: 5.8.3
|
typescript: 5.8.3
|
||||||
vite: 6.3.5(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0)
|
vite: 6.3.5(@types/node@24.0.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@types/node'
|
- '@types/node'
|
||||||
- babel-plugin-macros
|
- babel-plugin-macros
|
||||||
@ -13883,12 +13883,12 @@ snapshots:
|
|||||||
postcss-selector-parser: 6.0.10
|
postcss-selector-parser: 6.0.10
|
||||||
tailwindcss: 4.1.7
|
tailwindcss: 4.1.7
|
||||||
|
|
||||||
'@tailwindcss/vite@4.1.9(vite@6.3.5(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0))':
|
'@tailwindcss/vite@4.1.9(vite@6.3.5(@types/node@24.0.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tailwindcss/node': 4.1.9
|
'@tailwindcss/node': 4.1.9
|
||||||
'@tailwindcss/oxide': 4.1.9
|
'@tailwindcss/oxide': 4.1.9
|
||||||
tailwindcss: 4.1.9
|
tailwindcss: 4.1.9
|
||||||
vite: 6.3.5(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0)
|
vite: 6.3.5(@types/node@24.0.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0)
|
||||||
|
|
||||||
'@tanstack/react-table@8.21.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
'@tanstack/react-table@8.21.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -14497,6 +14497,10 @@ snapshots:
|
|||||||
|
|
||||||
'@types/range-parser@1.2.7': {}
|
'@types/range-parser@1.2.7': {}
|
||||||
|
|
||||||
|
'@types/react-dom@18.3.7(@types/react@18.2.47)':
|
||||||
|
dependencies:
|
||||||
|
'@types/react': 18.2.47
|
||||||
|
|
||||||
'@types/react-dom@18.3.7(@types/react@18.2.69)':
|
'@types/react-dom@18.3.7(@types/react@18.2.69)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/react': 18.2.69
|
'@types/react': 18.2.69
|
||||||
@ -14816,7 +14820,7 @@ snapshots:
|
|||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- babel-plugin-macros
|
- babel-plugin-macros
|
||||||
|
|
||||||
'@vanilla-extract/integration@6.5.0(@types/node@22.16.0)(lightningcss@1.30.1)(terser@5.42.0)':
|
'@vanilla-extract/integration@6.5.0(@types/node@24.0.0)(lightningcss@1.30.1)(terser@5.42.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/core': 7.27.4
|
'@babel/core': 7.27.4
|
||||||
'@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.27.4)
|
'@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.27.4)
|
||||||
@ -14829,8 +14833,8 @@ snapshots:
|
|||||||
lodash: 4.17.21
|
lodash: 4.17.21
|
||||||
mlly: 1.7.4
|
mlly: 1.7.4
|
||||||
outdent: 0.8.0
|
outdent: 0.8.0
|
||||||
vite: 5.4.19(@types/node@22.16.0)(lightningcss@1.30.1)(terser@5.42.0)
|
vite: 5.4.19(@types/node@24.0.0)(lightningcss@1.30.1)(terser@5.42.0)
|
||||||
vite-node: 1.6.1(@types/node@22.16.0)(lightningcss@1.30.1)(terser@5.42.0)
|
vite-node: 1.6.1(@types/node@24.0.0)(lightningcss@1.30.1)(terser@5.42.0)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@types/node'
|
- '@types/node'
|
||||||
- babel-plugin-macros
|
- babel-plugin-macros
|
||||||
@ -16727,11 +16731,11 @@ snapshots:
|
|||||||
run-exclusive: 2.2.19
|
run-exclusive: 2.2.19
|
||||||
tsafe: 1.8.5
|
tsafe: 1.8.5
|
||||||
|
|
||||||
exa-js@1.8.20(encoding@0.1.13)(ws@8.17.1):
|
exa-js@1.8.20(encoding@0.1.13):
|
||||||
dependencies:
|
dependencies:
|
||||||
cross-fetch: 4.1.0(encoding@0.1.13)
|
cross-fetch: 4.1.0(encoding@0.1.13)
|
||||||
dotenv: 16.4.7
|
dotenv: 16.4.7
|
||||||
openai: 5.9.0(ws@8.17.1)(zod@3.23.8)
|
openai: 5.9.0(zod@3.23.8)
|
||||||
zod: 3.23.8
|
zod: 3.23.8
|
||||||
zod-to-json-schema: 3.24.5(zod@3.23.8)
|
zod-to-json-schema: 3.24.5(zod@3.23.8)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
@ -18939,9 +18943,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
mimic-fn: 4.0.0
|
mimic-fn: 4.0.0
|
||||||
|
|
||||||
openai@5.9.0(ws@8.17.1)(zod@3.23.8):
|
openai@5.9.0(zod@3.23.8):
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
ws: 8.17.1
|
|
||||||
zod: 3.23.8
|
zod: 3.23.8
|
||||||
|
|
||||||
optionator@0.9.4:
|
optionator@0.9.4:
|
||||||
@ -19614,7 +19617,7 @@ snapshots:
|
|||||||
'@radix-ui/react-tooltip': 1.1.1(@types/react-dom@18.3.7(@types/react@18.2.47))(@types/react@18.2.47)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
'@radix-ui/react-tooltip': 1.1.1(@types/react-dom@18.3.7(@types/react@18.2.47))(@types/react@18.2.47)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
'@swc/core': 1.3.101(@swc/helpers@0.5.17)
|
'@swc/core': 1.3.101(@swc/helpers@0.5.17)
|
||||||
'@types/react': 18.2.47
|
'@types/react': 18.2.47
|
||||||
'@types/react-dom': 18.3.7(@types/react@18.2.69)
|
'@types/react-dom': 18.3.7(@types/react@18.2.47)
|
||||||
'@types/webpack': 5.28.5(@swc/core@1.3.101(@swc/helpers@0.5.17))(esbuild@0.19.11)
|
'@types/webpack': 5.28.5(@swc/core@1.3.101(@swc/helpers@0.5.17))(esbuild@0.19.11)
|
||||||
autoprefixer: 10.4.14(postcss@8.4.38)
|
autoprefixer: 10.4.14(postcss@8.4.38)
|
||||||
chalk: 4.1.2
|
chalk: 4.1.2
|
||||||
@ -20903,7 +20906,7 @@ snapshots:
|
|||||||
|
|
||||||
tslib@2.8.1: {}
|
tslib@2.8.1: {}
|
||||||
|
|
||||||
tsup@8.5.0(@swc/core@1.3.101(@swc/helpers@0.5.17))(jiti@2.4.2)(postcss@8.5.5)(typescript@5.8.3)(yaml@2.8.0):
|
tsup@8.5.0(@swc/core@1.3.101)(jiti@2.4.2)(postcss@8.5.5)(typescript@5.8.3)(yaml@2.8.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
bundle-require: 5.1.0(esbuild@0.25.5)
|
bundle-require: 5.1.0(esbuild@0.25.5)
|
||||||
cac: 6.7.14
|
cac: 6.7.14
|
||||||
@ -21297,13 +21300,13 @@ snapshots:
|
|||||||
'@types/unist': 3.0.3
|
'@types/unist': 3.0.3
|
||||||
vfile-message: 4.0.2
|
vfile-message: 4.0.2
|
||||||
|
|
||||||
vite-node@1.6.1(@types/node@22.16.0)(lightningcss@1.30.1)(terser@5.42.0):
|
vite-node@1.6.1(@types/node@24.0.0)(lightningcss@1.30.1)(terser@5.42.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
cac: 6.7.14
|
cac: 6.7.14
|
||||||
debug: 4.4.1
|
debug: 4.4.1
|
||||||
pathe: 1.1.2
|
pathe: 1.1.2
|
||||||
picocolors: 1.1.1
|
picocolors: 1.1.1
|
||||||
vite: 5.4.19(@types/node@22.16.0)(lightningcss@1.30.1)(terser@5.42.0)
|
vite: 5.4.19(@types/node@24.0.0)(lightningcss@1.30.1)(terser@5.42.0)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@types/node'
|
- '@types/node'
|
||||||
- less
|
- less
|
||||||
@ -21315,13 +21318,13 @@ snapshots:
|
|||||||
- supports-color
|
- supports-color
|
||||||
- terser
|
- terser
|
||||||
|
|
||||||
vite-node@3.2.3(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0):
|
vite-node@3.2.3(@types/node@24.0.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
cac: 6.7.14
|
cac: 6.7.14
|
||||||
debug: 4.4.1
|
debug: 4.4.1
|
||||||
es-module-lexer: 1.7.0
|
es-module-lexer: 1.7.0
|
||||||
pathe: 2.0.3
|
pathe: 2.0.3
|
||||||
vite: 6.3.5(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0)
|
vite: 6.3.5(@types/node@24.0.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@types/node'
|
- '@types/node'
|
||||||
- jiti
|
- jiti
|
||||||
@ -21336,29 +21339,29 @@ snapshots:
|
|||||||
- tsx
|
- tsx
|
||||||
- yaml
|
- yaml
|
||||||
|
|
||||||
vite-tsconfig-paths@4.3.2(typescript@5.8.3)(vite@6.3.5(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0)):
|
vite-tsconfig-paths@4.3.2(typescript@5.8.3)(vite@6.3.5(@types/node@24.0.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0)):
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.4.1
|
debug: 4.4.1
|
||||||
globrex: 0.1.2
|
globrex: 0.1.2
|
||||||
tsconfck: 3.1.6(typescript@5.8.3)
|
tsconfck: 3.1.6(typescript@5.8.3)
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
vite: 6.3.5(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0)
|
vite: 6.3.5(@types/node@24.0.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
- typescript
|
- typescript
|
||||||
|
|
||||||
vite@5.4.19(@types/node@22.16.0)(lightningcss@1.30.1)(terser@5.42.0):
|
vite@5.4.19(@types/node@24.0.0)(lightningcss@1.30.1)(terser@5.42.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
esbuild: 0.21.5
|
esbuild: 0.21.5
|
||||||
postcss: 8.5.5
|
postcss: 8.5.5
|
||||||
rollup: 4.43.0
|
rollup: 4.43.0
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/node': 22.16.0
|
'@types/node': 24.0.0
|
||||||
fsevents: 2.3.3
|
fsevents: 2.3.3
|
||||||
lightningcss: 1.30.1
|
lightningcss: 1.30.1
|
||||||
terser: 5.42.0
|
terser: 5.42.0
|
||||||
|
|
||||||
vite@6.3.5(@types/node@22.16.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0):
|
vite@6.3.5(@types/node@24.0.0)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0):
|
||||||
dependencies:
|
dependencies:
|
||||||
esbuild: 0.25.5
|
esbuild: 0.25.5
|
||||||
fdir: 6.4.6(picomatch@4.0.2)
|
fdir: 6.4.6(picomatch@4.0.2)
|
||||||
@ -21367,7 +21370,7 @@ snapshots:
|
|||||||
rollup: 4.43.0
|
rollup: 4.43.0
|
||||||
tinyglobby: 0.2.14
|
tinyglobby: 0.2.14
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/node': 22.16.0
|
'@types/node': 24.0.0
|
||||||
fsevents: 2.3.3
|
fsevents: 2.3.3
|
||||||
jiti: 2.4.2
|
jiti: 2.4.2
|
||||||
lightningcss: 1.30.1
|
lightningcss: 1.30.1
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user