Skip to content

Commit f966a5b

Browse files
committed
eng-1344 f10b upload obsidian relations and their schemas
1 parent 408ef40 commit f966a5b

File tree

3 files changed

+242
-22
lines changed

3 files changed

+242
-22
lines changed

apps/obsidian/src/utils/conceptConversion.ts

Lines changed: 148 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
/* eslint-disable @typescript-eslint/naming-convention */
22
import type { TFile } from "obsidian";
3-
import type { DiscourseNode } from "~/types";
3+
import type DiscourseGraphPlugin from "~/index";
4+
import type {
5+
DiscourseNode,
6+
DiscourseRelation,
7+
DiscourseRelationType,
8+
} from "~/types";
49
import type { SupabaseContext } from "./supabaseContext";
10+
import type { DiscourseNodeInVault } from "./syncDgNodesToSupabase";
511
import type { LocalConceptDataInput } from "@repo/database/inputTypes";
612
import type { ObsidianDiscourseNodeData } from "./syncDgNodesToSupabase";
713
import type { Json } from "@repo/database/dbTypes";
@@ -52,21 +58,157 @@ export const discourseNodeSchemaToLocalConcept = ({
5258
};
5359
};
5460

61+
const STANDARD_ROLES = ["source", "destination"];
62+
63+
export const discourseRelationTypeToLocalConcept = ({
64+
context,
65+
relationType,
66+
accountLocalId,
67+
}: {
68+
context: SupabaseContext;
69+
relationType: DiscourseRelationType;
70+
accountLocalId: string;
71+
}): LocalConceptDataInput => {
72+
const now = new Date().toISOString();
73+
const { id, label, complement, ...otherData } = relationType;
74+
return {
75+
space_id: context.spaceId,
76+
name: label,
77+
source_local_id: id,
78+
is_schema: true,
79+
author_local_id: accountLocalId,
80+
created: now,
81+
literal_content: {
82+
label,
83+
complement,
84+
source_data: otherData,
85+
} as unknown as Json,
86+
last_modified: now,
87+
};
88+
};
89+
90+
export const discourseRelationSchemaToLocalConcept = ({
91+
context,
92+
relation,
93+
accountLocalId,
94+
nodeTypesById,
95+
relationTypesById,
96+
}: {
97+
context: SupabaseContext;
98+
relation: DiscourseRelation;
99+
accountLocalId: string;
100+
nodeTypesById: Record<string, DiscourseNode>;
101+
relationTypesById: Record<string, DiscourseRelationType>;
102+
}): LocalConceptDataInput => {
103+
const sourceName =
104+
nodeTypesById[relation.sourceId]?.name ?? relation.sourceId;
105+
const destinationName =
106+
nodeTypesById[relation.destinationId]?.name ?? relation.destinationId;
107+
const relationType = relationTypesById[relation.relationshipTypeId];
108+
if (!relationType)
109+
throw new Error(`missing relation type ${relation.relationshipTypeId}`);
110+
const { label, complement } = relationType;
111+
const now = new Date().toISOString(); // TODO: Keep a date in the structure
112+
// TODO: Create an Id for the triple
113+
const compositeId = [
114+
relation.relationshipTypeId,
115+
relation.sourceId,
116+
relation.destinationId,
117+
].join(":");
118+
119+
return {
120+
space_id: context.spaceId,
121+
name: `${sourceName} -${label}-> ${destinationName}`,
122+
source_local_id: compositeId,
123+
is_schema: true,
124+
author_local_id: accountLocalId,
125+
created: now,
126+
last_modified: now,
127+
literal_content: {
128+
roles: STANDARD_ROLES,
129+
label,
130+
complement,
131+
},
132+
local_reference_content: {
133+
relation_type: relation.relationshipTypeId,
134+
source: relation.sourceId,
135+
destination: relation.destinationId,
136+
},
137+
};
138+
};
139+
55140
/**
56141
* Convert discourse node instance (file) to LocalConceptDataInput
57142
*/
58-
export const discourseNodeInstanceToLocalConcept = ({
143+
export const discourseNodeInstanceToLocalConcepts = ({
144+
plugin,
145+
allNodesByName,
59146
context,
60147
nodeData,
61148
accountLocalId,
62149
}: {
150+
plugin: DiscourseGraphPlugin;
151+
allNodesByName: Record<string, DiscourseNodeInVault>;
63152
context: SupabaseContext;
64153
nodeData: ObsidianDiscourseNodeData;
65154
accountLocalId: string;
66-
}): LocalConceptDataInput => {
155+
}): LocalConceptDataInput[] => {
67156
const extraData = getNodeExtraData(nodeData.file, accountLocalId);
68157
const { nodeInstanceId, nodeTypeId, ...otherData } = nodeData.frontmatter;
69-
return {
158+
const response: LocalConceptDataInput[] = [];
159+
for (const relType of plugin.settings.relationTypes) {
160+
const rels = otherData[relType.id];
161+
if (rels) {
162+
delete otherData[relType.id];
163+
const triples = plugin.settings.discourseRelations.filter(
164+
(r) => r.relationshipTypeId === relType.id && r.sourceId === nodeTypeId,
165+
);
166+
if (!triples.length) {
167+
// we're probably the target.
168+
continue;
169+
}
170+
const tripleDestTypes = new Set(triples.map((rel) => rel.destinationId));
171+
for (let rel of rels as string[]) {
172+
if (rel.startsWith("[[") && rel.endsWith("]]"))
173+
rel = rel.substring(2, rel.length - 2);
174+
if (rel.endsWith(".md")) rel = rel.substring(0, rel.length - 3);
175+
const target = allNodesByName[rel];
176+
if (!target) {
177+
console.error(`Could not find node name ${rel}`);
178+
continue;
179+
}
180+
const targetTypeId = target.frontmatter.nodeTypeId as string;
181+
const targetInstanceId = target.frontmatter.nodeInstanceId as string;
182+
if (!tripleDestTypes.has(targetTypeId)) {
183+
console.error(
184+
`Found a relation of type ${relType.id} between ${nodeData.file.path} and ${rel} but no relation fits`,
185+
);
186+
continue;
187+
}
188+
const compositeSchemaId = [relType.id, nodeTypeId, targetTypeId].join(
189+
":",
190+
);
191+
const compositeInstanceId = [
192+
relType.id,
193+
nodeInstanceId as string,
194+
targetInstanceId,
195+
].join(":");
196+
response.push({
197+
space_id: context.spaceId,
198+
name: `[[${nodeData.file.basename}]] -${relType.label}-> [[${target.file.basename}]]`,
199+
source_local_id: compositeInstanceId,
200+
schema_represented_by_local_id: compositeSchemaId,
201+
is_schema: false,
202+
local_reference_content: {
203+
source: nodeInstanceId as string,
204+
destination: targetInstanceId,
205+
},
206+
...extraData,
207+
});
208+
}
209+
}
210+
}
211+
response.push({
70212
space_id: context.spaceId,
71213
name: nodeData.file.path,
72214
source_local_id: nodeInstanceId as string,
@@ -77,7 +219,8 @@ export const discourseNodeInstanceToLocalConcept = ({
77219
source_data: otherData as unknown as Json,
78220
},
79221
...extraData,
80-
};
222+
});
223+
return response;
81224
};
82225

83226
export const relatedConcepts = (concept: LocalConceptDataInput): string[] => {

apps/obsidian/src/utils/syncDgNodesToSupabase.ts

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ import { default as DiscourseGraphPlugin } from "~/index";
1111
import { upsertNodesToSupabaseAsContentWithEmbeddings } from "./upsertNodesAsContentWithEmbeddings";
1212
import {
1313
orderConceptsByDependency,
14-
discourseNodeInstanceToLocalConcept,
14+
discourseNodeInstanceToLocalConcepts,
1515
discourseNodeSchemaToLocalConcept,
16+
discourseRelationSchemaToLocalConcept,
17+
discourseRelationTypeToLocalConcept,
1618
} from "./conceptConversion";
1719
import type { LocalConceptDataInput } from "@repo/database/inputTypes";
1820

@@ -177,7 +179,7 @@ const getLastSyncTime = async (
177179
return new Date(data?.last_modified || DEFAULT_TIME);
178180
};
179181

180-
type DiscourseNodeInVault = {
182+
export type DiscourseNodeInVault = {
181183
file: TFile;
182184
frontmatter: Record<string, unknown>;
183185
nodeTypeId: string;
@@ -203,7 +205,7 @@ const mergeChangeTypes = (
203205
* Step 1: Collect all discourse nodes from the vault
204206
* Filters markdown files that have nodeTypeId in frontmatter
205207
*/
206-
const collectDiscourseNodesFromVault = async (
208+
export const collectDiscourseNodesFromVault = async (
207209
plugin: DiscourseGraphPlugin,
208210
): Promise<DiscourseNodeInVault[]> => {
209211
const allFiles = plugin.app.vault.getMarkdownFiles();
@@ -504,8 +506,19 @@ const convertDgToSupabaseConcepts = async ({
504506
context: SupabaseContext;
505507
accountLocalId: string;
506508
plugin: DiscourseGraphPlugin;
509+
convertRelations?: boolean;
507510
}): Promise<void> => {
508511
const nodeTypes = plugin.settings.nodeTypes ?? [];
512+
const relationTypes = plugin.settings.relationTypes ?? [];
513+
const discourseRelations = plugin.settings.discourseRelations ?? [];
514+
const allNodes = await collectDiscourseNodesFromVault(plugin);
515+
const allNodesByName = Object.fromEntries(
516+
allNodes.map((n) => [n.file.basename, n]),
517+
);
518+
519+
const nodeTypesById = Object.fromEntries(
520+
nodeTypes.map((nodeType) => [nodeType.id, nodeType]),
521+
);
509522

510523
const nodesTypesToLocalConcepts = nodeTypes.map((nodeType) =>
511524
discourseNodeSchemaToLocalConcept({
@@ -515,21 +528,49 @@ const convertDgToSupabaseConcepts = async ({
515528
}),
516529
);
517530

518-
const nodeInstanceToLocalConcepts = nodesSince.map((node) =>
519-
discourseNodeInstanceToLocalConcept({
531+
const relationTypesById = Object.fromEntries(
532+
relationTypes.map((relationType) => [relationType.id, relationType]),
533+
);
534+
535+
const relationTypesToLocalConcepts = relationTypes.map((relationType) =>
536+
discourseRelationTypeToLocalConcept({
537+
context,
538+
relationType,
539+
accountLocalId,
540+
}),
541+
);
542+
543+
const discourseRelationsToLocalConcepts = discourseRelations.map((relation) =>
544+
discourseRelationSchemaToLocalConcept({
520545
context,
521-
nodeData: node,
546+
relation,
522547
accountLocalId,
548+
nodeTypesById,
549+
relationTypesById,
523550
}),
524551
);
525552

553+
const nodeInstanceToLocalConcepts = nodesSince
554+
.map((node) => {
555+
return discourseNodeInstanceToLocalConcepts({
556+
plugin,
557+
allNodesByName,
558+
context,
559+
nodeData: node,
560+
accountLocalId,
561+
});
562+
})
563+
.flat();
564+
526565
const conceptsToUpsert: LocalConceptDataInput[] = [
527566
...nodesTypesToLocalConcepts,
567+
...relationTypesToLocalConcepts,
568+
...discourseRelationsToLocalConcepts,
528569
...nodeInstanceToLocalConcepts,
529570
];
530571

531572
const { ordered } = orderConceptsByDependency(conceptsToUpsert);
532-
573+
console.log(ordered);
533574
const { error } = await supabaseClient.rpc("upsert_concepts", {
534575
data: ordered as Json,
535576
v_space_id: context.spaceId,

0 commit comments

Comments
 (0)