From e26caa0b12dc1a4c332e8c7f6c091bfb11502c3a Mon Sep 17 00:00:00 2001 From: elpresidank Date: Sun, 5 Apr 2026 21:09:33 -0500 Subject: [PATCH] saving --- .idea/.gitignore | 8 + .idea/compiler.xml | 6 + .idea/inspectionProfiles/Project_Default.xml | 130 + .idea/modules.xml | 8 + .idea/trustgraph.iml | 8 + .idea/vcs.xml | 6 + ai-context/trustgraph-templates/.gitignore | 25 - ai-context/trustgraph-templates/LICENSE | 202 -- ai-context/trustgraph-templates/README.md | 67 - .../trustgraph-templates/eslint.config.js | 29 - ai-context/trustgraph-templates/index.html | 16 - .../trustgraph-templates/package-lock.json | 3080 ----------------- ai-context/trustgraph-templates/package.json | 31 - ai-context/trustgraph-templates/public/tg.svg | 83 - ai-context/trustgraph-templates/retail.json | 1 - ai-context/trustgraph-templates/retail.ttl | 310 -- ai-context/trustgraph-templates/src/App.tsx | 56 - .../trustgraph-templates/src/assets/react.svg | 1 - .../src/components/common/Badge.tsx | 39 - .../src/components/common/Card.tsx | 35 - .../src/components/common/FilterBar.tsx | 78 - .../src/components/common/FilterButton.tsx | 31 - .../src/components/common/Header.tsx | 58 - .../src/components/common/LoadingState.tsx | 23 - .../src/components/common/MessageBubble.tsx | 97 - .../src/components/common/SearchInput.tsx | 73 - .../src/components/common/SectionLabel.tsx | 22 - .../src/components/common/StatusBar.tsx | 60 - .../src/components/common/Toaster.tsx | 120 - .../src/components/common/Typewriter.tsx | 37 - .../src/components/common/index.ts | 14 - .../src/components/graph/ExplainGraph.tsx | 451 --- .../src/components/graph/GraphCanvas.tsx | 596 ---- .../src/components/graph/GraphCanvasSVG.tsx | 456 --- .../src/components/graph/NodeDetailPanel.tsx | 70 - .../src/components/graph/ZoomControls.tsx | 73 - .../src/components/graph/index.ts | 6 - .../src/components/index.ts | 7 - ai-context/trustgraph-templates/src/config.ts | 2 - ai-context/trustgraph-templates/src/index.css | 18 - ai-context/trustgraph-templates/src/main.tsx | 29 - .../src/pages/DataView.tsx | 393 --- .../src/pages/ExplainView.tsx | 1411 -------- .../src/pages/GraphView.tsx | 93 - .../src/pages/OntologyView.tsx | 116 - .../src/pages/QueryView.tsx | 231 -- .../trustgraph-templates/src/pages/index.ts | 5 - .../trustgraph-templates/src/state/index.ts | 9 - .../src/state/toastStore.ts | 52 - .../src/state/useGraphData.ts | 243 -- .../src/state/useOntologySchema.ts | 178 - .../trustgraph-templates/src/theme/colors.ts | 72 - .../trustgraph-templates/src/theme/index.ts | 1 - .../trustgraph-templates/src/types/index.ts | 65 - .../trustgraph-templates/src/utils/index.ts | 1 - .../trustgraph-templates/src/utils/uri.ts | 9 - .../trustgraph-templates/src/vite-env.d.ts | 1 - .../trustgraph-retail-demo.jsx | 843 ----- ai-context/trustgraph-templates/tsconfig.json | 20 - .../trustgraph-templates/vite.config.js | 39 - .../test_graph_rag_request_translator.py | 54 + tests/unit/test_mcp/test_load_document.py | 49 + trustgraph/trustgraph/trustgraph_version.py | 1 + ts/.gitignore | 4 + ts/package.json | 16 + ts/packages/base/package.json | 21 + ts/packages/base/src/backend/index.ts | 12 + ts/packages/base/src/backend/nats.ts | 196 ++ ts/packages/base/src/backend/types.ts | 45 + ts/packages/base/src/errors.ts | 29 + ts/packages/base/src/index.ts | 10 + ts/packages/base/src/messaging/consumer.ts | 105 + ts/packages/base/src/messaging/index.ts | 4 + ts/packages/base/src/messaging/producer.ts | 40 + .../base/src/messaging/request-response.ts | 91 + ts/packages/base/src/messaging/subscriber.ts | 143 + ts/packages/base/src/metrics/index.ts | 1 + ts/packages/base/src/metrics/prometheus.ts | 68 + .../base/src/processor/async-processor.ts | 83 + .../base/src/processor/flow-processor.ts | 69 + ts/packages/base/src/processor/flow.ts | 83 + ts/packages/base/src/processor/index.ts | 3 + ts/packages/base/src/schema/index.ts | 3 + ts/packages/base/src/schema/messages.ts | 135 + ts/packages/base/src/schema/primitives.ts | 72 + ts/packages/base/src/schema/topics.ts | 62 + .../base/src/services/embeddings-service.ts | 46 + ts/packages/base/src/services/index.ts | 2 + ts/packages/base/src/services/llm-service.ts | 88 + ts/packages/base/src/spec/consumer-spec.ts | 32 + ts/packages/base/src/spec/index.ts | 4 + ts/packages/base/src/spec/parameter-spec.ts | 18 + ts/packages/base/src/spec/producer-spec.ts | 21 + ts/packages/base/src/spec/types.ts | 13 + ts/packages/base/tsconfig.json | 8 + ts/packages/cli/package.json | 25 + ts/packages/cli/src/commands/agent.ts | 34 + ts/packages/cli/src/commands/config.ts | 81 + ts/packages/cli/src/commands/flow.ts | 80 + ts/packages/cli/src/commands/graph-rag.ts | 58 + ts/packages/cli/src/commands/util.ts | 28 + ts/packages/cli/src/index.ts | 33 + ts/packages/cli/tsconfig.json | 12 + ts/packages/flow/package.json | 26 + .../flow/src/gateway/dispatch/manager.ts | 110 + ts/packages/flow/src/gateway/dispatch/mux.ts | 86 + ts/packages/flow/src/gateway/index.ts | 3 + ts/packages/flow/src/gateway/server.ts | 136 + ts/packages/flow/src/index.ts | 7 + .../flow/src/model/text-completion/claude.ts | 129 + .../flow/src/model/text-completion/openai.ts | 138 + .../flow/src/retrieval/document-rag.ts | 66 + ts/packages/flow/src/retrieval/graph-rag.ts | 207 ++ ts/packages/flow/tsconfig.json | 11 + ts/packages/mcp/package.json | 23 + ts/packages/mcp/src/index.ts | 2 + ts/packages/mcp/src/server.ts | 174 + ts/packages/mcp/src/socket-manager.ts | 147 + ts/packages/mcp/tsconfig.json | 11 + ts/pnpm-workspace.yaml | 2 + ts/tsconfig.base.json | 18 + ts/tsconfig.json | 12 + ts/turbo.json | 22 + 123 files changed, 3478 insertions(+), 10078 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/compiler.xml create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/trustgraph.iml create mode 100644 .idea/vcs.xml delete mode 100644 ai-context/trustgraph-templates/.gitignore delete mode 100644 ai-context/trustgraph-templates/LICENSE delete mode 100644 ai-context/trustgraph-templates/README.md delete mode 100644 ai-context/trustgraph-templates/eslint.config.js delete mode 100644 ai-context/trustgraph-templates/index.html delete mode 100644 ai-context/trustgraph-templates/package-lock.json delete mode 100644 ai-context/trustgraph-templates/package.json delete mode 100644 ai-context/trustgraph-templates/public/tg.svg delete mode 100644 ai-context/trustgraph-templates/retail.json delete mode 100644 ai-context/trustgraph-templates/retail.ttl delete mode 100644 ai-context/trustgraph-templates/src/App.tsx delete mode 100644 ai-context/trustgraph-templates/src/assets/react.svg delete mode 100644 ai-context/trustgraph-templates/src/components/common/Badge.tsx delete mode 100644 ai-context/trustgraph-templates/src/components/common/Card.tsx delete mode 100644 ai-context/trustgraph-templates/src/components/common/FilterBar.tsx delete mode 100644 ai-context/trustgraph-templates/src/components/common/FilterButton.tsx delete mode 100644 ai-context/trustgraph-templates/src/components/common/Header.tsx delete mode 100644 ai-context/trustgraph-templates/src/components/common/LoadingState.tsx delete mode 100644 ai-context/trustgraph-templates/src/components/common/MessageBubble.tsx delete mode 100644 ai-context/trustgraph-templates/src/components/common/SearchInput.tsx delete mode 100644 ai-context/trustgraph-templates/src/components/common/SectionLabel.tsx delete mode 100644 ai-context/trustgraph-templates/src/components/common/StatusBar.tsx delete mode 100644 ai-context/trustgraph-templates/src/components/common/Toaster.tsx delete mode 100644 ai-context/trustgraph-templates/src/components/common/Typewriter.tsx delete mode 100644 ai-context/trustgraph-templates/src/components/common/index.ts delete mode 100644 ai-context/trustgraph-templates/src/components/graph/ExplainGraph.tsx delete mode 100644 ai-context/trustgraph-templates/src/components/graph/GraphCanvas.tsx delete mode 100644 ai-context/trustgraph-templates/src/components/graph/GraphCanvasSVG.tsx delete mode 100644 ai-context/trustgraph-templates/src/components/graph/NodeDetailPanel.tsx delete mode 100644 ai-context/trustgraph-templates/src/components/graph/ZoomControls.tsx delete mode 100644 ai-context/trustgraph-templates/src/components/graph/index.ts delete mode 100644 ai-context/trustgraph-templates/src/components/index.ts delete mode 100644 ai-context/trustgraph-templates/src/config.ts delete mode 100644 ai-context/trustgraph-templates/src/index.css delete mode 100644 ai-context/trustgraph-templates/src/main.tsx delete mode 100644 ai-context/trustgraph-templates/src/pages/DataView.tsx delete mode 100644 ai-context/trustgraph-templates/src/pages/ExplainView.tsx delete mode 100644 ai-context/trustgraph-templates/src/pages/GraphView.tsx delete mode 100644 ai-context/trustgraph-templates/src/pages/OntologyView.tsx delete mode 100644 ai-context/trustgraph-templates/src/pages/QueryView.tsx delete mode 100644 ai-context/trustgraph-templates/src/pages/index.ts delete mode 100644 ai-context/trustgraph-templates/src/state/index.ts delete mode 100644 ai-context/trustgraph-templates/src/state/toastStore.ts delete mode 100644 ai-context/trustgraph-templates/src/state/useGraphData.ts delete mode 100644 ai-context/trustgraph-templates/src/state/useOntologySchema.ts delete mode 100644 ai-context/trustgraph-templates/src/theme/colors.ts delete mode 100644 ai-context/trustgraph-templates/src/theme/index.ts delete mode 100644 ai-context/trustgraph-templates/src/types/index.ts delete mode 100644 ai-context/trustgraph-templates/src/utils/index.ts delete mode 100644 ai-context/trustgraph-templates/src/utils/uri.ts delete mode 100644 ai-context/trustgraph-templates/src/vite-env.d.ts delete mode 100644 ai-context/trustgraph-templates/trustgraph-retail-demo.jsx delete mode 100644 ai-context/trustgraph-templates/tsconfig.json delete mode 100644 ai-context/trustgraph-templates/vite.config.js create mode 100644 tests/unit/test_gateway/test_graph_rag_request_translator.py create mode 100644 tests/unit/test_mcp/test_load_document.py create mode 100644 trustgraph/trustgraph/trustgraph_version.py create mode 100644 ts/.gitignore create mode 100644 ts/package.json create mode 100644 ts/packages/base/package.json create mode 100644 ts/packages/base/src/backend/index.ts create mode 100644 ts/packages/base/src/backend/nats.ts create mode 100644 ts/packages/base/src/backend/types.ts create mode 100644 ts/packages/base/src/errors.ts create mode 100644 ts/packages/base/src/index.ts create mode 100644 ts/packages/base/src/messaging/consumer.ts create mode 100644 ts/packages/base/src/messaging/index.ts create mode 100644 ts/packages/base/src/messaging/producer.ts create mode 100644 ts/packages/base/src/messaging/request-response.ts create mode 100644 ts/packages/base/src/messaging/subscriber.ts create mode 100644 ts/packages/base/src/metrics/index.ts create mode 100644 ts/packages/base/src/metrics/prometheus.ts create mode 100644 ts/packages/base/src/processor/async-processor.ts create mode 100644 ts/packages/base/src/processor/flow-processor.ts create mode 100644 ts/packages/base/src/processor/flow.ts create mode 100644 ts/packages/base/src/processor/index.ts create mode 100644 ts/packages/base/src/schema/index.ts create mode 100644 ts/packages/base/src/schema/messages.ts create mode 100644 ts/packages/base/src/schema/primitives.ts create mode 100644 ts/packages/base/src/schema/topics.ts create mode 100644 ts/packages/base/src/services/embeddings-service.ts create mode 100644 ts/packages/base/src/services/index.ts create mode 100644 ts/packages/base/src/services/llm-service.ts create mode 100644 ts/packages/base/src/spec/consumer-spec.ts create mode 100644 ts/packages/base/src/spec/index.ts create mode 100644 ts/packages/base/src/spec/parameter-spec.ts create mode 100644 ts/packages/base/src/spec/producer-spec.ts create mode 100644 ts/packages/base/src/spec/types.ts create mode 100644 ts/packages/base/tsconfig.json create mode 100644 ts/packages/cli/package.json create mode 100644 ts/packages/cli/src/commands/agent.ts create mode 100644 ts/packages/cli/src/commands/config.ts create mode 100644 ts/packages/cli/src/commands/flow.ts create mode 100644 ts/packages/cli/src/commands/graph-rag.ts create mode 100644 ts/packages/cli/src/commands/util.ts create mode 100644 ts/packages/cli/src/index.ts create mode 100644 ts/packages/cli/tsconfig.json create mode 100644 ts/packages/flow/package.json create mode 100644 ts/packages/flow/src/gateway/dispatch/manager.ts create mode 100644 ts/packages/flow/src/gateway/dispatch/mux.ts create mode 100644 ts/packages/flow/src/gateway/index.ts create mode 100644 ts/packages/flow/src/gateway/server.ts create mode 100644 ts/packages/flow/src/index.ts create mode 100644 ts/packages/flow/src/model/text-completion/claude.ts create mode 100644 ts/packages/flow/src/model/text-completion/openai.ts create mode 100644 ts/packages/flow/src/retrieval/document-rag.ts create mode 100644 ts/packages/flow/src/retrieval/graph-rag.ts create mode 100644 ts/packages/flow/tsconfig.json create mode 100644 ts/packages/mcp/package.json create mode 100644 ts/packages/mcp/src/index.ts create mode 100644 ts/packages/mcp/src/server.ts create mode 100644 ts/packages/mcp/src/socket-manager.ts create mode 100644 ts/packages/mcp/tsconfig.json create mode 100644 ts/pnpm-workspace.yaml create mode 100644 ts/tsconfig.base.json create mode 100644 ts/tsconfig.json create mode 100644 ts/turbo.json diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 00000000..9a897b64 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Ignored default folder with query files +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 00000000..6b327b41 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 00000000..379858e1 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,130 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000..babc8cb3 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/trustgraph.iml b/.idea/trustgraph.iml new file mode 100644 index 00000000..c956989b --- /dev/null +++ b/.idea/trustgraph.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..35eb1ddf --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/ai-context/trustgraph-templates/.gitignore b/ai-context/trustgraph-templates/.gitignore deleted file mode 100644 index 99e677ff..00000000 --- a/ai-context/trustgraph-templates/.gitignore +++ /dev/null @@ -1,25 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* -lerna-debug.log* - -node_modules -dist -dist-ssr -*.local - -# Editor directories and files -.vscode/* -!.vscode/extensions.json -.idea -.DS_Store -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? -*~ diff --git a/ai-context/trustgraph-templates/LICENSE b/ai-context/trustgraph-templates/LICENSE deleted file mode 100644 index 7e31bd3e..00000000 --- a/ai-context/trustgraph-templates/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to the Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by the Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding any notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. Please also get an approval - from the project maintainers before applying the Apache License - to your project. - - Copyright 2026 Knownext Inc. - Copyright 2026 Knownext Limited - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/ai-context/trustgraph-templates/README.md b/ai-context/trustgraph-templates/README.md deleted file mode 100644 index 7e94af46..00000000 --- a/ai-context/trustgraph-templates/README.md +++ /dev/null @@ -1,67 +0,0 @@ - -# Context Graph Demo - -A React application that demonstrates -[TrustGraph](https://trustgraph.ai/) context graph capabilities -The demo provides an interactive graph visualisation, natural-language -querying, explainability views, and ontology browsing — all powered by -a TrustGraph backend. Load your own data to explore. - -See it in action: [Context Graph demo video](https://www.youtube.com/watch?v=sWc7mkhITIo) - -## Features - -- **Graph view** — interactive force-directed graph of entities and - relationships, with domain-based filtering -- **Query view** — natural-language questions answered by the TrustGraph - knowledge graph -- **Explain view** — step-by-step explainability traces showing how - answers were derived -- **Data view** — browse the raw documents loaded into TrustGraph -- **Ontology view** — explore the ontology (types and predicates) - extracted from the dataset - -## Prerequisites - -- Node.js (v18+) -- A running [TrustGraph](https://trustgraph.ai/) instance (tested with - TrustGraph 2.1) - -## Preparing TrustGraph - -This demo requires TrustGraph to be running in ontology mode: - -1. Launch a flow using the `ontology` flow blueprint. -2. Load an OWL ontology into the workbench. -3. Process your data using the new flow. - -## Getting started - -Install dependencies: - -```bash -npm install -``` - -Start the development server: - -```bash -npm run dev -``` - -The Vite dev server proxies `/api/socket` (WebSocket) and other API -routes to the TrustGraph API gateway at `localhost:8088`. If your -TrustGraph instance is running on a different host or port, edit the -proxy targets in `vite.config.js`. - -Build for production: - -```bash -npm run build -``` - -## License - -Copyright 2026 Knownext Inc. and Knownext Limited. -Licensed under the Apache License 2.0 — see [LICENSE](LICENSE) for -details. diff --git a/ai-context/trustgraph-templates/eslint.config.js b/ai-context/trustgraph-templates/eslint.config.js deleted file mode 100644 index 4fa125da..00000000 --- a/ai-context/trustgraph-templates/eslint.config.js +++ /dev/null @@ -1,29 +0,0 @@ -import js from '@eslint/js' -import globals from 'globals' -import reactHooks from 'eslint-plugin-react-hooks' -import reactRefresh from 'eslint-plugin-react-refresh' -import { defineConfig, globalIgnores } from 'eslint/config' - -export default defineConfig([ - globalIgnores(['dist']), - { - files: ['**/*.{js,jsx}'], - extends: [ - js.configs.recommended, - reactHooks.configs.flat.recommended, - reactRefresh.configs.vite, - ], - languageOptions: { - ecmaVersion: 2020, - globals: globals.browser, - parserOptions: { - ecmaVersion: 'latest', - ecmaFeatures: { jsx: true }, - sourceType: 'module', - }, - }, - rules: { - 'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }], - }, - }, -]) diff --git a/ai-context/trustgraph-templates/index.html b/ai-context/trustgraph-templates/index.html deleted file mode 100644 index 3e6d7b10..00000000 --- a/ai-context/trustgraph-templates/index.html +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - TrustGraph Context Graph Demo - - - - - -
- - - diff --git a/ai-context/trustgraph-templates/package-lock.json b/ai-context/trustgraph-templates/package-lock.json deleted file mode 100644 index b2309e56..00000000 --- a/ai-context/trustgraph-templates/package-lock.json +++ /dev/null @@ -1,3080 +0,0 @@ -{ - "name": "retail-intelligence-demo", - "version": "0.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "retail-intelligence-demo", - "version": "0.0.0", - "dependencies": { - "@tanstack/react-query": "^5.90.21", - "@trustgraph/react-provider": "^1.4.0", - "@trustgraph/react-state": "^1.4.6", - "react": "^19.2.0", - "react-dom": "^19.2.0" - }, - "devDependencies": { - "@eslint/js": "^9.39.1", - "@types/react": "^19.2.7", - "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^5.1.1", - "eslint": "^9.39.1", - "eslint-plugin-react-hooks": "^7.0.1", - "eslint-plugin-react-refresh": "^0.4.24", - "globals": "^16.5.0", - "typescript": "~5.9.3", - "vite": "^7.3.1" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", - "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", - "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/generator": { - "version": "7.29.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", - "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", - "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", - "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", - "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", - "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", - "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", - "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz", - "integrity": "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz", - "integrity": "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz", - "integrity": "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz", - "integrity": "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz", - "integrity": "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz", - "integrity": "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz", - "integrity": "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz", - "integrity": "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz", - "integrity": "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz", - "integrity": "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz", - "integrity": "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz", - "integrity": "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz", - "integrity": "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz", - "integrity": "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz", - "integrity": "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz", - "integrity": "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz", - "integrity": "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz", - "integrity": "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz", - "integrity": "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz", - "integrity": "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz", - "integrity": "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.4.tgz", - "integrity": "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz", - "integrity": "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz", - "integrity": "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz", - "integrity": "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz", - "integrity": "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", - "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", - "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/config-array": { - "version": "0.21.2", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", - "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/object-schema": "^2.1.7", - "debug": "^4.3.1", - "minimatch": "^3.1.5" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/config-helpers": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", - "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.17.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/core": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", - "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", - "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.14.0", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.1", - "minimatch": "^3.1.5", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@eslint/js": { - "version": "9.39.4", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", - "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - } - }, - "node_modules/@eslint/object-schema": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", - "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/plugin-kit": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", - "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.17.0", - "levn": "^0.4.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.4.0" - }, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", - "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.3", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz", - "integrity": "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", - "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", - "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", - "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", - "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", - "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", - "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", - "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", - "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", - "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", - "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", - "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", - "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", - "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", - "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", - "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", - "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", - "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", - "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", - "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", - "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", - "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", - "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", - "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", - "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", - "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@tanstack/query-core": { - "version": "5.91.0", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.91.0.tgz", - "integrity": "sha512-FYXN8Kk9Q5VKuV6AIVaNwMThSi0nvAtR4X7HQoigf6ePOtFcavJYVIzgFhOVdtbBQtCJE3KimDIMMJM2DR1hjw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - } - }, - "node_modules/@tanstack/react-query": { - "version": "5.91.0", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.91.0.tgz", - "integrity": "sha512-S8FODsDTNv0Ym+o/JVBvA6EWiWVhg6K2Q4qFehZyFKk6uW4H9OPbXl4kyiN9hAly0uHJ/1GEbR6kAI4MZWfjEA==", - "license": "MIT", - "dependencies": { - "@tanstack/query-core": "5.91.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - }, - "peerDependencies": { - "react": "^18 || ^19" - } - }, - "node_modules/@trustgraph/client": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@trustgraph/client/-/client-1.6.0.tgz", - "integrity": "sha512-z09X1TNmaRrmZXt4b5aXtJr/D7XjF7Lm7/lpYIPUO0NTJ4QYm2ZDPE/z1bJxpJf29y/Q2A65bWY6+TzPoO9kZQ==", - "license": "Apache-2.0", - "peer": true - }, - "node_modules/@trustgraph/react-provider": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@trustgraph/react-provider/-/react-provider-1.4.0.tgz", - "integrity": "sha512-CRtwrbzEOiKoAekXpjIhz8jbvNbaw6Fm0E2EgLnVp5Jf/6GVfPJ6v3eeqG4Z0AlO23cq8k0j88i+nxGI6llQIQ==", - "license": "Apache-2.0", - "peerDependencies": { - "@trustgraph/client": "^1.4.0", - "react": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@trustgraph/react-state": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@trustgraph/react-state/-/react-state-1.6.0.tgz", - "integrity": "sha512-NsTfmbNE0zTz/H0/aMTOYjGvJ89bpuoZP56/n9Jtc/35Bx5sqtRi+ECCCkWvPV1nHMQC7MWtlMtmAfhuZm4Bgw==", - "license": "MIT", - "dependencies": { - "@trustgraph/react-provider": "^1.4.0", - "compute-cosine-similarity": "^1.1.0", - "uuid": "^11.0.3" - }, - "peerDependencies": { - "@tanstack/react-query": "^5.0.0", - "react": "^18.0.0 || ^19.0.0", - "zustand": "^4.0.0 || ^5.0.0" - } - }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", - "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.2" - } - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/react": { - "version": "19.2.14", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", - "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "csstype": "^3.2.2" - } - }, - "node_modules/@types/react-dom": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", - "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "@types/react": "^19.2.0" - } - }, - "node_modules/@vitejs/plugin-react": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.2.0.tgz", - "integrity": "sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.29.0", - "@babel/plugin-transform-react-jsx-self": "^7.27.1", - "@babel/plugin-transform-react-jsx-source": "^7.27.1", - "@rolldown/pluginutils": "1.0.0-rc.3", - "@types/babel__core": "^7.20.5", - "react-refresh": "^0.18.0" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/acorn": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/baseline-browser-mapping": { - "version": "2.10.8", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.8.tgz", - "integrity": "sha512-PCLz/LXGBsNTErbtB6i5u4eLpHeMfi93aUv5duMmj6caNu6IphS4q6UevDnL36sZQv9lrP11dbPKGMaXPwMKfQ==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.cjs" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001780", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001780.tgz", - "integrity": "sha512-llngX0E7nQci5BPJDqoZSbuZ5Bcs9F5db7EtgfwBerX9XGtkkiO4NwfDDIRzHTTwcYC8vC7bmeUEPGrKlR/TkQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/compute-cosine-similarity": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/compute-cosine-similarity/-/compute-cosine-similarity-1.1.0.tgz", - "integrity": "sha512-FXhNx0ILLjGi9Z9+lglLzM12+0uoTnYkHm7GiadXDAr0HGVLm25OivUS1B/LPkbzzvlcXz/1EvWg9ZYyJSdhTw==", - "dependencies": { - "compute-dot": "^1.1.0", - "compute-l2norm": "^1.1.0", - "validate.io-array": "^1.0.5", - "validate.io-function": "^1.0.2" - } - }, - "node_modules/compute-dot": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/compute-dot/-/compute-dot-1.1.0.tgz", - "integrity": "sha512-L5Ocet4DdMrXboss13K59OK23GXjiSia7+7Ukc7q4Bl+RVpIXK2W9IHMbWDZkh+JUEvJAwOKRaJDiFUa1LTnJg==", - "dependencies": { - "validate.io-array": "^1.0.3", - "validate.io-function": "^1.0.2" - } - }, - "node_modules/compute-l2norm": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/compute-l2norm/-/compute-l2norm-1.1.0.tgz", - "integrity": "sha512-6EHh1Elj90eU28SXi+h2PLnTQvZmkkHWySpoFz+WOlVNLz3DQoC4ISUHSV9n5jMxPHtKGJ01F4uu2PsXBB8sSg==", - "dependencies": { - "validate.io-array": "^1.0.3", - "validate.io-function": "^1.0.2" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/csstype": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/electron-to-chromium": { - "version": "1.5.321", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.321.tgz", - "integrity": "sha512-L2C7Q279W2D/J4PLZLk7sebOILDSWos7bMsMNN06rK482umHUrh/3lM8G7IlHFOYip2oAg5nha1rCMxr/rs6ZQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/esbuild": { - "version": "0.27.4", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz", - "integrity": "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.4", - "@esbuild/android-arm": "0.27.4", - "@esbuild/android-arm64": "0.27.4", - "@esbuild/android-x64": "0.27.4", - "@esbuild/darwin-arm64": "0.27.4", - "@esbuild/darwin-x64": "0.27.4", - "@esbuild/freebsd-arm64": "0.27.4", - "@esbuild/freebsd-x64": "0.27.4", - "@esbuild/linux-arm": "0.27.4", - "@esbuild/linux-arm64": "0.27.4", - "@esbuild/linux-ia32": "0.27.4", - "@esbuild/linux-loong64": "0.27.4", - "@esbuild/linux-mips64el": "0.27.4", - "@esbuild/linux-ppc64": "0.27.4", - "@esbuild/linux-riscv64": "0.27.4", - "@esbuild/linux-s390x": "0.27.4", - "@esbuild/linux-x64": "0.27.4", - "@esbuild/netbsd-arm64": "0.27.4", - "@esbuild/netbsd-x64": "0.27.4", - "@esbuild/openbsd-arm64": "0.27.4", - "@esbuild/openbsd-x64": "0.27.4", - "@esbuild/openharmony-arm64": "0.27.4", - "@esbuild/sunos-x64": "0.27.4", - "@esbuild/win32-arm64": "0.27.4", - "@esbuild/win32-ia32": "0.27.4", - "@esbuild/win32-x64": "0.27.4" - } - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "9.39.4", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", - "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.8.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.2", - "@eslint/config-helpers": "^0.4.2", - "@eslint/core": "^0.17.0", - "@eslint/eslintrc": "^3.3.5", - "@eslint/js": "9.39.4", - "@eslint/plugin-kit": "^0.4.1", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@types/estree": "^1.0.6", - "ajv": "^6.14.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.6", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.4.0", - "eslint-visitor-keys": "^4.2.1", - "espree": "^10.4.0", - "esquery": "^1.5.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.5", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } - } - }, - "node_modules/eslint-plugin-react-hooks": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz", - "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.24.4", - "@babel/parser": "^7.24.4", - "hermes-parser": "^0.25.1", - "zod": "^3.25.0 || ^4.0.0", - "zod-validation-error": "^3.5.0 || ^4.0.0" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" - } - }, - "node_modules/eslint-plugin-react-refresh": { - "version": "0.4.26", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.26.tgz", - "integrity": "sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "eslint": ">=8.40" - } - }, - "node_modules/eslint-scope": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", - "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/espree": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.15.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", - "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^4.0.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/flatted": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", - "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", - "dev": true, - "license": "ISC" - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/globals": { - "version": "16.5.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", - "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/hermes-estree": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", - "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", - "dev": true, - "license": "MIT" - }, - "node_modules/hermes-parser": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", - "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "hermes-estree": "0.25.1" - } - }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" - }, - "node_modules/node-releases": { - "version": "2.0.36", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", - "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", - "dev": true, - "license": "MIT" - }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/postcss": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", - "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/react": { - "version": "19.2.4", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", - "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-dom": { - "version": "19.2.4", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", - "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", - "license": "MIT", - "dependencies": { - "scheduler": "^0.27.0" - }, - "peerDependencies": { - "react": "^19.2.4" - } - }, - "node_modules/react-refresh": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", - "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/rollup": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", - "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "1.0.8" - }, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" - }, - "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.59.0", - "@rollup/rollup-android-arm64": "4.59.0", - "@rollup/rollup-darwin-arm64": "4.59.0", - "@rollup/rollup-darwin-x64": "4.59.0", - "@rollup/rollup-freebsd-arm64": "4.59.0", - "@rollup/rollup-freebsd-x64": "4.59.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", - "@rollup/rollup-linux-arm-musleabihf": "4.59.0", - "@rollup/rollup-linux-arm64-gnu": "4.59.0", - "@rollup/rollup-linux-arm64-musl": "4.59.0", - "@rollup/rollup-linux-loong64-gnu": "4.59.0", - "@rollup/rollup-linux-loong64-musl": "4.59.0", - "@rollup/rollup-linux-ppc64-gnu": "4.59.0", - "@rollup/rollup-linux-ppc64-musl": "4.59.0", - "@rollup/rollup-linux-riscv64-gnu": "4.59.0", - "@rollup/rollup-linux-riscv64-musl": "4.59.0", - "@rollup/rollup-linux-s390x-gnu": "4.59.0", - "@rollup/rollup-linux-x64-gnu": "4.59.0", - "@rollup/rollup-linux-x64-musl": "4.59.0", - "@rollup/rollup-openbsd-x64": "4.59.0", - "@rollup/rollup-openharmony-arm64": "4.59.0", - "@rollup/rollup-win32-arm64-msvc": "4.59.0", - "@rollup/rollup-win32-ia32-msvc": "4.59.0", - "@rollup/rollup-win32-x64-gnu": "4.59.0", - "@rollup/rollup-win32-x64-msvc": "4.59.0", - "fsevents": "~2.3.2" - } - }, - "node_modules/scheduler": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", - "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", - "license": "MIT" - }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/uuid": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", - "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/esm/bin/uuid" - } - }, - "node_modules/validate.io-array": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/validate.io-array/-/validate.io-array-1.0.6.tgz", - "integrity": "sha512-DeOy7CnPEziggrOO5CZhVKJw6S3Yi7e9e65R1Nl/RTN1vTQKnzjfvks0/8kQ40FP/dsjRAOd4hxmJ7uLa6vxkg==", - "license": "MIT" - }, - "node_modules/validate.io-function": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/validate.io-function/-/validate.io-function-1.0.2.tgz", - "integrity": "sha512-LlFybRJEriSuBnUhQyG5bwglhh50EpTL2ul23MPIuR1odjO7XaMLFV8vHGwp7AZciFxtYOeiSCT5st+XSPONiQ==" - }, - "node_modules/vite": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", - "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", - "dev": true, - "license": "MIT", - "dependencies": { - "esbuild": "^0.27.0", - "fdir": "^6.5.0", - "picomatch": "^4.0.3", - "postcss": "^8.5.6", - "rollup": "^4.43.0", - "tinyglobby": "^0.2.15" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "lightningcss": "^1.21.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/zod": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", - "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/zod-validation-error": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", - "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.0.0" - }, - "peerDependencies": { - "zod": "^3.25.0 || ^4.0.0" - } - }, - "node_modules/zustand": { - "version": "5.0.12", - "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.12.tgz", - "integrity": "sha512-i77ae3aZq4dhMlRhJVCYgMLKuSiZAaUPAct2AksxQ+gOtimhGMdXljRT21P5BNpeT4kXlLIckvkPM029OljD7g==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">=12.20.0" - }, - "peerDependencies": { - "@types/react": ">=18.0.0", - "immer": ">=9.0.6", - "react": ">=18.0.0", - "use-sync-external-store": ">=1.2.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "immer": { - "optional": true - }, - "react": { - "optional": true - }, - "use-sync-external-store": { - "optional": true - } - } - } - } -} diff --git a/ai-context/trustgraph-templates/package.json b/ai-context/trustgraph-templates/package.json deleted file mode 100644 index cc484076..00000000 --- a/ai-context/trustgraph-templates/package.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "name": "retail-intelligence-demo", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "tsc -b && vite build", - "lint": "eslint .", - "preview": "vite preview" - }, - "dependencies": { - "@tanstack/react-query": "^5.90.21", - "@trustgraph/react-provider": "^1.4.0", - "@trustgraph/react-state": "^1.4.6", - "react": "^19.2.0", - "react-dom": "^19.2.0" - }, - "devDependencies": { - "@eslint/js": "^9.39.1", - "@types/react": "^19.2.7", - "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^5.1.1", - "eslint": "^9.39.1", - "eslint-plugin-react-hooks": "^7.0.1", - "eslint-plugin-react-refresh": "^0.4.24", - "globals": "^16.5.0", - "typescript": "~5.9.3", - "vite": "^7.3.1" - } -} diff --git a/ai-context/trustgraph-templates/public/tg.svg b/ai-context/trustgraph-templates/public/tg.svg deleted file mode 100644 index 7123ae45..00000000 --- a/ai-context/trustgraph-templates/public/tg.svg +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/ai-context/trustgraph-templates/retail.json b/ai-context/trustgraph-templates/retail.json deleted file mode 100644 index 565ab45c..00000000 --- a/ai-context/trustgraph-templates/retail.json +++ /dev/null @@ -1 +0,0 @@ -{"metadata":{"name":"TrustGraph Retail Intelligence Ontology","description":"Ontology for retail ecosystem modeling: consumers, brands, retail channels, and AI agents","version":"1.0","created":"2026-02-25T13:38:28.636Z","modified":"2026-02-25T13:38:28.636Z","creator":"","namespace":"http://trustgraph.ai/retail#","namespaces":{"owl":"http://www.w3.org/2002/07/owl#","rdf":"http://www.w3.org/1999/02/22-rdf-syntax-ns#","rdfs":"http://www.w3.org/2000/01/rdf-schema#","xsd":"http://www.w3.org/2001/XMLSchema#","":"http://trustgraph.ai/retail#"}},"classes":{"retail#Consumer":{"uri":"http://trustgraph.ai/retail#Consumer","type":"owl:Class","rdfs:label":[{"value":"Consumer","lang":"en"}],"rdfs:comment":"Individuals and segments interacting with brands through retail channels"},"retail#Brand":{"uri":"http://trustgraph.ai/retail#Brand","type":"owl:Class","rdfs:label":[{"value":"Brand","lang":"en"}],"rdfs:comment":"Product brands seeking to connect with consumers through retail experiences"},"retail#Retail":{"uri":"http://trustgraph.ai/retail#Retail","type":"owl:Class","rdfs:label":[{"value":"Retail","lang":"en"}],"rdfs:comment":"Channels, touchpoints, and experiences where brands meet consumers"},"retail#Agent":{"uri":"http://trustgraph.ai/retail#Agent","type":"owl:Class","rdfs:label":[{"value":"Agent","lang":"en"}],"rdfs:comment":"AI agents that orchestrate personalized brand-consumer connections"}},"objectProperties":{"retail#hasAffinityFor":{"uri":"http://trustgraph.ai/retail#hasAffinityFor","type":"owl:ObjectProperty","rdfs:label":[{"value":"has affinity for","lang":"en"}],"rdfs:domain":"retail#Consumer","rdfs:range":"retail#Brand"},"retail#frequents":{"uri":"http://trustgraph.ai/retail#frequents","type":"owl:ObjectProperty","rdfs:label":[{"value":"frequents","lang":"en"}],"rdfs:domain":"retail#Consumer","rdfs:range":"retail#Brand"},"retail#purchasesFrom":{"uri":"http://trustgraph.ai/retail#purchasesFrom","type":"owl:ObjectProperty","rdfs:label":[{"value":"purchases from","lang":"en"}],"rdfs:domain":"retail#Consumer","rdfs:range":"retail#Brand"},"retail#advocatesFor":{"uri":"http://trustgraph.ai/retail#advocatesFor","type":"owl:ObjectProperty","rdfs:label":[{"value":"advocates for","lang":"en"}],"rdfs:domain":"retail#Consumer","rdfs:range":"retail#Brand"},"retail#loyalTo":{"uri":"http://trustgraph.ai/retail#loyalTo","type":"owl:ObjectProperty","rdfs:label":[{"value":"loyal to","lang":"en"}],"rdfs:domain":"retail#Consumer","rdfs:range":"retail#Brand"},"retail#shopsVia":{"uri":"http://trustgraph.ai/retail#shopsVia","type":"owl:ObjectProperty","rdfs:label":[{"value":"shops via","lang":"en"}],"rdfs:domain":"retail#Consumer","rdfs:range":"retail#Retail"},"retail#discoversThrough":{"uri":"http://trustgraph.ai/retail#discoversThrough","type":"owl:ObjectProperty","rdfs:label":[{"value":"discovers through","lang":"en"}],"rdfs:domain":"retail#Consumer","rdfs:range":"retail#Retail"},"retail#experiences":{"uri":"http://trustgraph.ai/retail#experiences","type":"owl:ObjectProperty","rdfs:label":[{"value":"experiences","lang":"en"}],"rdfs:domain":"retail#Consumer","rdfs:range":"retail#Retail"},"retail#memberOf":{"uri":"http://trustgraph.ai/retail#memberOf","type":"owl:ObjectProperty","rdfs:label":[{"value":"member of","lang":"en"}],"rdfs:domain":"retail#Consumer","rdfs:range":"retail#Retail"},"retail#merchandisesIn":{"uri":"http://trustgraph.ai/retail#merchandisesIn","type":"owl:ObjectProperty","rdfs:label":[{"value":"merchandises in","lang":"en"}],"rdfs:domain":"retail#Brand","rdfs:range":"retail#Retail"},"retail#activatesVia":{"uri":"http://trustgraph.ai/retail#activatesVia","type":"owl:ObjectProperty","rdfs:label":[{"value":"activates via","lang":"en"}],"rdfs:domain":"retail#Brand","rdfs:range":"retail#Retail"},"retail#promotesOn":{"uri":"http://trustgraph.ai/retail#promotesOn","type":"owl:ObjectProperty","rdfs:label":[{"value":"promotes on","lang":"en"}],"rdfs:domain":"retail#Brand","rdfs:range":"retail#Retail"},"retail#sellsThrough":{"uri":"http://trustgraph.ai/retail#sellsThrough","type":"owl:ObjectProperty","rdfs:label":[{"value":"sells through","lang":"en"}],"rdfs:domain":"retail#Brand","rdfs:range":"retail#Retail"},"retail#rewardsVia":{"uri":"http://trustgraph.ai/retail#rewardsVia","type":"owl:ObjectProperty","rdfs:label":[{"value":"rewards via","lang":"en"}],"rdfs:domain":"retail#Brand","rdfs:range":"retail#Retail"},"retail#recommendsTo":{"uri":"http://trustgraph.ai/retail#recommendsTo","type":"owl:ObjectProperty","rdfs:label":[{"value":"recommends to","lang":"en"}],"rdfs:domain":"retail#Agent","rdfs:range":"retail#Consumer"},"retail#personalizesFor":{"uri":"http://trustgraph.ai/retail#personalizesFor","type":"owl:ObjectProperty","rdfs:label":[{"value":"personalizes for","lang":"en"}],"rdfs:domain":"retail#Agent","rdfs:range":"retail#Consumer"},"retail#monitorsSentimentOf":{"uri":"http://trustgraph.ai/retail#monitorsSentimentOf","type":"owl:ObjectProperty","rdfs:label":[{"value":"monitors sentiment of","lang":"en"}],"rdfs:domain":"retail#Agent","rdfs:range":"retail#Consumer"},"retail#optimizesJourneyFor":{"uri":"http://trustgraph.ai/retail#optimizesJourneyFor","type":"owl:ObjectProperty","rdfs:label":[{"value":"optimizes journey for","lang":"en"}],"rdfs:domain":"retail#Agent","rdfs:range":"retail#Consumer"},"retail#orchestratesCampaignFor":{"uri":"http://trustgraph.ai/retail#orchestratesCampaignFor","type":"owl:ObjectProperty","rdfs:label":[{"value":"orchestrates campaign for","lang":"en"}],"rdfs:domain":"retail#Agent","rdfs:range":"retail#Brand"},"retail#analyzesPerceptionOf":{"uri":"http://trustgraph.ai/retail#analyzesPerceptionOf","type":"owl:ObjectProperty","rdfs:label":[{"value":"analyzes perception of","lang":"en"}],"rdfs:domain":"retail#Agent","rdfs:range":"retail#Brand"},"retail#curatesProductsFor":{"uri":"http://trustgraph.ai/retail#curatesProductsFor","type":"owl:ObjectProperty","rdfs:label":[{"value":"curates products for","lang":"en"}],"rdfs:domain":"retail#Agent","rdfs:range":"retail#Brand"},"retail#tailorsExperienceAt":{"uri":"http://trustgraph.ai/retail#tailorsExperienceAt","type":"owl:ObjectProperty","rdfs:label":[{"value":"tailors experience at","lang":"en"}],"rdfs:domain":"retail#Agent","rdfs:range":"retail#Retail"},"retail#deploysCampaignAt":{"uri":"http://trustgraph.ai/retail#deploysCampaignAt","type":"owl:ObjectProperty","rdfs:label":[{"value":"deploys campaign at","lang":"en"}],"rdfs:domain":"retail#Agent","rdfs:range":"retail#Retail"},"retail#optimizesFlowAt":{"uri":"http://trustgraph.ai/retail#optimizesFlowAt","type":"owl:ObjectProperty","rdfs:label":[{"value":"optimizes flow at","lang":"en"}],"rdfs:domain":"retail#Agent","rdfs:range":"retail#Retail"}},"datatypeProperties":{"retail#segment":{"uri":"http://trustgraph.ai/retail#segment","type":"owl:DatatypeProperty","rdfs:range":"xsd:string","rdfs:label":[{"value":"segment","lang":"en"}],"rdfs:domain":"retail#Consumer"},"retail#preferences":{"uri":"http://trustgraph.ai/retail#preferences","type":"owl:DatatypeProperty","rdfs:range":"xsd:string","rdfs:label":[{"value":"preferences","lang":"en"}],"rdfs:domain":"retail#Consumer"},"retail#journeyStage":{"uri":"http://trustgraph.ai/retail#journeyStage","type":"owl:DatatypeProperty","rdfs:range":"xsd:string","rdfs:label":[{"value":"journey stage","lang":"en"}],"rdfs:domain":"retail#Consumer"},"retail#lifetimeValue":{"uri":"http://trustgraph.ai/retail#lifetimeValue","type":"owl:DatatypeProperty","rdfs:range":"xsd:decimal","rdfs:label":[{"value":"lifetime value","lang":"en"}],"rdfs:domain":"retail#Consumer"},"retail#sentiment":{"uri":"http://trustgraph.ai/retail#sentiment","type":"owl:DatatypeProperty","rdfs:range":"xsd:decimal","rdfs:label":[{"value":"sentiment","lang":"en"}],"rdfs:domain":"retail#Consumer"},"retail#size":{"uri":"http://trustgraph.ai/retail#size","type":"owl:DatatypeProperty","rdfs:range":"xsd:string","rdfs:label":[{"value":"size","lang":"en"}],"rdfs:domain":"retail#Consumer"},"retail#avgSpend":{"uri":"http://trustgraph.ai/retail#avgSpend","type":"owl:DatatypeProperty","rdfs:range":"xsd:string","rdfs:label":[{"value":"average spend","lang":"en"}],"rdfs:domain":"retail#Consumer"},"retail#loyalty":{"uri":"http://trustgraph.ai/retail#loyalty","type":"owl:DatatypeProperty","rdfs:range":"xsd:decimal","rdfs:label":[{"value":"loyalty","lang":"en"}],"rdfs:domain":"retail#Consumer"},"retail#identity":{"uri":"http://trustgraph.ai/retail#identity","type":"owl:DatatypeProperty","rdfs:range":"xsd:string","rdfs:label":[{"value":"identity","lang":"en"}],"rdfs:domain":"retail#Brand"},"retail#positioning":{"uri":"http://trustgraph.ai/retail#positioning","type":"owl:DatatypeProperty","rdfs:range":"xsd:string","rdfs:label":[{"value":"positioning","lang":"en"}],"rdfs:domain":"retail#Brand"},"retail#campaigns":{"uri":"http://trustgraph.ai/retail#campaigns","type":"owl:DatatypeProperty","rdfs:range":"xsd:integer","rdfs:label":[{"value":"campaigns","lang":"en"}],"rdfs:domain":"retail#Brand"},"retail#products":{"uri":"http://trustgraph.ai/retail#products","type":"owl:DatatypeProperty","rdfs:range":"xsd:string","rdfs:label":[{"value":"products","lang":"en"}],"rdfs:domain":"retail#Brand"},"retail#partnerships":{"uri":"http://trustgraph.ai/retail#partnerships","type":"owl:DatatypeProperty","rdfs:range":"xsd:string","rdfs:label":[{"value":"partnerships","lang":"en"}],"rdfs:domain":"retail#Brand"},"retail#category":{"uri":"http://trustgraph.ai/retail#category","type":"owl:DatatypeProperty","rdfs:range":"xsd:string","rdfs:label":[{"value":"category","lang":"en"}],"rdfs:domain":"retail#Brand"},"retail#channel":{"uri":"http://trustgraph.ai/retail#channel","type":"owl:DatatypeProperty","rdfs:range":"xsd:string","rdfs:label":[{"value":"channel","lang":"en"}],"rdfs:domain":"retail#Retail"},"retail#location":{"uri":"http://trustgraph.ai/retail#location","type":"owl:DatatypeProperty","rdfs:range":"xsd:string","rdfs:label":[{"value":"location","lang":"en"}],"rdfs:domain":"retail#Retail"},"retail#traffic":{"uri":"http://trustgraph.ai/retail#traffic","type":"owl:DatatypeProperty","rdfs:range":"xsd:string","rdfs:label":[{"value":"traffic","lang":"en"}],"rdfs:domain":"retail#Retail"},"retail#conversionRate":{"uri":"http://trustgraph.ai/retail#conversionRate","type":"owl:DatatypeProperty","rdfs:range":"xsd:string","rdfs:label":[{"value":"conversion rate","lang":"en"}],"rdfs:domain":"retail#Retail"},"retail#experienceScore":{"uri":"http://trustgraph.ai/retail#experienceScore","type":"owl:DatatypeProperty","rdfs:range":"xsd:decimal","rdfs:label":[{"value":"experience score","lang":"en"}],"rdfs:domain":"retail#Retail"},"retail#capability":{"uri":"http://trustgraph.ai/retail#capability","type":"owl:DatatypeProperty","rdfs:range":"xsd:string","rdfs:label":[{"value":"capability","lang":"en"}],"rdfs:domain":"retail#Agent"},"retail#contextSources":{"uri":"http://trustgraph.ai/retail#contextSources","type":"owl:DatatypeProperty","rdfs:range":"xsd:string","rdfs:label":[{"value":"context sources","lang":"en"}],"rdfs:domain":"retail#Agent"},"retail#accuracy":{"uri":"http://trustgraph.ai/retail#accuracy","type":"owl:DatatypeProperty","rdfs:range":"xsd:string","rdfs:label":[{"value":"accuracy","lang":"en"}],"rdfs:domain":"retail#Agent"},"retail#latency":{"uri":"http://trustgraph.ai/retail#latency","type":"owl:DatatypeProperty","rdfs:range":"xsd:string","rdfs:label":[{"value":"latency","lang":"en"}],"rdfs:domain":"retail#Agent"},"retail#decisionsPerDay":{"uri":"http://trustgraph.ai/retail#decisionsPerDay","type":"owl:DatatypeProperty","rdfs:range":"xsd:string","rdfs:label":[{"value":"decisions per day","lang":"en"}],"rdfs:domain":"retail#Agent"}}} diff --git a/ai-context/trustgraph-templates/retail.ttl b/ai-context/trustgraph-templates/retail.ttl deleted file mode 100644 index 165f3568..00000000 --- a/ai-context/trustgraph-templates/retail.ttl +++ /dev/null @@ -1,310 +0,0 @@ -@prefix owl: . -@prefix rdf: . -@prefix rdfs: . -@prefix xsd: . -@prefix : . - -# Ontology declaration - a owl:Ontology ; - rdfs:label "TrustGraph Retail Intelligence Ontology" ; - rdfs:comment "Ontology for retail ecosystem modeling: consumers, brands, retail channels, and AI agents" . - -# ============================================================================= -# Classes -# ============================================================================= - -:Consumer a owl:Class ; - rdfs:label "Consumer" ; - rdfs:comment "Individuals and segments interacting with brands through retail channels" . - -:Brand a owl:Class ; - rdfs:label "Brand" ; - rdfs:comment "Product brands seeking to connect with consumers through retail experiences" . - -:Retail a owl:Class ; - rdfs:label "Retail" ; - rdfs:comment "Channels, touchpoints, and experiences where brands meet consumers" . - -:Agent a owl:Class ; - rdfs:label "Agent" ; - rdfs:comment "AI agents that orchestrate personalized brand-consumer connections" . - -# ============================================================================= -# Datatype Properties - Consumer -# ============================================================================= - -:segment a owl:DatatypeProperty ; - rdfs:label "segment" ; - rdfs:domain :Consumer ; - rdfs:range xsd:string . - -:preferences a owl:DatatypeProperty ; - rdfs:label "preferences" ; - rdfs:domain :Consumer ; - rdfs:range xsd:string . - -:journeyStage a owl:DatatypeProperty ; - rdfs:label "journey stage" ; - rdfs:domain :Consumer ; - rdfs:range xsd:string . - -:lifetimeValue a owl:DatatypeProperty ; - rdfs:label "lifetime value" ; - rdfs:domain :Consumer ; - rdfs:range xsd:decimal . - -:sentiment a owl:DatatypeProperty ; - rdfs:label "sentiment" ; - rdfs:domain :Consumer ; - rdfs:range xsd:decimal . - -:size a owl:DatatypeProperty ; - rdfs:label "size" ; - rdfs:domain :Consumer ; - rdfs:range xsd:string . - -:avgSpend a owl:DatatypeProperty ; - rdfs:label "average spend" ; - rdfs:domain :Consumer ; - rdfs:range xsd:string . - -:loyalty a owl:DatatypeProperty ; - rdfs:label "loyalty" ; - rdfs:domain :Consumer ; - rdfs:range xsd:decimal . - -# ============================================================================= -# Datatype Properties - Brand -# ============================================================================= - -:identity a owl:DatatypeProperty ; - rdfs:label "identity" ; - rdfs:domain :Brand ; - rdfs:range xsd:string . - -:positioning a owl:DatatypeProperty ; - rdfs:label "positioning" ; - rdfs:domain :Brand ; - rdfs:range xsd:string . - -:campaigns a owl:DatatypeProperty ; - rdfs:label "campaigns" ; - rdfs:domain :Brand ; - rdfs:range xsd:integer . - -:products a owl:DatatypeProperty ; - rdfs:label "products" ; - rdfs:domain :Brand ; - rdfs:range xsd:string . - -:partnerships a owl:DatatypeProperty ; - rdfs:label "partnerships" ; - rdfs:domain :Brand ; - rdfs:range xsd:string . - -:category a owl:DatatypeProperty ; - rdfs:label "category" ; - rdfs:domain :Brand ; - rdfs:range xsd:string . - -# ============================================================================= -# Datatype Properties - Retail -# ============================================================================= - -:channel a owl:DatatypeProperty ; - rdfs:label "channel" ; - rdfs:domain :Retail ; - rdfs:range xsd:string . - -:location a owl:DatatypeProperty ; - rdfs:label "location" ; - rdfs:domain :Retail ; - rdfs:range xsd:string . - -:traffic a owl:DatatypeProperty ; - rdfs:label "traffic" ; - rdfs:domain :Retail ; - rdfs:range xsd:string . - -:conversionRate a owl:DatatypeProperty ; - rdfs:label "conversion rate" ; - rdfs:domain :Retail ; - rdfs:range xsd:string . - -:experienceScore a owl:DatatypeProperty ; - rdfs:label "experience score" ; - rdfs:domain :Retail ; - rdfs:range xsd:decimal . - -# ============================================================================= -# Datatype Properties - Agent -# ============================================================================= - -:capability a owl:DatatypeProperty ; - rdfs:label "capability" ; - rdfs:domain :Agent ; - rdfs:range xsd:string . - -:contextSources a owl:DatatypeProperty ; - rdfs:label "context sources" ; - rdfs:domain :Agent ; - rdfs:range xsd:string . - -:accuracy a owl:DatatypeProperty ; - rdfs:label "accuracy" ; - rdfs:domain :Agent ; - rdfs:range xsd:string . - -:latency a owl:DatatypeProperty ; - rdfs:label "latency" ; - rdfs:domain :Agent ; - rdfs:range xsd:string . - -:decisionsPerDay a owl:DatatypeProperty ; - rdfs:label "decisions per day" ; - rdfs:domain :Agent ; - rdfs:range xsd:string . - -# ============================================================================= -# Object Properties - Consumer <-> Brand -# ============================================================================= - -:hasAffinityFor a owl:ObjectProperty ; - rdfs:label "has affinity for" ; - rdfs:domain :Consumer ; - rdfs:range :Brand . - -:frequents a owl:ObjectProperty ; - rdfs:label "frequents" ; - rdfs:domain :Consumer ; - rdfs:range :Brand . - -:purchasesFrom a owl:ObjectProperty ; - rdfs:label "purchases from" ; - rdfs:domain :Consumer ; - rdfs:range :Brand . - -:advocatesFor a owl:ObjectProperty ; - rdfs:label "advocates for" ; - rdfs:domain :Consumer ; - rdfs:range :Brand . - -:loyalTo a owl:ObjectProperty ; - rdfs:label "loyal to" ; - rdfs:domain :Consumer ; - rdfs:range :Brand . - -# ============================================================================= -# Object Properties - Consumer <-> Retail -# ============================================================================= - -:shopsVia a owl:ObjectProperty ; - rdfs:label "shops via" ; - rdfs:domain :Consumer ; - rdfs:range :Retail . - -:discoversThrough a owl:ObjectProperty ; - rdfs:label "discovers through" ; - rdfs:domain :Consumer ; - rdfs:range :Retail . - -:experiences a owl:ObjectProperty ; - rdfs:label "experiences" ; - rdfs:domain :Consumer ; - rdfs:range :Retail . - -:memberOf a owl:ObjectProperty ; - rdfs:label "member of" ; - rdfs:domain :Consumer ; - rdfs:range :Retail . - -# ============================================================================= -# Object Properties - Brand <-> Retail -# ============================================================================= - -:merchandisesIn a owl:ObjectProperty ; - rdfs:label "merchandises in" ; - rdfs:domain :Brand ; - rdfs:range :Retail . - -:activatesVia a owl:ObjectProperty ; - rdfs:label "activates via" ; - rdfs:domain :Brand ; - rdfs:range :Retail . - -:promotesOn a owl:ObjectProperty ; - rdfs:label "promotes on" ; - rdfs:domain :Brand ; - rdfs:range :Retail . - -:sellsThrough a owl:ObjectProperty ; - rdfs:label "sells through" ; - rdfs:domain :Brand ; - rdfs:range :Retail . - -:rewardsVia a owl:ObjectProperty ; - rdfs:label "rewards via" ; - rdfs:domain :Brand ; - rdfs:range :Retail . - -# ============================================================================= -# Object Properties - Agent <-> Consumer -# ============================================================================= - -:recommendsTo a owl:ObjectProperty ; - rdfs:label "recommends to" ; - rdfs:domain :Agent ; - rdfs:range :Consumer . - -:personalizesFor a owl:ObjectProperty ; - rdfs:label "personalizes for" ; - rdfs:domain :Agent ; - rdfs:range :Consumer . - -:monitorsSentimentOf a owl:ObjectProperty ; - rdfs:label "monitors sentiment of" ; - rdfs:domain :Agent ; - rdfs:range :Consumer . - -:optimizesJourneyFor a owl:ObjectProperty ; - rdfs:label "optimizes journey for" ; - rdfs:domain :Agent ; - rdfs:range :Consumer . - -# ============================================================================= -# Object Properties - Agent <-> Brand -# ============================================================================= - -:orchestratesCampaignFor a owl:ObjectProperty ; - rdfs:label "orchestrates campaign for" ; - rdfs:domain :Agent ; - rdfs:range :Brand . - -:analyzesPerceptionOf a owl:ObjectProperty ; - rdfs:label "analyzes perception of" ; - rdfs:domain :Agent ; - rdfs:range :Brand . - -:curatesProductsFor a owl:ObjectProperty ; - rdfs:label "curates products for" ; - rdfs:domain :Agent ; - rdfs:range :Brand . - -# ============================================================================= -# Object Properties - Agent <-> Retail -# ============================================================================= - -:tailorsExperienceAt a owl:ObjectProperty ; - rdfs:label "tailors experience at" ; - rdfs:domain :Agent ; - rdfs:range :Retail . - -:deploysCampaignAt a owl:ObjectProperty ; - rdfs:label "deploys campaign at" ; - rdfs:domain :Agent ; - rdfs:range :Retail . - -:optimizesFlowAt a owl:ObjectProperty ; - rdfs:label "optimizes flow at" ; - rdfs:domain :Agent ; - rdfs:range :Retail . diff --git a/ai-context/trustgraph-templates/src/App.tsx b/ai-context/trustgraph-templates/src/App.tsx deleted file mode 100644 index 891f8fb1..00000000 --- a/ai-context/trustgraph-templates/src/App.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { useState, useEffect } from "react"; -import type { TabKey, DomainKey, Entity } from "./types"; -import { Header, StatusBar, Toaster } from "./components"; -import { GraphView, QueryView, ExplainView, DataView, OntologyView } from "./pages"; -import { useGraphData, toast } from "./state"; - -export default function App() { - const [activeTab, setActiveTab] = useState("graph"); - const [activeFilter, setActiveFilter] = useState(null); - const [selectedNode, setSelectedNode] = useState(null); - const { entities, isLoading } = useGraphData(); - - // Notification when graph loads - useEffect(() => { - if (!isLoading && entities.length > 0) { - toast.success(`Graph loaded: ${entities.length} entities`); - } - }, [isLoading, entities.length]); - - const handleTabChange = (tab: TabKey) => { - setActiveTab(tab); - if (tab !== "graph") { - setSelectedNode(null); - } - }; - - return ( -
-
- - {activeTab === "graph" && ( - - )} - - {activeTab === "query" && } - - {activeTab === "explain" && } - - {activeTab === "data" && } - - {activeTab === "ontology" && } - - - -
- ); -} diff --git a/ai-context/trustgraph-templates/src/assets/react.svg b/ai-context/trustgraph-templates/src/assets/react.svg deleted file mode 100644 index 6c87de9b..00000000 --- a/ai-context/trustgraph-templates/src/assets/react.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ai-context/trustgraph-templates/src/components/common/Badge.tsx b/ai-context/trustgraph-templates/src/components/common/Badge.tsx deleted file mode 100644 index d5a0836e..00000000 --- a/ai-context/trustgraph-templates/src/components/common/Badge.tsx +++ /dev/null @@ -1,39 +0,0 @@ -interface BadgeProps { - children: React.ReactNode; - color: string; - size?: "small" | "medium"; - selected?: boolean; - onClick?: () => void; -} - -export function Badge({ - children, - color, - size = "medium", - selected = false, - onClick, -}: BadgeProps) { - const isSmall = size === "small"; - - return ( - - ); -} diff --git a/ai-context/trustgraph-templates/src/components/common/Card.tsx b/ai-context/trustgraph-templates/src/components/common/Card.tsx deleted file mode 100644 index 89b17156..00000000 --- a/ai-context/trustgraph-templates/src/components/common/Card.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { surface, border } from "../../theme"; - -interface CardProps { - children: React.ReactNode; - padding?: number | string; - borderRadius?: number; - borderColor?: string; - onClick?: () => void; - style?: React.CSSProperties; -} - -export function Card({ - children, - padding = 24, - borderRadius = 12, - borderColor = border.subtle, - onClick, - style, -}: CardProps) { - return ( -
- {children} -
- ); -} diff --git a/ai-context/trustgraph-templates/src/components/common/FilterBar.tsx b/ai-context/trustgraph-templates/src/components/common/FilterBar.tsx deleted file mode 100644 index 01a7f63c..00000000 --- a/ai-context/trustgraph-templates/src/components/common/FilterBar.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import { FilterButton } from "./FilterButton"; -import { text, border } from "../../theme"; - -export interface FilterItem { - key: string; - label: string; - icon?: string; - color?: string; -} - -interface FilterBarProps { - items: FilterItem[]; - selectedKey: string | null; - onSelect: (key: string | null) => void; - stats?: string; - showAll?: boolean; - allLabel?: string; - emptyMessage?: string; - maxItems?: number; -} - -export function FilterBar({ - items, - selectedKey, - onSelect, - stats, - showAll = true, - allLabel = "All", - emptyMessage, - maxItems = 10, -}: FilterBarProps) { - const displayItems = items.slice(0, maxItems); - - return ( -
- - FILTER: - - - {emptyMessage && items.length === 0 ? ( - {emptyMessage} - ) : ( - <> - {showAll && ( - onSelect(null)} - /> - )} - {displayItems.map((item) => ( - onSelect(selectedKey === item.key ? null : item.key)} - /> - ))} - - )} - - {stats && ( -
- {stats} -
- )} -
- ); -} diff --git a/ai-context/trustgraph-templates/src/components/common/FilterButton.tsx b/ai-context/trustgraph-templates/src/components/common/FilterButton.tsx deleted file mode 100644 index 7b0ea0a9..00000000 --- a/ai-context/trustgraph-templates/src/components/common/FilterButton.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { text, border } from "../../theme"; - -interface FilterButtonProps { - label: string; - icon?: string; - color?: string; - isActive: boolean; - onClick: () => void; -} - -export function FilterButton({ label, icon, color, isActive, onClick }: FilterButtonProps) { - const activeColor = color || "#fff"; - - return ( - - ); -} diff --git a/ai-context/trustgraph-templates/src/components/common/Header.tsx b/ai-context/trustgraph-templates/src/components/common/Header.tsx deleted file mode 100644 index bcb968a9..00000000 --- a/ai-context/trustgraph-templates/src/components/common/Header.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import type { TabKey } from "../../types"; - -interface HeaderProps { - activeTab: TabKey; - onTabChange: (tab: TabKey) => void; -} - -export function Header({ activeTab, onTabChange }: HeaderProps) { - return ( -
-
- TrustGraph -
-
- TrustGraph -
-
- CONTEXT GRAPH DEMO -
-
-
-
- {(["graph", "query", "explain", "data", "ontology"] as const).map((tab) => { - const labels: Record = { - graph: "◈ Context Graph", - query: "⚡ Agent Query", - explain: "◉ Explain", - data: "▤ Table Explorer", - ontology: "◇ Ontology", - }; - return ( - - ); - })} -
-
- ); -} diff --git a/ai-context/trustgraph-templates/src/components/common/LoadingState.tsx b/ai-context/trustgraph-templates/src/components/common/LoadingState.tsx deleted file mode 100644 index 2f129d3d..00000000 --- a/ai-context/trustgraph-templates/src/components/common/LoadingState.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { semantic, text } from "../../theme"; - -interface LoadingStateProps { - message?: string; - variant?: "loading" | "error"; -} - -export function LoadingState({ message, variant = "loading" }: LoadingStateProps) { - const isError = variant === "error"; - const defaultMessage = isError ? "Error loading data" : "Loading..."; - - return ( -
- {message || defaultMessage} -
- ); -} diff --git a/ai-context/trustgraph-templates/src/components/common/MessageBubble.tsx b/ai-context/trustgraph-templates/src/components/common/MessageBubble.tsx deleted file mode 100644 index bccb3c13..00000000 --- a/ai-context/trustgraph-templates/src/components/common/MessageBubble.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import { semantic, text, surface, border, withGlow } from "../../theme"; - -export interface Message { - role: string; - text: string; - type?: string; -} - -interface MessageBubbleProps { - message: Message; -} - -export function MessageBubble({ message }: MessageBubbleProps) { - const isUser = message.role === "human"; - const messageType = message.type; - - const getTypeStyles = () => { - switch (messageType) { - case "thinking": - return { - bg: withGlow(semantic.thinking, 0.08), - border: withGlow(semantic.thinking, 0.2), - icon: "◈", - label: "THINKING", - color: semantic.thinking, - }; - case "observation": - return { - bg: withGlow(semantic.observation, 0.08), - border: withGlow(semantic.observation, 0.2), - icon: "◉", - label: "OBSERVATION", - color: semantic.observation, - }; - case "answer": - return { - bg: withGlow(semantic.answer, 0.08), - border: withGlow(semantic.answer, 0.2), - icon: "✓", - label: "ANSWER", - color: semantic.answer, - }; - default: - return null; - } - }; - - const typeStyles = getTypeStyles(); - - if (isUser) { - return ( -
-
- YOU -
-
- {message.text} -
-
- ); - } - - return ( -
- {typeStyles && ( -
- {typeStyles.icon} - {typeStyles.label} -
- )} -
- {message.text} -
-
- ); -} diff --git a/ai-context/trustgraph-templates/src/components/common/SearchInput.tsx b/ai-context/trustgraph-templates/src/components/common/SearchInput.tsx deleted file mode 100644 index d822910e..00000000 --- a/ai-context/trustgraph-templates/src/components/common/SearchInput.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { text, surface, border, palette } from "../../theme"; - -interface SearchInputProps { - value: string; - onChange: (value: string) => void; - onSubmit: () => void; - placeholder?: string; - buttonText?: string; - isLoading?: boolean; - buttonColor?: string; - disabled?: boolean; -} - -export function SearchInput({ - value, - onChange, - onSubmit, - placeholder = "Search...", - buttonText = "Search", - isLoading = false, - buttonColor = palette.blue, - disabled = false, -}: SearchInputProps) { - const handleKeyDown = (e: React.KeyboardEvent) => { - if (e.key === "Enter" && !e.shiftKey) { - e.preventDefault(); - onSubmit(); - } - }; - - const isDisabled = disabled || isLoading || !value.trim(); - - return ( -
- onChange(e.target.value)} - onKeyDown={handleKeyDown} - placeholder={placeholder} - disabled={isLoading} - style={{ - flex: 1, - padding: "12px 16px", - borderRadius: 8, - border: `1px solid ${border.medium}`, - background: surface.card, - color: text.primary, - fontSize: 14, - fontFamily: "'IBM Plex Sans', sans-serif", - outline: "none", - }} - /> - -
- ); -} diff --git a/ai-context/trustgraph-templates/src/components/common/SectionLabel.tsx b/ai-context/trustgraph-templates/src/components/common/SectionLabel.tsx deleted file mode 100644 index 56deb1b9..00000000 --- a/ai-context/trustgraph-templates/src/components/common/SectionLabel.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { text } from "../../theme"; - -interface SectionLabelProps { - children: React.ReactNode; - marginBottom?: number; - marginTop?: number; -} - -export function SectionLabel({ children, marginBottom = 10, marginTop }: SectionLabelProps) { - return ( -
- {children} -
- ); -} diff --git a/ai-context/trustgraph-templates/src/components/common/StatusBar.tsx b/ai-context/trustgraph-templates/src/components/common/StatusBar.tsx deleted file mode 100644 index c3f23e09..00000000 --- a/ai-context/trustgraph-templates/src/components/common/StatusBar.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { useConnectionState } from "@trustgraph/react-provider"; -import { useProgressStateStore } from "@trustgraph/react-state"; -import { semantic, palette, text, border } from "../../theme"; - -export function StatusBar() { - const connectionState = useConnectionState(); - const activity = useProgressStateStore((state) => state.activity); - - const getStatusDisplay = () => { - if (!connectionState) return { color: text.subtle, text: "Initializing..." }; - switch (connectionState.status) { - case "authenticated": - return { color: semantic.success, text: "Authenticated" }; - case "connected": - return { color: semantic.success, text: "Connected" }; - case "unauthenticated": - return { color: semantic.info, text: "Connected" }; - case "connecting": - return { color: palette.amber, text: "Connecting..." }; - case "reconnecting": - return { color: semantic.warning, text: `Reconnecting (${connectionState.reconnectAttempt}/${connectionState.maxAttempts})...` }; - case "failed": - return { color: semantic.error, text: "Connection failed" }; - default: - return { color: text.subtle, text: connectionState.status }; - } - }; - - const status = getStatusDisplay(); - const activeActivity = activity.size > 0 ? Array.from(activity)[0] : null; - - return ( -
-
- {activeActivity ? ( - <> - - {activeActivity}... - - ) : ( - <> - - Ready - - )} -
-
- {status.text} - | - trustgraph.ai -
-
- ); -} diff --git a/ai-context/trustgraph-templates/src/components/common/Toaster.tsx b/ai-context/trustgraph-templates/src/components/common/Toaster.tsx deleted file mode 100644 index 45c87d2f..00000000 --- a/ai-context/trustgraph-templates/src/components/common/Toaster.tsx +++ /dev/null @@ -1,120 +0,0 @@ -import { useToastStore, Toast, ToastType } from "../../state/toastStore"; -import { semantic, surface, text } from "../../theme"; - -const typeStyles: Record = { - success: { color: semantic.success, icon: "✓" }, - error: { color: semantic.error, icon: "✕" }, - warning: { color: semantic.warning, icon: "!" }, - info: { color: semantic.info, icon: "i" }, -}; - -function ToastItem({ toast, onDismiss }: { toast: Toast; onDismiss: () => void }) { - const style = typeStyles[toast.type]; - - return ( -
- - {style.icon} - - - {toast.message} - - -
- ); -} - -export function Toaster() { - const toasts = useToastStore((state) => state.toasts); - const removeToast = useToastStore((state) => state.removeToast); - - if (toasts.length === 0) return null; - - return ( - <> - -
- {toasts.map((toast) => ( - removeToast(toast.id)} - /> - ))} -
- - ); -} diff --git a/ai-context/trustgraph-templates/src/components/common/Typewriter.tsx b/ai-context/trustgraph-templates/src/components/common/Typewriter.tsx deleted file mode 100644 index 8503deec..00000000 --- a/ai-context/trustgraph-templates/src/components/common/Typewriter.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { useState, useEffect, useRef } from "react"; - -interface TypewriterProps { - text: string; - speed?: number; - onDone?: () => void; -} - -export function Typewriter({ text, speed = 12, onDone }: TypewriterProps) { - const [displayed, setDisplayed] = useState(""); - const idx = useRef(0); - - useEffect(() => { - idx.current = 0; - setDisplayed(""); - const interval = setInterval(() => { - idx.current++; - if (idx.current >= text.length) { - setDisplayed(text); - clearInterval(interval); - onDone?.(); - } else { - setDisplayed(text.slice(0, idx.current)); - } - }, speed); - return () => clearInterval(interval); - }, [text, speed, onDone]); - - return ( - - {displayed} - - ▌ - - - ); -} diff --git a/ai-context/trustgraph-templates/src/components/common/index.ts b/ai-context/trustgraph-templates/src/components/common/index.ts deleted file mode 100644 index 540ae1f9..00000000 --- a/ai-context/trustgraph-templates/src/components/common/index.ts +++ /dev/null @@ -1,14 +0,0 @@ -export { SectionLabel } from "./SectionLabel"; -export { FilterButton } from "./FilterButton"; -export { Header } from "./Header"; -export { StatusBar } from "./StatusBar"; -export { Typewriter } from "./Typewriter"; -export { Card } from "./Card"; -export { Badge } from "./Badge"; -export { LoadingState } from "./LoadingState"; -export { Toaster } from "./Toaster"; -export { SearchInput } from "./SearchInput"; -export { FilterBar } from "./FilterBar"; -export type { FilterItem } from "./FilterBar"; -export { MessageBubble } from "./MessageBubble"; -export type { Message } from "./MessageBubble"; diff --git a/ai-context/trustgraph-templates/src/components/graph/ExplainGraph.tsx b/ai-context/trustgraph-templates/src/components/graph/ExplainGraph.tsx deleted file mode 100644 index 8d24510c..00000000 --- a/ai-context/trustgraph-templates/src/components/graph/ExplainGraph.tsx +++ /dev/null @@ -1,451 +0,0 @@ -import { useEffect, useRef, useState, useCallback, useMemo } from "react"; -import { ZoomControls } from "./ZoomControls"; -import { border, palette, text, withGlow } from "../../theme"; - -// ── Types ─────────────────────────────────────────────────────────── - -export interface ExplainGraphNode { - id: string; - label: string; - color?: string; -} - -export interface ExplainGraphEdge { - id: string; - from: string; - to: string; - label: string; - reasoning?: string; -} - -interface LayoutNode extends ExplainGraphNode { - x: number; - y: number; - vx: number; - vy: number; -} - -interface ExplainGraphProps { - nodes: ExplainGraphNode[]; - edges: ExplainGraphEdge[]; - highlightedNodeIds: string[]; - highlightedEdgeIds: string[]; - onNodeClick?: (nodeId: string) => void; - onEdgeClick?: (edgeId: string) => void; -} - -// ── Simple force layout ───────────────────────────────────────────── - -function computeLayout( - nodes: ExplainGraphNode[], - edges: ExplainGraphEdge[], - width: number, - height: number, -): LayoutNode[] { - if (nodes.length === 0 || width === 0) return []; - - const cx = width / 2; - const cy = height / 2; - - // Initial positions: circle layout - const layoutNodes: LayoutNode[] = nodes.map((n, i) => { - const angle = (Math.PI * 2 * i) / nodes.length - Math.PI / 2; - const radius = Math.min(cx, cy) * 0.55; - return { - ...n, - x: cx + Math.cos(angle) * radius, - y: cy + Math.sin(angle) * radius, - vx: 0, - vy: 0, - }; - }); - - // Run simple force simulation - const iterations = 120; - const repulsion = 2000; - const attraction = 0.005; - const damping = 0.85; - const centerPull = 0.01; - - const nodeMap = new Map(layoutNodes.map((n, i) => [n.id, i])); - - for (let iter = 0; iter < iterations; iter++) { - // Repulsion between all pairs - for (let i = 0; i < layoutNodes.length; i++) { - for (let j = i + 1; j < layoutNodes.length; j++) { - const a = layoutNodes[i]; - const b = layoutNodes[j]; - let dx = a.x - b.x; - let dy = a.y - b.y; - const dist = Math.sqrt(dx * dx + dy * dy) || 1; - const force = repulsion / (dist * dist); - dx = (dx / dist) * force; - dy = (dy / dist) * force; - a.vx += dx; - a.vy += dy; - b.vx -= dx; - b.vy -= dy; - } - } - - // Attraction along edges - for (const edge of edges) { - const ai = nodeMap.get(edge.from); - const bi = nodeMap.get(edge.to); - if (ai === undefined || bi === undefined) continue; - const a = layoutNodes[ai]; - const b = layoutNodes[bi]; - const dx = b.x - a.x; - const dy = b.y - a.y; - const fx = dx * attraction; - const fy = dy * attraction; - a.vx += fx; - a.vy += fy; - b.vx -= fx; - b.vy -= fy; - } - - // Center pull - for (const n of layoutNodes) { - n.vx += (cx - n.x) * centerPull; - n.vy += (cy - n.y) * centerPull; - } - - // Apply velocity - for (const n of layoutNodes) { - n.vx *= damping; - n.vy *= damping; - n.x += n.vx; - n.y += n.vy; - - // Keep in bounds with padding - const pad = 40; - n.x = Math.max(pad, Math.min(width - pad, n.x)); - n.y = Math.max(pad, Math.min(height - pad, n.y)); - } - } - - return layoutNodes; -} - -// ── Component ─────────────────────────────────────────────────────── - -export function ExplainGraph({ - nodes, - edges, - highlightedNodeIds, - highlightedEdgeIds, - onNodeClick, - onEdgeClick, -}: ExplainGraphProps) { - const containerRef = useRef(null); - const svgRef = useRef(null); - const [containerSize, setContainerSize] = useState({ width: 0, height: 0 }); - const [hovered, setHovered] = useState(null); - - // Zoom and pan - const [zoom, setZoom] = useState(1); - const [pan, setPan] = useState({ x: 0, y: 0 }); - const isPanningRef = useRef(false); - const lastPanPosRef = useRef({ x: 0, y: 0 }); - - // Track container size - useEffect(() => { - const container = containerRef.current; - if (!container) return; - const ro = new ResizeObserver((entries) => { - const entry = entries[0]; - if (entry) { - setContainerSize({ width: entry.contentRect.width, height: entry.contentRect.height }); - } - }); - ro.observe(container); - return () => ro.disconnect(); - }, []); - - // Layout - const layoutNodes = useMemo( - () => computeLayout(nodes, edges, containerSize.width, containerSize.height), - [nodes, edges, containerSize], - ); - - const nodeMap = useMemo( - () => new Map(layoutNodes.map(n => [n.id, n])), - [layoutNodes], - ); - - // Grid lines - const gridLines = useMemo(() => { - const lines: React.ReactElement[] = []; - const { width, height } = containerSize; - if (width === 0) return lines; - for (let x = 0; x < width; x += 30) { - lines.push(); - } - for (let y = 0; y < height; y += 30) { - lines.push(); - } - return lines; - }, [containerSize]); - - // Zoom - const handleWheel = useCallback((e: React.WheelEvent) => { - e.preventDefault(); - const delta = e.deltaY > 0 ? 0.9 : 1.1; - const newZoom = Math.min(4, Math.max(0.25, zoom * delta)); - const svg = svgRef.current; - if (!svg) return; - const rect = svg.getBoundingClientRect(); - const cursorX = e.clientX - rect.left; - const cursorY = e.clientY - rect.top; - const zoomRatio = newZoom / zoom; - setPan(p => ({ x: cursorX - (cursorX - p.x) * zoomRatio, y: cursorY - (cursorY - p.y) * zoomRatio })); - setZoom(newZoom); - }, [zoom]); - - // Pan - const handleMouseDown = useCallback((e: React.MouseEvent) => { - if (e.button === 1 || (e.button === 0 && e.shiftKey)) { - e.preventDefault(); - isPanningRef.current = true; - lastPanPosRef.current = { x: e.clientX, y: e.clientY }; - } - }, []); - - const handleMouseMove = useCallback((e: React.MouseEvent) => { - if (!isPanningRef.current) return; - const dx = e.clientX - lastPanPosRef.current.x; - const dy = e.clientY - lastPanPosRef.current.y; - lastPanPosRef.current = { x: e.clientX, y: e.clientY }; - setPan(p => ({ x: p.x + dx, y: p.y + dy })); - }, []); - - const handleMouseUp = useCallback(() => { isPanningRef.current = false; }, []); - - const handleResetView = useCallback(() => { setZoom(1); setPan({ x: 0, y: 0 }); }, []); - - const hasHighlights = highlightedNodeIds.length > 0 || highlightedEdgeIds.length > 0; - const NODE_R = 10; - - if (containerSize.width === 0) { - return
; - } - - return ( -
- - {gridLines} - - - {/* Edges */} - {edges.map((edge) => { - const from = nodeMap.get(edge.from); - const to = nodeMap.get(edge.to); - if (!from || !to) return null; - - const isHighlighted = highlightedEdgeIds.includes(edge.id); - const isDimmed = hasHighlights && !isHighlighted; - const isEdgeHovered = hovered === `edge-${edge.id}`; - - const mx = (from.x + to.x) / 2 + (from.y - to.y) * 0.15; - const my = (from.y + to.y) / 2 + (to.x - from.x) * 0.15; - const path = `M ${from.x} ${from.y} Q ${mx} ${my} ${to.x} ${to.y}`; - - const alpha = isDimmed ? 0.15 : isHighlighted || isEdgeHovered ? 0.8 : 0.35; - const edgeColor = palette.cyan; - - return ( - onEdgeClick?.(edge.id)} - onMouseEnter={() => setHovered(`edge-${edge.id}`)} - onMouseLeave={() => setHovered(null)} - > - {/* Wider invisible hit area */} - - - {/* Edge label */} - - {edge.label} - - {/* Arrowhead */} - {(() => { - // Point on curve near the end (t=0.85) - const t = 0.85; - const ax = (1 - t) * (1 - t) * from.x + 2 * (1 - t) * t * mx + t * t * to.x; - const ay = (1 - t) * (1 - t) * from.y + 2 * (1 - t) * t * my + t * t * to.y; - const dx = to.x - ax; - const dy = to.y - ay; - const len = Math.sqrt(dx * dx + dy * dy) || 1; - const ux = dx / len; - const uy = dy / len; - // Arrow tip at edge of node circle - const tipX = to.x - ux * NODE_R; - const tipY = to.y - uy * NODE_R; - const arrowSize = 5; - const p1x = tipX - ux * arrowSize + uy * arrowSize * 0.4; - const p1y = tipY - uy * arrowSize - ux * arrowSize * 0.4; - const p2x = tipX - ux * arrowSize - uy * arrowSize * 0.4; - const p2y = tipY - uy * arrowSize + ux * arrowSize * 0.4; - return ( - - ); - })()} - - ); - })} - - {/* Nodes */} - {layoutNodes.map((node) => { - const isHighlighted = highlightedNodeIds.includes(node.id); - const isHovered = hovered === node.id; - const isDimmed = hasHighlights && !isHighlighted; - const nodeColor = node.color || palette.blue; - const alpha = isDimmed ? 0.25 : 1; - const r = isHighlighted || isHovered ? NODE_R * 1.3 : NODE_R; - - return ( - onNodeClick?.(node.id)} - onMouseEnter={() => setHovered(node.id)} - onMouseLeave={() => setHovered(null)} - > - {/* Glow */} - {(isHighlighted || isHovered) && ( - - )} - {/* Circle */} - - {/* Label */} - - {node.label} - - - ); - })} - - - - setZoom(z => Math.min(4, z * 1.2))} - onZoomOut={() => setZoom(z => Math.max(0.25, z / 1.2))} - onReset={handleResetView} - /> - - {/* Empty state */} - {nodes.length === 0 && ( -
- Graph will populate as explain events arrive -
- )} - - {/* Tooltip */} - {hovered && !hovered.startsWith("edge-") && (() => { - const node = nodeMap.get(hovered); - if (!node) return null; - return ( -
-
- {node.label} -
-
- ); - })()} - - {/* Edge tooltip */} - {hovered?.startsWith("edge-") && (() => { - const edgeId = hovered.slice(5); - const edge = edges.find(e => e.id === edgeId); - if (!edge) return null; - const from = nodeMap.get(edge.from); - const to = nodeMap.get(edge.to); - if (!from || !to) return null; - const mx = ((from.x + to.x) / 2) * zoom + pan.x; - const my = ((from.y + to.y) / 2) * zoom + pan.y; - return ( -
-
- {edge.label} -
- {edge.reasoning && ( -
- {edge.reasoning.length > 150 ? edge.reasoning.slice(0, 150) + "..." : edge.reasoning} -
- )} -
- ); - })()} -
- ); -} diff --git a/ai-context/trustgraph-templates/src/components/graph/GraphCanvas.tsx b/ai-context/trustgraph-templates/src/components/graph/GraphCanvas.tsx deleted file mode 100644 index b75f36fc..00000000 --- a/ai-context/trustgraph-templates/src/components/graph/GraphCanvas.tsx +++ /dev/null @@ -1,596 +0,0 @@ -import { useEffect, useRef, useCallback, useState, MouseEvent } from "react"; -import type { DomainKey, Entity, GraphNode, OntologyType, Relationship } from "../../types"; -import { ZoomControls } from "./ZoomControls"; -import { border } from "../../theme"; - -interface GraphCanvasProps { - entities: Entity[]; - relationships: Relationship[]; - ontology: OntologyType; - highlightedEntities: string[]; - onNodeClick: (node: GraphNode) => void; - activeFilter: DomainKey | null; -} - -const SETTLE_TIME = 10000; // 10 seconds until nodes settle -const FRAME_INTERVAL = 1000 / 30; // 30fps - -export function GraphCanvas({ entities, relationships, ontology, highlightedEntities, onNodeClick, activeFilter }: GraphCanvasProps) { - const containerRef = useRef(null); - const staticCanvasRef = useRef(null); - const nodesCanvasRef = useRef(null); - const edgesCanvasRef = useRef(null); - const nodesRef = useRef([]); - const animRef = useRef(0); - const hoveredRef = useRef(null); - const settledRef = useRef(false); - const startTimeRef = useRef(0); - const timeRef = useRef(0); - const lastFrameTimeRef = useRef(0); - - // Store view state in refs to avoid triggering resets - const highlightedRef = useRef(highlightedEntities); - const activeFilterRef = useRef(activeFilter); - const relationshipsRef = useRef(relationships); - const ontologyRef = useRef(ontology); - - const [hovered, setHovered] = useState(null); - const [containerSize, setContainerSize] = useState({ width: 0, height: 0 }); - - // Zoom and pan state - const [zoom, setZoom] = useState(1); - const [pan, setPan] = useState({ x: 0, y: 0 }); - const zoomRef = useRef(1); - const panRef = useRef({ x: 0, y: 0 }); - const isPanningRef = useRef(false); - const lastPanPosRef = useRef({ x: 0, y: 0 }); - - // Keep zoom/pan refs in sync - zoomRef.current = zoom; - panRef.current = pan; - - // Keep refs in sync with props - useEffect(() => { - highlightedRef.current = highlightedEntities; - }, [highlightedEntities]); - - useEffect(() => { - activeFilterRef.current = activeFilter; - }, [activeFilter]); - - useEffect(() => { - relationshipsRef.current = relationships; - }, [relationships]); - - useEffect(() => { - ontologyRef.current = ontology; - }, [ontology]); - - // Track container size changes - useEffect(() => { - const container = containerRef.current; - if (!container) return; - - const resizeObserver = new ResizeObserver((entries) => { - const entry = entries[0]; - if (entry) { - setContainerSize({ - width: entry.contentRect.width, - height: entry.contentRect.height, - }); - } - }); - - resizeObserver.observe(container); - return () => resizeObserver.disconnect(); - }, []); - - // Draw static layer (grid + domain labels) - const drawStaticLayer = useCallback((ctx: CanvasRenderingContext2D, canvas: HTMLCanvasElement, domainPositions: Record) => { - ctx.clearRect(0, 0, canvas.width, canvas.height); - - // Grid stays fixed (no transform) - ctx.strokeStyle = border.grid; - ctx.lineWidth = 1; - for (let x = 0; x < canvas.width; x += 60) { - ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, canvas.height); ctx.stroke(); - } - for (let y = 0; y < canvas.height; y += 60) { - ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(canvas.width, y); ctx.stroke(); - } - - // Domain labels with zoom/pan transform - ctx.save(); - ctx.translate(panRef.current.x, panRef.current.y); - ctx.scale(zoomRef.current, zoomRef.current); - - const currentOntology = ontologyRef.current; - (Object.entries(domainPositions) as [DomainKey, { x: number; y: number }][]).forEach(([domain, pos]) => { - const data = currentOntology[domain]; - ctx.font = "bold 22px 'IBM Plex Mono', monospace"; - ctx.fillStyle = data.color + "44"; - ctx.textAlign = "center"; - ctx.fillText(data.label.toUpperCase(), pos.x, pos.y - Math.min(canvas.width, canvas.height) * 0.14); - }); - - ctx.restore(); - }, []); - - // Draw nodes layer - reads from refs - const drawNodesLayer = useCallback((ctx: CanvasRenderingContext2D, canvas: HTMLCanvasElement, time: number) => { - ctx.clearRect(0, 0, canvas.width, canvas.height); - - // Apply zoom/pan transform - ctx.save(); - ctx.translate(panRef.current.x, panRef.current.y); - ctx.scale(zoomRef.current, zoomRef.current); - - const nodes = nodesRef.current; - const settled = settledRef.current; - const highlighted = highlightedRef.current; - const filter = activeFilterRef.current; - const rels = relationshipsRef.current; - - nodes.forEach((node) => { - const isHighlighted = highlighted && highlighted.includes(node.id); - const isHovered = hoveredRef.current === node.id; - const isDimmed = highlighted && highlighted.length > 0 && !isHighlighted; - const isFiltered = filter && node.domain !== filter && !rels.some( - r => r.domain.includes(filter) && (r.from === node.id || r.to === node.id) - ); - - const alpha = isFiltered ? 0.15 : isDimmed ? 0.3 : 1; - const r = isHighlighted || isHovered ? node.r * 1.4 : node.r; - const pulseR = isHighlighted && !settled ? Math.sin(time * 3) * 3 : 0; - - // Glow - if ((isHighlighted || isHovered) && !isFiltered) { - ctx.beginPath(); - ctx.arc(node.x, node.y, r + 12 + pulseR, 0, Math.PI * 2); - const grd = ctx.createRadialGradient(node.x, node.y, r, node.x, node.y, r + 12 + pulseR); - grd.addColorStop(0, node.glow); - grd.addColorStop(1, "rgba(0,0,0,0)"); - ctx.fillStyle = grd; - ctx.fill(); - } - - // Node circle - ctx.beginPath(); - ctx.arc(node.x, node.y, r, 0, Math.PI * 2); - ctx.fillStyle = node.color + Math.round(alpha * 255 * 0.2).toString(16).padStart(2, "0"); - ctx.fill(); - ctx.strokeStyle = node.color + Math.round(alpha * 255).toString(16).padStart(2, "0"); - ctx.lineWidth = isHighlighted ? 2.5 : 1.5; - ctx.stroke(); - - // Label - ctx.font = `${isHighlighted ? "bold " : ""}${isHovered ? 17 : 14}px 'IBM Plex Sans', sans-serif`; - ctx.fillStyle = `rgba(255,255,255,${alpha * (isHighlighted ? 1 : 0.75)})`; - ctx.textAlign = "center"; - ctx.fillText(node.label, node.x, node.y + r + 18); - - // Update node positions (spring physics + drift) - only if not settled - if (!settled) { - node.x += (node.targetX - node.x) * 0.02; - node.y += (node.targetY - node.y) * 0.02; - node.x += Math.sin(time + node.targetX * 0.01) * 0.3; - node.y += Math.cos(time + node.targetY * 0.01) * 0.3; - } - }); - - ctx.restore(); - }, []); - - // Draw edges layer - reads from refs - const drawEdgesLayer = useCallback((ctx: CanvasRenderingContext2D, canvas: HTMLCanvasElement, time: number) => { - ctx.clearRect(0, 0, canvas.width, canvas.height); - - // Apply zoom/pan transform - ctx.save(); - ctx.translate(panRef.current.x, panRef.current.y); - ctx.scale(zoomRef.current, zoomRef.current); - - const nodes = nodesRef.current; - const highlighted = highlightedRef.current; - const filter = activeFilterRef.current; - const rels = relationshipsRef.current; - - const filteredRels = filter - ? rels.filter((r) => r.domain.includes(filter)) - : rels; - - filteredRels.forEach((rel) => { - const fromNode = nodes.find((n) => n.id === rel.from); - const toNode = nodes.find((n) => n.id === rel.to); - if (!fromNode || !toNode) return; - - const isHighlighted = - highlighted && - highlighted.includes(rel.from) && - highlighted.includes(rel.to); - - const baseAlpha = isHighlighted ? 0.7 : 0.12; - const pulse = isHighlighted ? Math.sin(time * 4) * 0.15 + 0.15 : 0; - - ctx.beginPath(); - ctx.moveTo(fromNode.x, fromNode.y); - // Curved edges - const mx = (fromNode.x + toNode.x) / 2 + (fromNode.y - toNode.y) * 0.1; - const my = (fromNode.y + toNode.y) / 2 + (toNode.x - fromNode.x) * 0.1; - ctx.quadraticCurveTo(mx, my, toNode.x, toNode.y); - - const gradient = ctx.createLinearGradient(fromNode.x, fromNode.y, toNode.x, toNode.y); - gradient.addColorStop(0, fromNode.color + Math.round((baseAlpha + pulse) * 255).toString(16).padStart(2, "0")); - gradient.addColorStop(1, toNode.color + Math.round((baseAlpha + pulse) * 255).toString(16).padStart(2, "0")); - ctx.strokeStyle = gradient; - ctx.lineWidth = isHighlighted ? 3 : 1.5; - ctx.stroke(); - - // Animated particles on highlighted edges - if (isHighlighted) { - const t = (time * 2) % 1; - const px = (1 - t) * (1 - t) * fromNode.x + 2 * (1 - t) * t * mx + t * t * toNode.x; - const py = (1 - t) * (1 - t) * fromNode.y + 2 * (1 - t) * t * my + t * t * toNode.y; - ctx.beginPath(); - ctx.arc(px, py, 3, 0, Math.PI * 2); - ctx.fillStyle = "#fff"; - ctx.fill(); - } - }); - - ctx.restore(); - }, []); - - // Animation loop function - separate from setup - const runAnimation = useCallback(() => { - const nodesCanvas = nodesCanvasRef.current; - const edgesCanvas = edgesCanvasRef.current; - const nodesCtx = nodesCanvas?.getContext("2d"); - const edgesCtx = edgesCanvas?.getContext("2d"); - - if (!nodesCtx || !nodesCanvas || !edgesCtx || !edgesCanvas) return; - - // Capture validated references for the closure - const validNodesCtx = nodesCtx; - const validNodesCanvas = nodesCanvas; - const validEdgesCtx = edgesCtx; - const validEdgesCanvas = edgesCanvas; - - function animate(currentTime: number) { - // Throttle to target fps - if (currentTime - lastFrameTimeRef.current < FRAME_INTERVAL) { - animRef.current = requestAnimationFrame(animate); - return; - } - lastFrameTimeRef.current = currentTime; - timeRef.current += 0.01; - - // Check if we should settle - if (!settledRef.current && currentTime - startTimeRef.current > SETTLE_TIME) { - settledRef.current = true; - } - - const hasHighlights = highlightedRef.current && highlightedRef.current.length > 0; - const isSettled = settledRef.current; - - // Draw edges layer - drawEdgesLayer(validEdgesCtx, validEdgesCanvas, timeRef.current); - - // Draw nodes layer - if (!isSettled || hasHighlights || hoveredRef.current) { - drawNodesLayer(validNodesCtx, validNodesCanvas, timeRef.current); - } - - // Continue animation if not settled, or if there are highlights - if (!isSettled || hasHighlights) { - animRef.current = requestAnimationFrame(animate); - } else { - // Settled with no highlights - do one final draw and stop - drawNodesLayer(validNodesCtx, validNodesCanvas, timeRef.current); - drawEdgesLayer(validEdgesCtx, validEdgesCanvas, timeRef.current); - animRef.current = 0; - } - } - - animRef.current = requestAnimationFrame(animate); - }, [drawNodesLayer, drawEdgesLayer]); - - // Main setup - only runs when data or size changes - useEffect(() => { - const staticCanvas = staticCanvasRef.current; - const nodesCanvas = nodesCanvasRef.current; - const edgesCanvas = edgesCanvasRef.current; - if (!staticCanvas || !nodesCanvas || !edgesCanvas || containerSize.width === 0) return; - - // Cancel any existing animation - if (animRef.current) { - cancelAnimationFrame(animRef.current); - animRef.current = 0; - } - - // Setup all canvases - [staticCanvas, nodesCanvas, edgesCanvas].forEach(canvas => { - canvas.width = containerSize.width * 2; - canvas.height = containerSize.height * 2; - canvas.style.width = containerSize.width + "px"; - canvas.style.height = containerSize.height + "px"; - }); - - const cx = staticCanvas.width / 2; - const cy = staticCanvas.height / 2; - - // Position nodes in domain clusters - const domainKeys = Object.keys(ontology); - const domainPositions: Record = {}; - domainKeys.forEach((domain, i) => { - const angle = (Math.PI * 2 * i) / domainKeys.length - Math.PI / 2; - const radius = Math.min(cx, cy) * 0.45; - domainPositions[domain] = { - x: cx + Math.cos(angle) * radius, - y: cy + Math.sin(angle) * radius, - }; - }); - - nodesRef.current = entities.map((e) => { - const dp = domainPositions[e.domain]; - const subIdx = ontology[e.domain].subclasses.findIndex((s) => s.id === e.id); - const total = ontology[e.domain].subclasses.length; - const angle = ((Math.PI * 2) / total) * subIdx - Math.PI / 2; - const radius = Math.min(staticCanvas.width, staticCanvas.height) * 0.1; - return { - ...e, - x: dp.x + Math.cos(angle) * radius, - y: dp.y + Math.sin(angle) * radius, - vx: 0, - vy: 0, - targetX: dp.x + Math.cos(angle) * radius, - targetY: dp.y + Math.sin(angle) * radius, - r: 18, - }; - }); - - const staticCtx = staticCanvas.getContext("2d"); - if (!staticCtx) return; - - // Draw static layer once - drawStaticLayer(staticCtx, staticCanvas, domainPositions); - - // Reset animation state - settledRef.current = false; - startTimeRef.current = performance.now(); - timeRef.current = 0; - lastFrameTimeRef.current = 0; - - // Start animation - runAnimation(); - - return () => { - if (animRef.current) { - cancelAnimationFrame(animRef.current); - animRef.current = 0; - } - }; - }, [entities, ontology, containerSize, drawStaticLayer, runAnimation]); - - // Restart animation when highlights change (without resetting positions) - useEffect(() => { - const hasHighlights = highlightedEntities && highlightedEntities.length > 0; - - // If we have highlights and animation isn't running, restart it - if (hasHighlights && animRef.current === 0) { - runAnimation(); - } - }, [highlightedEntities, runAnimation]); - - // Redraw on filter change (without resetting) - useEffect(() => { - const nodesCanvas = nodesCanvasRef.current; - const edgesCanvas = edgesCanvasRef.current; - const nodesCtx = nodesCanvas?.getContext("2d"); - const edgesCtx = edgesCanvas?.getContext("2d"); - - if (nodesCtx && nodesCanvas && edgesCtx && edgesCanvas && settledRef.current && animRef.current === 0) { - drawNodesLayer(nodesCtx, nodesCanvas, timeRef.current); - drawEdgesLayer(edgesCtx, edgesCanvas, timeRef.current); - } - }, [activeFilter, drawNodesLayer, drawEdgesLayer]); - - const handleMouseMove = useCallback((e: MouseEvent) => { - // Handle panning first - if (isPanningRef.current) { - const dx = (e.clientX - lastPanPosRef.current.x) * 2; - const dy = (e.clientY - lastPanPosRef.current.y) * 2; - lastPanPosRef.current = { x: e.clientX, y: e.clientY }; - setPan(p => ({ x: p.x + dx, y: p.y + dy })); - return; - } - - const canvas = nodesCanvasRef.current; - if (!canvas) return; - const rect = canvas.getBoundingClientRect(); - - // Transform screen coordinates to world coordinates (accounting for zoom/pan) - const screenX = (e.clientX - rect.left) * 2; - const screenY = (e.clientY - rect.top) * 2; - const x = (screenX - panRef.current.x) / zoomRef.current; - const y = (screenY - panRef.current.y) / zoomRef.current; - - const nodes = nodesRef.current; - let found: string | null = null; - for (const node of nodes) { - const dx = node.x - x; - const dy = node.y - y; - if (Math.sqrt(dx * dx + dy * dy) < node.r * 1.5) { - found = node.id; - break; - } - } - const wasHovered = hoveredRef.current; - hoveredRef.current = found; - setHovered(found); - canvas.style.cursor = isPanningRef.current ? "grabbing" : (found ? "pointer" : "default"); - - // Redraw if hover state changed and we're settled - if (wasHovered !== found && settledRef.current) { - const nodesCanvas = nodesCanvasRef.current; - const edgesCanvas = edgesCanvasRef.current; - const nodesCtx = nodesCanvas?.getContext("2d"); - const edgesCtx = edgesCanvas?.getContext("2d"); - - if (nodesCtx && nodesCanvas && edgesCtx && edgesCanvas) { - drawNodesLayer(nodesCtx, nodesCanvas, timeRef.current); - drawEdgesLayer(edgesCtx, edgesCanvas, timeRef.current); - } - } - }, [drawNodesLayer, drawEdgesLayer]); - - const handleClick = useCallback((e: MouseEvent) => { - // Don't trigger click if we were panning - if (e.shiftKey) return; - if (hoveredRef.current && onNodeClick) { - const node = nodesRef.current.find((n) => n.id === hoveredRef.current); - if (node) onNodeClick(node); - } - }, [onNodeClick]); - - // Redraw all layers (used when zoom/pan changes) - const redrawAllLayers = useCallback(() => { - const staticCanvas = staticCanvasRef.current; - const nodesCanvas = nodesCanvasRef.current; - const edgesCanvas = edgesCanvasRef.current; - const staticCtx = staticCanvas?.getContext("2d"); - const nodesCtx = nodesCanvas?.getContext("2d"); - const edgesCtx = edgesCanvas?.getContext("2d"); - - if (!staticCtx || !staticCanvas || !nodesCtx || !nodesCanvas || !edgesCtx || !edgesCanvas) return; - - // Recalculate domain positions for static layer redraw - const cx = staticCanvas.width / 2; - const cy = staticCanvas.height / 2; - const domainKeys = Object.keys(ontologyRef.current); - const domainPositions: Record = {}; - domainKeys.forEach((domain, i) => { - const angle = (Math.PI * 2 * i) / domainKeys.length - Math.PI / 2; - const radius = Math.min(cx, cy) * 0.45; - domainPositions[domain] = { - x: cx + Math.cos(angle) * radius, - y: cy + Math.sin(angle) * radius, - }; - }); - - drawStaticLayer(staticCtx, staticCanvas, domainPositions); - drawEdgesLayer(edgesCtx, edgesCanvas, timeRef.current); - drawNodesLayer(nodesCtx, nodesCanvas, timeRef.current); - }, [drawStaticLayer, drawEdgesLayer, drawNodesLayer]); - - // Zoom handler - zoom towards cursor position - const handleWheel = useCallback((e: React.WheelEvent) => { - e.preventDefault(); - const delta = e.deltaY > 0 ? 0.9 : 1.1; - const newZoom = Math.min(4, Math.max(0.25, zoomRef.current * delta)); - - const canvas = nodesCanvasRef.current; - if (!canvas) return; - const rect = canvas.getBoundingClientRect(); - const cursorX = (e.clientX - rect.left) * 2; // Account for 2x canvas scaling - const cursorY = (e.clientY - rect.top) * 2; - - // Adjust pan to zoom towards cursor - const zoomRatio = newZoom / zoomRef.current; - const newPanX = cursorX - (cursorX - panRef.current.x) * zoomRatio; - const newPanY = cursorY - (cursorY - panRef.current.y) * zoomRatio; - - setZoom(newZoom); - setPan({ x: newPanX, y: newPanY }); - }, []); - - // Pan handlers - const handleMouseDown = useCallback((e: MouseEvent) => { - if (e.button === 1 || (e.button === 0 && e.shiftKey)) { - e.preventDefault(); - isPanningRef.current = true; - lastPanPosRef.current = { x: e.clientX, y: e.clientY }; - } - }, []); - - const handleMouseUp = useCallback(() => { - isPanningRef.current = false; - }, []); - - // Reset zoom/pan - const handleResetView = useCallback(() => { - setZoom(1); - setPan({ x: 0, y: 0 }); - }, []); - - // Redraw when zoom/pan changes - useEffect(() => { - if (containerSize.width > 0) { - redrawAllLayers(); - } - }, [zoom, pan, containerSize, redrawAllLayers]); - - const canvasStyle: React.CSSProperties = { - position: "absolute", - top: 0, - left: 0, - width: "100%", - height: "100%", - }; - - return ( -
- {/* Layer 1: Static (grid + domain labels) */} - - {/* Layer 2: Edges */} - - {/* Layer 3: Nodes (on top for interaction) */} - - - setZoom(z => Math.min(4, z * 1.2))} - onZoomOut={() => setZoom(z => Math.max(0.25, z / 1.2))} - onReset={handleResetView} - /> - - {/* Tooltip */} - {hovered && (() => { - const node = nodesRef.current.find((n) => n.id === hovered); - if (!node) return null; - // Transform node position to screen coordinates - const sx = (node.x * zoomRef.current + panRef.current.x) / 2; - const sy = (node.y * zoomRef.current + panRef.current.y) / 2; - return ( -
-
- {node.icon} {node.label} -
-
- {Object.entries(node.props || {}).map(([k, v]) => ( -
{k}: {String(v)}
- ))} -
-
- ); - })()} -
- ); -} diff --git a/ai-context/trustgraph-templates/src/components/graph/GraphCanvasSVG.tsx b/ai-context/trustgraph-templates/src/components/graph/GraphCanvasSVG.tsx deleted file mode 100644 index 39964f2e..00000000 --- a/ai-context/trustgraph-templates/src/components/graph/GraphCanvasSVG.tsx +++ /dev/null @@ -1,456 +0,0 @@ -import { useEffect, useRef, useState, useCallback, useMemo } from "react"; -import type { DomainKey, Entity, GraphNode, OntologyType, Relationship } from "../../types"; -import { ZoomControls } from "./ZoomControls"; -import { border } from "../../theme"; - -interface GraphCanvasSVGProps { - entities: Entity[]; - relationships: Relationship[]; - ontology: OntologyType; - highlightedEntities: string[]; - onNodeClick: (node: GraphNode) => void; - activeFilter: DomainKey | null; -} - -const SETTLE_TIME = 10000; // 10 seconds until nodes settle - -export function GraphCanvasSVG({ entities, relationships, ontology, highlightedEntities, onNodeClick, activeFilter }: GraphCanvasSVGProps) { - const containerRef = useRef(null); - const svgRef = useRef(null); - const [containerSize, setContainerSize] = useState({ width: 0, height: 0 }); - const [hovered, setHovered] = useState(null); - const [settled, setSettled] = useState(false); - const [time, setTime] = useState(0); - const animRef = useRef(0); - const startTimeRef = useRef(0); - const lastFrameTimeRef = useRef(0); - - // Zoom and pan state - const [zoom, setZoom] = useState(1); - const [pan, setPan] = useState({ x: 0, y: 0 }); - const isPanningRef = useRef(false); - const lastPanPosRef = useRef({ x: 0, y: 0 }); - - // Track container size - useEffect(() => { - const container = containerRef.current; - if (!container) return; - - const resizeObserver = new ResizeObserver((entries) => { - const entry = entries[0]; - if (entry) { - setContainerSize({ - width: entry.contentRect.width, - height: entry.contentRect.height, - }); - } - }); - - resizeObserver.observe(container); - return () => resizeObserver.disconnect(); - }, []); - - // Calculate node positions - const { nodes, domainPositions } = useMemo(() => { - if (containerSize.width === 0) return { nodes: [], domainPositions: {} }; - - const width = containerSize.width; - const height = containerSize.height; - const cx = width / 2; - const cy = height / 2; - - const domainKeys = Object.keys(ontology); - const domainPositions: Record = {}; - domainKeys.forEach((domain, i) => { - const angle = (Math.PI * 2 * i) / domainKeys.length - Math.PI / 2; - const radius = Math.min(cx, cy) * 0.45; - domainPositions[domain] = { - x: cx + Math.cos(angle) * radius, - y: cy + Math.sin(angle) * radius, - }; - }); - - const nodes: GraphNode[] = entities.map((e) => { - const dp = domainPositions[e.domain]; - const subIdx = ontology[e.domain].subclasses.findIndex((s) => s.id === e.id); - const total = ontology[e.domain].subclasses.length; - const angle = ((Math.PI * 2) / total) * subIdx - Math.PI / 2; - const radius = Math.min(width, height) * 0.1; - const x = dp.x + Math.cos(angle) * radius; - const y = dp.y + Math.sin(angle) * radius; - return { - ...e, - x, - y, - vx: 0, - vy: 0, - targetX: x, - targetY: y, - r: 9, // Half size since we're not doing 2x canvas scaling - }; - }); - - return { nodes, domainPositions }; - }, [entities, ontology, containerSize]); - - // Animation loop for breathing effect - useEffect(() => { - if (containerSize.width === 0) return; - - startTimeRef.current = performance.now(); - setSettled(false); - setTime(0); - - const frameInterval = 1000 / 30; // 30fps - - function animate(currentTime: number) { - if (currentTime - lastFrameTimeRef.current < frameInterval) { - animRef.current = requestAnimationFrame(animate); - return; - } - lastFrameTimeRef.current = currentTime; - - // Check if should settle - if (currentTime - startTimeRef.current > SETTLE_TIME) { - setSettled(true); - // Continue animation only if there are highlights - if (highlightedEntities && highlightedEntities.length > 0) { - setTime(t => t + 0.01); - animRef.current = requestAnimationFrame(animate); - } - return; - } - - setTime(t => t + 0.01); - animRef.current = requestAnimationFrame(animate); - } - - animRef.current = requestAnimationFrame(animate); - return () => cancelAnimationFrame(animRef.current); - }, [containerSize, entities, ontology]); - - // Restart animation when highlights change - useEffect(() => { - if (highlightedEntities && highlightedEntities.length > 0 && settled && animRef.current === 0) { - const frameInterval = 1000 / 30; - - function animate(currentTime: number) { - if (currentTime - lastFrameTimeRef.current < frameInterval) { - animRef.current = requestAnimationFrame(animate); - return; - } - lastFrameTimeRef.current = currentTime; - setTime(t => t + 0.01); - - if (highlightedEntities && highlightedEntities.length > 0) { - animRef.current = requestAnimationFrame(animate); - } else { - animRef.current = 0; - } - } - - animRef.current = requestAnimationFrame(animate); - } - }, [highlightedEntities, settled]); - - // Generate grid lines - const gridLines = useMemo(() => { - const lines: React.ReactElement[] = []; - const { width, height } = containerSize; - if (width === 0) return lines; - - for (let x = 0; x < width; x += 30) { - lines.push( - - ); - } - for (let y = 0; y < height; y += 30) { - lines.push( - - ); - } - return lines; - }, [containerSize]); - - // Calculate edge path with curve - const getEdgePath = useCallback((fromNode: GraphNode, toNode: GraphNode, time: number, isSettled: boolean) => { - const driftX1 = isSettled ? 0 : Math.sin(time + fromNode.targetX * 0.01) * 0.3; - const driftY1 = isSettled ? 0 : Math.cos(time + fromNode.targetY * 0.01) * 0.3; - const driftX2 = isSettled ? 0 : Math.sin(time + toNode.targetX * 0.01) * 0.3; - const driftY2 = isSettled ? 0 : Math.cos(time + toNode.targetY * 0.01) * 0.3; - - const x1 = fromNode.x + driftX1; - const y1 = fromNode.y + driftY1; - const x2 = toNode.x + driftX2; - const y2 = toNode.y + driftY2; - - const mx = (x1 + x2) / 2 + (y1 - y2) * 0.1; - const my = (y1 + y2) / 2 + (x2 - x1) * 0.1; - - return { path: `M ${x1} ${y1} Q ${mx} ${my} ${x2} ${y2}`, mx, my, x1, y1, x2, y2 }; - }, []); - - // Get node position with drift - const getNodePosition = useCallback((node: GraphNode, time: number, isSettled: boolean) => { - if (isSettled) { - return { x: node.x, y: node.y }; - } - const driftX = Math.sin(time + node.targetX * 0.01) * 0.3; - const driftY = Math.cos(time + node.targetY * 0.01) * 0.3; - return { x: node.x + driftX, y: node.y + driftY }; - }, []); - - const handleNodeClick = useCallback((node: GraphNode) => { - onNodeClick(node); - }, [onNodeClick]); - - // Zoom handler - zoom towards cursor position - const handleWheel = useCallback((e: React.WheelEvent) => { - e.preventDefault(); - const delta = e.deltaY > 0 ? 0.9 : 1.1; - const newZoom = Math.min(4, Math.max(0.25, zoom * delta)); - - // Get cursor position relative to SVG - const svg = svgRef.current; - if (!svg) return; - const rect = svg.getBoundingClientRect(); - const cursorX = e.clientX - rect.left; - const cursorY = e.clientY - rect.top; - - // Adjust pan to zoom towards cursor - const zoomRatio = newZoom / zoom; - const newPanX = cursorX - (cursorX - pan.x) * zoomRatio; - const newPanY = cursorY - (cursorY - pan.y) * zoomRatio; - - setZoom(newZoom); - setPan({ x: newPanX, y: newPanY }); - }, [zoom, pan]); - - // Pan handlers - const handleMouseDown = useCallback((e: React.MouseEvent) => { - // Only pan with middle mouse or when holding space (we'll just use middle mouse for now) - if (e.button === 1 || e.button === 0 && e.shiftKey) { - e.preventDefault(); - isPanningRef.current = true; - lastPanPosRef.current = { x: e.clientX, y: e.clientY }; - } - }, []); - - const handleMouseMove = useCallback((e: React.MouseEvent) => { - if (!isPanningRef.current) return; - const dx = e.clientX - lastPanPosRef.current.x; - const dy = e.clientY - lastPanPosRef.current.y; - lastPanPosRef.current = { x: e.clientX, y: e.clientY }; - setPan(p => ({ x: p.x + dx, y: p.y + dy })); - }, []); - - const handleMouseUp = useCallback(() => { - isPanningRef.current = false; - }, []); - - // Reset zoom/pan - const handleResetView = useCallback(() => { - setZoom(1); - setPan({ x: 0, y: 0 }); - }, []); - - if (containerSize.width === 0) { - return
; - } - - const filteredRels = activeFilter - ? relationships.filter((r) => r.domain.includes(activeFilter)) - : relationships; - - return ( -
- - {/* Grid - outside transform so it stays fixed */} - {gridLines} - - {/* Transformed content */} - - - {/* Domain labels */} - - {(Object.entries(domainPositions) as [DomainKey, { x: number; y: number }][]).map(([domain, pos]) => { - const data = ontology[domain]; - return ( - - {data.label.toUpperCase()} - - ); - })} - - - {/* Edges */} - - {filteredRels.map((rel, i) => { - const fromNode = nodes.find((n) => n.id === rel.from); - const toNode = nodes.find((n) => n.id === rel.to); - if (!fromNode || !toNode) return null; - - const isHighlighted = - highlightedEntities && - highlightedEntities.includes(rel.from) && - highlightedEntities.includes(rel.to); - - const { path, mx, my, x1, y1, x2, y2 } = getEdgePath(fromNode, toNode, time, settled); - const baseAlpha = isHighlighted ? 0.7 : 0.12; - const pulse = isHighlighted ? Math.sin(time * 4) * 0.15 + 0.15 : 0; - const alpha = Math.min(1, baseAlpha + pulse); - - // Particle position on curve (quadratic bezier) - const t = (time * 2) % 1; - const px = (1 - t) * (1 - t) * x1 + 2 * (1 - t) * t * mx + t * t * x2; - const py = (1 - t) * (1 - t) * y1 + 2 * (1 - t) * t * my + t * t * y2; - - return ( - - - - - - - - - {isHighlighted && ( - - )} - - ); - })} - - - {/* Nodes */} - - {nodes.map((node) => { - const isHighlighted = highlightedEntities && highlightedEntities.includes(node.id); - const isHovered = hovered === node.id; - const isDimmed = highlightedEntities && highlightedEntities.length > 0 && !isHighlighted; - const isFiltered = activeFilter && node.domain !== activeFilter && !relationships.some( - r => r.domain.includes(activeFilter) && (r.from === node.id || r.to === node.id) - ); - - const alpha = isFiltered ? 0.15 : isDimmed ? 0.3 : 1; - const r = isHighlighted || isHovered ? node.r * 1.4 : node.r; - const pulseR = isHighlighted && !settled ? Math.sin(time * 3) * 1.5 : 0; - const { x, y } = getNodePosition(node, time, settled); - - return ( - handleNodeClick(node)} - onMouseEnter={() => setHovered(node.id)} - onMouseLeave={() => setHovered(null)} - > - {/* Glow */} - {(isHighlighted || isHovered) && !isFiltered && ( - - - - - - - - - )} - - {/* Node circle */} - - - {/* Label */} - - {node.label} - - - ); - })} - - {/* Close transform group */} - - - setZoom(z => Math.min(4, z * 1.2))} - onZoomOut={() => setZoom(z => Math.max(0.25, z / 1.2))} - onReset={handleResetView} - /> - - {/* Tooltip */} - {hovered && (() => { - const node = nodes.find((n) => n.id === hovered); - if (!node) return null; - const { x, y } = getNodePosition(node, time, settled); - return ( -
-
- {node.icon} {node.label} -
-
- {Object.entries(node.props || {}).map(([k, v]) => ( -
{k}: {String(v)}
- ))} -
-
- ); - })()} -
- ); -} diff --git a/ai-context/trustgraph-templates/src/components/graph/NodeDetailPanel.tsx b/ai-context/trustgraph-templates/src/components/graph/NodeDetailPanel.tsx deleted file mode 100644 index ffb93a9d..00000000 --- a/ai-context/trustgraph-templates/src/components/graph/NodeDetailPanel.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import type { Entity, Relationship, OntologyType } from "../../types"; -import { SectionLabel, Card } from "../common"; -import { text, border } from "../../theme"; - -interface NodeDetailPanelProps { - node: Entity; - relationships: Relationship[]; - entities: Entity[]; - ontology: OntologyType; - propertyLabels: Record; - onClose: () => void; - onNodeSelect: (node: Entity) => void; -} - -export function NodeDetailPanel({ node, relationships, entities, ontology, propertyLabels, onClose, onNodeSelect }: NodeDetailPanelProps) { - // Filter relationships for this node - const nodeRelationships = relationships.filter( - r => r.from === node.id || r.to === node.id - ); - - return ( -
-
-
- {ontology[node.domain].label.toUpperCase()} ENTITY -
- -
-
- {node.icon} {node.label} -
-
- PROPERTIES - {Object.entries(node.props || {}).map(([k, v]) => ( -
- {propertyLabels[k] || k} - {String(v)} -
- ))} -
-
- RELATIONSHIPS - {nodeRelationships.map((r, i) => { - const otherId = r.from === node.id ? r.to : r.from; - const other = entities.find(e => e.id === otherId); - const direction = r.from === node.id ? "→" : "←"; - return ( - { if (other) onNodeSelect(other); }} - style={{ marginBottom: 4 }} - > -
- {direction} {other?.label} -
-
- {r.predicate.replace(/_/g, " ")} -
-
- ); - })} -
-
- ); -} diff --git a/ai-context/trustgraph-templates/src/components/graph/ZoomControls.tsx b/ai-context/trustgraph-templates/src/components/graph/ZoomControls.tsx deleted file mode 100644 index 7e3e3ab3..00000000 --- a/ai-context/trustgraph-templates/src/components/graph/ZoomControls.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { surface, border, text } from "../../theme"; - -interface ZoomControlsProps { - zoom: number; - onZoomIn: () => void; - onZoomOut: () => void; - onReset: () => void; -} - -export function ZoomControls({ zoom, onZoomIn, onZoomOut, onReset }: ZoomControlsProps) { - const buttonStyle: React.CSSProperties = { - width: 28, - height: 28, - border: "none", - borderRadius: 4, - background: border.medium, - color: text.subtle, - cursor: "pointer", - fontSize: 16, - fontWeight: "bold", - }; - - return ( - <> - {/* Zoom controls */} -
- - - -
- - {/* Zoom indicator */} - {zoom !== 1 && ( -
- {Math.round(zoom * 100)}% -
- )} - - ); -} diff --git a/ai-context/trustgraph-templates/src/components/graph/index.ts b/ai-context/trustgraph-templates/src/components/graph/index.ts deleted file mode 100644 index 6f8ebcaf..00000000 --- a/ai-context/trustgraph-templates/src/components/graph/index.ts +++ /dev/null @@ -1,6 +0,0 @@ -export { GraphCanvas } from "./GraphCanvas"; -export { GraphCanvasSVG } from "./GraphCanvasSVG"; -export { ExplainGraph } from "./ExplainGraph"; -export type { ExplainGraphNode, ExplainGraphEdge } from "./ExplainGraph"; -export { NodeDetailPanel } from "./NodeDetailPanel"; -export { ZoomControls } from "./ZoomControls"; diff --git a/ai-context/trustgraph-templates/src/components/index.ts b/ai-context/trustgraph-templates/src/components/index.ts deleted file mode 100644 index aca26425..00000000 --- a/ai-context/trustgraph-templates/src/components/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -// Common shared components -export { SectionLabel, FilterButton, Header, StatusBar, Typewriter, Card, Badge, LoadingState, Toaster, SearchInput, FilterBar, MessageBubble } from "./common"; -export type { FilterItem, Message } from "./common"; - -// Graph visualization components -export { GraphCanvas, GraphCanvasSVG, ExplainGraph, NodeDetailPanel, ZoomControls } from "./graph"; -export type { ExplainGraphNode, ExplainGraphEdge } from "./graph"; diff --git a/ai-context/trustgraph-templates/src/config.ts b/ai-context/trustgraph-templates/src/config.ts deleted file mode 100644 index 1a831d15..00000000 --- a/ai-context/trustgraph-templates/src/config.ts +++ /dev/null @@ -1,2 +0,0 @@ -// TrustGraph collection identifier -export const COLLECTION = "default"; diff --git a/ai-context/trustgraph-templates/src/index.css b/ai-context/trustgraph-templates/src/index.css deleted file mode 100644 index 57ced3fc..00000000 --- a/ai-context/trustgraph-templates/src/index.css +++ /dev/null @@ -1,18 +0,0 @@ -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -html, body, #root { - width: 100%; - height: 100%; - background: #0A0A0F; -} - -body { - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} diff --git a/ai-context/trustgraph-templates/src/main.tsx b/ai-context/trustgraph-templates/src/main.tsx deleted file mode 100644 index 9f851904..00000000 --- a/ai-context/trustgraph-templates/src/main.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { StrictMode } from 'react' -import { createRoot } from 'react-dom/client' -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { SocketProvider } from '@trustgraph/react-provider' -import { NotificationProvider, NotificationHandler } from '@trustgraph/react-state' -import { toast } from './state' -import './index.css' -import App from './App' - -const queryClient = new QueryClient() - -const notificationHandler: NotificationHandler = { - success: (message: string) => toast.success(message), - error: (message: string) => toast.error(message), - warning: (message: string) => toast.warning(message), - info: (message: string) => toast.info(message), -} - -createRoot(document.getElementById('root')!).render( - - - - - - - - - , -) diff --git a/ai-context/trustgraph-templates/src/pages/DataView.tsx b/ai-context/trustgraph-templates/src/pages/DataView.tsx deleted file mode 100644 index 58e355e7..00000000 --- a/ai-context/trustgraph-templates/src/pages/DataView.tsx +++ /dev/null @@ -1,393 +0,0 @@ -import { useState, useCallback, useMemo } from "react"; -import { SectionLabel, Card, LoadingState, SearchInput, FilterBar } from "../components"; -import type { FilterItem } from "../components"; -import { useSchemas, useEmbeddings, useRowEmbeddingsQuery, useRowsQuery } from "@trustgraph/react-state"; -import { COLLECTION } from "../config"; -import { semantic, palette, text, border, surface } from "../theme"; - -// Schema field type -interface SchemaField { - name: string; - type: string; - description?: string; -} - -// Schema type based on what useSchemas returns -interface SchemaData { - name: string; - description?: string; - fields?: SchemaField[]; - indexes?: { name: string; fields: string[] }[]; -} - -interface SchemaInfo { - key: string; - name: string; - description?: string; - fields: SchemaField[]; - indexes: { name: string; fields: string[] }[]; -} - -// Type for accumulated results with schema info and row data -interface AccumulatedMatch { - schemaKey: string; - index_name: string; - index_value: string[]; - text: string; - score: number; - rowData?: Record; -} - -export function DataView() { - // Input state - const [searchTerm, setSearchTerm] = useState(""); - - // Filter state (display only - doesn't trigger re-fetch) - const [selectedSchema, setSelectedSchema] = useState(null); - - // Results state - const [allMatches, setAllMatches] = useState([]); - const [isSearching, setIsSearching] = useState(false); - const [hasSearched, setHasSearched] = useState(false); - - // Fetch schemas - const { schemas: rawSchemas, schemasLoading, schemasError } = useSchemas(); - - // Embeddings hook - we'll use refetch for manual triggering - const [embeddingsTerm, setEmbeddingsTerm] = useState(""); - const { embeddings, isLoading: embeddingsLoading, refetch: _refetchEmbeddings } = useEmbeddings({ - flow: "default", - term: embeddingsTerm, - }); - - // Row embeddings query - const { executeQueryAsync } = useRowEmbeddingsQuery({ flow: "default" }); - - // Rows query for fetching full row data - const { executeQueryAsync: executeRowsQueryAsync } = useRowsQuery({ flow: "default" }); - - // Parse schemas into usable format - const schemas: SchemaInfo[] = useMemo(() => { - return (rawSchemas || []).map((s: unknown, idx: number) => { - if (Array.isArray(s)) { - const schemaData = s[1] as SchemaData | undefined; - return { - key: String(s[0]), - name: schemaData?.name || String(s[0]), - description: schemaData?.description, - fields: schemaData?.fields || [], - indexes: schemaData?.indexes || [], - }; - } - const schemaObj = s as SchemaData & { key?: string }; - return { - key: schemaObj.key || schemaObj.name || `schema-${idx}`, - name: schemaObj.name || `Schema ${idx}`, - description: schemaObj.description, - fields: schemaObj.fields || [], - indexes: schemaObj.indexes || [], - }; - }); - }, [rawSchemas]); - - // Build GraphQL query for a schema - const buildGraphQLQuery = useCallback((schema: SchemaInfo) => { - const gqlName = schema.key.replace(/-/g, '_'); - const fieldNames = schema.fields.map(f => f.name).join('\n '); - return `query { ${gqlName} { ${fieldNames} } }`; - }, []); - - // Core search function - searches ALL schemas, stores ALL results - const performSearch = useCallback(async (vectors: number[][]) => { - try { - // Always search ALL schemas - const embeddingsResults = await Promise.all( - schemas.map(async (schema) => { - try { - const matches = await executeQueryAsync({ - vectors, - schemaName: schema.key, - collection: COLLECTION, - limit: 10, - }); - return matches.map(m => ({ ...m, schemaKey: schema.key })); - } catch { - return []; - } - }) - ); - - const flatMatches = embeddingsResults.flat(); - - // Deduplicate - const seen = new Set(); - const uniqueMatches = flatMatches.filter(match => { - const key = `${match.schemaKey}:${match.index_value.join(',')}`; - if (seen.has(key)) return false; - seen.add(key); - return true; - }); - - // Fetch full row data for schemas with matches - const schemaKeysWithMatches = [...new Set(uniqueMatches.map(m => m.schemaKey))]; - const rowDataBySchema: Record[]> = {}; - - await Promise.all( - schemaKeysWithMatches.map(async (schemaKey) => { - const schema = schemas.find(s => s.key === schemaKey); - if (!schema || schema.fields.length === 0) return; - - try { - const query = buildGraphQLQuery(schema); - const result = await executeRowsQueryAsync({ query, collection: COLLECTION }); - const gqlName = schemaKey.replace(/-/g, '_'); - const rows = (result?.data as Record)?.[gqlName] || []; - rowDataBySchema[schemaKey] = rows as Record[]; - } catch (err) { - console.error(`Failed to fetch rows for ${schemaKey}:`, err); - } - }) - ); - - // Match row data to embeddings results - const matchesWithRowData = uniqueMatches.map(match => { - const rows = rowDataBySchema[match.schemaKey] || []; - const indexFields = match.index_name.split('.'); - const indexFieldName = indexFields[indexFields.length - 1]; - - const matchedRow = rows.find(row => { - const rowValue = row[indexFieldName]; - return match.index_value.some(iv => - String(rowValue).toLowerCase() === iv.toLowerCase() - ); - }); - - return { ...match, rowData: matchedRow }; - }); - - setAllMatches(matchesWithRowData); - setHasSearched(true); - } finally { - setIsSearching(false); - } - }, [schemas, executeQueryAsync, executeRowsQueryAsync, buildGraphQLQuery]); - - // Handle search button click - const handleSearch = useCallback(async () => { - const term = searchTerm.trim(); - if (!term) return; - - setIsSearching(true); - setAllMatches([]); - - // If same term, use refetch; otherwise set new term - if (term === embeddingsTerm && embeddings && embeddings.length > 0) { - // Same term - we already have embeddings, just re-run the search - await performSearch(embeddings); - } else { - // New term - update embeddings term and wait for it - setEmbeddingsTerm(term); - } - }, [searchTerm, embeddingsTerm, embeddings, performSearch]); - - // When embeddings become available for a new term, run the search - // This only triggers when embeddingsTerm changes and embeddings load - const prevEmbeddingsTermRef = useMemo(() => ({ current: "" }), []); - - if ( - isSearching && - embeddingsTerm && - embeddings && - embeddings.length > 0 && - !embeddingsLoading && - prevEmbeddingsTermRef.current !== embeddingsTerm - ) { - prevEmbeddingsTermRef.current = embeddingsTerm; - performSearch(embeddings); - } - - // Filter results for display (doesn't affect stored data) - const displayMatches = useMemo(() => { - if (!selectedSchema) return allMatches; - return allMatches.filter(m => m.schemaKey === selectedSchema); - }, [allMatches, selectedSchema]); - - // Group filtered matches by schema for display - const matchesBySchema = useMemo(() => { - return displayMatches.reduce((acc, match) => { - if (!acc[match.schemaKey]) { - acc[match.schemaKey] = []; - } - acc[match.schemaKey].push(match); - return acc; - }, {} as Record); - }, [displayMatches]); - - if (schemasLoading) { - return ; - } - - if (schemasError) { - return ; - } - - // Build filter items from schemas - const filterItems: FilterItem[] = schemas.slice(0, 10).map((schema) => ({ - key: schema.key, - label: schema.name, - })); - - const filterStats = selectedSchema - ? `${displayMatches.length} of ${allMatches.length} results` - : `${allMatches.length} results`; - - return ( -
- {/* Schema Filter Bar */} - - - {/* Search Input */} -
- SEARCH DATA - -
- - {/* Results Area */} -
- {!hasSearched && !isSearching ? ( -
- Enter a search term to find data across tables. -
- ) : isSearching ? ( -
- Searching... -
- ) : displayMatches.length === 0 ? ( -
- {selectedSchema ? "No matches in this schema. Try selecting 'All'." : "No matches found."} -
- ) : ( -
- {Object.entries(matchesBySchema).map(([schemaKey, schemaMatches]) => { - if (!schemaMatches || schemaMatches.length === 0) return null; - const schema = schemas.find(s => s.key === schemaKey); - - return ( - - {/* Table Header */} -
- - ▤ {schema?.name || schemaKey} - - - {schemaMatches.length} matches - -
- - {/* Results List */} -
- {schemaMatches.map((match, idx) => ( -
{ - e.currentTarget.style.background = surface.card; - }} - onMouseLeave={(e) => { - e.currentTarget.style.background = "transparent"; - }} - > - {match.rowData ? ( -
- {Object.entries(match.rowData).map(([key, value]) => ( -
- - {key} - -
- {String(value ?? "")} -
-
- ))} -
- ) : ( -
- {match.text} -
- )} - -
- 0.8 ? semantic.success : match.score > 0.5 ? palette.amber : text.subtle, - }}> - {(match.score * 100).toFixed(1)}% match - -
-
- ))} -
-
- ); - })} -
- )} -
-
- ); -} diff --git a/ai-context/trustgraph-templates/src/pages/ExplainView.tsx b/ai-context/trustgraph-templates/src/pages/ExplainView.tsx deleted file mode 100644 index 5f69fbe5..00000000 --- a/ai-context/trustgraph-templates/src/pages/ExplainView.tsx +++ /dev/null @@ -1,1411 +0,0 @@ -import { useState, useEffect, useRef, useCallback, useMemo } from "react"; -import { SectionLabel, SearchInput, ExplainGraph } from "../components"; -import type { ExplainGraphNode, ExplainGraphEdge } from "../components"; -import { COLLECTION } from "../config"; -import { useInference } from "@trustgraph/react-state"; -import type { ExplainEvent, Triple, Term } from "@trustgraph/react-state"; -import { useSocket } from "@trustgraph/react-provider"; -import type { BaseApi } from "@trustgraph/react-provider"; -import { palette, text, border, withGlow, semantic } from "../theme"; - -// ── Namespaces ────────────────────────────────────────────────────── -const TG = "https://trustgraph.ai/ns/"; -const TG_QUERY = TG + "query"; -const TG_CONCEPT = TG + "concept"; -const TG_ENTITY = TG + "entity"; -const TG_EDGE_COUNT = TG + "edgeCount"; -const TG_SELECTED_EDGE = TG + "selectedEdge"; -const TG_EDGE = TG + "edge"; -const TG_REASONING = TG + "reasoning"; -const TG_CONTENT = TG + "content"; -const TG_CONTAINS = TG + "contains"; -const TG_CHUNK_COUNT = TG + "chunkCount"; -const TG_ACTION = TG + "action"; -const TG_ARGUMENTS = TG + "arguments"; -const TG_THOUGHT = TG + "thought"; -const TG_OBSERVATION = TG + "observation"; -const TG_DOCUMENT = TG + "document"; -const RDF_TYPE = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type"; -const PROV = "http://www.w3.org/ns/prov#"; -const PROV_STARTED_AT_TIME = PROV + "startedAtTime"; -const PROV_WAS_DERIVED_FROM = PROV + "wasDerivedFrom"; -const RDFS_LABEL = "http://www.w3.org/2000/01/rdf-schema#label"; - -// ── Types ─────────────────────────────────────────────────────────── - -interface EdgeSelection { - edgeUri: string; - edge?: { s: string; p: string; o: string }; - edgeLabels?: { s: string; p: string; o: string }; - reasoning?: string; - sources?: ProvenanceChain[]; -} - -interface ProvenanceChain { - chain: { uri: string; label: string }[]; -} - -interface QuestionData { - query?: string; - timestamp?: string; -} - -interface GroundingData { - concepts: string[]; -} - -interface ExplorationData { - edgeCount?: string; - chunkCount?: string; - entities: string[]; - entityLabels?: string[]; -} - -interface FocusData { - edgeSelections: EdgeSelection[]; -} - -interface SynthesisData { - contentLength?: number; -} - -interface AnalysisData { - action?: string; - arguments?: string; - thoughtUri?: string; - observationUri?: string; -} - -interface ConclusionData { - documentUri?: string; -} - -interface ReflectionData { - documentUri?: string; - reflectionType?: string; -} - -type EventData = QuestionData | GroundingData | ExplorationData | FocusData | SynthesisData | AnalysisData | ConclusionData | ReflectionData; - -interface SourcePanelState { - chunkUri: string; - documentUri: string; - documentTitle?: string; - documentTags?: string[]; - chunkText?: string; - loading: boolean; - error?: string; -} - -interface ExplainNode { - explainId: string; - explainGraph: string; - eventType: string; - data?: EventData; - fetched: boolean; - fetching: boolean; - error?: string; -} - -// ── Helpers ───────────────────────────────────────────────────────── - -function shortUri(uri: string): string { - if (uri.startsWith("urn:trustgraph:prov:")) return "tg:prov:" + uri.slice(20); - if (uri.startsWith("urn:trustgraph:")) return "tg:" + uri.slice(15); - if (uri.startsWith(TG)) return "tg:" + uri.slice(TG.length); - if (uri.startsWith(PROV)) return "prov:" + uri.slice(PROV.length); - if (uri.startsWith("http://www.w3.org/2000/01/rdf-schema#")) return "rdfs:" + uri.slice(37); - if (uri.startsWith("http://www.w3.org/1999/02/22-rdf-syntax-ns#")) return "rdf:" + uri.slice(43); - if (uri.startsWith("urn:")) return uri; - const pos = Math.max(uri.lastIndexOf("#"), uri.lastIndexOf("/")); - return pos >= 0 ? uri.slice(pos + 1) : uri; -} - -// Ordered type checks — mirrors the Python ExplainEntity.from_triples logic. -// Each entry: [type URI to look for, display name]. -// First match wins. -const TYPE_CHECKS: [string, string][] = [ - [TG + "GraphRagQuestion", "question"], - [TG + "DocRagQuestion", "question"], - [TG + "AgentQuestion", "question"], - [TG + "Question", "question"], - [TG + "Grounding", "grounding"], - [TG + "Exploration", "exploration"], - [TG + "Focus", "focus"], - [TG + "Synthesis", "synthesis"], - [TG + "Reflection", "reflection"], - [TG + "Thought", "reflection"], - [TG + "Observation", "reflection"], - [TG + "Analysis", "analysis"], - [TG + "Conclusion", "conclusion"], -]; - -function getEventTypeFromTriples(triples: Triple[]): string { - const types = new Set(); - for (const t of triples) { - if (predIri(t) === RDF_TYPE) types.add(objValue(t)); - } - for (const [typeUri, displayName] of TYPE_CHECKS) { - if (types.has(typeUri)) return displayName; - } - return "unknown"; -} - -function eventTypeColor(eventType: string): string { - switch (eventType) { - case "question": return palette.amber; - case "grounding": return palette.orange; - case "exploration": return palette.blue; - case "focus": return palette.purple; - case "analysis": return palette.purple; - case "reflection": return palette.cyan; - case "synthesis": return palette.emerald; - case "conclusion": return palette.emerald; - default: return text.muted; - } -} - -// Get predicate IRI from a triple -function predIri(triple: Triple): string { - return triple.p.t === "i" ? triple.p.i : ""; -} - -// Get object value (string) from a triple -function objValue(triple: Triple): string { - const o = triple.o; - if (o.t === "i") return o.i; - if (o.t === "l") return o.v; - if (o.t === "b") return o.d; - return ""; -} - -// Get object as quoted triple {s, p, o} if it's a triple term -function objQuotedTriple(triple: Triple): { s: string; p: string; o: string } | null { - const o = triple.o; - if (o.t === "t" && o.tr) { - return { - s: o.tr.s.t === "i" ? o.tr.s.i : (o.tr.s as any).v || "", - p: o.tr.p.t === "i" ? o.tr.p.i : (o.tr.p as any).v || "", - o: o.tr.o.t === "i" ? o.tr.o.i : (o.tr.o as any).v || "", - }; - } - return null; -} - -// ── KG query helpers (using the socket API) ───────────────────────── - -async function queryTriples( - api: ReturnType, - subject: string, - predicate?: string, - limit = 100, - collection = COLLECTION, - graph?: string, -): Promise { - const s: Term = { t: "i", i: subject }; - const p: Term | undefined = predicate ? { t: "i", i: predicate } : undefined; - return api.triplesQuery(s, p, undefined, limit, collection, graph); -} - -// Backoff retry for eventually-consistent event triples. -// Calls onUpdate each time new triples arrive, settles when two consecutive -// fetches return the same count, or after maxTries (6 = 1 initial + 5 retries). -// Backoff: 50ms × 3 each retry, capped at 1500ms. -async function queryTriplesUntilSettled( - api: ReturnType, - subject: string, - onUpdate: (triples: Triple[]) => void, - limit = 100, - collection = COLLECTION, - graph?: string, - maxTries = 6, -): Promise { - let prevCount = -1; - let settled: Triple[] = []; - let delay = 50; - - for (let attempt = 0; attempt < maxTries; attempt++) { - const triples = await queryTriples(api, subject, undefined, limit, collection, graph); - - if (triples.length !== prevCount) { - settled = triples; - onUpdate(triples); - } else { - // Two consecutive identical counts — settled - return settled; - } - - prevCount = triples.length; - - if (attempt < maxTries - 1) { - await new Promise(r => setTimeout(r, delay)); - delay = Math.min(delay * 3, 1500); - } - } - - return settled; -} - -// Resolve rdfs:label for a URI, with cache -async function resolveLabel( - api: ReturnType, - uri: string, - cache: Map, -): Promise { - if (cache.has(uri)) return cache.get(uri)!; - try { - const triples = await api.triplesQuery( - { t: "i", i: uri }, - { t: "i", i: RDFS_LABEL }, - undefined, - 1, - COLLECTION, - ); - const label = triples.length > 0 ? objValue(triples[0]) : shortUri(uri); - cache.set(uri, label); - return label; - } catch { - const fallback = shortUri(uri); - cache.set(uri, fallback); - return fallback; - } -} - -// Trace prov:wasDerivedFrom chain up to root -async function traceProvenanceChain( - api: ReturnType, - startUri: string, - labelCache: Map, - maxDepth = 10, -): Promise { - const chain: { uri: string; label: string }[] = []; - let current: string | null = startUri; - - for (let i = 0; i < maxDepth && current; i++) { - const label = await resolveLabel(api, current, labelCache); - chain.push({ uri: current, label }); - - // Find parent - const parentTriples = await api.triplesQuery( - { t: "i", i: current }, - { t: "i", i: PROV_WAS_DERIVED_FROM }, - undefined, - 1, - COLLECTION, - ); - - const parentUri = parentTriples.length > 0 ? objValue(parentTriples[0]) : null; - if (!parentUri || parentUri === current) break; - current = parentUri; - } - - return { chain }; -} - -// Query edge provenance: find subgraphs containing the edge via tg:contains -async function queryEdgeProvenance( - api: ReturnType, - edge: { s: string; p: string; o: string }, - labelCache: Map, -): Promise { - // Find subgraphs that contain this edge: ?subgraph tg:contains <> - const oTerm: Term = (edge.o.startsWith("http") || edge.o.startsWith("urn:")) - ? { t: "i", i: edge.o } - : { t: "l", v: edge.o }; - - const containsTriples = await api.triplesQuery( - undefined, - { t: "i", i: TG_CONTAINS }, - { - t: "t", - tr: { - s: { t: "i", i: edge.s }, - p: { t: "i", i: edge.p }, - o: oTerm, - }, - }, - 10, - COLLECTION, - ); - - // For each subgraph, follow wasDerivedFrom to sources - const chains: ProvenanceChain[] = []; - for (const t of containsTriples) { - const subgraphUri = t.s.t === "i" ? t.s.i : ""; - if (!subgraphUri) continue; - - const derivedTriples = await api.triplesQuery( - { t: "i", i: subgraphUri }, - { t: "i", i: PROV_WAS_DERIVED_FROM }, - undefined, - 10, - COLLECTION, - ); - - for (const dt of derivedTriples) { - const sourceUri = objValue(dt); - if (sourceUri) { - const chain = await traceProvenanceChain(api, sourceUri, labelCache); - chains.push(chain); - } - } - } - - return chains; -} - -// ── Parse basic event data (synchronous, from already-fetched triples) ── - -function parseBasicEventData(eventType: string, triples: Triple[]): EventData { - switch (eventType) { - case "question": { - const data: QuestionData = {}; - for (const t of triples) { - const p = predIri(t); - if (p === TG_QUERY) data.query = objValue(t); - if (p === PROV_STARTED_AT_TIME) data.timestamp = objValue(t); - } - return data; - } - - case "grounding": { - const concepts: string[] = []; - for (const t of triples) { - if (predIri(t) === TG_CONCEPT) { - const v = objValue(t); - if (v) concepts.push(v); - } - } - return { concepts } as GroundingData; - } - - case "exploration": { - const data: ExplorationData = { entities: [] }; - for (const t of triples) { - const p = predIri(t); - if (p === TG_EDGE_COUNT) data.edgeCount = objValue(t); - if (p === TG_CHUNK_COUNT) data.chunkCount = objValue(t); - if (p === TG_ENTITY) { - const uri = objValue(t); - if (uri) data.entities.push(uri); - } - } - return data; - } - - case "focus": { - const edgeSelUris: string[] = []; - for (const t of triples) { - if (predIri(t) === TG_SELECTED_EDGE) { - const uri = objValue(t); - if (uri) edgeSelUris.push(uri); - } - } - return { - edgeSelections: edgeSelUris.map(uri => ({ edgeUri: uri })), - } as FocusData; - } - - case "synthesis": { - const data: SynthesisData = {}; - for (const t of triples) { - if (predIri(t) === TG_CONTENT) { - data.contentLength = objValue(t).length; - } - } - return data; - } - - case "analysis": { - const data: AnalysisData = {}; - for (const t of triples) { - const p = predIri(t); - if (p === TG_ACTION) data.action = objValue(t); - if (p === TG_ARGUMENTS) data.arguments = objValue(t); - if (p === TG_THOUGHT) data.thoughtUri = objValue(t); - if (p === TG_OBSERVATION) data.observationUri = objValue(t); - } - return data; - } - - case "conclusion": { - const data: ConclusionData = {}; - for (const t of triples) { - if (predIri(t) === TG_DOCUMENT) data.documentUri = objValue(t); - } - return data; - } - - case "reflection": { - const data: ReflectionData = {}; - for (const t of triples) { - if (predIri(t) === TG_DOCUMENT) data.documentUri = objValue(t); - } - return data; - } - - default: - return {}; - } -} - -// ── Enrich event data (async — labels, edge details, provenance) ──── - -async function enrichEventData( - api: ReturnType, - eventType: string, - _triples: Triple[], - basicData: EventData, - labelCache: Map, - explainGraph: string, -): Promise { - switch (eventType) { - case "exploration": { - const data = { ...(basicData as ExplorationData) }; - if (data.entities.length > 0) { - data.entityLabels = await Promise.all( - data.entities.map(uri => resolveLabel(api, uri, labelCache)) - ); - } - return data; - } - - case "focus": { - const basic = basicData as FocusData; - const edgeSelections = await Promise.all(basic.edgeSelections.map(async (basicSel) => { - const edgeTriples = await queryTriples( - api, basicSel.edgeUri, undefined, 100, COLLECTION, explainGraph, - ); - - const sel: EdgeSelection = { edgeUri: basicSel.edgeUri }; - for (const et of edgeTriples) { - const p = predIri(et); - if (p === TG_EDGE) sel.edge = objQuotedTriple(et) || undefined; - if (p === TG_REASONING) sel.reasoning = objValue(et); - } - - if (sel.edge) { - const [labels, sources] = await Promise.all([ - Promise.all([ - resolveLabel(api, sel.edge.s, labelCache), - resolveLabel(api, sel.edge.p, labelCache), - resolveLabel(api, sel.edge.o, labelCache), - ]), - queryEdgeProvenance(api, sel.edge, labelCache), - ]); - sel.edgeLabels = { s: labels[0], p: labels[1], o: labels[2] }; - sel.sources = sources; - } - - return sel; - })); - - return { edgeSelections } as FocusData; - } - - default: - return basicData; - } -} - -// ── Component ─────────────────────────────────────────────────────── - -type QueryMode = "graph-rag" | "doc-rag" | "agent"; - -const queryModeLabels: Record = { - "graph-rag": "Graph RAG", - "doc-rag": "Doc RAG", - "agent": "Agent", -}; - -export function ExplainView() { - const [input, setInput] = useState(""); - const [queryMode, setQueryMode] = useState("graph-rag"); - const [response, setResponse] = useState(""); - const [agentMessages, setAgentMessages] = useState<{ type: string; text: string; done?: boolean }[]>([]); - const [isQuerying, setIsQuerying] = useState(false); - const [explainNodes, setExplainNodes] = useState([]); - const [error, setError] = useState(null); - const [highlightedNodeIds, setHighlightedNodeIds] = useState([]); - const [highlightedEdgeIds, setHighlightedEdgeIds] = useState([]); - const [sourcePanel, setSourcePanel] = useState(null); - const scrollRef = useRef(null); - const explainScrollRef = useRef(null); - const labelCacheRef = useRef(new Map()); - - const { graphRag, documentRag, agent } = useInference({}); - const socket = useSocket(); - - useEffect(() => { - scrollRef.current?.scrollIntoView({ behavior: "smooth" }); - }, [response]); - - useEffect(() => { - explainScrollRef.current?.scrollIntoView({ behavior: "smooth" }); - }, [explainNodes]); - - // Fetch event data when new nodes arrive - // Use a ref to access current nodes without re-rendering - const nodesRef = useRef(explainNodes); - nodesRef.current = explainNodes; - - const fetchNode = useCallback(async (explainId: string) => { - setExplainNodes(prev => prev.map(n => - n.explainId === explainId ? { ...n, fetching: true } : n - )); - - try { - const api = socket.flow("default"); - const node = nodesRef.current.find(n => n.explainId === explainId); - if (!node) return; - - const updateNode = (updates: Partial) => { - setExplainNodes(prev => prev.map(n => - n.explainId === explainId ? { ...n, ...updates } : n - )); - }; - - // Phase 1: Fetch event triples with backoff until settled. - // These are eventually consistent — render progressively as they arrive. - let latestEventType = "unknown"; - let latestBasicData: EventData = {}; - - const settledTriples = await queryTriplesUntilSettled( - api, node.explainId, - (triples) => { - latestEventType = getEventTypeFromTriples(triples); - latestBasicData = parseBasicEventData(latestEventType, triples); - updateNode({ eventType: latestEventType, data: latestBasicData, fetched: true, fetching: false }); - }, - 100, COLLECTION, node.explainGraph, - ); - - if (settledTriples.length === 0) { - updateNode({ fetched: true, fetching: false }); - return; - } - - // Phase 2: Enrich with KG lookups (labels, edge details, provenance). - // These reference known-to-exist data — no retry needed, just fetch once. - const enriched = await enrichEventData(api, latestEventType, settledTriples, latestBasicData, labelCacheRef.current, node.explainGraph); - if (enriched !== latestBasicData) { - updateNode({ data: enriched }); - } - } catch (err) { - setExplainNodes(prev => prev.map(n => - n.explainId === explainId - ? { ...n, error: String(err), fetching: false } - : n - )); - } - }, [socket]); - - useEffect(() => { - for (const node of explainNodes) { - if (!node.fetched && !node.fetching && !node.error) { - fetchNode(node.explainId); - } - } - }, [explainNodes, fetchNode]); - - const addExplainEvent = useCallback((event: ExplainEvent) => { - setExplainNodes(prev => { - if (prev.some(n => n.explainId === event.explainId)) return prev; - return [...prev, { - explainId: event.explainId, - explainGraph: event.explainGraph, - eventType: "unknown", - fetched: false, - fetching: false, - }]; - }); - }, []); - - const handleSubmit = useCallback(async (query: string) => { - if (!query.trim() || isQuerying) return; - - setIsQuerying(true); - setResponse(""); - setAgentMessages([]); - setExplainNodes([]); - setHighlightedNodeIds([]); - setHighlightedEdgeIds([]); - setSourcePanel(null); - setError(null); - setInput(""); - labelCacheRef.current.clear(); - - const trimmed = query.trim(); - - try { - switch (queryMode) { - case "graph-rag": { - await graphRag({ - input: trimmed, - collection: COLLECTION, - options: { maxSubgraphSize: 150 }, - callbacks: { - onChunk: (chunk: string) => setResponse(prev => prev + chunk), - onExplain: addExplainEvent, - onError: (err: string) => setError(err), - }, - }); - break; - } - - case "doc-rag": { - await documentRag({ - input: trimmed, - collection: COLLECTION, - callbacks: { - onChunk: (chunk: string) => setResponse(prev => prev + chunk), - onExplain: addExplainEvent, - onError: (err: string) => setError(err), - }, - }); - break; - } - - case "agent": { - // Track current streaming message per type - const accum: Record = {}; - - const appendChunk = (type: string, chunk: string, complete?: boolean) => { - accum[type] = (accum[type] || "") + chunk; - const currentText = accum[type]; - setAgentMessages(prev => { - // Find existing in-progress message of this type at end - const lastIdx = prev.length - 1; - if (lastIdx >= 0 && prev[lastIdx].type === type && !prev[lastIdx].done) { - const updated = [...prev]; - updated[lastIdx] = { type, text: currentText, done: !!complete }; - return updated; - } - // New message - return [...prev, { type, text: currentText, done: !!complete }]; - }); - if (complete) { - accum[type] = ""; - } - }; - - await agent({ - input: trimmed, - callbacks: { - onThink: (chunk: string, complete?: boolean) => appendChunk("thinking", chunk, complete), - onObserve: (chunk: string, complete?: boolean) => appendChunk("observation", chunk, complete), - onAnswer: (chunk: string, complete?: boolean) => appendChunk("answer", chunk, complete), - onExplain: addExplainEvent, - onError: (err: string) => setError(err), - }, - }); - break; - } - } - } catch (err) { - setError(String(err)); - } finally { - setIsQuerying(false); - } - }, [graphRag, documentRag, agent, queryMode, isQuerying, addExplainEvent]); - - // ── Derive graph nodes and edges from explain events ────────────── - const { graphNodes, graphEdges } = useMemo(() => { - const nodeMap = new Map(); - const edgeList: ExplainGraphEdge[] = []; - - for (const node of explainNodes) { - if (!node.fetched || !node.data) continue; - - if (node.eventType === "exploration") { - const d = node.data as ExplorationData; - const labels = d.entityLabels || []; - d.entities.forEach((uri, i) => { - if (!nodeMap.has(uri)) { - nodeMap.set(uri, { id: uri, label: labels[i] || shortUri(uri), color: palette.blue }); - } - }); - } - - if (node.eventType === "focus") { - const d = node.data as FocusData; - for (const sel of d.edgeSelections) { - if (!sel.edge) continue; - const { s, p, o } = sel.edge; - const sLabel = sel.edgeLabels?.s || shortUri(s); - const pLabel = sel.edgeLabels?.p || shortUri(p); - const oLabel = sel.edgeLabels?.o || shortUri(o); - - // Ensure nodes exist - if (!nodeMap.has(s)) nodeMap.set(s, { id: s, label: sLabel, color: palette.pink }); - if (!nodeMap.has(o)) nodeMap.set(o, { id: o, label: oLabel, color: palette.pink }); - - edgeList.push({ - id: sel.edgeUri, - from: s, - to: o, - label: pLabel, - reasoning: sel.reasoning, - }); - } - } - } - - return { graphNodes: Array.from(nodeMap.values()), graphEdges: edgeList }; - }, [explainNodes]); - - // ── Entity/edge click → neighbourhood highlight on graph ───────── - const handleEntityClick = useCallback((entityUri: string) => { - // Highlight this node + connected edges + neighbour nodes - const connectedEdges = graphEdges.filter(e => e.from === entityUri || e.to === entityUri); - const neighbourIds = new Set([entityUri]); - const edgeIds: string[] = []; - for (const e of connectedEdges) { - edgeIds.push(e.id); - neighbourIds.add(e.from); - neighbourIds.add(e.to); - } - setHighlightedNodeIds(Array.from(neighbourIds)); - setHighlightedEdgeIds(edgeIds); - }, [graphEdges]); - - const handleEdgeClick = useCallback((sel: EdgeSelection) => { - // Highlight this edge + its two endpoint nodes - const nodeIds: string[] = []; - if (sel.edge) { - nodeIds.push(sel.edge.s, sel.edge.o); - } - setHighlightedNodeIds(nodeIds); - setHighlightedEdgeIds([sel.edgeUri]); - }, []); - - const handleSourceClick = useCallback((source: ProvenanceChain) => { - // chain[0] = chunk (closest to edge), chain[last] = root document - const chunkNode = source.chain[0]; - const docNode = source.chain[source.chain.length - 1]; - if (!chunkNode || !docNode) return; - - // Same chunk — ignore (use the × button to close) - if (sourcePanel?.chunkUri === chunkNode.uri) return; - - setSourcePanel({ - chunkUri: chunkNode.uri, - documentUri: docNode.uri, - loading: true, - }); - - const librarian = socket.librarian(); - - // Fetch parent document metadata (title, tags) from librarian - librarian.getDocumentMetadata(docNode.uri).then(meta => { - setSourcePanel(prev => prev?.chunkUri === chunkNode.uri - ? { ...prev, documentTitle: meta?.title, documentTags: meta?.tags } - : prev - ); - }).catch(() => { - // Metadata not available — that's OK - }); - - // The chunk URI is itself a document ID in the librarian — stream it directly - let chunkText = ""; - librarian.streamDocument( - chunkNode.uri, - (content, _chunkIndex, _totalChunks, complete) => { - try { - chunkText += atob(content); - } catch { - chunkText += content; - } - if (complete) { - setSourcePanel(prev => prev?.chunkUri === chunkNode.uri - ? { ...prev, chunkText, loading: false } - : prev - ); - } - }, - (err) => { - setSourcePanel(prev => prev?.chunkUri === chunkNode.uri - ? { ...prev, loading: false, error: err } - : prev - ); - }, - ); - }, [socket, sourcePanel?.chunkUri]); - - return ( -
- {/* LHS: Query + Response */} -
-
- {queryModeLabels[queryMode].toUpperCase()} QUERY -
- {(["graph-rag", "doc-rag", "agent"] as QueryMode[]).map(mode => ( - - ))} -
- handleSubmit(input)} - placeholder="Ask a question..." - buttonText="Query" - isLoading={isQuerying} - buttonColor={palette.cyan} - /> -
- -
- {error && ( -
-
ERROR
-
{error}
-
- )} - - {!response && !isQuerying && !error && agentMessages.length === 0 && ( -
- Ask a question to see {queryModeLabels[queryMode]} in action with live explainability. -
- )} - - {/* Streaming response for graph-rag and doc-rag */} - {(response || (isQuerying && queryMode !== "agent")) && ( -
- {response && ( -
-
- RESPONSE -
-
{response}
-
- )} - {isQuerying && ( -
- {response ? "Streaming..." : "Processing query..."} -
- )} -
- )} - - {/* Agent messages */} - {queryMode === "agent" && agentMessages.length > 0 && ( -
- {agentMessages.map((msg, i) => { - const colors: Record = { - thinking: palette.purple, - observation: palette.blue, - answer: palette.emerald, - }; - const color = colors[msg.type] || text.muted; - return ( -
-
- {msg.type} -
-
{msg.text}
-
- ); - })} -
- )} - - {queryMode === "agent" && isQuerying && agentMessages.length === 0 && ( -
- Agent is working... -
- )} - -
-
- - {/* Source text panel — shown when a provenance link is clicked */} - {sourcePanel && ( -
- {/* Header with document metadata */} -
-
- SOURCE - {sourcePanel.documentTitle ? ( - - {sourcePanel.documentTitle} - - ) : ( - - {shortUri(sourcePanel.documentUri)} - - )} - {sourcePanel.documentTags && sourcePanel.documentTags.length > 0 && ( - - {sourcePanel.documentTags.map((tag, i) => ( - - {tag} - - ))} - - )} -
- -
- - {/* Chunk text content */} -
- {sourcePanel.loading && ( -
- Loading source text... -
- )} - {sourcePanel.error && ( -
- {sourcePanel.error} -
- )} - {sourcePanel.chunkText && ( -
- {sourcePanel.chunkText} -
- )} -
-
- )} -
- - {/* RHS: Graph + Explainability panel */} -
- {/* Graph view — top half */} -
- { - setHighlightedNodeIds(prev => - prev.includes(nodeId) ? prev.filter(id => id !== nodeId) : [...prev, nodeId] - ); - }} - onEdgeClick={(edgeId) => { - setHighlightedEdgeIds(prev => - prev.includes(edgeId) ? prev.filter(id => id !== edgeId) : [...prev, edgeId] - ); - }} - /> -
- - {/* Event cards — bottom half */} -
-
- - EVENTS - {explainNodes.length > 0 && ( - - {explainNodes.length} event{explainNodes.length !== 1 ? "s" : ""} - - )} - -
- -
- {explainNodes.length === 0 && !isQuerying && ( -
- Explain events will appear here as the query progresses. -
- )} - - {isQuerying && explainNodes.length === 0 && ( -
- Waiting for explain events... -
- )} - -
- {explainNodes.map((node, idx) => ( - - ))} -
-
-
-
-
-
- ); -} - -// ── ExplainCard ───────────────────────────────────────────────────── - -function ExplainCard({ node, index, onEntityClick, onEdgeClick, onSourceClick }: { - node: ExplainNode; - index: number; - onEntityClick?: (uri: string) => void; - onEdgeClick?: (sel: EdgeSelection) => void; - onSourceClick?: (source: ProvenanceChain) => void; -}) { - const typeColor = eventTypeColor(node.eventType); - - return ( -
- {/* Header */} -
- - {index + 1} - - - {node.eventType} - - {node.fetching && ( - loading... - )} -
- - {/* Event data */} - {node.fetched && node.data && ( - - )} - - {node.error && ( -
{node.error}
- )} -
- ); -} - -// ── EventDataView ─────────────────────────────────────────────────── - -function EventDataView({ eventType, data, onEntityClick, onEdgeClick, onSourceClick }: { - eventType: string; - data: EventData; - onEntityClick?: (uri: string) => void; - onEdgeClick?: (sel: EdgeSelection) => void; - onSourceClick?: (source: ProvenanceChain) => void; -}) { - const mono = { fontFamily: "'IBM Plex Mono', monospace" } as const; - - switch (eventType) { - case "question": { - const d = data as QuestionData; - return ( -
- {d.query && ( -
- Query: {d.query} -
- )} - {d.timestamp && ( -
- {d.timestamp} -
- )} -
- ); - } - - case "grounding": { - const d = data as GroundingData; - return ( -
- {d.concepts.length > 0 && ( - <> -
- {d.concepts.length} concept{d.concepts.length !== 1 ? "s" : ""} extracted -
-
- {d.concepts.map((concept, i) => ( - - {concept} - - ))} -
- - )} -
- ); - } - - case "exploration": { - const d = data as ExplorationData; - return ( -
- {d.edgeCount && ( -
- Subgraph extracted: {d.edgeCount} edges -
- )} - {d.chunkCount && ( -
- Chunks retrieved: {d.chunkCount} -
- )} - {d.entityLabels && d.entityLabels.length > 0 && ( -
-
- {d.entityLabels.length} seed entit{d.entityLabels.length !== 1 ? "ies" : "y"} -
-
- {d.entityLabels.map((label, i) => ( - onEntityClick?.(d.entities[i])} - style={{ - fontSize: 11, padding: "3px 8px", borderRadius: 4, - background: withGlow(palette.blue, 0.1), - border: `1px solid ${withGlow(palette.blue, 0.2)}`, - color: text.secondary, ...mono, - cursor: onEntityClick ? "pointer" : "default", - transition: "all 0.15s ease", - }} - onMouseEnter={e => { if (onEntityClick) (e.currentTarget.style.background = withGlow(palette.blue, 0.25)); }} - onMouseLeave={e => { (e.currentTarget.style.background = withGlow(palette.blue, 0.1)); }} - > - {label} - - ))} -
-
- )} -
- ); - } - - case "focus": { - const d = data as FocusData; - return ( -
- {d.edgeSelections && d.edgeSelections.length > 0 && ( - <> -
- Focused on {d.edgeSelections.length} edge{d.edgeSelections.length !== 1 ? "s" : ""} -
- {d.edgeSelections.map((sel, i) => ( - onEdgeClick?.(sel)} onSourceClick={onSourceClick} /> - ))} - - )} -
- ); - } - - case "synthesis": { - const d = data as SynthesisData; - return ( -
- {d.contentLength != null && ( -
- Synthesis: {d.contentLength} chars -
- )} -
- ); - } - - case "analysis": { - const d = data as AnalysisData; - let parsedArgs: Record | null = null; - if (d.arguments) { - try { parsedArgs = JSON.parse(d.arguments); } catch { /* ignore */ } - } - return ( -
- {d.action && ( -
- Tool: {d.action} -
- )} - {parsedArgs && Object.entries(parsedArgs).map(([key, val]) => ( -
- {key}: {String(val)} -
- ))} - {!parsedArgs && d.arguments && ( -
- {d.arguments} -
- )} -
- ); - } - - case "conclusion": { - const d = data as ConclusionData; - return ( -
- {d.documentUri && ( -
- {shortUri(d.documentUri)} -
- )} -
- ); - } - - case "reflection": { - const d = data as ReflectionData; - return ( -
- {d.documentUri && ( -
- {shortUri(d.documentUri)} -
- )} -
- ); - } - - default: - return null; - } -} - -// ── EdgeSelectionView ─────────────────────────────────────────────── - -function EdgeSelectionView({ sel, onClick, onSourceClick }: { - sel: EdgeSelection; - onClick?: () => void; - onSourceClick?: (source: ProvenanceChain) => void; -}) { - const mono = { fontFamily: "'IBM Plex Mono', monospace" } as const; - - return ( -
{ if (onClick) e.currentTarget.style.background = withGlow(palette.purple, 0.08); }} - onMouseLeave={e => { e.currentTarget.style.background = "transparent"; }} - > - {/* Edge triple */} - {sel.edgeLabels && ( -
- {sel.edgeLabels.s} - - {sel.edgeLabels.p} - - {sel.edgeLabels.o} -
- )} - - {/* Provenance sources — clickable to view source text */} - {sel.sources && sel.sources.length > 0 && ( -
- {sel.sources.map((source, si) => { - const chainLabel = source.chain.map(c => c.label).join(" → "); - return ( - { - e.stopPropagation(); - onSourceClick?.(source); - }} - title={`View source: ${chainLabel}`} - style={{ - fontSize: 10, padding: "2px 7px", borderRadius: 4, - background: withGlow(palette.amber, 0.08), - border: `1px solid ${withGlow(palette.amber, 0.2)}`, - color: text.hint, ...mono, - cursor: onSourceClick ? "pointer" : "default", - transition: "all 0.15s ease", - }} - onMouseEnter={e => { if (onSourceClick) { e.currentTarget.style.background = withGlow(palette.amber, 0.2); e.currentTarget.style.color = palette.amber; } }} - onMouseLeave={e => { e.currentTarget.style.background = withGlow(palette.amber, 0.08); e.currentTarget.style.color = text.hint; }} - > - {chainLabel} - - ); - })} -
- )} - - {/* Reasoning - compact */} - {sel.reasoning && ( -
- {sel.reasoning.length > 120 ? sel.reasoning.slice(0, 120) + "..." : sel.reasoning} -
- )} -
- ); -} diff --git a/ai-context/trustgraph-templates/src/pages/GraphView.tsx b/ai-context/trustgraph-templates/src/pages/GraphView.tsx deleted file mode 100644 index 4ddaadb7..00000000 --- a/ai-context/trustgraph-templates/src/pages/GraphView.tsx +++ /dev/null @@ -1,93 +0,0 @@ -import type { DomainKey, Entity, OntologyDomain } from "../types"; -import { GraphCanvasSVG as GraphCanvas, NodeDetailPanel, LoadingState, FilterBar } from "../components"; -import type { FilterItem } from "../components"; -import { useGraphData } from "../state"; - -interface GraphViewProps { - activeFilter: DomainKey | null; - onFilterChange: (filter: DomainKey | null) => void; - selectedNode: Entity | null; - onNodeSelect: (node: Entity | null) => void; -} - -export function GraphView({ activeFilter, onFilterChange, selectedNode, onNodeSelect }: GraphViewProps) { - const { entities, relationships, ontology, propertyLabels, isLoading, isError } = useGraphData(); - - const highlightedEntities = selectedNode - ? [selectedNode.id, ...relationships.filter(r => r.from === selectedNode.id || r.to === selectedNode.id).map(r => r.from === selectedNode.id ? r.to : r.from)] - : []; - - // Compute relevant filter domains based on selected node's connections - const relevantDomains = selectedNode - ? (() => { - const domains = new Set([selectedNode.domain]); - const connectedIds = relationships - .filter(r => r.from === selectedNode.id || r.to === selectedNode.id) - .map(r => r.from === selectedNode.id ? r.to : r.from); - for (const id of connectedIds) { - const entity = entities.find(e => e.id === id); - if (entity) domains.add(entity.domain); - } - return domains; - })() - : null; - - if (isLoading) { - return ; - } - - if (isError || !ontology) { - return ; - } - - // Build filter items from relevant domains - const filterItems: FilterItem[] = selectedNode - ? (Object.entries(ontology) as [DomainKey, OntologyDomain][]) - .filter(([key]) => relevantDomains?.has(key)) - .slice(0, 10) - .map(([key, data]) => ({ - key, - label: data.label, - icon: data.icon, - color: data.color, - })) - : []; - - return ( - <> - {/* Domain Filter Bar */} - onFilterChange(key as DomainKey | null)} - stats={`${entities.length} entities · ${relationships.length} relationships`} - emptyMessage={selectedNode ? undefined : "Select a node to filter"} - /> - - {/* Main Content */} -
-
- onNodeSelect(selectedNode?.id === node.id ? null : node)} - activeFilter={activeFilter} - /> -
- {selectedNode && ( - onNodeSelect(null)} - onNodeSelect={onNodeSelect} - /> - )} -
- - ); -} diff --git a/ai-context/trustgraph-templates/src/pages/OntologyView.tsx b/ai-context/trustgraph-templates/src/pages/OntologyView.tsx deleted file mode 100644 index e5191ef7..00000000 --- a/ai-context/trustgraph-templates/src/pages/OntologyView.tsx +++ /dev/null @@ -1,116 +0,0 @@ -import type { DomainKey, OntologyDomain } from "../types"; -import { SectionLabel, Card, Badge, LoadingState } from "../components"; -import { useGraphData, useOntologySchema } from "../state"; -import { getLocalName } from "../utils"; -import { text, surface, border } from "../theme"; - -export function OntologyView() { - const { ontology, isLoading: graphLoading } = useGraphData(); - const { schema, isLoading: schemaLoading } = useOntologySchema(); - - const isLoading = graphLoading || schemaLoading; - - if (isLoading || !ontology || !schema) { - return ; - } - - // Count total instances - const totalInstances = Object.values(ontology).reduce((sum, d) => sum + d.subclasses.length, 0); - - return ( -
-
- ONTOLOGY SCHEMA - - {/* Ontology class cards */} -
- {(Object.entries(ontology) as [DomainKey, OntologyDomain][]).map(([key, data]) => { - // Find datatype properties for this domain from schema - const domainProps = schema.datatypeProperties - .filter(p => p.domain && getLocalName(p.domain) === data.label) - .map(p => p.label); - - return ( - -
- {data.icon} -
-
{data.label}
-
owl:Class
-
-
-
{data.description}
- PROPERTIES ({domainProps.length}) -
- {domainProps.map((p) => ( - {p} - ))} -
- INSTANCES ({data.subclasses.length}) - {data.subclasses.map((sc) => ( -
- {sc.label} - {sc.id} -
- ))} -
- ); - })} -
- - {/* Relationship predicates (Object Properties) */} - - RELATIONSHIP PREDICATES ({schema.objectProperties.length}) -
- {schema.objectProperties.map((prop) => { - const fromDomain = prop.domain ? getLocalName(prop.domain).toLowerCase() as DomainKey : null; - const toDomain = prop.range ? getLocalName(prop.range).toLowerCase() as DomainKey : null; - - return ( - -
- {prop.label} -
-
- {fromDomain && ontology[fromDomain] && ( - {ontology[fromDomain].label} - )} - {fromDomain && toDomain && " → "} - {toDomain && ontology[toDomain] && ( - {ontology[toDomain].label} - )} -
-
- ); - })} -
-
- - {/* Triple count summary */} -
- {[ - { label: "Classes", value: schema.classes.length }, - { label: "Instances", value: totalInstances }, - { label: "Object Props", value: schema.objectProperties.length }, - { label: "Data Props", value: schema.datatypeProperties.length }, - ].map((s) => ( -
-
{s.value}
-
{s.label.toUpperCase()}
-
- ))} -
-
-
- ); -} diff --git a/ai-context/trustgraph-templates/src/pages/QueryView.tsx b/ai-context/trustgraph-templates/src/pages/QueryView.tsx deleted file mode 100644 index 73b72972..00000000 --- a/ai-context/trustgraph-templates/src/pages/QueryView.tsx +++ /dev/null @@ -1,231 +0,0 @@ -import { useState, useEffect, useRef } from "react"; -import { GraphCanvasSVG as GraphCanvas, NodeDetailPanel, SectionLabel, Badge, LoadingState, SearchInput, MessageBubble } from "../components"; -import { useGraphData } from "../state"; -import { COLLECTION } from "../config"; -import type { Entity } from "../types"; -import { useChat, useConversation, useEmbeddings, useGraphEmbeddings } from "@trustgraph/react-state"; -import { getLocalName } from "../utils"; -import { palette, text, border, withGlow } from "../theme"; - -// Type for embedding result items -interface EmbeddingResultItem { - id: string; - uri: string; - label: string; - color: string; - icon: string; - isEntity: boolean; -} - -export function QueryView() { - const [customInput, setCustomInput] = useState(""); - const [queryForEmbeddings, setQueryForEmbeddings] = useState(undefined); - const [selectedEntityId, setSelectedEntityId] = useState(null); - const [selectedNode, setSelectedNode] = useState(null); - const scrollRef = useRef(null); - - const { entities, relationships, ontology, propertyLabels, isLoading: graphLoading } = useGraphData(); - const { submitMessage, isSubmitting } = useChat(); - const messages = useConversation((state) => state.messages); - const setChatMode = useConversation((state) => state.setChatMode); - - // Get embeddings for the query text - only fetch when we have a committed query - const { embeddings, isLoading: embeddingsLoading } = useEmbeddings({ - flow: "default", - term: queryForEmbeddings || undefined, - }); - - // Get graph entities from embeddings - only fetch when we have embeddings - const hasEmbeddings = embeddings && embeddings.length > 0; - const { graphEmbeddings, isLoading: graphEmbeddingsLoading } = useGraphEmbeddings({ - vecs: hasEmbeddings ? embeddings : [[]], - limit: hasEmbeddings ? 10 : 0, - collection: COLLECTION, - }); - - // Set chat mode to agent on mount - useEffect(() => { - setChatMode("agent"); - }, [setChatMode]); - - // Auto-scroll to bottom when messages change - useEffect(() => { - scrollRef.current?.scrollIntoView({ behavior: "smooth" }); - }, [messages]); - - const handleSubmit = (query: string) => { - if (query.trim() && !isSubmitting) { - const trimmedQuery = query.trim(); - submitMessage({ input: trimmedQuery }); - setQueryForEmbeddings(trimmedQuery); - setSelectedEntityId(null); - setSelectedNode(null); - setCustomInput(""); - } - }; - - - // Match graph embedding entities to our loaded entities for labels and highlighting - // graphEmbeddings returns RDF terms: { t: "i", i: "http://..." } - // Only show matched entities, deduplicated by URI - const embeddingResults: EmbeddingResultItem[] = []; - const seenUris = new Set(); - - for (const ge of (hasEmbeddings && graphEmbeddings || []) as { t: string; i?: string }[]) { - const uri = ge.i; - if (!uri || seenUris.has(uri)) continue; - - const entityId = getLocalName(uri); - const found = entities.find(e => e.id === entityId || e.uri === uri); - - // Only include actual entities, not properties/concepts - if (found) { - seenUris.add(uri); - embeddingResults.push({ - id: entityId, - uri, - label: found.label, - color: found.color, - icon: found.icon, - isEntity: true, - }); - } - } - - // Auto-select first embedding result when results arrive - useEffect(() => { - if (embeddingResults.length > 0 && !selectedEntityId && !selectedNode) { - setSelectedEntityId(embeddingResults[0].id); - } - }, [embeddingResults.length, selectedEntityId, selectedNode]); - - // Extract entity IDs for highlighting on graph - // Priority: selectedNode (graph click) > selectedEntityId (button click) > all embedding results - const highlightedEntities = (() => { - const focusId = selectedNode?.id || selectedEntityId; - if (!focusId) { - return embeddingResults.map(e => e.id); - } - // Find all entities connected to the focused entity - const connected = new Set([focusId]); - for (const rel of relationships) { - if (rel.from === focusId) { - connected.add(rel.to); - } else if (rel.to === focusId) { - connected.add(rel.from); - } - } - return Array.from(connected); - })(); - - if (graphLoading || !ontology) { - return ; - } - - return ( -
-
- {/* Query input area */} -
- AGENT QUERIES - - handleSubmit(customInput)} - placeholder="Type your own question..." - buttonText="Ask" - isLoading={isSubmitting} - buttonColor={palette.amber} - /> -
- - {/* Related entities from graph embeddings */} - {queryForEmbeddings && ( -
- - RELATED ENTITIES {(embeddingsLoading || graphEmbeddingsLoading) && loading...} - -
- {embeddingResults.length === 0 && !embeddingsLoading && !graphEmbeddingsLoading && ( - No related concepts found - )} - {embeddingResults.map((item) => { - const isSelected = selectedEntityId === item.id; - return ( - { - setSelectedEntityId(isSelected ? null : item.id); - setSelectedNode(null); - }} - > - {item.icon} - {item.label} - - ); - })} -
-
- )} - - {/* Response area */} -
- {messages.length === 0 ? ( -
- Type your question to get started. -
- ) : ( -
- {messages.map((msg, idx) => ( - - ))} - {isSubmitting && ( -
- Processing... -
- )} -
-
- )} -
-
- - {/* Graph visualization */} -
- { - setSelectedNode(selectedNode?.id === node.id ? null : node); - setSelectedEntityId(null); - }} - activeFilter={null} - /> -
- {selectedNode && ( - setSelectedNode(null)} - onNodeSelect={(node) => { - setSelectedNode(node); - setSelectedEntityId(null); - }} - /> - )} -
- ); -} diff --git a/ai-context/trustgraph-templates/src/pages/index.ts b/ai-context/trustgraph-templates/src/pages/index.ts deleted file mode 100644 index 08ad3690..00000000 --- a/ai-context/trustgraph-templates/src/pages/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export { GraphView } from "./GraphView"; -export { QueryView } from "./QueryView"; -export { ExplainView } from "./ExplainView"; -export { DataView } from "./DataView"; -export { OntologyView } from "./OntologyView"; diff --git a/ai-context/trustgraph-templates/src/state/index.ts b/ai-context/trustgraph-templates/src/state/index.ts deleted file mode 100644 index 32554341..00000000 --- a/ai-context/trustgraph-templates/src/state/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -// 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"; diff --git a/ai-context/trustgraph-templates/src/state/toastStore.ts b/ai-context/trustgraph-templates/src/state/toastStore.ts deleted file mode 100644 index 2f5f1f12..00000000 --- a/ai-context/trustgraph-templates/src/state/toastStore.ts +++ /dev/null @@ -1,52 +0,0 @@ -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((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), -}; diff --git a/ai-context/trustgraph-templates/src/state/useGraphData.ts b/ai-context/trustgraph-templates/src/state/useGraphData.ts deleted file mode 100644 index 001be00d..00000000 --- a/ai-context/trustgraph-templates/src/state/useGraphData.ts +++ /dev/null @@ -1,243 +0,0 @@ -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(null); - const [isLoading, setIsLoading] = useState(true); - const [isError, setIsError] = useState(false); - const [error, setError] = useState(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(); - const allComments = new Map(); - const owlClasses = new Set(); - const propertyUris = new Set(); - - 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 = {}; - 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(); - 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(); - const entityProps = new Map>(); - - // 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, - }; -} diff --git a/ai-context/trustgraph-templates/src/state/useOntologySchema.ts b/ai-context/trustgraph-templates/src/state/useOntologySchema.ts deleted file mode 100644 index b5dcd14c..00000000 --- a/ai-context/trustgraph-templates/src/state/useOntologySchema.ts +++ /dev/null @@ -1,178 +0,0 @@ -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; - datatypePropertyUris: Set; -} - -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(); - - 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(); - 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(); - 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, - }; -} diff --git a/ai-context/trustgraph-templates/src/theme/colors.ts b/ai-context/trustgraph-templates/src/theme/colors.ts deleted file mode 100644 index c6ca9cb0..00000000 --- a/ai-context/trustgraph-templates/src/theme/colors.ts +++ /dev/null @@ -1,72 +0,0 @@ -// Primary palette (migrated from useGraphData.ts) -export const palette = { - emerald: "#6EE7B7", - pink: "#F9A8D4", - blue: "#93C5FD", - amber: "#FCD34D", - purple: "#C4B5FD", - rose: "#FDA4AF", - cyan: "#67E8F9", - red: "#FCA5A5", - orange: "#F97316", -}; - -// Semantic colors -export const semantic = { - success: palette.emerald, - error: "#f66", - warning: palette.orange, - info: palette.blue, - thinking: palette.blue, - observation: palette.purple, - answer: palette.emerald, - user: palette.amber, -}; - -// Text colors (dark theme) -export const text = { - primary: "#ddd", - secondary: "#bbb", - muted: "#aaa", - subtle: "#888", - faint: "#666", - disabled: "#555", - hint: "#444", -}; - -// Surface/background colors -export const surface = { - base: "#0A0A0F", - overlay: "rgba(15,15,20,0.95)", - overlayLight: "rgba(15,15,20,0.8)", - card: "rgba(255,255,255,0.02)", - cardHover: "rgba(255,255,255,0.04)", -}; - -// Border colors -export const border = { - subtle: "rgba(255,255,255,0.04)", - default: "rgba(255,255,255,0.06)", - medium: "rgba(255,255,255,0.1)", - grid: "rgba(255,255,255,0.015)", -}; - -// Helper: Generate glow color from hex -export function withGlow(hex: string, opacity = 0.4): string { - const r = parseInt(hex.slice(1, 3), 16); - const g = parseInt(hex.slice(3, 5), 16); - const b = parseInt(hex.slice(5, 7), 16); - return `rgba(${r},${g},${b},${opacity})`; -} - -// Domain color palette (array for cycling) -export const domainColors = [ - { color: palette.emerald, glow: withGlow(palette.emerald) }, - { color: palette.pink, glow: withGlow(palette.pink) }, - { color: palette.blue, glow: withGlow(palette.blue) }, - { color: palette.amber, glow: withGlow(palette.amber) }, - { color: palette.purple, glow: withGlow(palette.purple) }, - { color: palette.rose, glow: withGlow(palette.rose) }, - { color: palette.cyan, glow: withGlow(palette.cyan) }, - { color: palette.red, glow: withGlow(palette.red) }, -]; diff --git a/ai-context/trustgraph-templates/src/theme/index.ts b/ai-context/trustgraph-templates/src/theme/index.ts deleted file mode 100644 index bbdc1712..00000000 --- a/ai-context/trustgraph-templates/src/theme/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./colors"; diff --git a/ai-context/trustgraph-templates/src/types/index.ts b/ai-context/trustgraph-templates/src/types/index.ts deleted file mode 100644 index 89dd67dd..00000000 --- a/ai-context/trustgraph-templates/src/types/index.ts +++ /dev/null @@ -1,65 +0,0 @@ -// ── Domain Types ───────────────────────────────────────────────── -export type DomainKey = string; - -export interface EntityProps { - [key: string]: string | number; -} - -export interface Subclass { - id: string; - uri?: string; - label: string; - props: EntityProps; -} - -export interface OntologyDomain { - label: string; - color: string; - glow: string; - icon: string; - description: string; - properties: string[]; - subclasses: Subclass[]; -} - -export type OntologyType = Record; - -// ── Relationship Types ─────────────────────────────────────────── -export interface Relationship { - from: string; - to: string; - predicate: string; - strength?: number; - domain: [DomainKey, DomainKey]; -} - -// ── Query Types ────────────────────────────────────────────────── -export interface DemoQuery { - q: string; - thinking: string[]; - answer: string; - entities: string[]; - triples: number; -} - -// ── Entity Types ───────────────────────────────────────────────── -export interface Entity extends Subclass { - domain: DomainKey; - color: string; - glow: string; - icon: string; -} - -export interface GraphNode extends Entity { - x: number; - y: number; - vx: number; - vy: number; - targetX: number; - targetY: number; - r: number; -} - -// ── UI State Types ─────────────────────────────────────────────── -export type TabKey = "graph" | "query" | "explain" | "ontology" | "data"; -export type QueryPhase = "idle" | "thinking" | "answering" | "done"; diff --git a/ai-context/trustgraph-templates/src/utils/index.ts b/ai-context/trustgraph-templates/src/utils/index.ts deleted file mode 100644 index 1883c845..00000000 --- a/ai-context/trustgraph-templates/src/utils/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { getLocalName } from "./uri"; diff --git a/ai-context/trustgraph-templates/src/utils/uri.ts b/ai-context/trustgraph-templates/src/utils/uri.ts deleted file mode 100644 index 7f7e5fab..00000000 --- a/ai-context/trustgraph-templates/src/utils/uri.ts +++ /dev/null @@ -1,9 +0,0 @@ -/** - * Extract the local name from a URI by taking the fragment after # or the last path segment - */ -export 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; -} diff --git a/ai-context/trustgraph-templates/src/vite-env.d.ts b/ai-context/trustgraph-templates/src/vite-env.d.ts deleted file mode 100644 index 11f02fe2..00000000 --- a/ai-context/trustgraph-templates/src/vite-env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// diff --git a/ai-context/trustgraph-templates/trustgraph-retail-demo.jsx b/ai-context/trustgraph-templates/trustgraph-retail-demo.jsx deleted file mode 100644 index 32f4c01e..00000000 --- a/ai-context/trustgraph-templates/trustgraph-retail-demo.jsx +++ /dev/null @@ -1,843 +0,0 @@ -import { useState, useEffect, useRef, useCallback, useMemo } from "react"; - -// ═══════════════════════════════════════════════════════════════════ -// TRUSTGRAPH RETAIL INTELLIGENCE DEMO -// Ontology-Driven Context Graph: Consumer × Agent × Retail × Brand -// ═══════════════════════════════════════════════════════════════════ - -// ── Ontology Schema ────────────────────────────────────────────── -const ONTOLOGY = { - consumer: { - label: "Consumer", - color: "#6EE7B7", - glow: "rgba(110,231,183,0.4)", - icon: "👤", - description: "Individuals and segments interacting with brands through retail channels", - properties: ["segment", "preferences", "journeyStage", "lifetime_value", "sentiment"], - subclasses: [ - { id: "cs1", label: "Urban Millennials", props: { size: "2.4M", avgSpend: "$142/mo", loyalty: 0.78, journeyStage: "Engaged" } }, - { id: "cs2", label: "Active Families", props: { size: "1.8M", avgSpend: "$218/mo", loyalty: 0.85, journeyStage: "Loyal" } }, - { id: "cs3", label: "Eco-Conscious Gen Z", props: { size: "3.1M", avgSpend: "$96/mo", loyalty: 0.62, journeyStage: "Exploring" } }, - { id: "cs4", label: "Luxury Seekers", props: { size: "890K", avgSpend: "$384/mo", loyalty: 0.91, journeyStage: "Advocate" } }, - { id: "cs5", label: "Weekend Warriors", props: { size: "1.5M", avgSpend: "$167/mo", loyalty: 0.73, journeyStage: "Engaged" } }, - ], - }, - brand: { - label: "Brand", - color: "#F9A8D4", - glow: "rgba(249,168,212,0.4)", - icon: "✦", - description: "Product brands seeking to connect with consumers through retail experiences", - properties: ["identity", "positioning", "campaigns", "products", "partnerships"], - subclasses: [ - { id: "br1", label: "Lumière Beauty", props: { category: "Cosmetics", positioning: "Premium", campaigns: 12, sentiment: 0.87 } }, - { id: "br2", label: "Nordic Trail", props: { category: "Outdoor Apparel", positioning: "Sustainable", campaigns: 8, sentiment: 0.82 } }, - { id: "br3", label: "Velo Sport", props: { category: "Athletics", positioning: "Performance", campaigns: 15, sentiment: 0.79 } }, - { id: "br4", label: "Casa Verde", props: { category: "Home & Living", positioning: "Artisanal", campaigns: 6, sentiment: 0.90 } }, - { id: "br5", label: "Artisan Coffee Co.", props: { category: "F&B", positioning: "Community", campaigns: 10, sentiment: 0.85 } }, - ], - }, - retail: { - label: "Retail", - color: "#93C5FD", - glow: "rgba(147,197,253,0.4)", - icon: "🏬", - description: "Channels, touchpoints, and experiences where brands meet consumers", - properties: ["channel", "location", "traffic", "conversionRate", "experience_score"], - subclasses: [ - { id: "rt1", label: "Flagship Store NYC", props: { channel: "Physical", traffic: "48K/mo", conversion: "12.3%", experience: 0.91 } }, - { id: "rt2", label: "Mobile Commerce App", props: { channel: "Digital", traffic: "1.2M/mo", conversion: "4.7%", experience: 0.78 } }, - { id: "rt3", label: "Pop-Up Experience", props: { channel: "Experiential", traffic: "8K/event", conversion: "18.6%", experience: 0.95 } }, - { id: "rt4", label: "Social Commerce", props: { channel: "Social", traffic: "890K/mo", conversion: "3.2%", experience: 0.72 } }, - { id: "rt5", label: "Loyalty Hub", props: { channel: "Omnichannel", traffic: "340K/mo", conversion: "22.1%", experience: 0.88 } }, - ], - }, - agent: { - label: "Agent", - color: "#FCD34D", - glow: "rgba(252,211,77,0.4)", - icon: "⚡", - description: "AI agents that orchestrate personalized brand-consumer connections", - properties: ["capability", "contextSources", "accuracy", "latency", "decisions_per_day"], - subclasses: [ - { id: "ag1", label: "Recommendation Agent", props: { capability: "Product Discovery", accuracy: "94.2%", latency: "120ms", decisions: "2.1M/day" } }, - { id: "ag2", label: "Personalization Agent", props: { capability: "Experience Tailoring", accuracy: "91.8%", latency: "85ms", decisions: "890K/day" } }, - { id: "ag3", label: "Campaign Orchestrator", props: { capability: "Brand Activation", accuracy: "88.5%", latency: "200ms", decisions: "340K/day" } }, - { id: "ag4", label: "Sentiment Analyst", props: { capability: "Brand Perception", accuracy: "96.1%", latency: "150ms", decisions: "1.5M/day" } }, - { id: "ag5", label: "Journey Optimizer", props: { capability: "Path Optimization", accuracy: "89.7%", latency: "180ms", decisions: "560K/day" } }, - ], - }, -}; - -// ── Ontology Relationships (Triples) ────────────────────────────── -const RELATIONSHIPS = [ - // Consumer ↔ Brand - { from: "cs1", to: "br1", predicate: "has_affinity_for", strength: 0.85, domain: ["consumer", "brand"] }, - { from: "cs1", to: "br5", predicate: "frequents", strength: 0.69, domain: ["consumer", "brand"] }, - { from: "cs2", to: "br2", predicate: "has_affinity_for", strength: 0.78, domain: ["consumer", "brand"] }, - { from: "cs2", to: "br3", predicate: "purchases_from", strength: 0.88, domain: ["consumer", "brand"] }, - { from: "cs3", to: "br2", predicate: "advocates_for", strength: 0.71, domain: ["consumer", "brand"] }, - { from: "cs3", to: "br4", predicate: "has_affinity_for", strength: 0.65, domain: ["consumer", "brand"] }, - { from: "cs3", to: "br5", predicate: "frequents", strength: 0.58, domain: ["consumer", "brand"] }, - { from: "cs4", to: "br1", predicate: "loyal_to", strength: 0.92, domain: ["consumer", "brand"] }, - { from: "cs4", to: "br4", predicate: "purchases_from", strength: 0.82, domain: ["consumer", "brand"] }, - { from: "cs5", to: "br3", predicate: "has_affinity_for", strength: 0.76, domain: ["consumer", "brand"] }, - { from: "cs5", to: "br5", predicate: "frequents", strength: 0.74, domain: ["consumer", "brand"] }, - // Consumer ↔ Retail - { from: "cs1", to: "rt2", predicate: "shops_via", strength: 0.82, domain: ["consumer", "retail"] }, - { from: "cs1", to: "rt4", predicate: "discovers_through", strength: 0.71, domain: ["consumer", "retail"] }, - { from: "cs2", to: "rt1", predicate: "shops_via", strength: 0.85, domain: ["consumer", "retail"] }, - { from: "cs2", to: "rt5", predicate: "member_of", strength: 0.90, domain: ["consumer", "retail"] }, - { from: "cs3", to: "rt3", predicate: "experiences", strength: 0.88, domain: ["consumer", "retail"] }, - { from: "cs3", to: "rt4", predicate: "discovers_through", strength: 0.79, domain: ["consumer", "retail"] }, - { from: "cs4", to: "rt1", predicate: "shops_via", strength: 0.94, domain: ["consumer", "retail"] }, - { from: "cs4", to: "rt3", predicate: "experiences", strength: 0.86, domain: ["consumer", "retail"] }, - { from: "cs5", to: "rt2", predicate: "shops_via", strength: 0.72, domain: ["consumer", "retail"] }, - { from: "cs5", to: "rt5", predicate: "member_of", strength: 0.68, domain: ["consumer", "retail"] }, - // Brand ↔ Retail - { from: "br1", to: "rt1", predicate: "merchandises_in", strength: 0.90, domain: ["brand", "retail"] }, - { from: "br1", to: "rt3", predicate: "activates_via", strength: 0.85, domain: ["brand", "retail"] }, - { from: "br2", to: "rt3", predicate: "activates_via", strength: 0.82, domain: ["brand", "retail"] }, - { from: "br2", to: "rt4", predicate: "promotes_on", strength: 0.75, domain: ["brand", "retail"] }, - { from: "br3", to: "rt2", predicate: "sells_through", strength: 0.80, domain: ["brand", "retail"] }, - { from: "br3", to: "rt5", predicate: "rewards_via", strength: 0.77, domain: ["brand", "retail"] }, - { from: "br4", to: "rt1", predicate: "merchandises_in", strength: 0.88, domain: ["brand", "retail"] }, - { from: "br4", to: "rt4", predicate: "promotes_on", strength: 0.70, domain: ["brand", "retail"] }, - { from: "br5", to: "rt3", predicate: "activates_via", strength: 0.79, domain: ["brand", "retail"] }, - { from: "br5", to: "rt5", predicate: "rewards_via", strength: 0.83, domain: ["brand", "retail"] }, - // Agent ↔ Consumer - { from: "ag1", to: "cs1", predicate: "recommends_to", strength: 0.87, domain: ["agent", "consumer"] }, - { from: "ag1", to: "cs3", predicate: "recommends_to", strength: 0.81, domain: ["agent", "consumer"] }, - { from: "ag2", to: "cs4", predicate: "personalizes_for", strength: 0.93, domain: ["agent", "consumer"] }, - { from: "ag2", to: "cs2", predicate: "personalizes_for", strength: 0.84, domain: ["agent", "consumer"] }, - { from: "ag4", to: "cs1", predicate: "monitors_sentiment_of", strength: 0.78, domain: ["agent", "consumer"] }, - { from: "ag4", to: "cs3", predicate: "monitors_sentiment_of", strength: 0.82, domain: ["agent", "consumer"] }, - { from: "ag5", to: "cs2", predicate: "optimizes_journey_for", strength: 0.86, domain: ["agent", "consumer"] }, - { from: "ag5", to: "cs5", predicate: "optimizes_journey_for", strength: 0.75, domain: ["agent", "consumer"] }, - // Agent ↔ Brand - { from: "ag3", to: "br1", predicate: "orchestrates_campaign_for", strength: 0.88, domain: ["agent", "brand"] }, - { from: "ag3", to: "br2", predicate: "orchestrates_campaign_for", strength: 0.82, domain: ["agent", "brand"] }, - { from: "ag3", to: "br5", predicate: "orchestrates_campaign_for", strength: 0.79, domain: ["agent", "brand"] }, - { from: "ag4", to: "br1", predicate: "analyzes_perception_of", strength: 0.91, domain: ["agent", "brand"] }, - { from: "ag4", to: "br3", predicate: "analyzes_perception_of", strength: 0.85, domain: ["agent", "brand"] }, - { from: "ag1", to: "br3", predicate: "curates_products_for", strength: 0.83, domain: ["agent", "brand"] }, - { from: "ag1", to: "br4", predicate: "curates_products_for", strength: 0.77, domain: ["agent", "brand"] }, - // Agent ↔ Retail - { from: "ag2", to: "rt1", predicate: "tailors_experience_at", strength: 0.89, domain: ["agent", "retail"] }, - { from: "ag2", to: "rt2", predicate: "tailors_experience_at", strength: 0.85, domain: ["agent", "retail"] }, - { from: "ag3", to: "rt3", predicate: "deploys_campaign_at", strength: 0.81, domain: ["agent", "retail"] }, - { from: "ag3", to: "rt4", predicate: "deploys_campaign_at", strength: 0.86, domain: ["agent", "retail"] }, - { from: "ag5", to: "rt1", predicate: "optimizes_flow_at", strength: 0.84, domain: ["agent", "retail"] }, - { from: "ag5", to: "rt5", predicate: "optimizes_flow_at", strength: 0.80, domain: ["agent", "retail"] }, -]; - -// ── Pre-built Agent Queries & Responses ──────────────────────────── -const DEMO_QUERIES = [ - { - q: "Which brands should activate at the Pop-Up Experience to reach Eco-Conscious Gen Z?", - thinking: [ - "Traversing ontology: Consumer[cs3] → has_affinity_for → Brand[br2, br4, br5]", - "Traversing ontology: Consumer[cs3] → experiences → Retail[rt3]", - "Cross-referencing: Brand activations at Retail[rt3]", - "Ranking by: affinity strength × activation fit × conversion potential", - ], - answer: "Nordic Trail and Artisan Coffee Co. are the strongest activation candidates for the Pop-Up Experience targeting Eco-Conscious Gen Z. Nordic Trail's sustainability positioning aligns with this segment's values (affinity: 0.71) and already activates via experiential retail (strength: 0.82). Artisan Coffee Co. has existing frequency with this segment (0.58) and pop-up activation experience (0.79). Casa Verde is a secondary candidate — lower affinity (0.65) but high experiential fit.", - entities: ["cs3", "br2", "br5", "br4", "rt3"], - triples: 8, - }, - { - q: "How should Lumière Beauty optimize its engagement with Luxury Seekers across channels?", - thinking: [ - "Resolving entities: Brand[br1] = Lumière Beauty, Consumer[cs4] = Luxury Seekers", - "Traversing: Brand[br1] → merchandises_in → Retail[rt1], activates_via → Retail[rt3]", - "Traversing: Consumer[cs4] → shops_via → Retail[rt1], experiences → Retail[rt3]", - "Agent context: Personalization Agent[ag2] → personalizes_for → Consumer[cs4] (0.93)", - "Aggregating channel overlap and engagement signals", - ], - answer: "Lumière Beauty has exceptional alignment with Luxury Seekers — the strongest brand-consumer affinity in the graph (0.92). Both converge on Flagship Store NYC and Pop-Up Experiences. The Personalization Agent already achieves 93% accuracy for this segment. Recommendation: Deepen the flagship in-store experience with agent-powered personalized consultations, and create exclusive pop-up previews. The loyalty-to-advocacy pipeline is strong (journey stage: Advocate) — leverage this for referral programs through the Loyalty Hub.", - entities: ["br1", "cs4", "rt1", "rt3", "ag2", "rt5"], - triples: 12, - }, - { - q: "What's the optimal agent configuration for the Mobile Commerce channel?", - thinking: [ - "Resolving: Retail[rt2] = Mobile Commerce App", - "Traversing: Agent → tailors_experience_at → Retail[rt2]", - "Traversing: Consumer → shops_via → Retail[rt2]: [cs1, cs5]", - "Traversing: Brand → sells_through → Retail[rt2]: [br3]", - "Evaluating agent capabilities against mobile channel requirements", - ], - answer: "Mobile Commerce currently has the Personalization Agent deployed (experience tailoring, 85% strength). Primary consumer segments are Urban Millennials (0.82) and Weekend Warriors (0.72). Add the Recommendation Agent — it already serves Urban Millennials (0.87) and can curate Velo Sport products (the channel's primary brand). The Journey Optimizer should be connected to reduce the gap between the channel's high traffic (1.2M/mo) and moderate conversion (4.7%). Projected improvement: 2.1% conversion lift through graph-informed product sequencing.", - entities: ["rt2", "ag2", "ag1", "ag5", "cs1", "cs5", "br3"], - triples: 11, - }, -]; - -// ── Helper: find all entities ──────────────────────────────────────── -function getAllEntities() { - const all = []; - Object.entries(ONTOLOGY).forEach(([domain, data]) => { - data.subclasses.forEach((sc) => { - all.push({ ...sc, domain, color: data.color, glow: data.glow, icon: data.icon }); - }); - }); - return all; -} - -// ── Graph Visualization (Canvas-based force layout) ───────────────── -function GraphCanvas({ highlightedEntities, onNodeClick, activeFilter }) { - const canvasRef = useRef(null); - const nodesRef = useRef([]); - const animRef = useRef(null); - const hoveredRef = useRef(null); - const [hovered, setHovered] = useState(null); - - const entities = useMemo(() => getAllEntities(), []); - - useEffect(() => { - const canvas = canvasRef.current; - if (!canvas) return; - const rect = canvas.parentElement.getBoundingClientRect(); - canvas.width = rect.width * 2; - canvas.height = rect.height * 2; - canvas.style.width = rect.width + "px"; - canvas.style.height = rect.height + "px"; - - const cx = canvas.width / 2; - const cy = canvas.height / 2; - - // Position nodes in domain clusters - const domainPositions = { - consumer: { x: cx - cx * 0.35, y: cy - cy * 0.32 }, - brand: { x: cx + cx * 0.35, y: cy - cy * 0.32 }, - retail: { x: cx + cx * 0.35, y: cy + cy * 0.32 }, - agent: { x: cx - cx * 0.35, y: cy + cy * 0.32 }, - }; - - nodesRef.current = entities.map((e, i) => { - const dp = domainPositions[e.domain]; - const subIdx = ONTOLOGY[e.domain].subclasses.findIndex((s) => s.id === e.id); - const total = ONTOLOGY[e.domain].subclasses.length; - const angle = ((Math.PI * 2) / total) * subIdx - Math.PI / 2; - const radius = Math.min(canvas.width, canvas.height) * 0.1; - return { - ...e, - x: dp.x + Math.cos(angle) * radius, - y: dp.y + Math.sin(angle) * radius, - vx: 0, - vy: 0, - targetX: dp.x + Math.cos(angle) * radius, - targetY: dp.y + Math.sin(angle) * radius, - r: 18, - }; - }); - - const ctx = canvas.getContext("2d"); - let time = 0; - - function draw() { - time += 0.005; - ctx.clearRect(0, 0, canvas.width, canvas.height); - - // Subtle grid - ctx.strokeStyle = "rgba(255,255,255,0.015)"; - ctx.lineWidth = 1; - for (let x = 0; x < canvas.width; x += 60) { - ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, canvas.height); ctx.stroke(); - } - for (let y = 0; y < canvas.height; y += 60) { - ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(canvas.width, y); ctx.stroke(); - } - - // Domain labels - Object.entries(domainPositions).forEach(([domain, pos]) => { - const data = ONTOLOGY[domain]; - ctx.font = "bold 22px 'IBM Plex Mono', monospace"; - ctx.fillStyle = data.color + "44"; - ctx.textAlign = "center"; - ctx.fillText(data.label.toUpperCase(), pos.x, pos.y - Math.min(canvas.width, canvas.height) * 0.14); - }); - - // Draw edges - const nodes = nodesRef.current; - const filteredRels = activeFilter - ? RELATIONSHIPS.filter((r) => r.domain.includes(activeFilter)) - : RELATIONSHIPS; - - filteredRels.forEach((rel) => { - const fromNode = nodes.find((n) => n.id === rel.from); - const toNode = nodes.find((n) => n.id === rel.to); - if (!fromNode || !toNode) return; - - const isHighlighted = - highlightedEntities && - highlightedEntities.includes(rel.from) && - highlightedEntities.includes(rel.to); - - const baseAlpha = isHighlighted ? 0.7 : 0.12; - const pulse = isHighlighted ? Math.sin(time * 4) * 0.15 + 0.15 : 0; - - ctx.beginPath(); - ctx.moveTo(fromNode.x, fromNode.y); - // Curved edges - const mx = (fromNode.x + toNode.x) / 2 + (fromNode.y - toNode.y) * 0.1; - const my = (fromNode.y + toNode.y) / 2 + (toNode.x - fromNode.x) * 0.1; - ctx.quadraticCurveTo(mx, my, toNode.x, toNode.y); - - const gradient = ctx.createLinearGradient(fromNode.x, fromNode.y, toNode.x, toNode.y); - gradient.addColorStop(0, fromNode.color + Math.round((baseAlpha + pulse) * 255).toString(16).padStart(2, "0")); - gradient.addColorStop(1, toNode.color + Math.round((baseAlpha + pulse) * 255).toString(16).padStart(2, "0")); - ctx.strokeStyle = gradient; - ctx.lineWidth = isHighlighted ? 3 : 1.5; - ctx.stroke(); - - // Animated particles on highlighted edges - if (isHighlighted) { - const t = (time * 2 + rel.strength) % 1; - const px = (1 - t) * (1 - t) * fromNode.x + 2 * (1 - t) * t * mx + t * t * toNode.x; - const py = (1 - t) * (1 - t) * fromNode.y + 2 * (1 - t) * t * my + t * t * toNode.y; - ctx.beginPath(); - ctx.arc(px, py, 3, 0, Math.PI * 2); - ctx.fillStyle = "#fff"; - ctx.fill(); - } - }); - - // Draw nodes - nodes.forEach((node) => { - const isHighlighted = highlightedEntities && highlightedEntities.includes(node.id); - const isHovered = hoveredRef.current === node.id; - const isDimmed = highlightedEntities && highlightedEntities.length > 0 && !isHighlighted; - const isFiltered = activeFilter && node.domain !== activeFilter && !RELATIONSHIPS.some( - r => r.domain.includes(activeFilter) && (r.from === node.id || r.to === node.id) - ); - - const alpha = isFiltered ? 0.15 : isDimmed ? 0.3 : 1; - const r = isHighlighted || isHovered ? node.r * 1.4 : node.r; - const pulseR = isHighlighted ? Math.sin(time * 3) * 3 : 0; - - // Glow - if ((isHighlighted || isHovered) && !isFiltered) { - ctx.beginPath(); - ctx.arc(node.x, node.y, r + 12 + pulseR, 0, Math.PI * 2); - const grd = ctx.createRadialGradient(node.x, node.y, r, node.x, node.y, r + 12 + pulseR); - grd.addColorStop(0, node.glow); - grd.addColorStop(1, "rgba(0,0,0,0)"); - ctx.fillStyle = grd; - ctx.fill(); - } - - // Node circle - ctx.beginPath(); - ctx.arc(node.x, node.y, r, 0, Math.PI * 2); - ctx.fillStyle = node.color + Math.round(alpha * 255 * 0.2).toString(16).padStart(2, "0"); - ctx.fill(); - ctx.strokeStyle = node.color + Math.round(alpha * 255).toString(16).padStart(2, "0"); - ctx.lineWidth = isHighlighted ? 2.5 : 1.5; - ctx.stroke(); - - // Label - ctx.font = `${isHighlighted ? "bold " : ""}${isHovered ? 17 : 14}px 'IBM Plex Sans', sans-serif`; - ctx.fillStyle = `rgba(255,255,255,${alpha * (isHighlighted ? 1 : 0.75)})`; - ctx.textAlign = "center"; - ctx.fillText(node.label, node.x, node.y + r + 18); - - // Spring physics - node.x += (node.targetX - node.x) * 0.02; - node.y += (node.targetY - node.y) * 0.02; - node.x += Math.sin(time + node.targetX * 0.01) * 0.3; - node.y += Math.cos(time + node.targetY * 0.01) * 0.3; - }); - - animRef.current = requestAnimationFrame(draw); - } - - draw(); - return () => cancelAnimationFrame(animRef.current); - }, [entities, highlightedEntities, activeFilter]); - - const handleMouseMove = useCallback((e) => { - const canvas = canvasRef.current; - const rect = canvas.getBoundingClientRect(); - const x = (e.clientX - rect.left) * 2; - const y = (e.clientY - rect.top) * 2; - const nodes = nodesRef.current; - let found = null; - for (const node of nodes) { - const dx = node.x - x; - const dy = node.y - y; - if (Math.sqrt(dx * dx + dy * dy) < node.r * 1.5) { - found = node.id; - break; - } - } - hoveredRef.current = found; - setHovered(found); - canvas.style.cursor = found ? "pointer" : "default"; - }, []); - - const handleClick = useCallback((e) => { - if (hoveredRef.current && onNodeClick) { - const node = nodesRef.current.find((n) => n.id === hoveredRef.current); - if (node) onNodeClick(node); - } - }, [onNodeClick]); - - return ( -
- - {hovered && (() => { - const node = nodesRef.current.find((n) => n.id === hovered); - if (!node) return null; - const canvas = canvasRef.current; - const rect = canvas.getBoundingClientRect(); - const sx = node.x / 2; - const sy = node.y / 2; - return ( -
-
- {node.icon} {node.label} -
-
- {Object.entries(node.props || {}).map(([k, v]) => ( -
{k}: {String(v)}
- ))} -
-
- ); - })()} -
- ); -} - -// ── Typewriter Effect ──────────────────────────────────────────────── -function Typewriter({ text, speed = 12, onDone }) { - const [displayed, setDisplayed] = useState(""); - const idx = useRef(0); - - useEffect(() => { - idx.current = 0; - setDisplayed(""); - const interval = setInterval(() => { - idx.current++; - if (idx.current >= text.length) { - setDisplayed(text); - clearInterval(interval); - onDone && onDone(); - } else { - setDisplayed(text.slice(0, idx.current)); - } - }, speed); - return () => clearInterval(interval); - }, [text, speed]); - - return {displayed}; -} - -// ── Main App ───────────────────────────────────────────────────────── -export default function TrustGraphRetailDemo() { - const [activeTab, setActiveTab] = useState("graph"); - const [activeFilter, setActiveFilter] = useState(null); - const [selectedQuery, setSelectedQuery] = useState(null); - const [queryPhase, setQueryPhase] = useState("idle"); // idle, thinking, answering, done - const [thinkingStep, setThinkingStep] = useState(0); - const [selectedNode, setSelectedNode] = useState(null); - const [showOntology, setShowOntology] = useState(false); - - const runQuery = (idx) => { - setSelectedQuery(idx); - setQueryPhase("thinking"); - setThinkingStep(0); - const q = DEMO_QUERIES[idx]; - let step = 0; - const interval = setInterval(() => { - step++; - if (step >= q.thinking.length) { - clearInterval(interval); - setTimeout(() => setQueryPhase("answering"), 400); - } - setThinkingStep(step); - }, 800); - }; - - const highlightedEntities = selectedQuery !== null && queryPhase !== "idle" - ? DEMO_QUERIES[selectedQuery].entities - : selectedNode - ? [selectedNode.id, ...RELATIONSHIPS.filter(r => r.from === selectedNode.id || r.to === selectedNode.id).map(r => r.from === selectedNode.id ? r.to : r.from)] - : []; - - return ( -
- {/* ── Header ────────────────────────────────────────── */} -
-
-
TG
-
-
- TrustGraph -
-
- RETAIL INTELLIGENCE PLATFORM -
-
-
-
- {["graph", "query", "ontology"].map((tab) => ( - - ))} -
-
- - {/* ── Domain Filter Bar ──────────────────────────────── */} - {activeTab === "graph" && ( -
- FILTER: - - {Object.entries(ONTOLOGY).map(([key, data]) => ( - - ))} -
- {getAllEntities().length} entities · {RELATIONSHIPS.length} relationships -
-
- )} - - {/* ── Main Content ──────────────────────────────────── */} -
- - {/* ── Graph View ──────────────────────────────────── */} - {activeTab === "graph" && ( - <> -
- setSelectedNode(selectedNode?.id === node.id ? null : node)} - activeFilter={activeFilter} - /> -
- {/* Side panel for selected node */} - {selectedNode && ( -
-
-
- {ONTOLOGY[selectedNode.domain].label.toUpperCase()} ENTITY -
- -
-
- {selectedNode.icon} {selectedNode.label} -
-
-
PROPERTIES
- {Object.entries(selectedNode.props || {}).map(([k, v]) => ( -
- {k} - {String(v)} -
- ))} -
-
-
RELATIONSHIPS
- {RELATIONSHIPS.filter(r => r.from === selectedNode.id || r.to === selectedNode.id).map((r, i) => { - const otherId = r.from === selectedNode.id ? r.to : r.from; - const other = getAllEntities().find(e => e.id === otherId); - const direction = r.from === selectedNode.id ? "→" : "←"; - return ( -
{ const n = getAllEntities().find(e => e.id === otherId); if (n) setSelectedNode(n); }}> -
- {direction} {other?.label} -
-
- {r.predicate.replace(/_/g, " ")} · strength: {r.strength} -
-
- ); - })} -
-
- )} - - )} - - {/* ── Agent Query View ────────────────────────────── */} - {activeTab === "query" && ( -
-
- {/* Query selector */} -
-
- SELECT A QUERY TO SEE GRAPH-POWERED AGENT INTELLIGENCE -
- {DEMO_QUERIES.map((dq, idx) => ( - - ))} -
- - {/* Response area */} - {selectedQuery !== null && ( -
- {/* Graph traversal steps */} - {(queryPhase === "thinking" || queryPhase === "answering" || queryPhase === "done") && ( -
-
- ◈ GRAPH TRAVERSAL -
- {DEMO_QUERIES[selectedQuery].thinking.map((step, i) => ( -
- {i < thinkingStep && } - {step} -
- ))} - {queryPhase === "thinking" && ( -
- Traversing graph... -
- )} -
- )} - - {/* Answer */} - {(queryPhase === "answering" || queryPhase === "done") && ( -
-
-
- AGENT RESPONSE -
-
- {DEMO_QUERIES[selectedQuery].triples} triples traversed · {DEMO_QUERIES[selectedQuery].entities.length} entities resolved -
-
-
- setQueryPhase("done")} - /> -
-
- )} -
- )} -
- - {/* Graph visualization alongside query */} -
- {}} activeFilter={null} /> -
-
- )} - - {/* ── Ontology View ──────────────────────────────── */} - {activeTab === "ontology" && ( -
-
-
- ONTOLOGY SCHEMA · RETAIL INTELLIGENCE DOMAIN -
- - {/* Ontology class cards */} -
- {Object.entries(ONTOLOGY).map(([key, data]) => ( -
-
- {data.icon} -
-
{data.label}
-
owl:Class
-
-
-
{data.description}
-
PROPERTIES
-
- {data.properties.map((p) => ( - {p} - ))} -
-
- INSTANCES ({data.subclasses.length}) -
- {data.subclasses.map((sc) => ( -
- {sc.label} - {sc.id} -
- ))} -
- ))} -
- - {/* Relationship predicates */} -
-
- RELATIONSHIP PREDICATES -
-
- {[...new Set(RELATIONSHIPS.map(r => r.predicate))].map((pred) => { - const sample = RELATIONSHIPS.find(r => r.predicate === pred); - const fromDomain = sample.domain[0]; - const toDomain = sample.domain[1]; - return ( -
-
- {pred.replace(/_/g, " ")} -
-
- {ONTOLOGY[fromDomain].label} - {" → "} - {ONTOLOGY[toDomain].label} -
-
- ); - })} -
-
- - {/* Triple count summary */} -
- {[ - { label: "Classes", value: 4 }, - { label: "Instances", value: getAllEntities().length }, - { label: "Predicates", value: [...new Set(RELATIONSHIPS.map(r => r.predicate))].length }, - { label: "Triples", value: RELATIONSHIPS.length }, - ].map((s) => ( -
-
{s.value}
-
{s.label.toUpperCase()}
-
- ))} -
-
-
- )} -
- - {/* ── Bottom Status Bar ──────────────────────────────── */} -
-
- ◈ Ontology: Consumer × Agent × Retail × Brand - ⬡ GraphRAG: Active - ⚡ Agent Orchestration: Online -
-
- Context Graph Connected - | - trustgraph.ai -
-
-
- ); -} diff --git a/ai-context/trustgraph-templates/tsconfig.json b/ai-context/trustgraph-templates/tsconfig.json deleted file mode 100644 index a4c834a6..00000000 --- a/ai-context/trustgraph-templates/tsconfig.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2020", - "useDefineForClassFields": true, - "lib": ["ES2020", "DOM", "DOM.Iterable"], - "module": "ESNext", - "skipLibCheck": true, - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true - }, - "include": ["src"] -} diff --git a/ai-context/trustgraph-templates/vite.config.js b/ai-context/trustgraph-templates/vite.config.js deleted file mode 100644 index fba45ddf..00000000 --- a/ai-context/trustgraph-templates/vite.config.js +++ /dev/null @@ -1,39 +0,0 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' -import path from 'path' - -// https://vite.dev/config/ -export default defineConfig({ - plugins: [react()], - resolve: { - dedupe: ['react', 'react-dom', '@tanstack/react-query'], - alias: { - react: path.resolve('./node_modules/react'), - 'react-dom': path.resolve('./node_modules/react-dom'), - }, - }, - server: { - proxy: { - "/api/socket": { - // target: "wss://broker.app.trustgraph.ai/", - target: "ws://localhost:8088/", - changeOrigin: true, - ws: true, - secure: false, - rewrite: (path) => path.replace("/api/socket", "/api/v1/socket"), - }, - "/api/export-core": { - target: "http://localhost:8088/", - changeOrigin: true, - secure: false, - rewrite: (x) => x.replace("/api/export-core", "/api/v1/export-core"), - }, - "/api/import-core": { - target: "http://localhost:8088/", - changeOrigin: true, - secure: false, - rewrite: (x) => x.replace("/api/import-core", "/api/v1/import-core"), - }, - }, - }, -}) diff --git a/tests/unit/test_gateway/test_graph_rag_request_translator.py b/tests/unit/test_gateway/test_graph_rag_request_translator.py new file mode 100644 index 00000000..72820c39 --- /dev/null +++ b/tests/unit/test_gateway/test_graph_rag_request_translator.py @@ -0,0 +1,54 @@ +from trustgraph.messaging.translators.retrieval import GraphRagRequestTranslator + + +class TestGraphRagRequestTranslator: + def test_accepts_hyphenated_keys(self): + translator = GraphRagRequestTranslator() + + result = translator.to_pulsar( + { + "query": "q", + "user": "u", + "collection": "c", + "entity-limit": 8, + "triple-limit": 16, + "max-subgraph-size": 24, + "max-path-length": 2, + "edge-score-limit": 12, + "edge-limit": 6, + } + ) + + assert result.entity_limit == 8 + assert result.triple_limit == 16 + assert result.max_subgraph_size == 24 + assert result.max_path_length == 2 + assert result.edge_score_limit == 12 + assert result.edge_limit == 6 + + def test_accepts_snake_case_aliases_and_uses_aligned_default(self): + translator = GraphRagRequestTranslator() + + result = translator.to_pulsar( + { + "query": "q", + "user": "u", + "collection": "c", + "entity_limit": 5, + "triple_limit": 7, + "max_subgraph_size": 9, + "max_path_length": 1, + "edge_score_limit": 11, + "edge_limit": 3, + } + ) + + assert result.entity_limit == 5 + assert result.triple_limit == 7 + assert result.max_subgraph_size == 9 + assert result.max_path_length == 1 + assert result.edge_score_limit == 11 + assert result.edge_limit == 3 + + defaulted = translator.to_pulsar({"query": "q"}) + assert defaulted.max_subgraph_size == 150 diff --git a/tests/unit/test_mcp/test_load_document.py b/tests/unit/test_mcp/test_load_document.py new file mode 100644 index 00000000..54184a59 --- /dev/null +++ b/tests/unit/test_mcp/test_load_document.py @@ -0,0 +1,49 @@ +from types import SimpleNamespace +from unittest.mock import AsyncMock, MagicMock, patch +import base64 + +import pytest + +from trustgraph.mcp_server.mcp import McpServer, encode_document_content + + +class TestEncodeDocumentContent: + def test_encodes_text_payloads(self): + encoded = encode_document_content("hello world", "text/plain") + assert encoded == base64.b64encode(b"hello world").decode("utf-8") + + def test_passes_through_binary_payloads(self): + pdf_payload = "JVBERi0xLjQKJcTl8uXrpA==" + assert encode_document_content(pdf_payload, "application/pdf") == pdf_payload + + +@pytest.mark.asyncio +class TestLoadDocument: + @patch("trustgraph.mcp_server.mcp.get_socket_manager") + async def test_load_document_base64_encodes_text_content(self, mock_get_socket_manager): + server = McpServer() + + manager = MagicMock() + manager.request.return_value = self._single_response() + mock_get_socket_manager.return_value = manager + + ctx = SimpleNamespace( + request_id="req-123", + session=SimpleNamespace(send_log_message=AsyncMock()), + ) + + await server.load_document( + document="plain text body", + document_id="doc-1", + mime_type="text/plain", + title="Example", + user="trustgraph", + ctx=ctx, + ) + + manager.request.assert_called_once() + _, request_data, _ = manager.request.call_args.args + assert request_data["content"] == base64.b64encode(b"plain text body").decode("utf-8") + + async def _single_response(self): + yield {} diff --git a/trustgraph/trustgraph/trustgraph_version.py b/trustgraph/trustgraph/trustgraph_version.py new file mode 100644 index 00000000..0212ffee --- /dev/null +++ b/trustgraph/trustgraph/trustgraph_version.py @@ -0,0 +1 @@ +__version__ = "2.2.17" diff --git a/ts/.gitignore b/ts/.gitignore new file mode 100644 index 00000000..0c506cc9 --- /dev/null +++ b/ts/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +dist/ +*.tsbuildinfo +.turbo/ diff --git a/ts/package.json b/ts/package.json new file mode 100644 index 00000000..57dbb915 --- /dev/null +++ b/ts/package.json @@ -0,0 +1,16 @@ +{ + "name": "trustgraph-ts", + "private": true, + "scripts": { + "build": "turbo build", + "dev": "turbo dev", + "lint": "turbo lint", + "test": "turbo test", + "clean": "turbo clean" + }, + "devDependencies": { + "turbo": "^2.5.0", + "typescript": "^5.8.0" + }, + "packageManager": "pnpm@9.15.0" +} diff --git a/ts/packages/base/package.json b/ts/packages/base/package.json new file mode 100644 index 00000000..070e7e80 --- /dev/null +++ b/ts/packages/base/package.json @@ -0,0 +1,21 @@ +{ + "name": "@trustgraph/base", + "version": "0.1.0", + "type": "module", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "tsc", + "dev": "tsc --watch", + "clean": "rm -rf dist", + "test": "vitest run" + }, + "dependencies": { + "nats": "^2.29.0", + "prom-client": "^15.1.0" + }, + "devDependencies": { + "typescript": "^5.8.0", + "vitest": "^3.1.0" + } +} diff --git a/ts/packages/base/src/backend/index.ts b/ts/packages/base/src/backend/index.ts new file mode 100644 index 00000000..d2c5b714 --- /dev/null +++ b/ts/packages/base/src/backend/index.ts @@ -0,0 +1,12 @@ +export type { + Message, + BackendProducer, + BackendConsumer, + PubSubBackend, + CreateProducerOptions, + CreateConsumerOptions, + ConsumerType, + InitialPosition, +} from "./types.js"; + +export { NatsBackend } from "./nats.js"; diff --git a/ts/packages/base/src/backend/nats.ts b/ts/packages/base/src/backend/nats.ts new file mode 100644 index 00000000..7fc4bf43 --- /dev/null +++ b/ts/packages/base/src/backend/nats.ts @@ -0,0 +1,196 @@ +/** + * NATS JetStream backend implementation. + * + * Replaces Pulsar as the message broker. NATS JetStream provides + * at-least-once delivery, consumer groups, and replay — matching + * the QoS levels used by the Python Pulsar backend. + * + * Python reference: trustgraph-base/trustgraph/base/pulsar_backend.py + */ + +import { + connect, + type NatsConnection, + type JetStreamClient, + type JetStreamManager, + type ConsumerMessages, + type JsMsg, + StringCodec, + AckPolicy, +} from "nats"; + +import type { + PubSubBackend, + BackendProducer, + BackendConsumer, + CreateProducerOptions, + CreateConsumerOptions, + Message, +} from "./types.js"; + +const sc = StringCodec(); + +class NatsMessage implements Message { + constructor( + private readonly msg: JsMsg, + private readonly decoded: T, + ) {} + + value(): T { + return this.decoded; + } + + properties(): Record { + const headers = this.msg.headers; + const props: Record = {}; + if (headers) { + for (const [key, values] of headers) { + props[key] = values[0]; + } + } + return props; + } +} + +class NatsProducer implements BackendProducer { + constructor( + private readonly js: JetStreamClient, + private readonly subject: string, + ) {} + + async send(message: T, properties?: Record): Promise { + const data = sc.encode(JSON.stringify(message)); + const opts: Record = {}; + + if (properties && Object.keys(properties).length > 0) { + const { headers } = await import("nats"); + const hdrs = headers(); + for (const [key, val] of Object.entries(properties)) { + hdrs.append(key, val); + } + opts.headers = hdrs; + } + + await this.js.publish(this.subject, data, opts); + } + + async flush(): Promise { + // NATS publishes are flushed on the connection level + } + + async close(): Promise { + // No per-producer cleanup needed for NATS + } +} + +class NatsConsumer implements BackendConsumer { + private messages: ConsumerMessages | null = null; + + constructor( + private readonly js: JetStreamClient, + private readonly jsm: JetStreamManager, + private readonly subject: string, + private readonly subscription: string, + private readonly initialPosition: "latest" | "earliest", + ) {} + + async init(): Promise { + // Ensure stream exists + const streamName = this.streamNameFromSubject(this.subject); + try { + await this.jsm.streams.info(streamName); + } catch { + await this.jsm.streams.add({ + name: streamName, + subjects: [this.subject], + }); + } + + // Create or bind to durable consumer + const consumer = await this.js.consumers.get(streamName, this.subscription); + this.messages = await consumer.consume(); + } + + async receive(timeoutMs = 2000): Promise | null> { + if (!this.messages) throw new Error("Consumer not initialized"); + + const deadline = Date.now() + timeoutMs; + for await (const msg of this.messages) { + const decoded = JSON.parse(sc.decode(msg.data)) as T; + return new NatsMessage(msg, decoded); + } + + if (Date.now() >= deadline) return null; + return null; + } + + async acknowledge(message: Message): Promise { + const natsMsg = message as NatsMessage; + // Access internal JsMsg for ack — in practice we'd store the ref + // This is a simplified version; real impl tracks msg refs + void natsMsg; + } + + async negativeAcknowledge(message: Message): Promise { + void message; + } + + async unsubscribe(): Promise { + // Drain and close consumer + } + + async close(): Promise { + if (this.messages) { + this.messages.stop(); + } + } + + private streamNameFromSubject(subject: string): string { + // Convert topic like "tg.flow.text-completion" to stream name "tg_flow" + const parts = subject.split("."); + return parts.slice(0, 2).join("_"); + } +} + +export class NatsBackend implements PubSubBackend { + private connection: NatsConnection | null = null; + private js: JetStreamClient | null = null; + private jsm: JetStreamManager | null = null; + + constructor(private readonly url: string = "nats://localhost:4222") {} + + private async ensureConnected(): Promise { + if (!this.connection) { + this.connection = await connect({ servers: this.url }); + this.js = this.connection.jetstream(); + this.jsm = await this.connection.jetstreamManager(); + } + } + + async createProducer(options: CreateProducerOptions): Promise> { + await this.ensureConnected(); + return new NatsProducer(this.js!, options.topic); + } + + async createConsumer(options: CreateConsumerOptions): Promise> { + await this.ensureConnected(); + const consumer = new NatsConsumer( + this.js!, + this.jsm!, + options.topic, + options.subscription, + options.initialPosition ?? "latest", + ); + await consumer.init(); + return consumer; + } + + async close(): Promise { + if (this.connection) { + await this.connection.drain(); + this.connection = null; + this.js = null; + this.jsm = null; + } + } +} diff --git a/ts/packages/base/src/backend/types.ts b/ts/packages/base/src/backend/types.ts new file mode 100644 index 00000000..6f5640c2 --- /dev/null +++ b/ts/packages/base/src/backend/types.ts @@ -0,0 +1,45 @@ +/** + * Core pub/sub backend abstraction. + * + * Mirrors Python's backend.py Protocol classes. Any message broker + * (NATS, Pulsar, Redis Streams) implements these interfaces. + */ + +export interface Message { + value(): T; + properties(): Record; +} + +export interface BackendProducer { + send(message: T, properties?: Record): Promise; + flush(): Promise; + close(): Promise; +} + +export interface BackendConsumer { + receive(timeoutMs?: number): Promise | null>; + acknowledge(message: Message): Promise; + negativeAcknowledge(message: Message): Promise; + unsubscribe(): Promise; + close(): Promise; +} + +export type ConsumerType = "shared" | "exclusive" | "failover"; +export type InitialPosition = "latest" | "earliest"; + +export interface CreateProducerOptions { + topic: string; +} + +export interface CreateConsumerOptions { + topic: string; + subscription: string; + initialPosition?: InitialPosition; + consumerType?: ConsumerType; +} + +export interface PubSubBackend { + createProducer(options: CreateProducerOptions): Promise>; + createConsumer(options: CreateConsumerOptions): Promise>; + close(): Promise; +} diff --git a/ts/packages/base/src/errors.ts b/ts/packages/base/src/errors.ts new file mode 100644 index 00000000..e6be4fd8 --- /dev/null +++ b/ts/packages/base/src/errors.ts @@ -0,0 +1,29 @@ +/** + * Custom error types. + * + * Python reference: trustgraph-base/trustgraph/exceptions.py + */ + +export class TooManyRequestsError extends Error { + constructor(message = "Rate limit exceeded") { + super(message); + this.name = "TooManyRequestsError"; + } +} + +export class LlmError extends Error { + constructor( + message: string, + public readonly errorType: string = "llm-error", + ) { + super(message); + this.name = "LlmError"; + } +} + +export class ParseError extends Error { + constructor(message: string) { + super(message); + this.name = "ParseError"; + } +} diff --git a/ts/packages/base/src/index.ts b/ts/packages/base/src/index.ts new file mode 100644 index 00000000..2bd141ec --- /dev/null +++ b/ts/packages/base/src/index.ts @@ -0,0 +1,10 @@ +// @trustgraph/base — core abstractions for the TrustGraph TypeScript port + +export * from "./backend/index.js"; +export * from "./messaging/index.js"; +export * from "./processor/index.js"; +export * from "./schema/index.js"; +export * from "./spec/index.js"; +export * from "./services/index.js"; +export * from "./metrics/index.js"; +export * from "./errors.js"; diff --git a/ts/packages/base/src/messaging/consumer.ts b/ts/packages/base/src/messaging/consumer.ts new file mode 100644 index 00000000..4282f3b9 --- /dev/null +++ b/ts/packages/base/src/messaging/consumer.ts @@ -0,0 +1,105 @@ +/** + * High-level consumer with concurrency, retry, and rate-limit handling. + * + * Python reference: trustgraph-base/trustgraph/base/consumer.py + */ + +import type { PubSubBackend, BackendConsumer, Message } from "../backend/types.js"; +import { TooManyRequestsError } from "../errors.js"; + +export type MessageHandler = ( + message: T, + properties: Record, + flow: FlowContext, +) => Promise; + +export interface FlowContext { + id: string; + name: string; +} + +export interface ConsumerOptions { + pubsub: PubSubBackend; + topic: string; + subscription: string; + handler: MessageHandler; + concurrency?: number; + initialPosition?: "latest" | "earliest"; + rateLimitRetryMs?: number; + rateLimitTimeoutMs?: number; +} + +export class Consumer { + private backend: BackendConsumer | null = null; + private running = false; + private abortController = new AbortController(); + + private readonly concurrency: number; + private readonly rateLimitRetryMs: number; + + constructor(private readonly options: ConsumerOptions) { + this.concurrency = options.concurrency ?? 1; + this.rateLimitRetryMs = options.rateLimitRetryMs ?? 10_000; + } + + async start(flow: FlowContext): Promise { + this.backend = await this.options.pubsub.createConsumer({ + topic: this.options.topic, + subscription: this.options.subscription, + initialPosition: this.options.initialPosition ?? "latest", + }); + + this.running = true; + + // Spawn concurrent consumer tasks + const tasks = Array.from({ length: this.concurrency }, () => + this.consumeLoop(flow), + ); + // Run all concurrently — first rejection stops all + await Promise.all(tasks); + } + + async stop(): Promise { + this.running = false; + this.abortController.abort(); + if (this.backend) { + await this.backend.close(); + this.backend = null; + } + } + + private async consumeLoop(flow: FlowContext): Promise { + while (this.running) { + try { + const msg = await this.backend!.receive(2000); + if (!msg) continue; + + await this.handleWithRetry(msg, flow); + await this.backend!.acknowledge(msg); + } catch (err) { + if (!this.running) break; + console.error("[Consumer] Error in consume loop:", err); + // Brief pause before retry + await sleep(1000); + } + } + } + + private async handleWithRetry(msg: Message, flow: FlowContext): Promise { + try { + await this.options.handler(msg.value(), msg.properties(), flow); + } catch (err) { + if (err instanceof TooManyRequestsError) { + console.warn(`[Consumer] Rate limited, retrying in ${this.rateLimitRetryMs}ms`); + await sleep(this.rateLimitRetryMs); + await this.options.handler(msg.value(), msg.properties(), flow); + } else { + throw err; + } + } + } +} + +function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} diff --git a/ts/packages/base/src/messaging/index.ts b/ts/packages/base/src/messaging/index.ts new file mode 100644 index 00000000..74b2809d --- /dev/null +++ b/ts/packages/base/src/messaging/index.ts @@ -0,0 +1,4 @@ +export { Producer } from "./producer.js"; +export { Consumer, type MessageHandler, type FlowContext, type ConsumerOptions } from "./consumer.js"; +export { Subscriber, AsyncQueue } from "./subscriber.js"; +export { RequestResponse, type RequestResponseOptions } from "./request-response.js"; diff --git a/ts/packages/base/src/messaging/producer.ts b/ts/packages/base/src/messaging/producer.ts new file mode 100644 index 00000000..7e45d5d0 --- /dev/null +++ b/ts/packages/base/src/messaging/producer.ts @@ -0,0 +1,40 @@ +/** + * High-level async producer with queue buffering and retry. + * + * Python reference: trustgraph-base/trustgraph/base/producer.py + */ + +import type { PubSubBackend, BackendProducer } from "../backend/types.js"; +import type { ProducerMetrics } from "../metrics/prometheus.js"; + +export class Producer { + private backend: BackendProducer | null = null; + private running = false; + + constructor( + private readonly pubsub: PubSubBackend, + private readonly topic: string, + private readonly metrics?: ProducerMetrics, + ) {} + + async start(): Promise { + this.backend = await this.pubsub.createProducer({ topic: this.topic }); + this.running = true; + } + + async send(id: string, message: T): Promise { + if (!this.backend) throw new Error("Producer not started"); + + await this.backend.send(message, { id }); + this.metrics?.inc(); + } + + async stop(): Promise { + this.running = false; + if (this.backend) { + await this.backend.flush(); + await this.backend.close(); + this.backend = null; + } + } +} diff --git a/ts/packages/base/src/messaging/request-response.ts b/ts/packages/base/src/messaging/request-response.ts new file mode 100644 index 00000000..2c570c68 --- /dev/null +++ b/ts/packages/base/src/messaging/request-response.ts @@ -0,0 +1,91 @@ +/** + * Request/response pattern over pub/sub. + * + * Sends a request with a unique ID, subscribes for matching responses. + * Supports streaming (multiple responses per request) via a recipient callback. + * + * Python reference: trustgraph-base/trustgraph/base/request_response_spec.py + */ + +import { randomUUID } from "node:crypto"; +import { Producer } from "./producer.js"; +import { Subscriber } from "./subscriber.js"; +import type { PubSubBackend } from "../backend/types.js"; + +export interface RequestResponseOptions { + pubsub: PubSubBackend; + requestTopic: string; + responseTopic: string; + subscription: string; +} + +export class RequestResponse { + private producer: Producer; + private subscriber: Subscriber; + + constructor(private readonly options: RequestResponseOptions) { + this.producer = new Producer(options.pubsub, options.requestTopic); + this.subscriber = new Subscriber( + options.pubsub, + options.responseTopic, + options.subscription, + ); + } + + async start(): Promise { + await this.producer.start(); + await this.subscriber.start(); + } + + async stop(): Promise { + await this.producer.stop(); + await this.subscriber.stop(); + } + + /** + * Send a request and wait for responses. + * + * @param request - The request payload + * @param options.timeoutMs - Total timeout in milliseconds (default: 300s) + * @param options.recipient - Optional callback for streaming responses. + * Return `true` to indicate the final response has been received. + * If omitted, returns the first response. + */ + async request( + request: TReq, + options?: { + timeoutMs?: number; + recipient?: (response: TRes) => Promise; + }, + ): Promise { + const id = randomUUID(); + const timeoutMs = options?.timeoutMs ?? 300_000; + const recipient = options?.recipient; + + const queue = this.subscriber.subscribe(id); + + try { + await this.producer.send(id, request); + + const deadline = Date.now() + timeoutMs; + + while (true) { + const remaining = deadline - Date.now(); + if (remaining <= 0) { + throw new Error(`Request timed out after ${timeoutMs}ms`); + } + + const response = await queue.pop(remaining); + + if (recipient) { + const isFinal = await recipient(response); + if (isFinal) return response; + } else { + return response; + } + } + } finally { + this.subscriber.unsubscribe(id); + } + } +} diff --git a/ts/packages/base/src/messaging/subscriber.ts b/ts/packages/base/src/messaging/subscriber.ts new file mode 100644 index 00000000..1d685a91 --- /dev/null +++ b/ts/packages/base/src/messaging/subscriber.ts @@ -0,0 +1,143 @@ +/** + * Fan-out subscriber: routes responses to waiting callers by request ID. + * + * Python reference: trustgraph-base/trustgraph/base/subscriber.py + */ + +import type { PubSubBackend, BackendConsumer } from "../backend/types.js"; + +type Resolver = { + queue: AsyncQueue; +}; + +/** + * Simple async queue for inter-task communication (replaces asyncio.Queue). + */ +export class AsyncQueue { + private buffer: T[] = []; + private waiters: Array<(value: T) => void> = []; + + push(item: T): void { + const waiter = this.waiters.shift(); + if (waiter) { + waiter(item); + } else { + this.buffer.push(item); + } + } + + async pop(timeoutMs?: number): Promise { + const buffered = this.buffer.shift(); + if (buffered !== undefined) return buffered; + + return new Promise((resolve, reject) => { + let timer: ReturnType | undefined; + + const waiter = (value: T) => { + if (timer) clearTimeout(timer); + resolve(value); + }; + + this.waiters.push(waiter); + + if (timeoutMs !== undefined) { + timer = setTimeout(() => { + const idx = this.waiters.indexOf(waiter); + if (idx !== -1) this.waiters.splice(idx, 1); + reject(new Error(`Queue.pop timed out after ${timeoutMs}ms`)); + }, timeoutMs); + } + }); + } + + get length(): number { + return this.buffer.length; + } +} + +export class Subscriber { + private backend: BackendConsumer | null = null; + private running = false; + + // ID-specific subscriptions (request/response correlation) + private idSubscribers = new Map>(); + // Wildcard subscribers (receive all messages) + private allSubscribers = new Map>(); + + constructor( + private readonly pubsub: PubSubBackend, + private readonly topic: string, + private readonly subscription: string, + ) {} + + async start(): Promise { + this.backend = await this.pubsub.createConsumer({ + topic: this.topic, + subscription: this.subscription, + }); + this.running = true; + // Start the dispatch loop (fire and forget — runs until stop) + this.dispatchLoop().catch((err) => { + if (this.running) console.error("[Subscriber] dispatch loop error:", err); + }); + } + + async stop(): Promise { + this.running = false; + if (this.backend) { + await this.backend.close(); + this.backend = null; + } + } + + subscribe(id: string): AsyncQueue { + const queue = new AsyncQueue(); + this.idSubscribers.set(id, { queue }); + return queue; + } + + subscribeAll(id: string): AsyncQueue { + const queue = new AsyncQueue(); + this.allSubscribers.set(id, { queue }); + return queue; + } + + unsubscribe(id: string): void { + this.idSubscribers.delete(id); + } + + unsubscribeAll(id: string): void { + this.allSubscribers.delete(id); + } + + private async dispatchLoop(): Promise { + while (this.running) { + try { + const msg = await this.backend!.receive(2000); + if (!msg) continue; + + const props = msg.properties(); + const id = props.id; + const value = msg.value(); + + // Route to ID-specific subscriber + if (id) { + const sub = this.idSubscribers.get(id); + if (sub) { + sub.queue.push(value); + } + } + + // Broadcast to all-subscribers + for (const sub of this.allSubscribers.values()) { + sub.queue.push(value); + } + + await this.backend!.acknowledge(msg); + } catch (err) { + if (!this.running) break; + console.error("[Subscriber] Error:", err); + } + } + } +} diff --git a/ts/packages/base/src/metrics/index.ts b/ts/packages/base/src/metrics/index.ts new file mode 100644 index 00000000..ccff4d2b --- /dev/null +++ b/ts/packages/base/src/metrics/index.ts @@ -0,0 +1 @@ +export { ConsumerMetrics, ProducerMetrics, registry } from "./prometheus.js"; diff --git a/ts/packages/base/src/metrics/prometheus.ts b/ts/packages/base/src/metrics/prometheus.ts new file mode 100644 index 00000000..ebaa30bf --- /dev/null +++ b/ts/packages/base/src/metrics/prometheus.ts @@ -0,0 +1,68 @@ +/** + * Prometheus metrics wrappers. + * + * Python reference: trustgraph-base/trustgraph/base/metrics.py + */ + +import { Counter, Histogram, Registry, collectDefaultMetrics } from "prom-client"; + +export const registry = new Registry(); +collectDefaultMetrics({ register: registry }); + +export class ConsumerMetrics { + private requestHistogram: Histogram; + private processingCounter: Counter; + private rateLimitCounter: Counter; + + constructor(processor: string, flow: string, name: string) { + this.requestHistogram = new Histogram({ + name: "tg_consumer_request_duration_seconds", + help: "Consumer request processing time", + labelNames: ["processor", "flow", "name"], + registers: [registry], + }); + + this.processingCounter = new Counter({ + name: "tg_consumer_processing_total", + help: "Consumer processing outcomes", + labelNames: ["processor", "flow", "name", "status"], + registers: [registry], + }); + + this.rateLimitCounter = new Counter({ + name: "tg_consumer_rate_limit_total", + help: "Consumer rate limit events", + labelNames: ["processor", "flow", "name"], + registers: [registry], + }); + } + + recordTime(seconds: number): void { + this.requestHistogram.observe(seconds); + } + + process(status: "success" | "error"): void { + this.processingCounter.inc({ status }); + } + + rateLimit(): void { + this.rateLimitCounter.inc(); + } +} + +export class ProducerMetrics { + private counter: Counter; + + constructor(processor: string, flow: string, name: string) { + this.counter = new Counter({ + name: "tg_producer_items_total", + help: "Producer items sent", + labelNames: ["processor", "flow", "name"], + registers: [registry], + }); + } + + inc(): void { + this.counter.inc(); + } +} diff --git a/ts/packages/base/src/processor/async-processor.ts b/ts/packages/base/src/processor/async-processor.ts new file mode 100644 index 00000000..dd2e8367 --- /dev/null +++ b/ts/packages/base/src/processor/async-processor.ts @@ -0,0 +1,83 @@ +/** + * Base async processor — foundation for all TrustGraph services. + * + * Handles pub/sub lifecycle, configuration subscription, and graceful shutdown. + * + * Python reference: trustgraph-base/trustgraph/base/async_processor.py + */ + +import type { PubSubBackend } from "../backend/types.js"; +import { NatsBackend } from "../backend/nats.js"; +import { topics } from "../schema/topics.js"; + +export interface ProcessorConfig { + id: string; + pubsubUrl?: string; + metricsPort?: number; +} + +export type ConfigHandler = ( + config: Record, + version: number, +) => Promise; + +export abstract class AsyncProcessor { + protected pubsub: PubSubBackend; + protected running = false; + private configHandlers: ConfigHandler[] = []; + private shutdownCallbacks: Array<() => Promise> = []; + + constructor(protected readonly config: ProcessorConfig) { + this.pubsub = new NatsBackend(config.pubsubUrl ?? "nats://localhost:4222"); + } + + registerConfigHandler(handler: ConfigHandler): void { + this.configHandlers.push(handler); + } + + async start(): Promise { + this.running = true; + // Set up graceful shutdown + const shutdown = async () => { + console.log(`[${this.config.id}] Shutting down...`); + await this.stop(); + process.exit(0); + }; + process.on("SIGINT", shutdown); + process.on("SIGTERM", shutdown); + + await this.run(); + } + + async stop(): Promise { + this.running = false; + for (const cb of this.shutdownCallbacks) { + await cb(); + } + await this.pubsub.close(); + } + + protected onShutdown(callback: () => Promise): void { + this.shutdownCallbacks.push(callback); + } + + protected abstract run(): Promise; + + /** + * Static launch helper — parses env/args and starts the processor. + * Subclasses call: `MyProcessor.launch("my-service")` + */ + static async launch( + this: new (config: ProcessorConfig) => T, + id: string, + ): Promise { + const config: ProcessorConfig = { + id, + pubsubUrl: process.env.NATS_URL ?? process.env.PULSAR_HOST, + metricsPort: parseInt(process.env.METRICS_PORT ?? "8000", 10), + }; + + const processor = new this(config); + await processor.start(); + } +} diff --git a/ts/packages/base/src/processor/flow-processor.ts b/ts/packages/base/src/processor/flow-processor.ts new file mode 100644 index 00000000..c490508a --- /dev/null +++ b/ts/packages/base/src/processor/flow-processor.ts @@ -0,0 +1,69 @@ +/** + * Flow-aware processor that manages dynamic flow instances. + * + * Python reference: trustgraph-base/trustgraph/base/flow_processor.py + */ + +import { AsyncProcessor, type ProcessorConfig } from "./async-processor.js"; +import type { Spec } from "../spec/types.js"; +import { Flow, type FlowDefinition } from "./flow.js"; + +export abstract class FlowProcessor extends AsyncProcessor { + private specifications: Spec[] = []; + private flows = new Map(); + + constructor(config: ProcessorConfig) { + super(config); + this.registerConfigHandler(this.onConfigureFlows.bind(this)); + } + + registerSpecification(spec: Spec): void { + this.specifications.push(spec); + } + + protected async run(): Promise { + // The processor sits idle waiting for flow configurations + // to arrive via the config push topic. In the meantime, + // the consumer loop runs in the background. + await new Promise((resolve) => { + const check = () => { + if (!this.running) resolve(); + else setTimeout(check, 1000); + }; + check(); + }); + } + + private async onConfigureFlows( + config: Record, + version: number, + ): Promise { + const flowDefs = config.flows as Record | undefined; + if (!flowDefs) return; + + // Stop removed flows + for (const [name, flow] of this.flows) { + if (!(name in flowDefs)) { + await flow.stop(); + this.flows.delete(name); + } + } + + // Start new flows + for (const [name, defn] of Object.entries(flowDefs)) { + if (!this.flows.has(name)) { + const flow = new Flow(name, this.config.id, this.pubsub, defn, this.specifications); + await flow.start(); + this.flows.set(name, flow); + } + } + } + + override async stop(): Promise { + for (const flow of this.flows.values()) { + await flow.stop(); + } + this.flows.clear(); + await super.stop(); + } +} diff --git a/ts/packages/base/src/processor/flow.ts b/ts/packages/base/src/processor/flow.ts new file mode 100644 index 00000000..d27fb3ab --- /dev/null +++ b/ts/packages/base/src/processor/flow.ts @@ -0,0 +1,83 @@ +/** + * Runtime flow instance — created by FlowProcessor for each configured flow. + * + * Python reference: trustgraph-base/trustgraph/base/flow.py + */ + +import type { PubSubBackend } from "../backend/types.js"; +import type { Spec } from "../spec/types.js"; +import type { Producer } from "../messaging/producer.js"; +import type { Consumer } from "../messaging/consumer.js"; + +export interface FlowDefinition { + /** Topic overrides keyed by spec name */ + topics?: Record; + /** Parameter values keyed by spec name */ + parameters?: Record; +} + +export class Flow { + private producers = new Map>(); + private consumers = new Map>(); + private parameters = new Map(); + + constructor( + public readonly name: string, + public readonly processorId: string, + private readonly pubsub: PubSubBackend, + private readonly definition: FlowDefinition, + private readonly specifications: Spec[], + ) {} + + async start(): Promise { + for (const spec of this.specifications) { + await spec.add(this, this.pubsub, this.definition); + } + + // Start all consumers + for (const consumer of this.consumers.values()) { + consumer.start({ id: this.processorId, name: this.name }).catch((err) => { + console.error(`[Flow:${this.name}] Consumer error:`, err); + }); + } + } + + async stop(): Promise { + for (const consumer of this.consumers.values()) { + await consumer.stop(); + } + for (const producer of this.producers.values()) { + await producer.stop(); + } + } + + registerProducer(name: string, producer: Producer): void { + this.producers.set(name, producer); + } + + registerConsumer(name: string, consumer: Consumer): void { + this.consumers.set(name, consumer); + } + + setParameter(name: string, value: unknown): void { + this.parameters.set(name, value); + } + + producer(name: string): Producer { + const p = this.producers.get(name); + if (!p) throw new Error(`Producer "${name}" not found in flow "${this.name}"`); + return p as Producer; + } + + consumer(name: string): Consumer { + const c = this.consumers.get(name); + if (!c) throw new Error(`Consumer "${name}" not found in flow "${this.name}"`); + return c as Consumer; + } + + parameter(name: string): T { + const v = this.parameters.get(name); + if (v === undefined) throw new Error(`Parameter "${name}" not found in flow "${this.name}"`); + return v as T; + } +} diff --git a/ts/packages/base/src/processor/index.ts b/ts/packages/base/src/processor/index.ts new file mode 100644 index 00000000..e9d9b013 --- /dev/null +++ b/ts/packages/base/src/processor/index.ts @@ -0,0 +1,3 @@ +export { AsyncProcessor, type ProcessorConfig, type ConfigHandler } from "./async-processor.js"; +export { FlowProcessor } from "./flow-processor.js"; +export { Flow, type FlowDefinition } from "./flow.js"; diff --git a/ts/packages/base/src/schema/index.ts b/ts/packages/base/src/schema/index.ts new file mode 100644 index 00000000..6d586b8b --- /dev/null +++ b/ts/packages/base/src/schema/index.ts @@ -0,0 +1,3 @@ +export * from "./primitives.js"; +export * from "./topics.js"; +export * from "./messages.js"; diff --git a/ts/packages/base/src/schema/messages.ts b/ts/packages/base/src/schema/messages.ts new file mode 100644 index 00000000..269caa98 --- /dev/null +++ b/ts/packages/base/src/schema/messages.ts @@ -0,0 +1,135 @@ +/** + * Message types for service communication. + * + * Python reference: trustgraph-base/trustgraph/schema/services/ + */ + +import type { TgError, Triple, Term, RowSchema } from "./primitives.js"; + +// Text completion +export interface TextCompletionRequest { + system: string; + prompt: string; + model?: string; + temperature?: number; + streaming?: boolean; +} + +export interface TextCompletionResponse { + response: string; + model?: string; + inToken?: number; + outToken?: number; + error?: TgError; + endOfStream?: boolean; +} + +// Embeddings +export interface EmbeddingsRequest { + text: string[]; + model?: string; +} + +export interface EmbeddingsResponse { + vectors: number[][]; + error?: TgError; +} + +// Graph RAG +export interface GraphRagRequest { + query: string; + collection?: string; + entityLimit?: number; + tripleLimit?: number; + maxSubgraphSize?: number; + maxPathLength?: number; + streaming?: boolean; +} + +export interface GraphRagResponse { + response: string; + error?: TgError; + endOfStream?: boolean; +} + +// Document RAG +export interface DocumentRagRequest { + query: string; + collection?: string; + streaming?: boolean; +} + +export interface DocumentRagResponse { + response: string; + error?: TgError; + endOfStream?: boolean; +} + +// Agent +export interface AgentRequest { + question: string; + collection?: string; + streaming?: boolean; +} + +export interface AgentResponse { + answer: string; + error?: TgError; + endOfStream?: boolean; + endOfSession?: boolean; +} + +// Triples query +export interface TriplesQueryRequest { + s?: Term; + p?: Term; + o?: Term; + collection?: string; + limit?: number; +} + +export interface TriplesQueryResponse { + triples: Triple[]; + error?: TgError; +} + +// Graph embeddings query +export interface GraphEmbeddingsRequest { + vectors: number[][]; + limit?: number; + collection?: string; +} + +export interface GraphEmbeddingsResponse { + entities: Term[]; + error?: TgError; +} + +// Config +export type ConfigOperation = "get" | "list" | "delete" | "put" | "config"; + +export interface ConfigRequest { + operation: ConfigOperation; + keys?: string[]; + values?: Record; +} + +export interface ConfigResponse { + version?: number; + values?: Record; + directory?: string[]; + config?: Record; + error?: TgError; +} + +// Prompt +export interface PromptRequest { + name: string; + variables?: Record; +} + +export interface PromptResponse { + system: string; + prompt: string; + error?: TgError; +} diff --git a/ts/packages/base/src/schema/primitives.ts b/ts/packages/base/src/schema/primitives.ts new file mode 100644 index 00000000..72a017c1 --- /dev/null +++ b/ts/packages/base/src/schema/primitives.ts @@ -0,0 +1,72 @@ +/** + * Core data types mirroring the Python schema primitives. + * + * Python reference: trustgraph-base/trustgraph/schema/core/primitives.py + */ + +export interface TgError { + type: string; + message: string; +} + +// RDF Term types — discriminated union +export type TermType = "IRI" | "BLANK" | "LITERAL" | "TRIPLE"; + +export interface IriTerm { + type: "IRI"; + iri: string; +} + +export interface BlankTerm { + type: "BLANK"; + id: string; +} + +export interface LiteralTerm { + type: "LITERAL"; + value: string; + datatype?: string; + language?: string; +} + +export interface TripleTerm { + type: "TRIPLE"; + triple: Triple; +} + +export type Term = IriTerm | BlankTerm | LiteralTerm | TripleTerm; + +export interface Triple { + s: Term; + p: Term; + o: Term; + g?: Term; // Named graph (optional quad) +} + +export interface Field { + name: string; + type: string; + description?: string; +} + +export interface RowSchema { + name: string; + description?: string; + fields: Field[]; +} + +// LLM-related types +export interface LlmResult { + text: string; + inToken: number; + outToken: number; + model: string; +} + +export interface LlmChunk { + text: string; + inToken: number | null; + outToken: number | null; + model: string; + isFinal: boolean; +} diff --git a/ts/packages/base/src/schema/topics.ts b/ts/packages/base/src/schema/topics.ts new file mode 100644 index 00000000..ffc3f1a5 --- /dev/null +++ b/ts/packages/base/src/schema/topics.ts @@ -0,0 +1,62 @@ +/** + * Topic naming conventions. + * + * Python reference: trustgraph-base/trustgraph/schema/core/topic.py + * + * The Python version uses Pulsar URI format: "q1/tg/flow/queue-name" + * We use NATS subject format: "tg.flow.queue-name" + */ + +export type QoS = "q0" | "q1" | "q2"; + +export function topic( + name: string, + tenant = "tg", + namespace = "flow", +): string { + return `${tenant}.${namespace}.${name}`; +} + +// Well-known topics from the Python schema +export const topics = { + // Config + configRequest: topic("config-request"), + configResponse: topic("config-response"), + configPush: topic("config-push"), + + // Text completion + textCompletionRequest: topic("text-completion-request"), + textCompletionResponse: topic("text-completion-response"), + + // Embeddings + embeddingsRequest: topic("embeddings-request"), + embeddingsResponse: topic("embeddings-response"), + + // Graph RAG + graphRagRequest: topic("graph-rag-request"), + graphRagResponse: topic("graph-rag-response"), + + // Document RAG + documentRagRequest: topic("document-rag-request"), + documentRagResponse: topic("document-rag-response"), + + // Agent + agentRequest: topic("agent-request"), + agentResponse: topic("agent-response"), + + // Triples + triplesRequest: topic("triples-request"), + triplesResponse: topic("triples-response"), + + // Graph embeddings + graphEmbeddingsRequest: topic("graph-embeddings-request"), + graphEmbeddingsResponse: topic("graph-embeddings-response"), + + // Document embeddings + docEmbeddingsRequest: topic("doc-embeddings-request"), + docEmbeddingsResponse: topic("doc-embeddings-response"), + + // Prompt + promptRequest: topic("prompt-request"), + promptResponse: topic("prompt-response"), +} as const; diff --git a/ts/packages/base/src/services/embeddings-service.ts b/ts/packages/base/src/services/embeddings-service.ts new file mode 100644 index 00000000..89fbadd2 --- /dev/null +++ b/ts/packages/base/src/services/embeddings-service.ts @@ -0,0 +1,46 @@ +/** + * Base embeddings service. + * + * Python reference: trustgraph-base/trustgraph/base/embeddings_service.py + */ + +import { FlowProcessor } from "../processor/flow-processor.js"; +import { ConsumerSpec } from "../spec/consumer-spec.js"; +import { ProducerSpec } from "../spec/producer-spec.js"; +import { ParameterSpec } from "../spec/parameter-spec.js"; +import type { ProcessorConfig } from "../processor/async-processor.js"; +import type { FlowContext } from "../messaging/consumer.js"; +import type { EmbeddingsRequest, EmbeddingsResponse } from "../schema/messages.js"; + +export abstract class EmbeddingsService extends FlowProcessor { + constructor(config: ProcessorConfig) { + super(config); + + this.registerSpecification( + new ConsumerSpec( + "request", + this.onRequest.bind(this), + ), + ); + this.registerSpecification(new ProducerSpec("response")); + this.registerSpecification(new ParameterSpec("model")); + } + + private async onRequest( + msg: EmbeddingsRequest, + properties: Record, + _flowCtx: FlowContext, + ): Promise { + const requestId = properties.id; + if (!requestId) return; + + try { + const vectors = await this.onEmbeddings(msg.text, msg.model); + void vectors; // Producer send would go here + } catch (err) { + console.error(`[EmbeddingsService] Error processing request:`, err); + } + } + + abstract onEmbeddings(texts: string[], model?: string): Promise; +} diff --git a/ts/packages/base/src/services/index.ts b/ts/packages/base/src/services/index.ts new file mode 100644 index 00000000..b0788a9e --- /dev/null +++ b/ts/packages/base/src/services/index.ts @@ -0,0 +1,2 @@ +export { LlmService } from "./llm-service.js"; +export { EmbeddingsService } from "./embeddings-service.js"; diff --git a/ts/packages/base/src/services/llm-service.ts b/ts/packages/base/src/services/llm-service.ts new file mode 100644 index 00000000..d7f025e8 --- /dev/null +++ b/ts/packages/base/src/services/llm-service.ts @@ -0,0 +1,88 @@ +/** + * Base LLM service — handles message plumbing, subclasses implement the LLM call. + * + * Python reference: trustgraph-base/trustgraph/base/llm_service.py + */ + +import { FlowProcessor } from "../processor/flow-processor.js"; +import { ConsumerSpec } from "../spec/consumer-spec.js"; +import { ProducerSpec } from "../spec/producer-spec.js"; +import { ParameterSpec } from "../spec/parameter-spec.js"; +import type { ProcessorConfig } from "../processor/async-processor.js"; +import type { FlowContext } from "../messaging/consumer.js"; +import type { Flow } from "../processor/flow.js"; +import type { + TextCompletionRequest, + TextCompletionResponse, +} from "../schema/messages.js"; +import type { LlmResult, LlmChunk } from "../schema/primitives.js"; + +export abstract class LlmService extends FlowProcessor { + constructor(config: ProcessorConfig) { + super(config); + + this.registerSpecification( + new ConsumerSpec( + "request", + this.onRequest.bind(this), + ), + ); + this.registerSpecification(new ProducerSpec("response")); + this.registerSpecification(new ParameterSpec("model")); + this.registerSpecification(new ParameterSpec("temperature")); + } + + private async onRequest( + msg: TextCompletionRequest, + properties: Record, + flowCtx: FlowContext, + ): Promise { + // We need the actual flow instance to access producers/parameters. + // In the full implementation, FlowContext would carry a flow reference. + // For now this shows the pattern. + const requestId = properties.id; + if (!requestId) return; + + try { + if (msg.streaming && this.supportsStreaming()) { + for await (const chunk of this.generateContentStream( + msg.system, + msg.prompt, + msg.model, + msg.temperature, + )) { + // Send each chunk as a response with the same request ID + void chunk; // Producer send would go here + } + } else { + const result = await this.generateContent( + msg.system, + msg.prompt, + msg.model, + msg.temperature, + ); + void result; // Producer send would go here + } + } catch (err) { + console.error(`[LlmService] Error processing request:`, err); + } + } + + abstract generateContent( + system: string, + prompt: string, + model?: string, + temperature?: number, + ): Promise; + + abstract generateContentStream( + system: string, + prompt: string, + model?: string, + temperature?: number, + ): AsyncGenerator; + + supportsStreaming(): boolean { + return false; + } +} diff --git a/ts/packages/base/src/spec/consumer-spec.ts b/ts/packages/base/src/spec/consumer-spec.ts new file mode 100644 index 00000000..d74b7e41 --- /dev/null +++ b/ts/packages/base/src/spec/consumer-spec.ts @@ -0,0 +1,32 @@ +/** + * Consumer specification — declares a message consumer for a flow. + * + * Python reference: trustgraph-base/trustgraph/base/consumer_spec.py + */ + +import type { Spec } from "./types.js"; +import type { PubSubBackend } from "../backend/types.js"; +import type { Flow, FlowDefinition } from "../processor/flow.js"; +import { Consumer, type MessageHandler } from "../messaging/consumer.js"; + +export class ConsumerSpec implements Spec { + constructor( + public readonly name: string, + private readonly handler: MessageHandler, + private readonly concurrency = 1, + ) {} + + async add(flow: Flow, pubsub: PubSubBackend, definition: FlowDefinition): Promise { + const topic = definition.topics?.[this.name] ?? this.name; + + const consumer = new Consumer({ + pubsub, + topic, + subscription: `${flow.processorId}-${flow.name}-${this.name}`, + handler: this.handler, + concurrency: this.concurrency, + }); + + flow.registerConsumer(this.name, consumer as Consumer); + } +} diff --git a/ts/packages/base/src/spec/index.ts b/ts/packages/base/src/spec/index.ts new file mode 100644 index 00000000..42435af4 --- /dev/null +++ b/ts/packages/base/src/spec/index.ts @@ -0,0 +1,4 @@ +export type { Spec } from "./types.js"; +export { ConsumerSpec } from "./consumer-spec.js"; +export { ProducerSpec } from "./producer-spec.js"; +export { ParameterSpec } from "./parameter-spec.js"; diff --git a/ts/packages/base/src/spec/parameter-spec.ts b/ts/packages/base/src/spec/parameter-spec.ts new file mode 100644 index 00000000..5117a2cc --- /dev/null +++ b/ts/packages/base/src/spec/parameter-spec.ts @@ -0,0 +1,18 @@ +/** + * Parameter specification — declares a configuration parameter for a flow. + * + * Python reference: trustgraph-base/trustgraph/base/parameter_spec.py + */ + +import type { Spec } from "./types.js"; +import type { PubSubBackend } from "../backend/types.js"; +import type { Flow, FlowDefinition } from "../processor/flow.js"; + +export class ParameterSpec implements Spec { + constructor(public readonly name: string) {} + + async add(flow: Flow, _pubsub: PubSubBackend, definition: FlowDefinition): Promise { + const value = definition.parameters?.[this.name]; + flow.setParameter(this.name, value); + } +} diff --git a/ts/packages/base/src/spec/producer-spec.ts b/ts/packages/base/src/spec/producer-spec.ts new file mode 100644 index 00000000..1ced8430 --- /dev/null +++ b/ts/packages/base/src/spec/producer-spec.ts @@ -0,0 +1,21 @@ +/** + * Producer specification — declares a message producer for a flow. + * + * Python reference: trustgraph-base/trustgraph/base/producer_spec.py + */ + +import type { Spec } from "./types.js"; +import type { PubSubBackend } from "../backend/types.js"; +import type { Flow, FlowDefinition } from "../processor/flow.js"; +import { Producer } from "../messaging/producer.js"; + +export class ProducerSpec implements Spec { + constructor(public readonly name: string) {} + + async add(flow: Flow, pubsub: PubSubBackend, definition: FlowDefinition): Promise { + const topic = definition.topics?.[this.name] ?? this.name; + const producer = new Producer(pubsub, topic); + await producer.start(); + flow.registerProducer(this.name, producer as Producer); + } +} diff --git a/ts/packages/base/src/spec/types.ts b/ts/packages/base/src/spec/types.ts new file mode 100644 index 00000000..27be5ec3 --- /dev/null +++ b/ts/packages/base/src/spec/types.ts @@ -0,0 +1,13 @@ +/** + * Specification types for declarative flow configuration. + * + * Python reference: trustgraph-base/trustgraph/base/spec.py and siblings + */ + +import type { PubSubBackend } from "../backend/types.js"; +import type { Flow, FlowDefinition } from "../processor/flow.js"; + +export interface Spec { + name: string; + add(flow: Flow, pubsub: PubSubBackend, definition: FlowDefinition): Promise; +} diff --git a/ts/packages/base/tsconfig.json b/ts/packages/base/tsconfig.json new file mode 100644 index 00000000..5a24989c --- /dev/null +++ b/ts/packages/base/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src"] +} diff --git a/ts/packages/cli/package.json b/ts/packages/cli/package.json new file mode 100644 index 00000000..c18e4c5e --- /dev/null +++ b/ts/packages/cli/package.json @@ -0,0 +1,25 @@ +{ + "name": "@trustgraph/cli", + "version": "0.1.0", + "type": "module", + "bin": { + "tg": "dist/index.js" + }, + "scripts": { + "build": "tsc", + "dev": "tsc --watch", + "clean": "rm -rf dist", + "test": "vitest run" + }, + "dependencies": { + "@trustgraph/base": "workspace:*", + "@trustgraph/mcp": "workspace:*", + "commander": "^13.1.0", + "ws": "^8.18.0" + }, + "devDependencies": { + "@types/ws": "^8.5.0", + "typescript": "^5.8.0", + "vitest": "^3.1.0" + } +} diff --git a/ts/packages/cli/src/commands/agent.ts b/ts/packages/cli/src/commands/agent.ts new file mode 100644 index 00000000..13ccb293 --- /dev/null +++ b/ts/packages/cli/src/commands/agent.ts @@ -0,0 +1,34 @@ +/** + * Agent CLI commands. + * + * Python reference: trustgraph-cli/trustgraph/cli/invoke_agent.py + */ + +import type { Command } from "commander"; +import { createSocket, getOpts } from "./util.js"; + +export function registerAgentCommands(program: Command): void { + program + .command("agent") + .description("Ask the TrustGraph agent a question") + .argument("", "Question to ask") + .action(async (question: string, _opts, cmd) => { + const opts = getOpts(cmd); + const socket = await createSocket(opts); + + try { + const resp = await socket.request("agent", { question }, { + flowId: opts.flow, + onChunk: (chunk) => { + const c = chunk as { answer?: string }; + if (c.answer) process.stdout.write(c.answer); + }, + }); + + const r = resp as { answer?: string }; + if (r.answer) console.log(r.answer); + } finally { + await socket.close(); + } + }); +} diff --git a/ts/packages/cli/src/commands/config.ts b/ts/packages/cli/src/commands/config.ts new file mode 100644 index 00000000..28ecc263 --- /dev/null +++ b/ts/packages/cli/src/commands/config.ts @@ -0,0 +1,81 @@ +/** + * Config CLI commands. + * + * Python reference: trustgraph-cli/trustgraph/cli/show_config.py etc. + */ + +import type { Command } from "commander"; +import { createSocket, getOpts } from "./util.js"; + +export function registerConfigCommands(program: Command): void { + const config = program + .command("config") + .description("Configuration management"); + + config + .command("show") + .description("Show current configuration") + .action(async (_opts, cmd) => { + const opts = getOpts(cmd); + const socket = await createSocket(opts); + + try { + const resp = await socket.request("config", { operation: "config" }); + console.log(JSON.stringify(resp, null, 2)); + } finally { + await socket.close(); + } + }); + + config + .command("get") + .description("Get a configuration value") + .argument("", "Config key") + .action(async (key: string, _opts, cmd) => { + const opts = getOpts(cmd); + const socket = await createSocket(opts); + + try { + const resp = await socket.request("config", { operation: "get", keys: [key] }); + console.log(JSON.stringify(resp, null, 2)); + } finally { + await socket.close(); + } + }); + + config + .command("set") + .description("Set a configuration value") + .argument("", "Config key") + .argument("", "Config value (JSON)") + .action(async (key: string, value: string, _opts, cmd) => { + const opts = getOpts(cmd); + const socket = await createSocket(opts); + + try { + const parsed = JSON.parse(value); + const resp = await socket.request("config", { + operation: "put", + values: { [key]: parsed }, + }); + console.log(JSON.stringify(resp, null, 2)); + } finally { + await socket.close(); + } + }); + + config + .command("list") + .description("List configuration keys") + .action(async (_opts, cmd) => { + const opts = getOpts(cmd); + const socket = await createSocket(opts); + + try { + const resp = await socket.request("config", { operation: "list" }); + console.log(JSON.stringify(resp, null, 2)); + } finally { + await socket.close(); + } + }); +} diff --git a/ts/packages/cli/src/commands/flow.ts b/ts/packages/cli/src/commands/flow.ts new file mode 100644 index 00000000..0d18a32e --- /dev/null +++ b/ts/packages/cli/src/commands/flow.ts @@ -0,0 +1,80 @@ +/** + * Flow management CLI commands. + * + * Python reference: trustgraph-cli/trustgraph/cli/start_flow.py, stop_flow.py, etc. + */ + +import type { Command } from "commander"; +import { createSocket, getOpts } from "./util.js"; + +export function registerFlowCommands(program: Command): void { + const flow = program + .command("flow") + .description("Flow management"); + + flow + .command("list") + .description("List active flows") + .action(async (_opts, cmd) => { + const opts = getOpts(cmd); + const socket = await createSocket(opts); + + try { + const resp = await socket.request("flow", { operation: "list" }); + console.log(JSON.stringify(resp, null, 2)); + } finally { + await socket.close(); + } + }); + + flow + .command("start") + .description("Start a flow") + .argument("", "Flow name") + .action(async (name: string, _opts, cmd) => { + const opts = getOpts(cmd); + const socket = await createSocket(opts); + + try { + const resp = await socket.request("flow", { operation: "start", name }); + console.log(JSON.stringify(resp, null, 2)); + } finally { + await socket.close(); + } + }); + + flow + .command("stop") + .description("Stop a flow") + .argument("", "Flow name") + .action(async (name: string, _opts, cmd) => { + const opts = getOpts(cmd); + const socket = await createSocket(opts); + + try { + const resp = await socket.request("flow", { operation: "stop", name }); + console.log(JSON.stringify(resp, null, 2)); + } finally { + await socket.close(); + } + }); + + flow + .command("status") + .description("Show flow status") + .argument("[name]", "Flow name (all if omitted)") + .action(async (name: string | undefined, _opts, cmd) => { + const opts = getOpts(cmd); + const socket = await createSocket(opts); + + try { + const resp = await socket.request("flow", { + operation: "status", + ...(name ? { name } : {}), + }); + console.log(JSON.stringify(resp, null, 2)); + } finally { + await socket.close(); + } + }); +} diff --git a/ts/packages/cli/src/commands/graph-rag.ts b/ts/packages/cli/src/commands/graph-rag.ts new file mode 100644 index 00000000..d89b2606 --- /dev/null +++ b/ts/packages/cli/src/commands/graph-rag.ts @@ -0,0 +1,58 @@ +/** + * Graph RAG CLI commands. + * + * Python reference: trustgraph-cli/trustgraph/cli/invoke_graph_rag.py + */ + +import type { Command } from "commander"; +import { createSocket, getOpts } from "./util.js"; + +export function registerGraphRagCommands(program: Command): void { + program + .command("graph-rag") + .description("Query the knowledge graph using RAG") + .argument("", "Natural language query") + .option("--entity-limit ", "Max entities", "50") + .option("--triple-limit ", "Max triples per entity", "30") + .action(async (query: string, cmdOpts, cmd) => { + const opts = getOpts(cmd); + const socket = await createSocket(opts); + + try { + const resp = await socket.request( + "graph-rag", + { + query, + entity_limit: parseInt(cmdOpts.entityLimit, 10), + triple_limit: parseInt(cmdOpts.tripleLimit, 10), + }, + { flowId: opts.flow }, + ) as { response?: string }; + + console.log(resp.response ?? JSON.stringify(resp, null, 2)); + } finally { + await socket.close(); + } + }); + + program + .command("document-rag") + .description("Query documents using RAG") + .argument("", "Natural language query") + .action(async (query: string, _cmdOpts, cmd) => { + const opts = getOpts(cmd); + const socket = await createSocket(opts); + + try { + const resp = await socket.request( + "document-rag", + { query }, + { flowId: opts.flow }, + ) as { response?: string }; + + console.log(resp.response ?? JSON.stringify(resp, null, 2)); + } finally { + await socket.close(); + } + }); +} diff --git a/ts/packages/cli/src/commands/util.ts b/ts/packages/cli/src/commands/util.ts new file mode 100644 index 00000000..4492d200 --- /dev/null +++ b/ts/packages/cli/src/commands/util.ts @@ -0,0 +1,28 @@ +/** + * Shared CLI utilities. + */ + +import type { Command } from "commander"; +import { SocketManager } from "@trustgraph/mcp"; + +export interface CliOpts { + gateway: string; + token?: string; + flow: string; +} + +export function getOpts(cmd: Command): CliOpts { + // Walk up to root command to get global options + let root = cmd; + while (root.parent) root = root.parent; + return root.opts() as CliOpts; +} + +export async function createSocket(opts: CliOpts): Promise { + const socket = new SocketManager({ + gatewayUrl: opts.gateway, + token: opts.token, + }); + await socket.connect(); + return socket; +} diff --git a/ts/packages/cli/src/index.ts b/ts/packages/cli/src/index.ts new file mode 100644 index 00000000..20e3dbe4 --- /dev/null +++ b/ts/packages/cli/src/index.ts @@ -0,0 +1,33 @@ +#!/usr/bin/env node + +/** + * Unified TrustGraph CLI. + * + * Replaces the 60+ individual Python CLI scripts with a single + * `tg` command using subcommands. + * + * Python reference: trustgraph-cli/trustgraph/cli/ + */ + +import { Command } from "commander"; +import { registerAgentCommands } from "./commands/agent.js"; +import { registerGraphRagCommands } from "./commands/graph-rag.js"; +import { registerConfigCommands } from "./commands/config.js"; +import { registerFlowCommands } from "./commands/flow.js"; + +const program = new Command(); + +program + .name("tg") + .description("TrustGraph CLI — interact with TrustGraph services") + .version("0.1.0") + .option("-g, --gateway ", "Gateway WebSocket URL", "ws://localhost:8088/api/v1/socket") + .option("-t, --token ", "Authentication token") + .option("-f, --flow ", "Flow ID", "default"); + +registerAgentCommands(program); +registerGraphRagCommands(program); +registerConfigCommands(program); +registerFlowCommands(program); + +program.parse(); diff --git a/ts/packages/cli/tsconfig.json b/ts/packages/cli/tsconfig.json new file mode 100644 index 00000000..bbcfb7e7 --- /dev/null +++ b/ts/packages/cli/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src"], + "references": [ + { "path": "../base" }, + { "path": "../mcp" } + ] +} diff --git a/ts/packages/flow/package.json b/ts/packages/flow/package.json new file mode 100644 index 00000000..cac52410 --- /dev/null +++ b/ts/packages/flow/package.json @@ -0,0 +1,26 @@ +{ + "name": "@trustgraph/flow", + "version": "0.1.0", + "type": "module", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "tsc", + "dev": "tsc --watch", + "clean": "rm -rf dist", + "test": "vitest run" + }, + "dependencies": { + "@trustgraph/base": "workspace:*", + "openai": "^4.85.0", + "@anthropic-ai/sdk": "^0.39.0", + "@qdrant/js-client-rest": "^1.13.0", + "neo4j-driver": "^5.28.0", + "fastify": "^5.2.0", + "@fastify/websocket": "^11.0.0" + }, + "devDependencies": { + "typescript": "^5.8.0", + "vitest": "^3.1.0" + } +} diff --git a/ts/packages/flow/src/gateway/dispatch/manager.ts b/ts/packages/flow/src/gateway/dispatch/manager.ts new file mode 100644 index 00000000..0551bba0 --- /dev/null +++ b/ts/packages/flow/src/gateway/dispatch/manager.ts @@ -0,0 +1,110 @@ +/** + * Dispatcher manager — routes requests to backend services via pub/sub. + * + * Python reference: trustgraph-flow/trustgraph/gateway/dispatch/manager.py + */ + +import { NatsBackend, RequestResponse, type PubSubBackend } from "@trustgraph/base"; +import type { GatewayConfig } from "../server.js"; + +export type Responder = (response: unknown, complete: boolean) => Promise; + +export class DispatcherManager { + private pubsub: PubSubBackend; + private requestors = new Map>(); + + constructor(private readonly config: GatewayConfig) { + this.pubsub = new NatsBackend(config.natsUrl ?? "nats://localhost:4222"); + } + + async start(): Promise { + // Pre-create requestors for known global services + // Flow-specific requestors are created on demand + } + + async stop(): Promise { + for (const rr of this.requestors.values()) { + await rr.stop(); + } + await this.pubsub.close(); + } + + private async getRequestor( + requestTopic: string, + responseTopic: string, + key: string, + ): Promise> { + let rr = this.requestors.get(key); + if (!rr) { + rr = new RequestResponse({ + pubsub: this.pubsub, + requestTopic, + responseTopic, + subscription: `gateway-${key}`, + }); + await rr.start(); + this.requestors.set(key, rr); + } + return rr; + } + + async dispatchGlobalService( + kind: string, + request: Record, + ): Promise { + const requestTopic = `tg.flow.${kind}-request`; + const responseTopic = `tg.flow.${kind}-response`; + const rr = await this.getRequestor(requestTopic, responseTopic, `global:${kind}`); + return rr.request(request); + } + + async dispatchFlowService( + flow: string, + kind: string, + request: Record, + ): Promise { + const requestTopic = `tg.flow.${kind}-request`; + const responseTopic = `tg.flow.${kind}-response`; + const rr = await this.getRequestor(requestTopic, responseTopic, `flow:${flow}:${kind}`); + return rr.request(request); + } + + async dispatchGlobalServiceStreaming( + kind: string, + request: Record, + responder: Responder, + ): Promise { + const requestTopic = `tg.flow.${kind}-request`; + const responseTopic = `tg.flow.${kind}-response`; + const rr = await this.getRequestor(requestTopic, responseTopic, `global:${kind}`); + + await rr.request(request, { + recipient: async (response) => { + const res = response as Record; + const complete = !!res.complete || !!res.endOfStream || !!res.endOfSession; + await responder(res, complete); + return complete; + }, + }); + } + + async dispatchFlowServiceStreaming( + flow: string, + kind: string, + request: Record, + responder: Responder, + ): Promise { + const requestTopic = `tg.flow.${kind}-request`; + const responseTopic = `tg.flow.${kind}-response`; + const rr = await this.getRequestor(requestTopic, responseTopic, `flow:${flow}:${kind}`); + + await rr.request(request, { + recipient: async (response) => { + const res = response as Record; + const complete = !!res.complete || !!res.endOfStream || !!res.endOfSession; + await responder(res, complete); + return complete; + }, + }); + } +} diff --git a/ts/packages/flow/src/gateway/dispatch/mux.ts b/ts/packages/flow/src/gateway/dispatch/mux.ts new file mode 100644 index 00000000..6c504843 --- /dev/null +++ b/ts/packages/flow/src/gateway/dispatch/mux.ts @@ -0,0 +1,86 @@ +/** + * WebSocket multiplexer — handles concurrent requests over a single connection. + * + * Python reference: trustgraph-flow/trustgraph/gateway/dispatch/mux.py + */ + +import { AsyncQueue } from "@trustgraph/base"; + +const MAX_OUTSTANDING = 15; +const MAX_QUEUE_SIZE = 10; + +export interface MuxRequest { + id: string; + service: string; + flow?: string; + request: Record; +} + +export type MuxHandler = ( + request: MuxRequest, + respond: (response: unknown, complete: boolean) => Promise, +) => Promise; + +export class Mux { + private queue = new AsyncQueue(); + private outstanding = 0; + private running = true; + + constructor(private readonly handler: MuxHandler) {} + + receive(request: MuxRequest): void { + if (this.queue.length >= MAX_QUEUE_SIZE) { + console.warn("[Mux] Queue full, dropping request:", request.id); + return; + } + this.queue.push(request); + } + + async run(send: (data: string) => void): Promise { + while (this.running) { + if (this.outstanding >= MAX_OUTSTANDING) { + await sleep(50); + continue; + } + + try { + const request = await this.queue.pop(1000); + this.outstanding++; + + // Fire and forget — error handling inside + this.processRequest(request, send).finally(() => { + this.outstanding--; + }); + } catch { + // Timeout on queue pop — just loop + } + } + } + + stop(): void { + this.running = false; + } + + private async processRequest( + request: MuxRequest, + send: (data: string) => void, + ): Promise { + try { + await this.handler(request, async (response, complete) => { + send(JSON.stringify({ id: request.id, response, complete })); + }); + } catch (err) { + send( + JSON.stringify({ + id: request.id, + error: { type: "internal", message: String(err) }, + complete: true, + }), + ); + } + } +} + +function sleep(ms: number): Promise { + return new Promise((resolve) => setTimeout(resolve, ms)); +} diff --git a/ts/packages/flow/src/gateway/index.ts b/ts/packages/flow/src/gateway/index.ts new file mode 100644 index 00000000..15ab6529 --- /dev/null +++ b/ts/packages/flow/src/gateway/index.ts @@ -0,0 +1,3 @@ +export { createGateway, run, type GatewayConfig } from "./server.js"; +export { DispatcherManager } from "./dispatch/manager.js"; +export { Mux, type MuxRequest, type MuxHandler } from "./dispatch/mux.js"; diff --git a/ts/packages/flow/src/gateway/server.ts b/ts/packages/flow/src/gateway/server.ts new file mode 100644 index 00000000..a8a8395d --- /dev/null +++ b/ts/packages/flow/src/gateway/server.ts @@ -0,0 +1,136 @@ +/** + * API Gateway — HTTP + WebSocket server. + * + * Replaces the Python aiohttp gateway with Fastify. + * + * Python reference: trustgraph-flow/trustgraph/gateway/service.py + */ + +import Fastify from "fastify"; +import websocketPlugin from "@fastify/websocket"; +import { DispatcherManager } from "./dispatch/manager.js"; + +export interface GatewayConfig { + port: number; + metricsPort: number; + secret?: string; + natsUrl?: string; +} + +export async function createGateway(config: GatewayConfig) { + const app = Fastify({ logger: true }); + await app.register(websocketPlugin); + + const dispatcher = new DispatcherManager(config); + await dispatcher.start(); + + // Authentication middleware + app.addHook("onRequest", async (request, reply) => { + if (request.url === "/api/v1/metrics") return; + if (request.url === "/api/v1/socket") return; // Socket auth via query param + + if (config.secret) { + const auth = request.headers.authorization; + if (!auth || auth !== `Bearer ${config.secret}`) { + reply.code(401).send({ error: "Unauthorized" }); + } + } + }); + + // REST endpoint: POST /api/v1/:kind + app.post<{ Params: { kind: string } }>("/api/v1/:kind", async (request, reply) => { + const { kind } = request.params; + const body = request.body as Record; + + try { + const result = await dispatcher.dispatchGlobalService(kind, body); + return result; + } catch (err) { + reply.code(500).send({ error: { type: "internal", message: String(err) } }); + } + }); + + // REST endpoint: POST /api/v1/flow/:flow/service/:kind + app.post<{ Params: { flow: string; kind: string } }>( + "/api/v1/flow/:flow/service/:kind", + async (request, reply) => { + const { flow, kind } = request.params; + const body = request.body as Record; + + try { + const result = await dispatcher.dispatchFlowService(flow, kind, body); + return result; + } catch (err) { + reply.code(500).send({ error: { type: "internal", message: String(err) } }); + } + }, + ); + + // WebSocket endpoint: /api/v1/socket + app.get("/api/v1/socket", { websocket: true }, (socket, request) => { + // Auth via query param + const url = new URL(request.url, `http://${request.headers.host}`); + const token = url.searchParams.get("token"); + if (config.secret && token !== config.secret) { + socket.close(4001, "Unauthorized"); + return; + } + + socket.on("message", async (data) => { + try { + const msg = JSON.parse(data.toString()); + const { id, service, flow, request: req } = msg; + + const responder = async (response: unknown, complete: boolean) => { + socket.send(JSON.stringify({ id, response, complete })); + }; + + if (flow) { + await dispatcher.dispatchFlowServiceStreaming(flow, service, req, responder); + } else { + await dispatcher.dispatchGlobalServiceStreaming(service, req, responder); + } + } catch (err) { + const msg = JSON.parse(data.toString()); + socket.send( + JSON.stringify({ + id: msg.id, + error: { type: "internal", message: String(err) }, + complete: true, + }), + ); + } + }); + + socket.on("close", () => { + // Cleanup + }); + }); + + // Metrics endpoint + app.get("/api/v1/metrics", async () => { + const { registry } = await import("@trustgraph/base"); + return registry.metrics(); + }); + + return { + start: () => app.listen({ port: config.port, host: "0.0.0.0" }), + stop: async () => { + await app.close(); + await dispatcher.stop(); + }, + }; +} + +export async function run(): Promise { + const config: GatewayConfig = { + port: parseInt(process.env.GATEWAY_PORT ?? "8088", 10), + metricsPort: parseInt(process.env.METRICS_PORT ?? "8000", 10), + secret: process.env.GATEWAY_SECRET, + natsUrl: process.env.NATS_URL, + }; + + const gateway = await createGateway(config); + await gateway.start(); + console.log(`[Gateway] Listening on port ${config.port}`); +} diff --git a/ts/packages/flow/src/index.ts b/ts/packages/flow/src/index.ts new file mode 100644 index 00000000..56c19d51 --- /dev/null +++ b/ts/packages/flow/src/index.ts @@ -0,0 +1,7 @@ +// @trustgraph/flow — processing services + +export { createGateway, type GatewayConfig } from "./gateway/index.js"; +export { OpenAIProcessor } from "./model/text-completion/openai.js"; +export { ClaudeProcessor } from "./model/text-completion/claude.js"; +export { GraphRag, type GraphRagConfig, type GraphRagClients } from "./retrieval/graph-rag.js"; +export { DocumentRag, type DocumentRagClients } from "./retrieval/document-rag.js"; diff --git a/ts/packages/flow/src/model/text-completion/claude.ts b/ts/packages/flow/src/model/text-completion/claude.ts new file mode 100644 index 00000000..a736ca59 --- /dev/null +++ b/ts/packages/flow/src/model/text-completion/claude.ts @@ -0,0 +1,129 @@ +/** + * Anthropic Claude text completion service. + * + * Python reference: trustgraph-flow/trustgraph/model/text_completion/claude/llm.py + */ + +import Anthropic from "@anthropic-ai/sdk"; +import { LlmService, type ProcessorConfig, type LlmResult, type LlmChunk, TooManyRequestsError } from "@trustgraph/base"; + +export class ClaudeProcessor extends LlmService { + private client: Anthropic; + private defaultModel: string; + private defaultTemperature: number; + private maxOutput: number; + + constructor(config: ProcessorConfig & { + model?: string; + apiKey?: string; + temperature?: number; + maxOutput?: number; + }) { + super(config); + + this.defaultModel = config.model ?? "claude-sonnet-4-20250514"; + this.defaultTemperature = config.temperature ?? 0.0; + this.maxOutput = config.maxOutput ?? 8192; + + const apiKey = config.apiKey ?? process.env.CLAUDE_KEY; + if (!apiKey) throw new Error("Claude API key not specified"); + + this.client = new Anthropic({ apiKey }); + + console.log("[Claude] LLM service initialized"); + } + + async generateContent( + system: string, + prompt: string, + model?: string, + temperature?: number, + ): Promise { + const modelName = model ?? this.defaultModel; + const temp = temperature ?? this.defaultTemperature; + + try { + const response = await this.client.messages.create({ + model: modelName, + max_tokens: this.maxOutput, + temperature: temp, + system, + messages: [ + { role: "user", content: prompt }, + ], + }); + + const text = response.content[0].type === "text" + ? response.content[0].text + : ""; + + return { + text, + inToken: response.usage.input_tokens, + outToken: response.usage.output_tokens, + model: modelName, + }; + } catch (err) { + if (err instanceof Anthropic.RateLimitError) { + throw new TooManyRequestsError(); + } + throw err; + } + } + + override supportsStreaming(): boolean { + return true; + } + + async *generateContentStream( + system: string, + prompt: string, + model?: string, + temperature?: number, + ): AsyncGenerator { + const modelName = model ?? this.defaultModel; + const temp = temperature ?? this.defaultTemperature; + + try { + const stream = this.client.messages.stream({ + model: modelName, + max_tokens: this.maxOutput, + temperature: temp, + system, + messages: [ + { role: "user", content: prompt }, + ], + }); + + for await (const event of stream) { + if (event.type === "content_block_delta" && event.delta.type === "text_delta") { + yield { + text: event.delta.text, + inToken: null, + outToken: null, + model: modelName, + isFinal: false, + }; + } + } + + const finalMessage = await stream.finalMessage(); + yield { + text: "", + inToken: finalMessage.usage.input_tokens, + outToken: finalMessage.usage.output_tokens, + model: modelName, + isFinal: true, + }; + } catch (err) { + if (err instanceof Anthropic.RateLimitError) { + throw new TooManyRequestsError(); + } + throw err; + } + } +} + +export async function run(): Promise { + await ClaudeProcessor.launch("text-completion"); +} diff --git a/ts/packages/flow/src/model/text-completion/openai.ts b/ts/packages/flow/src/model/text-completion/openai.ts new file mode 100644 index 00000000..e743ecfc --- /dev/null +++ b/ts/packages/flow/src/model/text-completion/openai.ts @@ -0,0 +1,138 @@ +/** + * OpenAI text completion service. + * + * Python reference: trustgraph-flow/trustgraph/model/text_completion/openai/llm.py + */ + +import OpenAI from "openai"; +import { LlmService, type ProcessorConfig, type LlmResult, type LlmChunk, TooManyRequestsError } from "@trustgraph/base"; + +export class OpenAIProcessor extends LlmService { + private client: OpenAI; + private defaultModel: string; + private defaultTemperature: number; + private maxOutput: number; + + constructor(config: ProcessorConfig & { + model?: string; + apiKey?: string; + baseUrl?: string; + temperature?: number; + maxOutput?: number; + }) { + super(config); + + this.defaultModel = config.model ?? "gpt-4o"; + this.defaultTemperature = config.temperature ?? 0.0; + this.maxOutput = config.maxOutput ?? 4096; + + const apiKey = config.apiKey ?? process.env.OPENAI_TOKEN; + if (!apiKey) throw new Error("OpenAI API key not specified"); + + this.client = new OpenAI({ + apiKey, + baseURL: config.baseUrl ?? process.env.OPENAI_BASE_URL, + }); + + console.log("[OpenAI] LLM service initialized"); + } + + async generateContent( + system: string, + prompt: string, + model?: string, + temperature?: number, + ): Promise { + const modelName = model ?? this.defaultModel; + const temp = temperature ?? this.defaultTemperature; + + try { + const resp = await this.client.chat.completions.create({ + model: modelName, + messages: [ + { role: "system", content: system }, + { role: "user", content: prompt }, + ], + temperature: temp, + max_completion_tokens: this.maxOutput, + }); + + return { + text: resp.choices[0].message.content ?? "", + inToken: resp.usage?.prompt_tokens ?? 0, + outToken: resp.usage?.completion_tokens ?? 0, + model: modelName, + }; + } catch (err) { + if (err instanceof OpenAI.RateLimitError) { + throw new TooManyRequestsError(); + } + throw err; + } + } + + override supportsStreaming(): boolean { + return true; + } + + async *generateContentStream( + system: string, + prompt: string, + model?: string, + temperature?: number, + ): AsyncGenerator { + const modelName = model ?? this.defaultModel; + const temp = temperature ?? this.defaultTemperature; + + try { + const stream = await this.client.chat.completions.create({ + model: modelName, + messages: [ + { role: "system", content: system }, + { role: "user", content: prompt }, + ], + temperature: temp, + max_completion_tokens: this.maxOutput, + stream: true, + stream_options: { include_usage: true }, + }); + + let totalInputTokens = 0; + let totalOutputTokens = 0; + + for await (const chunk of stream) { + if (chunk.choices?.[0]?.delta?.content) { + yield { + text: chunk.choices[0].delta.content, + inToken: null, + outToken: null, + model: modelName, + isFinal: false, + }; + } + + if (chunk.usage) { + totalInputTokens = chunk.usage.prompt_tokens; + totalOutputTokens = chunk.usage.completion_tokens; + } + } + + yield { + text: "", + inToken: totalInputTokens, + outToken: totalOutputTokens, + model: modelName, + isFinal: true, + }; + } catch (err) { + if (err instanceof OpenAI.RateLimitError) { + throw new TooManyRequestsError(); + } + throw err; + } + } +} + +export async function run(): Promise { + await OpenAIProcessor.launch("text-completion"); +} diff --git a/ts/packages/flow/src/retrieval/document-rag.ts b/ts/packages/flow/src/retrieval/document-rag.ts new file mode 100644 index 00000000..485d5284 --- /dev/null +++ b/ts/packages/flow/src/retrieval/document-rag.ts @@ -0,0 +1,66 @@ +/** + * Document RAG retrieval pipeline. + * + * Simpler than Graph RAG — embeds the query, finds similar document chunks, + * and synthesizes an answer from the chunk content. + * + * Python reference: trustgraph-flow/trustgraph/retrieval/document_rag/ + */ + +import type { + RequestResponse, + TextCompletionRequest, + TextCompletionResponse, + EmbeddingsRequest, + EmbeddingsResponse, + PromptRequest, + PromptResponse, +} from "@trustgraph/base"; + +export interface DocumentRagClients { + llm: RequestResponse; + embeddings: RequestResponse; + docEmbeddings: RequestResponse; // Doc embedding query + prompt: RequestResponse; +} + +export type ChunkCallback = (text: string, endOfStream: boolean) => Promise; + +export class DocumentRag { + constructor(private readonly clients: DocumentRagClients) {} + + async query( + queryText: string, + options?: { + collection?: string; + streaming?: boolean; + chunkCallback?: ChunkCallback; + }, + ): Promise { + // Step 1: Embed the query + const embResp = await this.clients.embeddings.request({ text: [queryText] }); + const vectors = (embResp as EmbeddingsResponse).vectors; + + // Step 2: Find similar document chunks + const docResp = await this.clients.docEmbeddings.request({ vectors, limit: 10 }); + const chunks = docResp as { chunks: Array<{ content: string; document: string }> }; + + // Step 3: Build context from chunks + const context = (chunks.chunks ?? []) + .map((c) => c.content) + .join("\n\n---\n\n"); + + // Step 4: Synthesize answer + const promptResp = await this.clients.prompt.request({ + name: "document-rag-synthesize", + variables: { query: queryText, context }, + }); + + const resp = await this.clients.llm.request({ + system: (promptResp as PromptResponse).system, + prompt: (promptResp as PromptResponse).prompt, + }); + + return (resp as TextCompletionResponse).response; + } +} diff --git a/ts/packages/flow/src/retrieval/graph-rag.ts b/ts/packages/flow/src/retrieval/graph-rag.ts new file mode 100644 index 00000000..95902cb5 --- /dev/null +++ b/ts/packages/flow/src/retrieval/graph-rag.ts @@ -0,0 +1,207 @@ +/** + * Graph RAG retrieval pipeline. + * + * This is the core RAG pipeline that: + * 1. Extracts concepts from the query + * 2. Embeds concepts to find matching entities + * 3. Traverses the knowledge graph from those entities + * 4. Scores and filters edges + * 5. Synthesizes an answer with the selected context + * + * Python reference: trustgraph-flow/trustgraph/retrieval/graph_rag/graph_rag.py + */ + +import type { + RequestResponse, + TextCompletionRequest, + TextCompletionResponse, + EmbeddingsRequest, + EmbeddingsResponse, + GraphEmbeddingsRequest, + GraphEmbeddingsResponse, + TriplesQueryRequest, + TriplesQueryResponse, + PromptRequest, + PromptResponse, + Term, + Triple, +} from "@trustgraph/base"; + +export interface GraphRagConfig { + entityLimit?: number; + tripleLimit?: number; + maxSubgraphSize?: number; + maxPathLength?: number; + edgeScoreLimit?: number; + edgeLimit?: number; +} + +export interface GraphRagClients { + llm: RequestResponse; + embeddings: RequestResponse; + graphEmbeddings: RequestResponse; + triples: RequestResponse; + prompt: RequestResponse; +} + +export type ChunkCallback = (text: string, endOfStream: boolean) => Promise; + +export class GraphRag { + private config: Required; + + constructor( + private readonly clients: GraphRagClients, + config: GraphRagConfig = {}, + ) { + this.config = { + entityLimit: config.entityLimit ?? 50, + tripleLimit: config.tripleLimit ?? 30, + maxSubgraphSize: config.maxSubgraphSize ?? 1000, + maxPathLength: config.maxPathLength ?? 2, + edgeScoreLimit: config.edgeScoreLimit ?? 30, + edgeLimit: config.edgeLimit ?? 25, + }; + } + + async query( + queryText: string, + options?: { + collection?: string; + streaming?: boolean; + chunkCallback?: ChunkCallback; + }, + ): Promise { + // Step 1: Extract concepts from the query via prompt + LLM + const concepts = await this.extractConcepts(queryText); + + // Step 2: Embed concepts concurrently + const vectors = await this.getVectors(concepts); + + // Step 3: Find matching entities via graph embeddings + const entities = await this.getEntities(vectors); + + // Step 4: Traverse the knowledge graph from entities + const subgraph = await this.followEdges(entities); + + // Step 5: Score and filter edges via LLM + const scoredEdges = await this.scoreEdges(queryText, subgraph); + + // Step 6: Synthesize answer + const answer = await this.synthesize(queryText, scoredEdges, options?.chunkCallback); + + return answer; + } + + private async extractConcepts(query: string): Promise { + const promptResp = await this.clients.prompt.request({ + name: "extract-concepts", + variables: { query }, + }); + + const llmResp = await this.clients.llm.request({ + system: (promptResp as PromptResponse).system, + prompt: (promptResp as PromptResponse).prompt, + }); + + // Parse concepts from LLM response (newline-separated) + return (llmResp as TextCompletionResponse).response + .split("\n") + .map((c) => c.trim()) + .filter(Boolean); + } + + private async getVectors(concepts: string[]): Promise { + const resp = await this.clients.embeddings.request({ text: concepts }); + return (resp as EmbeddingsResponse).vectors; + } + + private async getEntities(vectors: number[][]): Promise { + const resp = await this.clients.graphEmbeddings.request({ + vectors, + limit: this.config.entityLimit, + }); + return (resp as GraphEmbeddingsResponse).entities; + } + + private async followEdges(entities: Term[]): Promise { + // Batch triple queries for all entities + const allTriples: Triple[] = []; + + const queries = entities.map((entity) => + this.clients.triples.request({ s: entity, limit: this.config.tripleLimit }), + ); + + const results = await Promise.all(queries); + for (const result of results) { + allTriples.push(...(result as TriplesQueryResponse).triples); + } + + // TODO: Multi-hop traversal up to maxPathLength + return allTriples.slice(0, this.config.maxSubgraphSize); + } + + private async scoreEdges(query: string, triples: Triple[]): Promise { + // TODO: LLM-based edge scoring and filtering + // For now, return top N edges + return triples.slice(0, this.config.edgeLimit); + } + + private async synthesize( + query: string, + edges: Triple[], + chunkCallback?: ChunkCallback, + ): Promise { + // Format edges as context + const context = edges + .map((t) => `${termToString(t.s)} -> ${termToString(t.p)} -> ${termToString(t.o)}`) + .join("\n"); + + const promptResp = await this.clients.prompt.request({ + name: "graph-rag-synthesize", + variables: { query, context }, + }); + + if (chunkCallback) { + // Streaming response + let fullText = ""; + await this.clients.llm.request( + { + system: (promptResp as PromptResponse).system, + prompt: (promptResp as PromptResponse).prompt, + streaming: true, + }, + { + recipient: async (resp) => { + const r = resp as TextCompletionResponse; + if (r.response) { + fullText += r.response; + await chunkCallback(r.response, !!r.endOfStream); + } + return !!r.endOfStream; + }, + }, + ); + return fullText; + } + + const resp = await this.clients.llm.request({ + system: (promptResp as PromptResponse).system, + prompt: (promptResp as PromptResponse).prompt, + }); + + return (resp as TextCompletionResponse).response; + } +} + +function termToString(term: Term): string { + switch (term.type) { + case "IRI": + return term.iri; + case "LITERAL": + return term.value; + case "BLANK": + return `_:${term.id}`; + case "TRIPLE": + return `(${termToString(term.triple.s)} ${termToString(term.triple.p)} ${termToString(term.triple.o)})`; + } +} diff --git a/ts/packages/flow/tsconfig.json b/ts/packages/flow/tsconfig.json new file mode 100644 index 00000000..6e302dec --- /dev/null +++ b/ts/packages/flow/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src"], + "references": [ + { "path": "../base" } + ] +} diff --git a/ts/packages/mcp/package.json b/ts/packages/mcp/package.json new file mode 100644 index 00000000..e7ac4055 --- /dev/null +++ b/ts/packages/mcp/package.json @@ -0,0 +1,23 @@ +{ + "name": "@trustgraph/mcp", + "version": "0.1.0", + "type": "module", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "tsc", + "dev": "tsc --watch", + "clean": "rm -rf dist", + "test": "vitest run" + }, + "dependencies": { + "@trustgraph/base": "workspace:*", + "@modelcontextprotocol/sdk": "^1.8.0", + "ws": "^8.18.0" + }, + "devDependencies": { + "@types/ws": "^8.5.0", + "typescript": "^5.8.0", + "vitest": "^3.1.0" + } +} diff --git a/ts/packages/mcp/src/index.ts b/ts/packages/mcp/src/index.ts new file mode 100644 index 00000000..ef270139 --- /dev/null +++ b/ts/packages/mcp/src/index.ts @@ -0,0 +1,2 @@ +export { createMcpServer, run } from "./server.js"; +export { SocketManager, type SocketManagerConfig } from "./socket-manager.js"; diff --git a/ts/packages/mcp/src/server.ts b/ts/packages/mcp/src/server.ts new file mode 100644 index 00000000..7e651f05 --- /dev/null +++ b/ts/packages/mcp/src/server.ts @@ -0,0 +1,174 @@ +/** + * TrustGraph MCP server. + * + * Exposes TrustGraph capabilities as MCP tools for AI assistants. + * Communicates with the TrustGraph gateway via WebSocket. + * + * Python reference: trustgraph-mcp/trustgraph/mcp_server/mcp.py + */ + +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; +import { z } from "zod"; +import { SocketManager } from "./socket-manager.js"; + +export function createMcpServer(config: { + gatewayUrl: string; + token?: string; + flowId?: string; +}) { + const server = new McpServer({ + name: "trustgraph", + version: "0.1.0", + }); + + const socket = new SocketManager({ + gatewayUrl: config.gatewayUrl, + token: config.token, + }); + + const flowId = config.flowId ?? "default"; + + // --- Text Completion --- + server.tool( + "text_completion", + "Run a text completion using the configured LLM", + { + system: z.string().describe("System prompt"), + prompt: z.string().describe("User prompt"), + }, + async ({ system, prompt }) => { + const resp = await socket.request("text-completion", { system, prompt }, { flowId }) as Record; + return { content: [{ type: "text" as const, text: String(resp.response ?? resp) }] }; + }, + ); + + // --- Graph RAG --- + server.tool( + "graph_rag", + "Query the knowledge graph using RAG", + { + query: z.string().describe("Natural language query"), + entity_limit: z.number().optional().describe("Max entities to retrieve"), + triple_limit: z.number().optional().describe("Max triples per entity"), + }, + async ({ query, entity_limit, triple_limit }) => { + const resp = await socket.request( + "graph-rag", + { query, entity_limit, triple_limit }, + { flowId }, + ) as Record; + return { content: [{ type: "text" as const, text: String(resp.response ?? resp) }] }; + }, + ); + + // --- Agent --- + server.tool( + "agent", + "Ask the TrustGraph agent a question", + { + question: z.string().describe("Question for the agent"), + }, + async ({ question }) => { + const resp = await socket.request("agent", { question }, { flowId }) as Record; + return { content: [{ type: "text" as const, text: String(resp.answer ?? resp) }] }; + }, + ); + + // --- Embeddings --- + server.tool( + "embeddings", + "Generate text embeddings", + { + text: z.array(z.string()).describe("Texts to embed"), + }, + async ({ text }) => { + const resp = await socket.request("embeddings", { text }, { flowId }) as Record; + return { content: [{ type: "text" as const, text: JSON.stringify(resp) }] }; + }, + ); + + // --- Triples Query --- + server.tool( + "triples_query", + "Query the knowledge graph for triples matching a pattern", + { + s: z.string().optional().describe("Subject IRI"), + p: z.string().optional().describe("Predicate IRI"), + o: z.string().optional().describe("Object IRI or literal"), + limit: z.number().optional().describe("Max results"), + }, + async ({ s, p, o, limit }) => { + const request: Record = { limit }; + if (s) request.s = { type: "IRI", iri: s }; + if (p) request.p = { type: "IRI", iri: p }; + if (o) request.o = { type: "IRI", iri: o }; + + const resp = await socket.request("triples-query", request, { flowId }) as Record; + return { content: [{ type: "text" as const, text: JSON.stringify(resp, null, 2) }] }; + }, + ); + + // --- Graph Embeddings Query --- + server.tool( + "graph_embeddings_query", + "Find entities similar to a text query using vector embeddings", + { + query: z.string().describe("Text to find similar entities for"), + limit: z.number().optional().describe("Max results"), + }, + async ({ query, limit }) => { + // First embed the query, then search + const embResp = await socket.request("embeddings", { text: [query] }, { flowId }) as { vectors: number[][] }; + const resp = await socket.request( + "graph-embeddings-query", + { vectors: embResp.vectors, limit: limit ?? 10 }, + { flowId }, + ) as Record; + return { content: [{ type: "text" as const, text: JSON.stringify(resp, null, 2) }] }; + }, + ); + + // --- Config --- + server.tool( + "get_config", + "Get configuration values", + { + keys: z.array(z.string()).describe("Config keys to retrieve"), + }, + async ({ keys }) => { + const resp = await socket.request("config", { operation: "get", keys }) as Record; + return { content: [{ type: "text" as const, text: JSON.stringify(resp, null, 2) }] }; + }, + ); + + server.tool( + "put_config", + "Set configuration values", + { + values: z.record(z.unknown()).describe("Key-value pairs to set"), + }, + async ({ values }) => { + const resp = await socket.request("config", { operation: "put", values }) as Record; + return { content: [{ type: "text" as const, text: JSON.stringify(resp) }] }; + }, + ); + + return { server, socket }; +} + +export async function run(): Promise { + const { server, socket } = createMcpServer({ + gatewayUrl: process.env.GATEWAY_URL ?? "ws://localhost:8088/api/v1/socket", + token: process.env.GATEWAY_SECRET, + flowId: process.env.FLOW_ID ?? "default", + }); + + const transport = new StdioServerTransport(); + await server.connect(transport); + + process.on("SIGINT", async () => { + await socket.close(); + process.exit(0); + }); +} diff --git a/ts/packages/mcp/src/socket-manager.ts b/ts/packages/mcp/src/socket-manager.ts new file mode 100644 index 00000000..78fdb7db --- /dev/null +++ b/ts/packages/mcp/src/socket-manager.ts @@ -0,0 +1,147 @@ +/** + * WebSocket manager for communicating with the TrustGraph gateway. + * + * Maintains a persistent connection per user and handles request/response + * correlation via UUIDs. + * + * Python reference: trustgraph-mcp/trustgraph/mcp_server/tg_socket.py + */ + +import WebSocket from "ws"; +import { randomUUID } from "node:crypto"; + +export interface SocketManagerConfig { + gatewayUrl: string; + token?: string; +} + +interface PendingRequest { + resolve: (value: unknown) => void; + reject: (error: Error) => void; + responses: unknown[]; + streaming: boolean; + onChunk?: (chunk: unknown) => void; +} + +export class SocketManager { + private ws: WebSocket | null = null; + private pending = new Map(); + private connected = false; + + constructor(private readonly config: SocketManagerConfig) {} + + async connect(): Promise { + if (this.connected) return; + + const url = new URL(this.config.gatewayUrl); + if (this.config.token) { + url.searchParams.set("token", this.config.token); + } + + return new Promise((resolve, reject) => { + this.ws = new WebSocket(url.toString()); + + this.ws.on("open", () => { + this.connected = true; + resolve(); + }); + + this.ws.on("error", (err) => { + if (!this.connected) reject(err); + else console.error("[SocketManager] WebSocket error:", err); + }); + + this.ws.on("message", (data) => { + try { + const msg = JSON.parse(data.toString()); + const { id, response, error, complete } = msg; + + const req = this.pending.get(id); + if (!req) return; + + if (error) { + req.reject(new Error(`${error.type}: ${error.message}`)); + this.pending.delete(id); + return; + } + + if (req.streaming && req.onChunk) { + req.onChunk(response); + } + + req.responses.push(response); + + if (complete) { + req.resolve(req.streaming ? req.responses : response); + this.pending.delete(id); + } + } catch (err) { + console.error("[SocketManager] Failed to parse message:", err); + } + }); + + this.ws.on("close", () => { + this.connected = false; + // Reject all pending requests + for (const [id, req] of this.pending) { + req.reject(new Error("WebSocket closed")); + } + this.pending.clear(); + }); + }); + } + + async request( + service: string, + requestData: Record, + options?: { + flowId?: string; + timeoutMs?: number; + onChunk?: (chunk: unknown) => void; + }, + ): Promise { + await this.connect(); + if (!this.ws) throw new Error("Not connected"); + + const id = randomUUID(); + const timeoutMs = options?.timeoutMs ?? 300_000; + + const msg = { + id, + service, + flow: options?.flowId ?? "default", + request: requestData, + }; + + return new Promise((resolve, reject) => { + const timer = setTimeout(() => { + this.pending.delete(id); + reject(new Error(`Request timed out after ${timeoutMs}ms`)); + }, timeoutMs); + + this.pending.set(id, { + resolve: (value) => { + clearTimeout(timer); + resolve(value); + }, + reject: (err) => { + clearTimeout(timer); + reject(err); + }, + responses: [], + streaming: !!options?.onChunk, + onChunk: options?.onChunk, + }); + + this.ws!.send(JSON.stringify(msg)); + }); + } + + async close(): Promise { + if (this.ws) { + this.ws.close(); + this.ws = null; + this.connected = false; + } + } +} diff --git a/ts/packages/mcp/tsconfig.json b/ts/packages/mcp/tsconfig.json new file mode 100644 index 00000000..6e302dec --- /dev/null +++ b/ts/packages/mcp/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src"], + "references": [ + { "path": "../base" } + ] +} diff --git a/ts/pnpm-workspace.yaml b/ts/pnpm-workspace.yaml new file mode 100644 index 00000000..dee51e92 --- /dev/null +++ b/ts/pnpm-workspace.yaml @@ -0,0 +1,2 @@ +packages: + - "packages/*" diff --git a/ts/tsconfig.base.json b/ts/tsconfig.base.json new file mode 100644 index 00000000..2ec96e64 --- /dev/null +++ b/ts/tsconfig.base.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "outDir": "dist", + "rootDir": "src" + } +} diff --git a/ts/tsconfig.json b/ts/tsconfig.json new file mode 100644 index 00000000..d3550018 --- /dev/null +++ b/ts/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "noEmit": true + }, + "references": [ + { "path": "packages/base" }, + { "path": "packages/flow" }, + { "path": "packages/cli" }, + { "path": "packages/mcp" } + ] +} diff --git a/ts/turbo.json b/ts/turbo.json new file mode 100644 index 00000000..91139d24 --- /dev/null +++ b/ts/turbo.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://turbo.build/schema.json", + "tasks": { + "build": { + "dependsOn": ["^build"], + "outputs": ["dist/**"] + }, + "dev": { + "cache": false, + "persistent": true + }, + "lint": { + "dependsOn": ["^build"] + }, + "test": { + "dependsOn": ["build"] + }, + "clean": { + "cache": false + } + } +}