Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 74 additions & 2 deletions apps/obsidian/src/utils/conceptConversion.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { TFile } from "obsidian";
import { DiscourseNode } from "~/types";
import {
DiscourseNode,
DiscourseRelation,
DiscourseRelationType,
} from "~/types";
import { SupabaseContext } from "./supabaseContext";
import { LocalConceptDataInput } from "@repo/database/inputTypes";
import { ObsidianDiscourseNodeData } from "./syncDgNodesToSupabase";
Expand Down Expand Up @@ -41,11 +45,79 @@ export const discourseNodeSchemaToLocalConcept = ({
is_schema: true,
author_local_id: accountLocalId,
created: now,
// TODO: get the template or any other info to put into literal_content jsonb
literal_content: {
...node,
} as unknown as Json,
last_modified: now,
};
};

export const discourseRelationTypeToLocalConcept = ({
context,
relationType,
accountLocalId,
}: {
context: SupabaseContext;
relationType: DiscourseRelationType;
accountLocalId: string;
}): LocalConceptDataInput => {
const now = new Date().toISOString();
return {
space_id: context.spaceId,
name: `${relationType.id}-${relationType.label}`,
represented_by_local_id: relationType.id,
is_schema: true,
author_local_id: accountLocalId,
created: now,
literal_content: {
...relationType,
} as unknown as Json,
last_modified: now,
};
};

export const discourseRelationSchemaToLocalConcept = ({
context,
relation,
accountLocalId,
nodeTypesById,
relationTypesById,
}: {
context: SupabaseContext;
relation: DiscourseRelation;
accountLocalId: string;
nodeTypesById: Record<string, DiscourseNode>;
relationTypesById: Record<string, DiscourseRelationType>;
}): LocalConceptDataInput => {
const sourceName =
nodeTypesById[relation.sourceId]?.name ?? relation.sourceId;
const destinationName =
nodeTypesById[relation.destinationId]?.name ?? relation.destinationId;
const relationLabel =
relationTypesById[relation.relationshipTypeId]?.label ??
relation.relationshipTypeId;
const now = new Date().toISOString();
const representedByLocalId = [
"relation",
relation.sourceId,
relation.relationshipTypeId,
relation.destinationId,
].join("_");

return {
space_id: context.spaceId,
name: `${sourceName} ${relationLabel} ${destinationName}`,
represented_by_local_id: representedByLocalId,
is_schema: true,
author_local_id: accountLocalId,
created: now,
last_modified: now,
literal_content: {
...relation,
} as unknown as Json,
};
};

/**
* Convert discourse node instance (file) to LocalConceptDataInput
*/
Expand Down
93 changes: 69 additions & 24 deletions apps/obsidian/src/utils/syncDgNodesToSupabase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import { upsertNodesToSupabaseAsContentWithEmbeddings } from "./upsertNodesAsCon
import {
orderConceptsByDependency,
discourseNodeInstanceToLocalConcept,
discourseNodeSchemaToLocalConcept,
discourseRelationSchemaToLocalConcept,
discourseRelationTypeToLocalConcept,
} from "./conceptConversion";
import { LocalConceptDataInput } from "@repo/database/inputTypes";

Expand Down Expand Up @@ -460,12 +463,20 @@ export const createOrUpdateDiscourseEmbedding = async (
throw new Error("accountLocalId not found in plugin settings");
}

await syncChangedNodesToSupabase({
changedNodes: allNodeInstances,
await upsertNodesToSupabaseAsContentWithEmbeddings({
obsidianNodes: allNodeInstances,
supabaseClient,
context,
accountLocalId,
plugin,
});

await convertDgToSupabaseConcepts({
nodesSince: allNodeInstances,
supabaseClient,
context,
accountLocalId,
plugin,
});

console.debug("Sync completed successfully");
Expand All @@ -480,14 +491,50 @@ const convertDgToSupabaseConcepts = async ({
supabaseClient,
context,
accountLocalId,
plugin,
}: {
nodesSince: ObsidianDiscourseNodeData[];
supabaseClient: DGSupabaseClient;
context: SupabaseContext;
accountLocalId: string;
plugin: DiscourseGraphPlugin;
}): Promise<void> => {
// TODO: handling schema (node types and relations) will be handled in the future by ENG-1181
// Schema upsert will need allNodeTypes parameter when enabled
const nodeTypes = plugin.settings.nodeTypes ?? [];
const relationTypes = plugin.settings.relationTypes ?? [];
const discourseRelations = plugin.settings.discourseRelations ?? [];

const nodeTypesById = Object.fromEntries(
nodeTypes.map((nodeType) => [nodeType.id, nodeType]),
);
const relationTypesById = Object.fromEntries(
relationTypes.map((relationType) => [relationType.id, relationType]),
);

const nodesTypesToLocalConcepts = nodeTypes.map((nodeType) =>
discourseNodeSchemaToLocalConcept({
context,
node: nodeType,
accountLocalId,
}),
);

const relationTypesToLocalConcepts = relationTypes.map((relationType) =>
discourseRelationTypeToLocalConcept({
context,
relationType,
accountLocalId,
}),
);

const discourseRelationsToLocalConcepts = discourseRelations.map((relation) =>
discourseRelationSchemaToLocalConcept({
context,
relation,
accountLocalId,
nodeTypesById,
relationTypesById,
}),
);

const nodeInstanceToLocalConcepts = nodesSince.map((node) => {
return discourseNodeInstanceToLocalConcept({
Expand All @@ -498,7 +545,9 @@ const convertDgToSupabaseConcepts = async ({
});

const conceptsToUpsert: LocalConceptDataInput[] = [
// ...nodesTypesToLocalConcepts,
...nodesTypesToLocalConcepts,
...relationTypesToLocalConcepts,
...discourseRelationsToLocalConcepts,
...nodeInstanceToLocalConcepts,
];

Expand Down Expand Up @@ -537,33 +586,29 @@ const syncChangedNodesToSupabase = async ({
context: SupabaseContext;
accountLocalId: string;
}): Promise<void> => {
if (changedNodes.length === 0) {
console.debug("No nodes to sync");
return;
if (changedNodes.length > 0) {
await upsertNodesToSupabaseAsContentWithEmbeddings({
obsidianNodes: changedNodes,
supabaseClient,
context,
accountLocalId,
plugin,
});
}

await upsertNodesToSupabaseAsContentWithEmbeddings({
obsidianNodes: changedNodes,
supabaseClient,
context,
accountLocalId,
plugin,
});

// Only upsert concepts for nodes with title changes or new files
// (concepts store the title, so content-only changes don't affect them)
const nodesNeedingConceptUpsert = changedNodes.filter((node) =>
node.changeTypes.includes("title"),
);

if (nodesNeedingConceptUpsert.length > 0) {
await convertDgToSupabaseConcepts({
nodesSince: nodesNeedingConceptUpsert,
supabaseClient,
context,
accountLocalId,
});
}
await convertDgToSupabaseConcepts({
nodesSince: nodesNeedingConceptUpsert,
supabaseClient,
context,
accountLocalId,
plugin,
});
};

/**
Expand Down