mirror of
https://github.com/trustgraph-ai/trustgraph.git
synced 2026-07-02 02:58:10 +02:00
Merge commit '9b2f675702' as 'ai-context/context-graph-demo'
This commit is contained in:
commit
ecaf3489f1
54 changed files with 10078 additions and 0 deletions
9
ai-context/context-graph-demo/src/state/index.ts
Normal file
9
ai-context/context-graph-demo/src/state/index.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
// Main data hook - provides entities, relationships, and ontology
|
||||
export { useGraphData } from "./useGraphData";
|
||||
|
||||
// Schema hook - for OWL ontology schema view
|
||||
export { useOntologySchema } from "./useOntologySchema";
|
||||
|
||||
// Toast notifications
|
||||
export { useToastStore, toast } from "./toastStore";
|
||||
export type { Toast, ToastType } from "./toastStore";
|
||||
52
ai-context/context-graph-demo/src/state/toastStore.ts
Normal file
52
ai-context/context-graph-demo/src/state/toastStore.ts
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
import { create } from "zustand";
|
||||
|
||||
export type ToastType = "success" | "error" | "warning" | "info";
|
||||
|
||||
export interface Toast {
|
||||
id: string;
|
||||
type: ToastType;
|
||||
message: string;
|
||||
persistent?: boolean;
|
||||
}
|
||||
|
||||
interface ToastStore {
|
||||
toasts: Toast[];
|
||||
addToast: (type: ToastType, message: string, persistent?: boolean) => void;
|
||||
removeToast: (id: string) => void;
|
||||
}
|
||||
|
||||
let toastId = 0;
|
||||
|
||||
export const useToastStore = create<ToastStore>((set) => ({
|
||||
toasts: [],
|
||||
|
||||
addToast: (type, message, persistent = false) => {
|
||||
const id = `toast-${++toastId}`;
|
||||
set((state) => ({
|
||||
toasts: [...state.toasts.slice(-3), { id, type, message, persistent }],
|
||||
}));
|
||||
|
||||
// Auto-dismiss after 6 seconds unless explicitly persistent
|
||||
if (!persistent) {
|
||||
setTimeout(() => {
|
||||
set((state) => ({
|
||||
toasts: state.toasts.filter((t) => t.id !== id),
|
||||
}));
|
||||
}, 6000);
|
||||
}
|
||||
},
|
||||
|
||||
removeToast: (id) => {
|
||||
set((state) => ({
|
||||
toasts: state.toasts.filter((t) => t.id !== id),
|
||||
}));
|
||||
},
|
||||
}));
|
||||
|
||||
// Helper functions for easy access outside React
|
||||
export const toast = {
|
||||
success: (message: string) => useToastStore.getState().addToast("success", message),
|
||||
error: (message: string) => useToastStore.getState().addToast("error", message),
|
||||
warning: (message: string) => useToastStore.getState().addToast("warning", message),
|
||||
info: (message: string) => useToastStore.getState().addToast("info", message),
|
||||
};
|
||||
243
ai-context/context-graph-demo/src/state/useGraphData.ts
Normal file
243
ai-context/context-graph-demo/src/state/useGraphData.ts
Normal file
|
|
@ -0,0 +1,243 @@
|
|||
import { useState, useEffect, useMemo } from "react";
|
||||
import { useSocket } from "@trustgraph/react-provider";
|
||||
import type { Triple } from "@trustgraph/react-state";
|
||||
import type { Entity, Relationship, DomainKey, OntologyType } from "../types";
|
||||
import { COLLECTION } from "../config";
|
||||
import { domainColors } from "../theme";
|
||||
|
||||
const RDF_TYPE = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type";
|
||||
const RDFS_LABEL = "http://www.w3.org/2000/01/rdf-schema#label";
|
||||
const RDFS_COMMENT = "http://www.w3.org/2000/01/rdf-schema#comment";
|
||||
const OWL_CLASS = "http://www.w3.org/2002/07/owl#Class";
|
||||
const OWL_DATATYPE_PROPERTY = "http://www.w3.org/2002/07/owl#DatatypeProperty";
|
||||
const OWL_OBJECT_PROPERTY = "http://www.w3.org/2002/07/owl#ObjectProperty";
|
||||
|
||||
// Helper to extract value from a Term
|
||||
function getTermValue(term: { t: string; i?: string; v?: string }): string {
|
||||
if (term.t === "i") return term.i || "";
|
||||
if (term.t === "l") return term.v || "";
|
||||
return "";
|
||||
}
|
||||
|
||||
// Helper to create a short ID from a URI
|
||||
function uriToId(uri: string): string {
|
||||
const hashIndex = uri.lastIndexOf("#");
|
||||
const slashIndex = uri.lastIndexOf("/");
|
||||
const index = Math.max(hashIndex, slashIndex);
|
||||
return index >= 0 ? uri.substring(index + 1) : uri;
|
||||
}
|
||||
|
||||
// Helper to get icon for a class (placeholder for now)
|
||||
function getClassIcon(_classUri: string): string {
|
||||
return "●";
|
||||
}
|
||||
|
||||
// Helper to extract predicate name from URI
|
||||
function predicateToName(uri: string): string {
|
||||
const hashIndex = uri.lastIndexOf("#");
|
||||
const slashIndex = uri.lastIndexOf("/");
|
||||
const index = Math.max(hashIndex, slashIndex);
|
||||
const name = index >= 0 ? uri.substring(index + 1) : uri;
|
||||
return name.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase();
|
||||
}
|
||||
|
||||
export function useGraphData(domain?: DomainKey) {
|
||||
const socket = useSocket();
|
||||
const [triples, setTriples] = useState<Triple[] | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [isError, setIsError] = useState(false);
|
||||
const [error, setError] = useState<Error | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
setIsError(false);
|
||||
setError(null);
|
||||
|
||||
const api = socket.flow("default");
|
||||
const result = await api.triplesQuery(
|
||||
undefined, undefined, undefined,
|
||||
10000, COLLECTION, "",
|
||||
);
|
||||
|
||||
if (!cancelled) {
|
||||
setTriples(result);
|
||||
setIsLoading(false);
|
||||
}
|
||||
} catch (err) {
|
||||
if (!cancelled) {
|
||||
setIsError(true);
|
||||
setError(err instanceof Error ? err : new Error(String(err)));
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
return () => { cancelled = true; };
|
||||
}, [socket]);
|
||||
|
||||
// Process all data from the query
|
||||
const { entities, relationships, ontology, propertyLabels } = useMemo(() => {
|
||||
if (isLoading || !triples) {
|
||||
return { entities: [], relationships: [], ontology: undefined, propertyLabels: {} };
|
||||
}
|
||||
|
||||
// First pass: collect all labels, comments, and find OWL classes and properties
|
||||
const allLabels = new Map<string, string>();
|
||||
const allComments = new Map<string, string>();
|
||||
const owlClasses = new Set<string>();
|
||||
const propertyUris = new Set<string>();
|
||||
|
||||
for (const triple of triples) {
|
||||
const subjectUri = getTermValue(triple.s);
|
||||
const predicate = getTermValue(triple.p);
|
||||
const objectUri = getTermValue(triple.o);
|
||||
|
||||
if (predicate === RDFS_LABEL) {
|
||||
allLabels.set(subjectUri, getTermValue(triple.o));
|
||||
} else if (predicate === RDFS_COMMENT) {
|
||||
allComments.set(subjectUri, getTermValue(triple.o));
|
||||
} else if (predicate === RDF_TYPE) {
|
||||
if (objectUri === OWL_CLASS) {
|
||||
owlClasses.add(subjectUri);
|
||||
} else if (objectUri === OWL_DATATYPE_PROPERTY || objectUri === OWL_OBJECT_PROPERTY) {
|
||||
propertyUris.add(subjectUri);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build property labels map: local name -> label
|
||||
const propertyLabels: Record<string, string> = {};
|
||||
for (const propUri of propertyUris) {
|
||||
const localName = uriToId(propUri);
|
||||
const label = allLabels.get(propUri);
|
||||
if (label) {
|
||||
propertyLabels[localName] = label;
|
||||
}
|
||||
}
|
||||
|
||||
// Build class config dynamically from discovered OWL classes
|
||||
const classConfig = new Map<string, { domain: DomainKey; color: string; glow: string; icon: string; label: string; description: string }>();
|
||||
let colorIndex = 0;
|
||||
for (const classUri of owlClasses) {
|
||||
const localName = uriToId(classUri).toLowerCase();
|
||||
const palette = domainColors[colorIndex % domainColors.length];
|
||||
classConfig.set(classUri, {
|
||||
domain: localName,
|
||||
color: palette.color,
|
||||
glow: palette.glow,
|
||||
icon: getClassIcon(classUri),
|
||||
label: allLabels.get(classUri) || uriToId(classUri),
|
||||
description: allComments.get(classUri) || "",
|
||||
});
|
||||
colorIndex++;
|
||||
}
|
||||
|
||||
// Second pass: find entities (instances of OWL classes) and their properties
|
||||
const entityMap = new Map<string, Entity>();
|
||||
const entityProps = new Map<string, Record<string, string | number>>();
|
||||
|
||||
// Collect entity properties first
|
||||
for (const triple of triples) {
|
||||
const subjectUri = getTermValue(triple.s);
|
||||
const predicate = getTermValue(triple.p);
|
||||
const value = getTermValue(triple.o);
|
||||
|
||||
// Skip schema-level predicates and URIs as values
|
||||
if (predicate !== RDF_TYPE && predicate !== RDFS_LABEL && predicate !== RDFS_COMMENT &&
|
||||
value && !value.startsWith("http")) {
|
||||
if (!entityProps.has(subjectUri)) {
|
||||
entityProps.set(subjectUri, {});
|
||||
}
|
||||
const propName = uriToId(predicate);
|
||||
entityProps.get(subjectUri)![propName] = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Find entities by type (instances of OWL classes)
|
||||
for (const triple of triples) {
|
||||
const subjectUri = getTermValue(triple.s);
|
||||
const predicate = getTermValue(triple.p);
|
||||
const objectUri = getTermValue(triple.o);
|
||||
|
||||
if (predicate === RDF_TYPE && classConfig.has(objectUri)) {
|
||||
const config = classConfig.get(objectUri)!;
|
||||
if (domain && config.domain !== domain) continue;
|
||||
|
||||
const entityId = uriToId(subjectUri);
|
||||
entityMap.set(subjectUri, {
|
||||
id: entityId,
|
||||
uri: subjectUri,
|
||||
label: allLabels.get(subjectUri) || entityId,
|
||||
props: entityProps.get(subjectUri) || {},
|
||||
domain: config.domain,
|
||||
color: config.color,
|
||||
glow: config.glow,
|
||||
icon: config.icon,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Find relationships: triples where both subject and object are known entities
|
||||
const relationships: Relationship[] = [];
|
||||
const entityUris = new Set(entityMap.keys());
|
||||
|
||||
for (const triple of triples) {
|
||||
const subjectUri = getTermValue(triple.s);
|
||||
const predicate = getTermValue(triple.p);
|
||||
const objectUri = getTermValue(triple.o);
|
||||
|
||||
// Skip rdf:type and rdfs:label
|
||||
if (predicate === RDF_TYPE || predicate === RDFS_LABEL) continue;
|
||||
|
||||
// If both subject and object are entities, it's a relationship
|
||||
if (entityUris.has(subjectUri) && entityUris.has(objectUri)) {
|
||||
const fromEntity = entityMap.get(subjectUri)!;
|
||||
const toEntity = entityMap.get(objectUri)!;
|
||||
|
||||
relationships.push({
|
||||
from: fromEntity.id,
|
||||
to: toEntity.id,
|
||||
predicate: predicateToName(predicate),
|
||||
domain: [fromEntity.domain, toEntity.domain],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const entities = Array.from(entityMap.values());
|
||||
|
||||
// Build ontology metadata dynamically from discovered classes
|
||||
const ontology: OntologyType = {};
|
||||
for (const [, config] of classConfig) {
|
||||
ontology[config.domain] = {
|
||||
label: config.label,
|
||||
color: config.color,
|
||||
glow: config.glow,
|
||||
icon: config.icon,
|
||||
description: config.description,
|
||||
properties: [],
|
||||
subclasses: entities.filter(e => e.domain === config.domain).map(e => ({
|
||||
id: e.id,
|
||||
uri: e.uri,
|
||||
label: e.label,
|
||||
props: e.props,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
return { entities, relationships, ontology, propertyLabels };
|
||||
}, [isLoading, triples, domain]);
|
||||
|
||||
return {
|
||||
entities,
|
||||
relationships,
|
||||
ontology,
|
||||
propertyLabels,
|
||||
isLoading,
|
||||
isError,
|
||||
error,
|
||||
};
|
||||
}
|
||||
178
ai-context/context-graph-demo/src/state/useOntologySchema.ts
Normal file
178
ai-context/context-graph-demo/src/state/useOntologySchema.ts
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
import { useMemo } from "react";
|
||||
import { useTriples } from "@trustgraph/react-state";
|
||||
import { COLLECTION } from "../config";
|
||||
const RDF_TYPE = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type";
|
||||
const RDFS_LABEL = "http://www.w3.org/2000/01/rdf-schema#label";
|
||||
const RDFS_DOMAIN = "http://www.w3.org/2000/01/rdf-schema#domain";
|
||||
const RDFS_RANGE = "http://www.w3.org/2000/01/rdf-schema#range";
|
||||
const RDFS_COMMENT = "http://www.w3.org/2000/01/rdf-schema#comment";
|
||||
const OWL_CLASS = "http://www.w3.org/2002/07/owl#Class";
|
||||
const OWL_OBJECT_PROPERTY = "http://www.w3.org/2002/07/owl#ObjectProperty";
|
||||
const OWL_DATATYPE_PROPERTY = "http://www.w3.org/2002/07/owl#DatatypeProperty";
|
||||
|
||||
// Helper to extract value from a Term
|
||||
function getTermValue(term: { t: string; i?: string; v?: string }): string {
|
||||
if (term.t === "i") return term.i || "";
|
||||
if (term.t === "l") return term.v || "";
|
||||
return "";
|
||||
}
|
||||
|
||||
// Helper to get local name from URI
|
||||
function getLocalName(uri: string): string {
|
||||
const hashIndex = uri.lastIndexOf("#");
|
||||
const slashIndex = uri.lastIndexOf("/");
|
||||
const index = Math.max(hashIndex, slashIndex);
|
||||
return index >= 0 ? uri.substring(index + 1) : uri;
|
||||
}
|
||||
|
||||
export interface OntologyClass {
|
||||
uri: string;
|
||||
label: string;
|
||||
comment?: string;
|
||||
}
|
||||
|
||||
export interface OntologyProperty {
|
||||
uri: string;
|
||||
label: string;
|
||||
domain?: string;
|
||||
range?: string;
|
||||
}
|
||||
|
||||
export interface OntologySchema {
|
||||
classes: OntologyClass[];
|
||||
objectProperties: OntologyProperty[];
|
||||
datatypeProperties: OntologyProperty[];
|
||||
// Sets for quick lookup
|
||||
objectPropertyUris: Set<string>;
|
||||
datatypePropertyUris: Set<string>;
|
||||
}
|
||||
|
||||
export function useOntologySchema() {
|
||||
// Query for classes
|
||||
const classTriples = useTriples({
|
||||
p: { t: "i", i: RDF_TYPE },
|
||||
o: { t: "i", i: OWL_CLASS },
|
||||
limit: 100,
|
||||
collection: COLLECTION,
|
||||
});
|
||||
|
||||
// Query for object properties
|
||||
const objectPropertyTriples = useTriples({
|
||||
p: { t: "i", i: RDF_TYPE },
|
||||
o: { t: "i", i: OWL_OBJECT_PROPERTY },
|
||||
limit: 100,
|
||||
collection: COLLECTION,
|
||||
});
|
||||
|
||||
// Query for datatype properties
|
||||
const datatypePropertyTriples = useTriples({
|
||||
p: { t: "i", i: RDF_TYPE },
|
||||
o: { t: "i", i: OWL_DATATYPE_PROPERTY },
|
||||
limit: 100,
|
||||
collection: COLLECTION,
|
||||
});
|
||||
|
||||
// Query for all triples to get labels, domains, ranges
|
||||
const allTriples = useTriples({
|
||||
limit: 1000,
|
||||
collection: COLLECTION,
|
||||
});
|
||||
|
||||
const isLoading = classTriples.isLoading || objectPropertyTriples.isLoading ||
|
||||
datatypePropertyTriples.isLoading || allTriples.isLoading;
|
||||
const isError = classTriples.isError || objectPropertyTriples.isError ||
|
||||
datatypePropertyTriples.isError || allTriples.isError;
|
||||
const error = classTriples.error || objectPropertyTriples.error ||
|
||||
datatypePropertyTriples.error || allTriples.error;
|
||||
|
||||
const schema = useMemo((): OntologySchema | undefined => {
|
||||
if (isLoading) return undefined;
|
||||
|
||||
// Build a map of URI -> metadata from all triples
|
||||
const metadata = new Map<string, { label?: string; domain?: string; range?: string; comment?: string }>();
|
||||
|
||||
for (const triple of allTriples.triples || []) {
|
||||
const subject = getTermValue(triple.s);
|
||||
const predicate = getTermValue(triple.p);
|
||||
const value = getTermValue(triple.o);
|
||||
|
||||
if (!metadata.has(subject)) {
|
||||
metadata.set(subject, {});
|
||||
}
|
||||
const meta = metadata.get(subject)!;
|
||||
|
||||
if (predicate === RDFS_LABEL) {
|
||||
meta.label = value;
|
||||
} else if (predicate === RDFS_DOMAIN) {
|
||||
meta.domain = value;
|
||||
} else if (predicate === RDFS_RANGE) {
|
||||
meta.range = value;
|
||||
} else if (predicate === RDFS_COMMENT) {
|
||||
meta.comment = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Build classes list
|
||||
const classes: OntologyClass[] = [];
|
||||
for (const triple of classTriples.triples || []) {
|
||||
const uri = getTermValue(triple.s);
|
||||
const meta = metadata.get(uri) || {};
|
||||
classes.push({
|
||||
uri,
|
||||
label: meta.label || getLocalName(uri),
|
||||
comment: meta.comment,
|
||||
});
|
||||
}
|
||||
|
||||
// Build object properties list
|
||||
const objectProperties: OntologyProperty[] = [];
|
||||
const objectPropertyUris = new Set<string>();
|
||||
for (const triple of objectPropertyTriples.triples || []) {
|
||||
const uri = getTermValue(triple.s);
|
||||
const meta = metadata.get(uri) || {};
|
||||
objectPropertyUris.add(uri);
|
||||
objectProperties.push({
|
||||
uri,
|
||||
label: meta.label || getLocalName(uri),
|
||||
domain: meta.domain,
|
||||
range: meta.range,
|
||||
});
|
||||
}
|
||||
|
||||
// Build datatype properties list
|
||||
const datatypeProperties: OntologyProperty[] = [];
|
||||
const datatypePropertyUris = new Set<string>();
|
||||
for (const triple of datatypePropertyTriples.triples || []) {
|
||||
const uri = getTermValue(triple.s);
|
||||
const meta = metadata.get(uri) || {};
|
||||
datatypePropertyUris.add(uri);
|
||||
datatypeProperties.push({
|
||||
uri,
|
||||
label: meta.label || getLocalName(uri),
|
||||
domain: meta.domain,
|
||||
range: meta.range,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
classes,
|
||||
objectProperties,
|
||||
datatypeProperties,
|
||||
objectPropertyUris,
|
||||
datatypePropertyUris,
|
||||
};
|
||||
}, [
|
||||
isLoading,
|
||||
classTriples.triples,
|
||||
objectPropertyTriples.triples,
|
||||
datatypePropertyTriples.triples,
|
||||
allTriples.triples,
|
||||
]);
|
||||
|
||||
return {
|
||||
schema,
|
||||
isLoading,
|
||||
isError,
|
||||
error,
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue