darabos commited on
Commit
663af8e
·
2 Parent(s): f8a1b37 73ef550

Merge remote-tracking branch 'public/main' into darabos-open-source-merge

Browse files
Files changed (37) hide show
  1. .github/workflows/docs.yaml +28 -0
  2. .github/workflows/release-lynxkite-app.yaml +18 -0
  3. .github/workflows/release-lynxkite-core.yaml +18 -0
  4. .github/workflows/release-lynxkite-graph-analytics.yaml +18 -0
  5. .github/workflows/test.yaml +0 -2
  6. README.md +20 -6
  7. docs/index.md +4 -2
  8. docs/license.md +11 -0
  9. docs/reference/lynxkite-core/executors/one_by_one.md +1 -0
  10. docs/reference/lynxkite-core/executors/simple.md +1 -0
  11. docs/reference/lynxkite-core/ops.md +1 -0
  12. docs/reference/lynxkite-core/workspace.md +1 -0
  13. docs/reference/lynxkite-graph-analytics/core.md +1 -0
  14. docs/reference/lynxkite-graph-analytics/operations.md +3 -0
  15. docs/usage/plugins.md +278 -0
  16. docs/usage/quickstart.md +25 -0
  17. lynxkite-app/pyproject.toml +4 -0
  18. lynxkite-app/src/lynxkite_app/main.py +8 -1
  19. lynxkite-app/web/package-lock.json +46 -1090
  20. lynxkite-app/web/package.json +1 -0
  21. lynxkite-app/web/src/Tooltip.tsx +1 -1
  22. lynxkite-app/web/src/index.css +21 -9
  23. lynxkite-app/web/src/workspace/Workspace.tsx +4 -2
  24. lynxkite-app/web/src/workspace/nodes/LynxKiteNode.tsx +1 -1
  25. lynxkite-app/web/src/workspace/nodes/NodeWithComment.tsx +58 -0
  26. lynxkite-app/web/tailwind.config.js +1 -1
  27. lynxkite-app/web/tests/basic.spec.ts +1 -1
  28. lynxkite-app/web/tests/errors.spec.ts +2 -2
  29. lynxkite-app/web/tests/examples.spec.ts +0 -3
  30. lynxkite-core/pyproject.toml +4 -0
  31. lynxkite-core/src/lynxkite/core/executors/one_by_one.py +32 -24
  32. lynxkite-core/src/lynxkite/core/executors/simple.py +7 -1
  33. lynxkite-core/src/lynxkite/core/ops.py +35 -7
  34. lynxkite-graph-analytics/pyproject.toml +1 -0
  35. lynxkite-graph-analytics/src/lynxkite_graph_analytics/core.py +35 -11
  36. lynxkite-pillow-example/pyproject.toml +4 -0
  37. mkdocs.yml +45 -4
.github/workflows/docs.yaml ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: docs
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ permissions:
7
+ contents: write
8
+ jobs:
9
+ deploy:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ - name: Configure Git Credentials
14
+ run: |
15
+ git config user.name github-actions[bot]
16
+ git config user.email 41898282+github-actions[bot]@users.noreply.github.com
17
+ - uses: actions/setup-python@v5
18
+ with:
19
+ python-version: 3.x
20
+ - run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV
21
+ - uses: actions/cache@v4
22
+ with:
23
+ key: mkdocs-material-${{ env.cache_id }}
24
+ path: .cache
25
+ restore-keys: |
26
+ mkdocs-material-
27
+ - run: pip install mkdocs-material mkdocstrings-python mkdocs-autorefs
28
+ - run: mkdocs gh-deploy --force
.github/workflows/release-lynxkite-app.yaml ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: release-lynxkite-app
2
+ on:
3
+ workflow_dispatch:
4
+ jobs:
5
+ publish:
6
+ runs-on: ubuntu-latest
7
+ permissions:
8
+ id-token: write
9
+ steps:
10
+ - uses: actions/checkout@v4
11
+ - name: Install uv
12
+ uses: astral-sh/setup-uv@v5
13
+ - name: Build
14
+ working-directory: lynxkite-app
15
+ run: uv build
16
+ - name: Publish
17
+ working-directory: lynxkite-app
18
+ run: uv publish
.github/workflows/release-lynxkite-core.yaml ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: release-lynxkite-core
2
+ on:
3
+ workflow_dispatch:
4
+ jobs:
5
+ publish:
6
+ runs-on: ubuntu-latest
7
+ permissions:
8
+ id-token: write
9
+ steps:
10
+ - uses: actions/checkout@v4
11
+ - name: Install uv
12
+ uses: astral-sh/setup-uv@v5
13
+ - name: Build
14
+ working-directory: lynxkite-core
15
+ run: uv build
16
+ - name: Publish
17
+ working-directory: lynxkite-core
18
+ run: uv publish
.github/workflows/release-lynxkite-graph-analytics.yaml ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: release-lynxkite-graph-analytics
2
+ on:
3
+ workflow_dispatch:
4
+ jobs:
5
+ publish:
6
+ runs-on: ubuntu-latest
7
+ permissions:
8
+ id-token: write
9
+ steps:
10
+ - uses: actions/checkout@v4
11
+ - name: Install uv
12
+ uses: astral-sh/setup-uv@v5
13
+ - name: Build
14
+ working-directory: lynxkite-graph-analytics
15
+ run: uv build
16
+ - name: Publish
17
+ working-directory: lynxkite-graph-analytics
18
+ run: uv publish
.github/workflows/test.yaml CHANGED
@@ -2,8 +2,6 @@ name: test
2
 
3
  on:
4
  pull_request:
5
- push:
6
- branches: [main]
7
 
8
  jobs:
9
  test:
 
2
 
3
  on:
4
  pull_request:
 
 
5
 
6
  jobs:
7
  test:
README.md CHANGED
@@ -7,14 +7,22 @@ sdk: docker
7
  app_port: 7860
8
  ---
9
 
10
- # LynxKite 2024
11
 
12
- This is an experimental rewrite of [LynxKite](https://github.com/lynxkite/lynxkite). It is not compatible with the
13
- original LynxKite. The primary goals of this rewrite are:
14
 
15
- - Target GPU clusters instead of Hadoop clusters. We use Python instead of Scala, RAPIDS instead of Apache Spark.
16
- - More extensible backend. Make it easy to add new LynxKite boxes. Make it easy to use our frontend for other purposes,
17
- configuring and executing other pipelines.
 
 
 
 
 
 
 
 
 
18
 
19
  ## Structure
20
 
@@ -67,3 +75,9 @@ To work on the documentation:
67
  uv pip install mkdocs-material mkdocstrings[python]
68
  mkdocs serve
69
  ```
 
 
 
 
 
 
 
7
  app_port: 7860
8
  ---
9
 
10
+ # LynxKite 2000:MM Enterprise
11
 
12
+ LynxKite 2000:MM is a GPU-accelerated data science platform and a general tool for collaboratively edited workflows.
 
13
 
14
+ Features include:
15
+
16
+ - A web UI for building and executing data science workflows.
17
+ - An extensive toolbox of graph analytics operations powered by NVIDIA RAPIDS (CUDA).
18
+ - An integrated collaborative code editor makes it easy to add new operations.
19
+ - An environment for visually designing neural network model architectures.
20
+ - The infrastructure for easily creating other workflow design environments. See `lynxkite-pillow-example` for a simple example.
21
+
22
+ This is the next evolution of the classical [LynxKite](https://github.com/lynxkite/lynxkite).
23
+ The two tools offer similar functionality, but are not compatible.
24
+ This version runs on GPU clusters instead of Hadoop clusters.
25
+ It targets CUDA instead of Apache Spark. It is much more extensible.
26
 
27
  ## Structure
28
 
 
75
  uv pip install mkdocs-material mkdocstrings[python]
76
  mkdocs serve
77
  ```
78
+
79
+ ## License
80
+
81
+ LynxKite 2000:MM Enterprise is built on top of the open-source [LynxKite 2000:MM](https://github.com/lynxkite/lynxkite-2000).
82
+
83
+ Inquire with [Lynx Analytics](https://www.lynxanalytics.com/) for the licensing of this repository.
docs/index.md CHANGED
@@ -1,3 +1,5 @@
1
- # Getting started
 
 
2
 
3
- Good luck getting started!
 
1
+ ---
2
+ title: Overview
3
+ ---
4
 
5
+ --8<-- "README.md"
docs/license.md ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # License
2
+
3
+ LynxKite 2000:MM is available under the GNU AGPLv3 license below.
4
+
5
+ Additionally, [Lynx Analytics](https://www.lynxanalytics.com/) offers a commercial license of LynxKite 2000:MM
6
+ that includes additional features and support. Get in touch if you are interested in life sciences tools
7
+ and cluster deployment!
8
+
9
+ ```
10
+ --8<-- "LICENSE"
11
+ ```
docs/reference/lynxkite-core/executors/one_by_one.md ADDED
@@ -0,0 +1 @@
 
 
1
+ ::: lynxkite.core.executors.one_by_one
docs/reference/lynxkite-core/executors/simple.md ADDED
@@ -0,0 +1 @@
 
 
1
+ ::: lynxkite.core.executors.simple
docs/reference/lynxkite-core/ops.md ADDED
@@ -0,0 +1 @@
 
 
1
+ ::: lynxkite.core.ops
docs/reference/lynxkite-core/workspace.md ADDED
@@ -0,0 +1 @@
 
 
1
+ ::: lynxkite.core.workspace
docs/reference/lynxkite-graph-analytics/core.md ADDED
@@ -0,0 +1 @@
 
 
1
+ ::: lynxkite_graph_analytics.core
docs/reference/lynxkite-graph-analytics/operations.md ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ ::: lynxkite_graph_analytics.lynxkite_ops
2
+ ::: lynxkite_graph_analytics.ml_ops
3
+ ::: lynxkite_graph_analytics.networkx_ops
docs/usage/plugins.md ADDED
@@ -0,0 +1,278 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Plugin development
2
+
3
+ Plugins can provide additional operations for an existing LynxKite environment,
4
+ and they can also provide new environments.
5
+
6
+ ## Creating a new plugin
7
+
8
+ `.py` files inside the LynxKite data directory are automatically imported each time a
9
+ workspace is executed. You can create a new plugin by creating a new `.py` file in the
10
+ data directory. LynxKite even includes an integrated editor for this purpose.
11
+ Click **New code file** in the directory where you want to create the file.
12
+
13
+ Plugins in subdirectories of the data directory are imported when executing workspaces
14
+ within those directories. This allows you to create plugins that are only available
15
+ in specific workspaces.
16
+
17
+ You can also create and distribute plugins as Python packages. In this case the
18
+ module name must start with `lynxkite_` for it to be automatically imported on startup.
19
+
20
+ ### Plugin dependencies
21
+
22
+ When creating a plugin as a "code file", you can create a `requirements.txt` file in the same
23
+ directory. This file will be used to install the dependencies of the plugin.
24
+
25
+ ## Adding new operations
26
+
27
+ Any piece of Python code can easily be wrapped into a LynxKite operation.
28
+ Let's say we have some code that calculates the length of a string column in a Pandas DataFrame:
29
+
30
+ ```python
31
+ df["length"] = df["my_column"].str.len()
32
+ ```
33
+
34
+ We can turn it into a LynxKite operation using the
35
+ [`@op`](../reference/lynxkite-core/ops.md#lynxkite.core.ops.op) decorator:
36
+
37
+ ```python
38
+ import pandas as pd
39
+ from lynxkite.core.ops import op
40
+
41
+ @op("LynxKite Graph Analytics", "Get column length")
42
+ def get_length(df: pd.DataFrame, *, column_name: str):
43
+ """
44
+ Gets the length of a string column.
45
+
46
+ Args:
47
+ column_name: The name of the column to get the length of.
48
+ """
49
+ df = df.copy()
50
+ df["length"] = df[column_name].str.len()
51
+ return df
52
+ ```
53
+
54
+ Let's review the changes we made.
55
+
56
+ ### The `@op` decorator
57
+
58
+ The [`@op`](../reference/lynxkite-core/ops.md#lynxkite.core.ops.op) decorator registers a
59
+ function as a LynxKite operation. The first argument is the name of the environment,
60
+ the second argument is the name of the operation.
61
+
62
+ When defining multiple operations, you can use
63
+ [`ops.op_registration`](../reference/lynxkite-core/ops.md#lynxkite.core.ops.op_registration)
64
+ for convenience:
65
+ ```python
66
+ op = ops.op_registration("LynxKite Graph Analytics")
67
+
68
+ @op("An operation")
69
+ def my_op():
70
+ ...
71
+ ```
72
+
73
+ ### The function signature
74
+
75
+ `*` in the list of function arguments marks the start of keyword-only arguments.
76
+ The arguments before `*` will become _inputs_ of the operation. The arguments after `*` will
77
+ be its _parameters_.
78
+
79
+ ```python
80
+ # /--- inputs ---\ /- parameters -\
81
+ def get_length(df: pd.DataFrame, *, column_name: str):
82
+ ```
83
+
84
+ LynxKite uses the type annotations of the function arguments to provide input validation,
85
+ conversion, and the right UI on the frontend.
86
+
87
+ The types supported for **inputs** are determined by the environment. For graph analytics,
88
+ the possibilities are:
89
+
90
+ - `pandas.DataFrame`
91
+ - `networkx.Graph`
92
+ - [`lynxkite_graph_analytics.Bundle`](../reference/lynxkite-graph-analytics/core.md#lynxkite_graph_analytics.core.Bundle)
93
+
94
+ The inputs of an operation are automatically converted to the right type, when possible.
95
+
96
+ To make an input optional, use an optional type, like `pd.DataFrame | None`.
97
+
98
+ The position of the input and output connectors can be controlled using the
99
+ [`@ops.input_position`](../reference/lynxkite-core/ops.md#lynxkite.core.ops.input_position) and
100
+ [`@ops.output_position`](../reference/lynxkite-core/ops.md#lynxkite.core.ops.output_position)
101
+ decorators. By default, inputs are on the left and outputs on the right.
102
+
103
+ All **parameters** are stored in LynxKite workspaces as strings. If a type annotation is provided,
104
+ LynxKite will convert the string to the right type and provide the right UI.
105
+
106
+ - `str`, `int`, `float` are presented as a text box and converted to the given type.
107
+ - `bool` is presented as a checkbox.
108
+ - [`lynxkite.core.ops.LongStr`](../reference/lynxkite-core/ops.md#lynxkite.core.ops.LongStr)
109
+ is presented as a text area.
110
+ - Enums are presented as a dropdown list.
111
+ - Pydantic models are presented as their JSON string representations. (Unless you add custom UI
112
+ for them.) They are converted to the model object when your function is called.
113
+
114
+ ### Slow operations
115
+
116
+ If the function takes a significant amount of time to run, we must either:
117
+
118
+ - Write an asynchronous function.
119
+ - Pass `slow=True` to the `@op` decorator. LynxKite will run the function in a separate thread.
120
+
121
+ `slow=True` also causes the results of the operation to be cached on disk. As long as
122
+ its inputs don't change, the operation will not be run again. This is useful for both
123
+ synchronous and synchronous operations.
124
+
125
+ ### Documentation
126
+
127
+ The docstring of the function is used as the operation's description. You can use
128
+ Google-style or Numpy-style docstrings.
129
+ (See [Griffe's documentation](https://mkdocstrings.github.io/griffe/reference/docstrings/).)
130
+
131
+ The docstring should be omitted for simple operations like the one above.
132
+
133
+ ### Outputting results
134
+
135
+ The return value of the function is the output of the operation. It will be passed to the
136
+ next operation in the pipeline.
137
+
138
+ An operation can have multiple outputs. In this case, the return value must be a dictionary,
139
+ and the list of outputs must be declared in the `@op` decorator.
140
+
141
+ ```python
142
+ @op("LynxKite Graph Analytics", "Train/test split", outputs=["train", "test"])
143
+ def test_split(df: pd.DataFrame, *, test_ratio=0.1):
144
+ test = df.sample(frac=test_ratio).reset_index()
145
+ train = df.drop(test.index).reset_index()
146
+ return {"train": train, "test": test}
147
+ ```
148
+
149
+ ### Displaying results
150
+
151
+ The outputs of the operation can be used by other operations. But we can also generate results
152
+ that are meant to be viewed by the user. The different options for this are controlled by the `view`
153
+ argument of the `@op` decorator.
154
+
155
+ The `view` argument can be one of the following:
156
+
157
+ - `matplotlib`: Just plot something with Matplotlib and it will be displayed in the UI.
158
+
159
+ ```python
160
+ @op("LynxKite Graph Analytics", "Plot column histogram", view="matplotlib")
161
+ def plot(df: pd.DataFrame, *, column_name: str):
162
+ df[column_name].value_counts().sort_index().plot.bar()
163
+ ```
164
+
165
+ - `visualization`: Draws a chart using [ECharts](https://echarts.apache.org/examples/en/index.html).
166
+ You need to return a dictionary with the chart configuration, which ECharts calls `option`.
167
+
168
+ ```python
169
+ @op("View loss", view="visualization")
170
+ def view_loss(bundle: core.Bundle):
171
+ loss = bundle.dfs["training"].training_loss.tolist()
172
+ v = {
173
+ "title": {"text": "Training loss"},
174
+ "xAxis": {"type": "category"},
175
+ "yAxis": {"type": "value"},
176
+ "series": [{"data": loss, "type": "line"}],
177
+ }
178
+ return v
179
+ ```
180
+
181
+ - `image`: Return an image as a
182
+ [data URL](https://developer.mozilla.org/en-US/docs/Web/URI/Reference/Schemes/data)
183
+ and it will be displayed.
184
+ - `molecule`: Return a molecule as a PDB or SDF string, or an `rdkit.Chem.Mol` object.
185
+ It will be displayed using [3Dmol.js](https://3Dmol.org/).
186
+ - `table_view`: Return
187
+ [`Bundle.to_dict()`](../reference/lynxkite-graph-analytics/core.md#lynxkite_graph_analytics.core.Bundle.to_dict).
188
+
189
+ ## Adding new environments
190
+
191
+ A new environment means a completely new set of operations, and (optionally) a new
192
+ executor. There's nothing to be done for setting up a new environment. Just start
193
+ registering operations into it.
194
+
195
+ ### No executor
196
+
197
+ By default, the new environment will have no executor. This can be useful!
198
+
199
+ LynxKite workspaces are stored as straightforward JSON files and updated on every modification.
200
+ You can use LynxKite for configuring workflows and have a separate system
201
+ read the JSON files.
202
+
203
+ Since the code of the operations is not executed in this case, you can create functions that do nothing.
204
+ Alternatively, you can use the
205
+ [`register_passive_op`](../reference/lynxkite-core/ops.md#lynxkite.core.ops.register_passive_op)
206
+ and
207
+ [`passive_op_registration`](../reference/lynxkite-core/ops.md#lynxkite.core.ops.passive_op_registration)
208
+ functions to easily whip up a set of operations:
209
+
210
+ ```python
211
+ from lynxkite.core.ops import passive_op_registration, Parameter as P
212
+
213
+ op = passive_op_registration("My Environment")
214
+ op('Scrape documents', params=[P('url', '')])
215
+ op('Conversation logs')
216
+ op('Extract graph')
217
+ op('Compute embeddings', params=[P.options('method', ['LLM', 'graph', 'random']), P('dimensions', 1234)])
218
+ op('Vector DB', params=[P.options('backend', ['ANN', 'HNSW'])])
219
+ op('Chat UI', outputs=[])
220
+ op('Chat backend')
221
+ ```
222
+
223
+ ### Built-in executors
224
+
225
+ LynxKite comes with two built-in executors. You can register these for your environment
226
+ and you're good to go.
227
+
228
+ ```python
229
+ from lynxkite.core.executors import simple
230
+ simple.register("My Environment")
231
+ ```
232
+
233
+ The [`simple` executor](../reference/lynxkite-core/executors/simple.md)
234
+ runs each operation once, passing the output of the preceding operation
235
+ as the input to the next one. No tricks. You can use any types as inputs and outputs.
236
+
237
+ ```python
238
+ from lynxkite.core.executors import one_by_one
239
+ one_by_one.register("My Environment")
240
+ ```
241
+
242
+ The [`one_by_one` executor](../reference/lynxkite-core/executors/one_by_one.md)
243
+ expects that the code for operations is the code for transforming
244
+ a single element. If an operation returns an iterable, it will be split up
245
+ into its elements, and the next operation is called for each element.
246
+
247
+ Sometimes you need the full contents of an input. The `one_by_one` executor
248
+ lets you choose between the two modes by the orientation of the input connector.
249
+ If the input connector is horizontal (left or right), it takes single elements.
250
+ If the input connector is vertical (top or bottom), it takes an iterable of all the incoming data.
251
+
252
+ A unique advantage of this setup is that horizontal inputs can have loops across
253
+ horizontal inputs. Just make sure that loops eventually discard all elements, so you don't
254
+ end up with an infinite loop.
255
+
256
+ ### Custom executors
257
+
258
+ A custom executor can be registered using
259
+ [`@ops.register_executor`](../reference/lynxkite-core/ops.md#lynxkite.core.ops.register_executor).
260
+
261
+ ```python
262
+ @ops.register_executor(ENV)
263
+ async def execute(ws: workspace.Workspace):
264
+ catalog = ops.CATALOGS[ws.env]
265
+ ...
266
+ ```
267
+
268
+ The executor must be an asynchronous function that takes a
269
+ [`workspace.Workspace`](../reference/lynxkite-core/workspace.md#lynxkite.core.workspace.Workspace)
270
+ as an argument. The return value is ignored and it's up to you how you process the workspace.
271
+
272
+ To update the frontend as the executor processes the workspace, call
273
+ [`WorkspaceNode.publish_started`](../reference/lynxkite-core/workspace.md#lynxkite.core.workspace.WorkspaceNode.publish_started)
274
+ when starting to execute a node, and
275
+ [`WorkspaceNode.publish_result`](../reference/lynxkite-core/workspace.md#lynxkite.core.workspace.WorkspaceNode.publish_result)
276
+ to publish the results. Use
277
+ [`WorkspaceNode.publish_error`](../reference/lynxkite-core/workspace.md#lynxkite.core.workspace.WorkspaceNode.publish_error)
278
+ if the node failed.
docs/usage/quickstart.md ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Quickstart
2
+
3
+ Install the LynxKite application with `pip`:
4
+ ```bash
5
+ pip install lynxkite
6
+ ```
7
+
8
+ To be able to do anything useful, you also need to install one or more LynxKite environments.
9
+ If you want to work with data science and graph analytics, install the `lynxkite-graph-analytics` package:
10
+ ```bash
11
+ pip install lynxkite-graph-analytics
12
+ ```
13
+
14
+ Create a folder for storing your LynxKite projects:
15
+ ```bash
16
+ mkdir ~/lynxkite_projects
17
+ ```
18
+
19
+ You're ready to run LynxKite!
20
+ ```bash
21
+ cd ~/lynxkite_projects
22
+ lynxkite
23
+ ```
24
+
25
+ Open [http://localhost:8000/](http://localhost:8000/) in your browser.
lynxkite-app/pyproject.toml CHANGED
@@ -12,6 +12,10 @@ dependencies = [
12
  "sse-starlette>=2.2.1",
13
  "griffe>=1.7.3",
14
  ]
 
 
 
 
15
 
16
  [project.optional-dependencies]
17
  dev = [
 
12
  "sse-starlette>=2.2.1",
13
  "griffe>=1.7.3",
14
  ]
15
+ classifiers = ["Private :: Do Not Upload"]
16
+
17
+ [project.urls]
18
+ Homepage = "https://github.com/lynxkite/lynxkite-2000/"
19
 
20
  [project.optional-dependencies]
21
  dev = [
lynxkite-app/src/lynxkite_app/main.py CHANGED
@@ -33,10 +33,17 @@ app.include_router(crdt.router)
33
  app.add_middleware(GZipMiddleware)
34
 
35
 
 
 
 
 
 
 
 
36
  @app.get("/api/catalog")
37
  def get_catalog(workspace: str):
38
  ops.load_user_scripts(workspace)
39
- return {k: {op.name: op.model_dump() for op in v.values()} for k, v in ops.CATALOGS.items()}
40
 
41
 
42
  class SaveRequest(workspace.BaseConfig):
 
33
  app.add_middleware(GZipMiddleware)
34
 
35
 
36
+ def _get_ops(env: str):
37
+ catalog = ops.CATALOGS[env]
38
+ res = {op.name: op.model_dump() for op in catalog.values()}
39
+ res.setdefault("Comment", ops.COMMENT_OP.model_dump())
40
+ return res
41
+
42
+
43
  @app.get("/api/catalog")
44
  def get_catalog(workspace: str):
45
  ops.load_user_scripts(workspace)
46
+ return {env: _get_ops(env) for env in ops.CATALOGS}
47
 
48
 
49
  class SaveRequest(workspace.BaseConfig):
lynxkite-app/web/package-lock.json CHANGED
@@ -38,21 +38,17 @@
38
  "yjs": "^13.6.20"
39
  },
40
  "devDependencies": {
41
- "@eslint/js": "^9.15.0",
42
  "@playwright/test": "^1.50.1",
 
43
  "@types/node": "^22.13.1",
44
  "@types/react": "^18.3.14",
45
  "@types/react-dom": "^18.3.2",
46
  "@vitejs/plugin-react-swc": "^3.5.0",
47
  "autoprefixer": "^10.4.20",
48
- "eslint": "^9.15.0",
49
- "eslint-plugin-react-hooks": "^5.0.0",
50
- "eslint-plugin-react-refresh": "^0.4.14",
51
  "globals": "^15.12.0",
52
  "postcss": "^8.4.49",
53
  "tailwindcss": "^3.4.16",
54
  "typescript": "~5.6.2",
55
- "typescript-eslint": "^8.15.0",
56
  "vite": "^6.3.4"
57
  },
58
  "optionalDependencies": {
@@ -801,146 +797,6 @@
801
  "node": ">=18"
802
  }
803
  },
804
- "node_modules/@eslint-community/eslint-utils": {
805
- "version": "4.4.1",
806
- "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz",
807
- "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==",
808
- "dev": true,
809
- "license": "MIT",
810
- "dependencies": {
811
- "eslint-visitor-keys": "^3.4.3"
812
- },
813
- "engines": {
814
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
815
- },
816
- "funding": {
817
- "url": "https://opencollective.com/eslint"
818
- },
819
- "peerDependencies": {
820
- "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
821
- }
822
- },
823
- "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
824
- "version": "3.4.3",
825
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
826
- "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
827
- "dev": true,
828
- "license": "Apache-2.0",
829
- "engines": {
830
- "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
831
- },
832
- "funding": {
833
- "url": "https://opencollective.com/eslint"
834
- }
835
- },
836
- "node_modules/@eslint-community/regexpp": {
837
- "version": "4.12.1",
838
- "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz",
839
- "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==",
840
- "dev": true,
841
- "license": "MIT",
842
- "engines": {
843
- "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
844
- }
845
- },
846
- "node_modules/@eslint/config-array": {
847
- "version": "0.19.1",
848
- "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.1.tgz",
849
- "integrity": "sha512-fo6Mtm5mWyKjA/Chy1BYTdn5mGJoDNjC7C64ug20ADsRDGrA85bN3uK3MaKbeRkRuuIEAR5N33Jr1pbm411/PA==",
850
- "dev": true,
851
- "license": "Apache-2.0",
852
- "dependencies": {
853
- "@eslint/object-schema": "^2.1.5",
854
- "debug": "^4.3.1",
855
- "minimatch": "^3.1.2"
856
- },
857
- "engines": {
858
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
859
- }
860
- },
861
- "node_modules/@eslint/core": {
862
- "version": "0.9.1",
863
- "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.9.1.tgz",
864
- "integrity": "sha512-GuUdqkyyzQI5RMIWkHhvTWLCyLo1jNK3vzkSyaExH5kHPDHcuL2VOpHjmMY+y3+NC69qAKToBqldTBgYeLSr9Q==",
865
- "dev": true,
866
- "license": "Apache-2.0",
867
- "dependencies": {
868
- "@types/json-schema": "^7.0.15"
869
- },
870
- "engines": {
871
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
872
- }
873
- },
874
- "node_modules/@eslint/eslintrc": {
875
- "version": "3.2.0",
876
- "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.2.0.tgz",
877
- "integrity": "sha512-grOjVNN8P3hjJn/eIETF1wwd12DdnwFDoyceUJLYYdkpbwq3nLi+4fqrTAONx7XDALqlL220wC/RHSC/QTI/0w==",
878
- "dev": true,
879
- "license": "MIT",
880
- "dependencies": {
881
- "ajv": "^6.12.4",
882
- "debug": "^4.3.2",
883
- "espree": "^10.0.1",
884
- "globals": "^14.0.0",
885
- "ignore": "^5.2.0",
886
- "import-fresh": "^3.2.1",
887
- "js-yaml": "^4.1.0",
888
- "minimatch": "^3.1.2",
889
- "strip-json-comments": "^3.1.1"
890
- },
891
- "engines": {
892
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
893
- },
894
- "funding": {
895
- "url": "https://opencollective.com/eslint"
896
- }
897
- },
898
- "node_modules/@eslint/eslintrc/node_modules/globals": {
899
- "version": "14.0.0",
900
- "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
901
- "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
902
- "dev": true,
903
- "license": "MIT",
904
- "engines": {
905
- "node": ">=18"
906
- },
907
- "funding": {
908
- "url": "https://github.com/sponsors/sindresorhus"
909
- }
910
- },
911
- "node_modules/@eslint/js": {
912
- "version": "9.16.0",
913
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.16.0.tgz",
914
- "integrity": "sha512-tw2HxzQkrbeuvyj1tG2Yqq+0H9wGoI2IMk4EOsQeX+vmd75FtJAzf+gTA69WF+baUKRYQ3x2kbLE08js5OsTVg==",
915
- "dev": true,
916
- "license": "MIT",
917
- "engines": {
918
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
919
- }
920
- },
921
- "node_modules/@eslint/object-schema": {
922
- "version": "2.1.5",
923
- "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.5.tgz",
924
- "integrity": "sha512-o0bhxnL89h5Bae5T318nFoFzGy+YE5i/gGkoPAgkmTVdRKTiv3p8JHevPiPaMwoloKfEiiaHlawCqaZMqRm+XQ==",
925
- "dev": true,
926
- "license": "Apache-2.0",
927
- "engines": {
928
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
929
- }
930
- },
931
- "node_modules/@eslint/plugin-kit": {
932
- "version": "0.2.4",
933
- "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.4.tgz",
934
- "integrity": "sha512-zSkKow6H5Kdm0ZUQUB2kV5JIXqoG0+uH5YADhaEHswm664N9Db8dXSi0nMJpacpMf+MyyglF1vnZohpEg5yUtg==",
935
- "dev": true,
936
- "license": "Apache-2.0",
937
- "dependencies": {
938
- "levn": "^0.4.1"
939
- },
940
- "engines": {
941
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
942
- }
943
- },
944
  "node_modules/@floating-ui/core": {
945
  "version": "1.6.9",
946
  "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.9.tgz",
@@ -966,72 +822,6 @@
966
  "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==",
967
  "license": "MIT"
968
  },
969
- "node_modules/@humanfs/core": {
970
- "version": "0.19.1",
971
- "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
972
- "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
973
- "dev": true,
974
- "license": "Apache-2.0",
975
- "engines": {
976
- "node": ">=18.18.0"
977
- }
978
- },
979
- "node_modules/@humanfs/node": {
980
- "version": "0.16.6",
981
- "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz",
982
- "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==",
983
- "dev": true,
984
- "license": "Apache-2.0",
985
- "dependencies": {
986
- "@humanfs/core": "^0.19.1",
987
- "@humanwhocodes/retry": "^0.3.0"
988
- },
989
- "engines": {
990
- "node": ">=18.18.0"
991
- }
992
- },
993
- "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": {
994
- "version": "0.3.1",
995
- "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz",
996
- "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==",
997
- "dev": true,
998
- "license": "Apache-2.0",
999
- "engines": {
1000
- "node": ">=18.18"
1001
- },
1002
- "funding": {
1003
- "type": "github",
1004
- "url": "https://github.com/sponsors/nzakas"
1005
- }
1006
- },
1007
- "node_modules/@humanwhocodes/module-importer": {
1008
- "version": "1.0.1",
1009
- "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
1010
- "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
1011
- "dev": true,
1012
- "license": "Apache-2.0",
1013
- "engines": {
1014
- "node": ">=12.22"
1015
- },
1016
- "funding": {
1017
- "type": "github",
1018
- "url": "https://github.com/sponsors/nzakas"
1019
- }
1020
- },
1021
- "node_modules/@humanwhocodes/retry": {
1022
- "version": "0.4.1",
1023
- "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.1.tgz",
1024
- "integrity": "sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==",
1025
- "dev": true,
1026
- "license": "Apache-2.0",
1027
- "engines": {
1028
- "node": ">=18.18"
1029
- },
1030
- "funding": {
1031
- "type": "github",
1032
- "url": "https://github.com/sponsors/nzakas"
1033
- }
1034
- },
1035
  "node_modules/@iconify-json/tabler": {
1036
  "version": "1.2.10",
1037
  "resolved": "https://registry.npmjs.org/@iconify-json/tabler/-/tabler-1.2.10.tgz",
@@ -1992,6 +1782,36 @@
1992
  "yjs": "^13.5.13"
1993
  }
1994
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1995
  "node_modules/@types/d3-color": {
1996
  "version": "3.1.3",
1997
  "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
@@ -2142,230 +1962,6 @@
2142
  "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
2143
  "license": "MIT"
2144
  },
2145
- "node_modules/@typescript-eslint/eslint-plugin": {
2146
- "version": "8.17.0",
2147
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.17.0.tgz",
2148
- "integrity": "sha512-HU1KAdW3Tt8zQkdvNoIijfWDMvdSweFYm4hWh+KwhPstv+sCmWb89hCIP8msFm9N1R/ooh9honpSuvqKWlYy3w==",
2149
- "dev": true,
2150
- "license": "MIT",
2151
- "dependencies": {
2152
- "@eslint-community/regexpp": "^4.10.0",
2153
- "@typescript-eslint/scope-manager": "8.17.0",
2154
- "@typescript-eslint/type-utils": "8.17.0",
2155
- "@typescript-eslint/utils": "8.17.0",
2156
- "@typescript-eslint/visitor-keys": "8.17.0",
2157
- "graphemer": "^1.4.0",
2158
- "ignore": "^5.3.1",
2159
- "natural-compare": "^1.4.0",
2160
- "ts-api-utils": "^1.3.0"
2161
- },
2162
- "engines": {
2163
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
2164
- },
2165
- "funding": {
2166
- "type": "opencollective",
2167
- "url": "https://opencollective.com/typescript-eslint"
2168
- },
2169
- "peerDependencies": {
2170
- "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0",
2171
- "eslint": "^8.57.0 || ^9.0.0"
2172
- },
2173
- "peerDependenciesMeta": {
2174
- "typescript": {
2175
- "optional": true
2176
- }
2177
- }
2178
- },
2179
- "node_modules/@typescript-eslint/parser": {
2180
- "version": "8.17.0",
2181
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.17.0.tgz",
2182
- "integrity": "sha512-Drp39TXuUlD49F7ilHHCG7TTg8IkA+hxCuULdmzWYICxGXvDXmDmWEjJYZQYgf6l/TFfYNE167m7isnc3xlIEg==",
2183
- "dev": true,
2184
- "license": "BSD-2-Clause",
2185
- "dependencies": {
2186
- "@typescript-eslint/scope-manager": "8.17.0",
2187
- "@typescript-eslint/types": "8.17.0",
2188
- "@typescript-eslint/typescript-estree": "8.17.0",
2189
- "@typescript-eslint/visitor-keys": "8.17.0",
2190
- "debug": "^4.3.4"
2191
- },
2192
- "engines": {
2193
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
2194
- },
2195
- "funding": {
2196
- "type": "opencollective",
2197
- "url": "https://opencollective.com/typescript-eslint"
2198
- },
2199
- "peerDependencies": {
2200
- "eslint": "^8.57.0 || ^9.0.0"
2201
- },
2202
- "peerDependenciesMeta": {
2203
- "typescript": {
2204
- "optional": true
2205
- }
2206
- }
2207
- },
2208
- "node_modules/@typescript-eslint/scope-manager": {
2209
- "version": "8.17.0",
2210
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.17.0.tgz",
2211
- "integrity": "sha512-/ewp4XjvnxaREtqsZjF4Mfn078RD/9GmiEAtTeLQ7yFdKnqwTOgRMSvFz4et9U5RiJQ15WTGXPLj89zGusvxBg==",
2212
- "dev": true,
2213
- "license": "MIT",
2214
- "dependencies": {
2215
- "@typescript-eslint/types": "8.17.0",
2216
- "@typescript-eslint/visitor-keys": "8.17.0"
2217
- },
2218
- "engines": {
2219
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
2220
- },
2221
- "funding": {
2222
- "type": "opencollective",
2223
- "url": "https://opencollective.com/typescript-eslint"
2224
- }
2225
- },
2226
- "node_modules/@typescript-eslint/type-utils": {
2227
- "version": "8.17.0",
2228
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.17.0.tgz",
2229
- "integrity": "sha512-q38llWJYPd63rRnJ6wY/ZQqIzPrBCkPdpIsaCfkR3Q4t3p6sb422zougfad4TFW9+ElIFLVDzWGiGAfbb/v2qw==",
2230
- "dev": true,
2231
- "license": "MIT",
2232
- "dependencies": {
2233
- "@typescript-eslint/typescript-estree": "8.17.0",
2234
- "@typescript-eslint/utils": "8.17.0",
2235
- "debug": "^4.3.4",
2236
- "ts-api-utils": "^1.3.0"
2237
- },
2238
- "engines": {
2239
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
2240
- },
2241
- "funding": {
2242
- "type": "opencollective",
2243
- "url": "https://opencollective.com/typescript-eslint"
2244
- },
2245
- "peerDependencies": {
2246
- "eslint": "^8.57.0 || ^9.0.0"
2247
- },
2248
- "peerDependenciesMeta": {
2249
- "typescript": {
2250
- "optional": true
2251
- }
2252
- }
2253
- },
2254
- "node_modules/@typescript-eslint/types": {
2255
- "version": "8.17.0",
2256
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.17.0.tgz",
2257
- "integrity": "sha512-gY2TVzeve3z6crqh2Ic7Cr+CAv6pfb0Egee7J5UAVWCpVvDI/F71wNfolIim4FE6hT15EbpZFVUj9j5i38jYXA==",
2258
- "dev": true,
2259
- "license": "MIT",
2260
- "engines": {
2261
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
2262
- },
2263
- "funding": {
2264
- "type": "opencollective",
2265
- "url": "https://opencollective.com/typescript-eslint"
2266
- }
2267
- },
2268
- "node_modules/@typescript-eslint/typescript-estree": {
2269
- "version": "8.17.0",
2270
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.17.0.tgz",
2271
- "integrity": "sha512-JqkOopc1nRKZpX+opvKqnM3XUlM7LpFMD0lYxTqOTKQfCWAmxw45e3qlOCsEqEB2yuacujivudOFpCnqkBDNMw==",
2272
- "dev": true,
2273
- "license": "BSD-2-Clause",
2274
- "dependencies": {
2275
- "@typescript-eslint/types": "8.17.0",
2276
- "@typescript-eslint/visitor-keys": "8.17.0",
2277
- "debug": "^4.3.4",
2278
- "fast-glob": "^3.3.2",
2279
- "is-glob": "^4.0.3",
2280
- "minimatch": "^9.0.4",
2281
- "semver": "^7.6.0",
2282
- "ts-api-utils": "^1.3.0"
2283
- },
2284
- "engines": {
2285
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
2286
- },
2287
- "funding": {
2288
- "type": "opencollective",
2289
- "url": "https://opencollective.com/typescript-eslint"
2290
- },
2291
- "peerDependenciesMeta": {
2292
- "typescript": {
2293
- "optional": true
2294
- }
2295
- }
2296
- },
2297
- "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
2298
- "version": "2.0.1",
2299
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
2300
- "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
2301
- "dev": true,
2302
- "license": "MIT",
2303
- "dependencies": {
2304
- "balanced-match": "^1.0.0"
2305
- }
2306
- },
2307
- "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
2308
- "version": "9.0.5",
2309
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
2310
- "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
2311
- "dev": true,
2312
- "license": "ISC",
2313
- "dependencies": {
2314
- "brace-expansion": "^2.0.1"
2315
- },
2316
- "engines": {
2317
- "node": ">=16 || 14 >=14.17"
2318
- },
2319
- "funding": {
2320
- "url": "https://github.com/sponsors/isaacs"
2321
- }
2322
- },
2323
- "node_modules/@typescript-eslint/utils": {
2324
- "version": "8.17.0",
2325
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.17.0.tgz",
2326
- "integrity": "sha512-bQC8BnEkxqG8HBGKwG9wXlZqg37RKSMY7v/X8VEWD8JG2JuTHuNK0VFvMPMUKQcbk6B+tf05k+4AShAEtCtJ/w==",
2327
- "dev": true,
2328
- "license": "MIT",
2329
- "dependencies": {
2330
- "@eslint-community/eslint-utils": "^4.4.0",
2331
- "@typescript-eslint/scope-manager": "8.17.0",
2332
- "@typescript-eslint/types": "8.17.0",
2333
- "@typescript-eslint/typescript-estree": "8.17.0"
2334
- },
2335
- "engines": {
2336
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
2337
- },
2338
- "funding": {
2339
- "type": "opencollective",
2340
- "url": "https://opencollective.com/typescript-eslint"
2341
- },
2342
- "peerDependencies": {
2343
- "eslint": "^8.57.0 || ^9.0.0"
2344
- },
2345
- "peerDependenciesMeta": {
2346
- "typescript": {
2347
- "optional": true
2348
- }
2349
- }
2350
- },
2351
- "node_modules/@typescript-eslint/visitor-keys": {
2352
- "version": "8.17.0",
2353
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.17.0.tgz",
2354
- "integrity": "sha512-1Hm7THLpO6ww5QU6H/Qp+AusUUl+z/CAm3cNZZ0jQvon9yicgO7Rwd+/WWRpMKLYV6p2UvdbR27c86rzCPpreg==",
2355
- "dev": true,
2356
- "license": "MIT",
2357
- "dependencies": {
2358
- "@typescript-eslint/types": "8.17.0",
2359
- "eslint-visitor-keys": "^4.2.0"
2360
- },
2361
- "engines": {
2362
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
2363
- },
2364
- "funding": {
2365
- "type": "opencollective",
2366
- "url": "https://opencollective.com/typescript-eslint"
2367
- }
2368
- },
2369
  "node_modules/@ungap/structured-clone": {
2370
  "version": "1.2.1",
2371
  "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.1.tgz",
@@ -2461,33 +2057,6 @@
2461
  "node": ">=0.4.0"
2462
  }
2463
  },
2464
- "node_modules/acorn-jsx": {
2465
- "version": "5.3.2",
2466
- "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
2467
- "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
2468
- "dev": true,
2469
- "license": "MIT",
2470
- "peerDependencies": {
2471
- "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
2472
- }
2473
- },
2474
- "node_modules/ajv": {
2475
- "version": "6.12.6",
2476
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
2477
- "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
2478
- "dev": true,
2479
- "license": "MIT",
2480
- "dependencies": {
2481
- "fast-deep-equal": "^3.1.1",
2482
- "fast-json-stable-stringify": "^2.0.0",
2483
- "json-schema-traverse": "^0.4.1",
2484
- "uri-js": "^4.2.2"
2485
- },
2486
- "funding": {
2487
- "type": "github",
2488
- "url": "https://github.com/sponsors/epoberezkin"
2489
- }
2490
- },
2491
  "node_modules/ansi-regex": {
2492
  "version": "6.1.0",
2493
  "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
@@ -2664,17 +2233,6 @@
2664
  "url": "https://github.com/sponsors/sindresorhus"
2665
  }
2666
  },
2667
- "node_modules/brace-expansion": {
2668
- "version": "1.1.11",
2669
- "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
2670
- "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
2671
- "dev": true,
2672
- "license": "MIT",
2673
- "dependencies": {
2674
- "balanced-match": "^1.0.0",
2675
- "concat-map": "0.0.1"
2676
- }
2677
- },
2678
  "node_modules/braces": {
2679
  "version": "3.0.3",
2680
  "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
@@ -2818,23 +2376,6 @@
2818
  "url": "https://github.com/sponsors/wooorm"
2819
  }
2820
  },
2821
- "node_modules/chalk": {
2822
- "version": "4.1.2",
2823
- "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
2824
- "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
2825
- "dev": true,
2826
- "license": "MIT",
2827
- "dependencies": {
2828
- "ansi-styles": "^4.1.0",
2829
- "supports-color": "^7.1.0"
2830
- },
2831
- "engines": {
2832
- "node": ">=10"
2833
- },
2834
- "funding": {
2835
- "url": "https://github.com/chalk/chalk?sponsor=1"
2836
- }
2837
- },
2838
  "node_modules/character-entities": {
2839
  "version": "2.0.2",
2840
  "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz",
@@ -2983,13 +2524,6 @@
2983
  "node": ">= 6"
2984
  }
2985
  },
2986
- "node_modules/concat-map": {
2987
- "version": "0.0.1",
2988
- "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
2989
- "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
2990
- "dev": true,
2991
- "license": "MIT"
2992
- },
2993
  "node_modules/confbox": {
2994
  "version": "0.1.8",
2995
  "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz",
@@ -3243,13 +2777,6 @@
3243
  "url": "https://github.com/sponsors/wooorm"
3244
  }
3245
  },
3246
- "node_modules/deep-is": {
3247
- "version": "0.1.4",
3248
- "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
3249
- "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
3250
- "dev": true,
3251
- "license": "MIT"
3252
- },
3253
  "node_modules/deferred-leveldown": {
3254
  "version": "5.3.0",
3255
  "resolved": "https://registry.npmjs.org/deferred-leveldown/-/deferred-leveldown-5.3.0.tgz",
@@ -3509,186 +3036,6 @@
3509
  "node": ">=6"
3510
  }
3511
  },
3512
- "node_modules/escape-string-regexp": {
3513
- "version": "4.0.0",
3514
- "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
3515
- "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
3516
- "dev": true,
3517
- "license": "MIT",
3518
- "engines": {
3519
- "node": ">=10"
3520
- },
3521
- "funding": {
3522
- "url": "https://github.com/sponsors/sindresorhus"
3523
- }
3524
- },
3525
- "node_modules/eslint": {
3526
- "version": "9.16.0",
3527
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.16.0.tgz",
3528
- "integrity": "sha512-whp8mSQI4C8VXd+fLgSM0lh3UlmcFtVwUQjyKCFfsp+2ItAIYhlq/hqGahGqHE6cv9unM41VlqKk2VtKYR2TaA==",
3529
- "dev": true,
3530
- "license": "MIT",
3531
- "dependencies": {
3532
- "@eslint-community/eslint-utils": "^4.2.0",
3533
- "@eslint-community/regexpp": "^4.12.1",
3534
- "@eslint/config-array": "^0.19.0",
3535
- "@eslint/core": "^0.9.0",
3536
- "@eslint/eslintrc": "^3.2.0",
3537
- "@eslint/js": "9.16.0",
3538
- "@eslint/plugin-kit": "^0.2.3",
3539
- "@humanfs/node": "^0.16.6",
3540
- "@humanwhocodes/module-importer": "^1.0.1",
3541
- "@humanwhocodes/retry": "^0.4.1",
3542
- "@types/estree": "^1.0.6",
3543
- "@types/json-schema": "^7.0.15",
3544
- "ajv": "^6.12.4",
3545
- "chalk": "^4.0.0",
3546
- "cross-spawn": "^7.0.5",
3547
- "debug": "^4.3.2",
3548
- "escape-string-regexp": "^4.0.0",
3549
- "eslint-scope": "^8.2.0",
3550
- "eslint-visitor-keys": "^4.2.0",
3551
- "espree": "^10.3.0",
3552
- "esquery": "^1.5.0",
3553
- "esutils": "^2.0.2",
3554
- "fast-deep-equal": "^3.1.3",
3555
- "file-entry-cache": "^8.0.0",
3556
- "find-up": "^5.0.0",
3557
- "glob-parent": "^6.0.2",
3558
- "ignore": "^5.2.0",
3559
- "imurmurhash": "^0.1.4",
3560
- "is-glob": "^4.0.0",
3561
- "json-stable-stringify-without-jsonify": "^1.0.1",
3562
- "lodash.merge": "^4.6.2",
3563
- "minimatch": "^3.1.2",
3564
- "natural-compare": "^1.4.0",
3565
- "optionator": "^0.9.3"
3566
- },
3567
- "bin": {
3568
- "eslint": "bin/eslint.js"
3569
- },
3570
- "engines": {
3571
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
3572
- },
3573
- "funding": {
3574
- "url": "https://eslint.org/donate"
3575
- },
3576
- "peerDependencies": {
3577
- "jiti": "*"
3578
- },
3579
- "peerDependenciesMeta": {
3580
- "jiti": {
3581
- "optional": true
3582
- }
3583
- }
3584
- },
3585
- "node_modules/eslint-plugin-react-hooks": {
3586
- "version": "5.1.0",
3587
- "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.1.0.tgz",
3588
- "integrity": "sha512-mpJRtPgHN2tNAvZ35AMfqeB3Xqeo273QxrHJsbBEPWODRM4r0yB6jfoROqKEYrOn27UtRPpcpHc2UqyBSuUNTw==",
3589
- "dev": true,
3590
- "license": "MIT",
3591
- "engines": {
3592
- "node": ">=10"
3593
- },
3594
- "peerDependencies": {
3595
- "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0"
3596
- }
3597
- },
3598
- "node_modules/eslint-plugin-react-refresh": {
3599
- "version": "0.4.16",
3600
- "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.16.tgz",
3601
- "integrity": "sha512-slterMlxAhov/DZO8NScf6mEeMBBXodFUolijDvrtTxyezyLoTQaa73FyYus/VbTdftd8wBgBxPMRk3poleXNQ==",
3602
- "dev": true,
3603
- "license": "MIT",
3604
- "peerDependencies": {
3605
- "eslint": ">=8.40"
3606
- }
3607
- },
3608
- "node_modules/eslint-scope": {
3609
- "version": "8.2.0",
3610
- "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz",
3611
- "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==",
3612
- "dev": true,
3613
- "license": "BSD-2-Clause",
3614
- "dependencies": {
3615
- "esrecurse": "^4.3.0",
3616
- "estraverse": "^5.2.0"
3617
- },
3618
- "engines": {
3619
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
3620
- },
3621
- "funding": {
3622
- "url": "https://opencollective.com/eslint"
3623
- }
3624
- },
3625
- "node_modules/eslint-visitor-keys": {
3626
- "version": "4.2.0",
3627
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz",
3628
- "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==",
3629
- "dev": true,
3630
- "license": "Apache-2.0",
3631
- "engines": {
3632
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
3633
- },
3634
- "funding": {
3635
- "url": "https://opencollective.com/eslint"
3636
- }
3637
- },
3638
- "node_modules/espree": {
3639
- "version": "10.3.0",
3640
- "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz",
3641
- "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==",
3642
- "dev": true,
3643
- "license": "BSD-2-Clause",
3644
- "dependencies": {
3645
- "acorn": "^8.14.0",
3646
- "acorn-jsx": "^5.3.2",
3647
- "eslint-visitor-keys": "^4.2.0"
3648
- },
3649
- "engines": {
3650
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
3651
- },
3652
- "funding": {
3653
- "url": "https://opencollective.com/eslint"
3654
- }
3655
- },
3656
- "node_modules/esquery": {
3657
- "version": "1.6.0",
3658
- "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
3659
- "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
3660
- "dev": true,
3661
- "license": "BSD-3-Clause",
3662
- "dependencies": {
3663
- "estraverse": "^5.1.0"
3664
- },
3665
- "engines": {
3666
- "node": ">=0.10"
3667
- }
3668
- },
3669
- "node_modules/esrecurse": {
3670
- "version": "4.3.0",
3671
- "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
3672
- "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
3673
- "dev": true,
3674
- "license": "BSD-2-Clause",
3675
- "dependencies": {
3676
- "estraverse": "^5.2.0"
3677
- },
3678
- "engines": {
3679
- "node": ">=4.0"
3680
- }
3681
- },
3682
- "node_modules/estraverse": {
3683
- "version": "5.3.0",
3684
- "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
3685
- "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
3686
- "dev": true,
3687
- "license": "BSD-2-Clause",
3688
- "engines": {
3689
- "node": ">=4.0"
3690
- }
3691
- },
3692
  "node_modules/estree-util-is-identifier-name": {
3693
  "version": "3.0.0",
3694
  "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz",
@@ -3696,17 +3043,7 @@
3696
  "license": "MIT",
3697
  "funding": {
3698
  "type": "opencollective",
3699
- "url": "https://opencollective.com/unified"
3700
- }
3701
- },
3702
- "node_modules/esutils": {
3703
- "version": "2.0.3",
3704
- "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
3705
- "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
3706
- "dev": true,
3707
- "license": "BSD-2-Clause",
3708
- "engines": {
3709
- "node": ">=0.10.0"
3710
  }
3711
  },
3712
  "node_modules/extend": {
@@ -3715,13 +3052,6 @@
3715
  "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
3716
  "license": "MIT"
3717
  },
3718
- "node_modules/fast-deep-equal": {
3719
- "version": "3.1.3",
3720
- "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
3721
- "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
3722
- "dev": true,
3723
- "license": "MIT"
3724
- },
3725
  "node_modules/fast-glob": {
3726
  "version": "3.3.2",
3727
  "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
@@ -3752,20 +3082,6 @@
3752
  "node": ">= 6"
3753
  }
3754
  },
3755
- "node_modules/fast-json-stable-stringify": {
3756
- "version": "2.1.0",
3757
- "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
3758
- "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
3759
- "dev": true,
3760
- "license": "MIT"
3761
- },
3762
- "node_modules/fast-levenshtein": {
3763
- "version": "2.0.6",
3764
- "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
3765
- "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
3766
- "dev": true,
3767
- "license": "MIT"
3768
- },
3769
  "node_modules/fastparse": {
3770
  "version": "1.1.2",
3771
  "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz",
@@ -3782,19 +3098,6 @@
3782
  "reusify": "^1.0.4"
3783
  }
3784
  },
3785
- "node_modules/file-entry-cache": {
3786
- "version": "8.0.0",
3787
- "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
3788
- "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
3789
- "dev": true,
3790
- "license": "MIT",
3791
- "dependencies": {
3792
- "flat-cache": "^4.0.0"
3793
- },
3794
- "engines": {
3795
- "node": ">=16.0.0"
3796
- }
3797
- },
3798
  "node_modules/fill-range": {
3799
  "version": "7.1.1",
3800
  "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
@@ -3808,44 +3111,6 @@
3808
  "node": ">=8"
3809
  }
3810
  },
3811
- "node_modules/find-up": {
3812
- "version": "5.0.0",
3813
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
3814
- "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
3815
- "dev": true,
3816
- "license": "MIT",
3817
- "dependencies": {
3818
- "locate-path": "^6.0.0",
3819
- "path-exists": "^4.0.0"
3820
- },
3821
- "engines": {
3822
- "node": ">=10"
3823
- },
3824
- "funding": {
3825
- "url": "https://github.com/sponsors/sindresorhus"
3826
- }
3827
- },
3828
- "node_modules/flat-cache": {
3829
- "version": "4.0.1",
3830
- "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
3831
- "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
3832
- "dev": true,
3833
- "license": "MIT",
3834
- "dependencies": {
3835
- "flatted": "^3.2.9",
3836
- "keyv": "^4.5.4"
3837
- },
3838
- "engines": {
3839
- "node": ">=16"
3840
- }
3841
- },
3842
- "node_modules/flatted": {
3843
- "version": "3.3.2",
3844
- "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz",
3845
- "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==",
3846
- "dev": true,
3847
- "license": "ISC"
3848
- },
3849
  "node_modules/follow-redirects": {
3850
  "version": "1.15.9",
3851
  "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
@@ -4075,23 +3340,6 @@
4075
  "url": "https://github.com/sponsors/ljharb"
4076
  }
4077
  },
4078
- "node_modules/graphemer": {
4079
- "version": "1.4.0",
4080
- "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
4081
- "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
4082
- "dev": true,
4083
- "license": "MIT"
4084
- },
4085
- "node_modules/has-flag": {
4086
- "version": "4.0.0",
4087
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
4088
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
4089
- "dev": true,
4090
- "license": "MIT",
4091
- "engines": {
4092
- "node": ">=8"
4093
- }
4094
- },
4095
  "node_modules/has-symbols": {
4096
  "version": "1.1.0",
4097
  "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
@@ -4202,16 +3450,6 @@
4202
  "license": "BSD-3-Clause",
4203
  "optional": true
4204
  },
4205
- "node_modules/ignore": {
4206
- "version": "5.3.2",
4207
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
4208
- "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
4209
- "dev": true,
4210
- "license": "MIT",
4211
- "engines": {
4212
- "node": ">= 4"
4213
- }
4214
- },
4215
  "node_modules/immediate": {
4216
  "version": "3.3.0",
4217
  "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.3.0.tgz",
@@ -4235,16 +3473,6 @@
4235
  "url": "https://github.com/sponsors/sindresorhus"
4236
  }
4237
  },
4238
- "node_modules/imurmurhash": {
4239
- "version": "0.1.4",
4240
- "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
4241
- "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
4242
- "dev": true,
4243
- "license": "MIT",
4244
- "engines": {
4245
- "node": ">=0.8.19"
4246
- }
4247
- },
4248
  "node_modules/inherits": {
4249
  "version": "2.0.4",
4250
  "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
@@ -4469,13 +3697,6 @@
4469
  "node": ">=6"
4470
  }
4471
  },
4472
- "node_modules/json-buffer": {
4473
- "version": "3.0.1",
4474
- "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
4475
- "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
4476
- "dev": true,
4477
- "license": "MIT"
4478
- },
4479
  "node_modules/json-parse-even-better-errors": {
4480
  "version": "2.3.1",
4481
  "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
@@ -4505,20 +3726,6 @@
4505
  "node": ">=16.0.0"
4506
  }
4507
  },
4508
- "node_modules/json-schema-traverse": {
4509
- "version": "0.4.1",
4510
- "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
4511
- "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
4512
- "dev": true,
4513
- "license": "MIT"
4514
- },
4515
- "node_modules/json-stable-stringify-without-jsonify": {
4516
- "version": "1.0.1",
4517
- "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
4518
- "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
4519
- "dev": true,
4520
- "license": "MIT"
4521
- },
4522
  "node_modules/json5": {
4523
  "version": "2.2.3",
4524
  "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
@@ -4531,16 +3738,6 @@
4531
  "node": ">=6"
4532
  }
4533
  },
4534
- "node_modules/keyv": {
4535
- "version": "4.5.4",
4536
- "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
4537
- "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
4538
- "dev": true,
4539
- "license": "MIT",
4540
- "dependencies": {
4541
- "json-buffer": "3.0.1"
4542
- }
4543
- },
4544
  "node_modules/kolorist": {
4545
  "version": "1.8.0",
4546
  "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz",
@@ -4693,20 +3890,6 @@
4693
  "node": ">=6"
4694
  }
4695
  },
4696
- "node_modules/levn": {
4697
- "version": "0.4.1",
4698
- "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
4699
- "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
4700
- "dev": true,
4701
- "license": "MIT",
4702
- "dependencies": {
4703
- "prelude-ls": "^1.2.1",
4704
- "type-check": "~0.4.0"
4705
- },
4706
- "engines": {
4707
- "node": ">= 0.8.0"
4708
- }
4709
- },
4710
  "node_modules/lib0": {
4711
  "version": "0.2.99",
4712
  "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.99.tgz",
@@ -4763,34 +3946,32 @@
4763
  "url": "https://github.com/sponsors/antfu"
4764
  }
4765
  },
4766
- "node_modules/locate-path": {
4767
- "version": "6.0.0",
4768
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
4769
- "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
4770
- "dev": true,
4771
- "license": "MIT",
4772
- "dependencies": {
4773
- "p-locate": "^5.0.0"
4774
- },
4775
- "engines": {
4776
- "node": ">=10"
4777
- },
4778
- "funding": {
4779
- "url": "https://github.com/sponsors/sindresorhus"
4780
- }
4781
- },
4782
  "node_modules/lodash": {
4783
  "version": "4.17.21",
4784
  "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
4785
  "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
4786
  "license": "MIT"
4787
  },
 
 
 
 
 
 
 
4788
  "node_modules/lodash.debounce": {
4789
  "version": "4.0.8",
4790
  "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
4791
  "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
4792
  "license": "MIT"
4793
  },
 
 
 
 
 
 
 
4794
  "node_modules/lodash.merge": {
4795
  "version": "4.6.2",
4796
  "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@@ -5494,19 +4675,6 @@
5494
  "node": ">= 0.6"
5495
  }
5496
  },
5497
- "node_modules/minimatch": {
5498
- "version": "3.1.2",
5499
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
5500
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
5501
- "dev": true,
5502
- "license": "ISC",
5503
- "dependencies": {
5504
- "brace-expansion": "^1.1.7"
5505
- },
5506
- "engines": {
5507
- "node": "*"
5508
- }
5509
- },
5510
  "node_modules/minimist": {
5511
  "version": "1.2.8",
5512
  "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
@@ -5587,13 +4755,6 @@
5587
  "license": "MIT",
5588
  "optional": true
5589
  },
5590
- "node_modules/natural-compare": {
5591
- "version": "1.4.0",
5592
- "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
5593
- "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
5594
- "dev": true,
5595
- "license": "MIT"
5596
- },
5597
  "node_modules/netcdfjs": {
5598
  "version": "3.0.0",
5599
  "resolved": "https://registry.npmjs.org/netcdfjs/-/netcdfjs-3.0.0.tgz",
@@ -5671,56 +4832,6 @@
5671
  "node": ">= 6"
5672
  }
5673
  },
5674
- "node_modules/optionator": {
5675
- "version": "0.9.4",
5676
- "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
5677
- "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
5678
- "dev": true,
5679
- "license": "MIT",
5680
- "dependencies": {
5681
- "deep-is": "^0.1.3",
5682
- "fast-levenshtein": "^2.0.6",
5683
- "levn": "^0.4.1",
5684
- "prelude-ls": "^1.2.1",
5685
- "type-check": "^0.4.0",
5686
- "word-wrap": "^1.2.5"
5687
- },
5688
- "engines": {
5689
- "node": ">= 0.8.0"
5690
- }
5691
- },
5692
- "node_modules/p-limit": {
5693
- "version": "3.1.0",
5694
- "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
5695
- "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
5696
- "dev": true,
5697
- "license": "MIT",
5698
- "dependencies": {
5699
- "yocto-queue": "^0.1.0"
5700
- },
5701
- "engines": {
5702
- "node": ">=10"
5703
- },
5704
- "funding": {
5705
- "url": "https://github.com/sponsors/sindresorhus"
5706
- }
5707
- },
5708
- "node_modules/p-locate": {
5709
- "version": "5.0.0",
5710
- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
5711
- "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
5712
- "dev": true,
5713
- "license": "MIT",
5714
- "dependencies": {
5715
- "p-limit": "^3.0.2"
5716
- },
5717
- "engines": {
5718
- "node": ">=10"
5719
- },
5720
- "funding": {
5721
- "url": "https://github.com/sponsors/sindresorhus"
5722
- }
5723
- },
5724
  "node_modules/package-json-from-dist": {
5725
  "version": "1.0.1",
5726
  "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
@@ -5796,16 +4907,6 @@
5796
  "url": "https://github.com/sponsors/sindresorhus"
5797
  }
5798
  },
5799
- "node_modules/path-exists": {
5800
- "version": "4.0.0",
5801
- "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
5802
- "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
5803
- "dev": true,
5804
- "license": "MIT",
5805
- "engines": {
5806
- "node": ">=8"
5807
- }
5808
- },
5809
  "node_modules/path-key": {
5810
  "version": "3.1.1",
5811
  "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
@@ -6104,16 +5205,6 @@
6104
  "dev": true,
6105
  "license": "MIT"
6106
  },
6107
- "node_modules/prelude-ls": {
6108
- "version": "1.2.1",
6109
- "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
6110
- "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
6111
- "dev": true,
6112
- "license": "MIT",
6113
- "engines": {
6114
- "node": ">= 0.8.0"
6115
- }
6116
- },
6117
  "node_modules/prettier": {
6118
  "version": "3.4.2",
6119
  "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz",
@@ -6152,16 +5243,6 @@
6152
  "license": "MIT",
6153
  "optional": true
6154
  },
6155
- "node_modules/punycode": {
6156
- "version": "2.3.1",
6157
- "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
6158
- "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
6159
- "dev": true,
6160
- "license": "MIT",
6161
- "engines": {
6162
- "node": ">=6"
6163
- }
6164
- },
6165
  "node_modules/queue-microtask": {
6166
  "version": "1.2.3",
6167
  "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@@ -6508,19 +5589,6 @@
6508
  "loose-envify": "^1.1.0"
6509
  }
6510
  },
6511
- "node_modules/semver": {
6512
- "version": "7.6.3",
6513
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
6514
- "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
6515
- "dev": true,
6516
- "license": "ISC",
6517
- "bin": {
6518
- "semver": "bin/semver.js"
6519
- },
6520
- "engines": {
6521
- "node": ">=10"
6522
- }
6523
- },
6524
  "node_modules/set-cookie-parser": {
6525
  "version": "2.7.1",
6526
  "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
@@ -6726,19 +5794,6 @@
6726
  "node": ">=8"
6727
  }
6728
  },
6729
- "node_modules/strip-json-comments": {
6730
- "version": "3.1.1",
6731
- "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
6732
- "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
6733
- "dev": true,
6734
- "license": "MIT",
6735
- "engines": {
6736
- "node": ">=8"
6737
- },
6738
- "funding": {
6739
- "url": "https://github.com/sponsors/sindresorhus"
6740
- }
6741
- },
6742
  "node_modules/style-to-object": {
6743
  "version": "1.0.8",
6744
  "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.8.tgz",
@@ -6771,19 +5826,6 @@
6771
  "node": ">=16 || 14 >=14.17"
6772
  }
6773
  },
6774
- "node_modules/supports-color": {
6775
- "version": "7.2.0",
6776
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
6777
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
6778
- "dev": true,
6779
- "license": "MIT",
6780
- "dependencies": {
6781
- "has-flag": "^4.0.0"
6782
- },
6783
- "engines": {
6784
- "node": ">=8"
6785
- }
6786
- },
6787
  "node_modules/supports-preserve-symlinks-flag": {
6788
  "version": "1.0.0",
6789
  "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
@@ -6958,19 +6000,6 @@
6958
  "url": "https://github.com/sponsors/wooorm"
6959
  }
6960
  },
6961
- "node_modules/ts-api-utils": {
6962
- "version": "1.4.3",
6963
- "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz",
6964
- "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==",
6965
- "dev": true,
6966
- "license": "MIT",
6967
- "engines": {
6968
- "node": ">=16"
6969
- },
6970
- "peerDependencies": {
6971
- "typescript": ">=4.2.0"
6972
- }
6973
- },
6974
  "node_modules/ts-interface-checker": {
6975
  "version": "0.1.13",
6976
  "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
@@ -6990,19 +6019,6 @@
6990
  "integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==",
6991
  "license": "ISC"
6992
  },
6993
- "node_modules/type-check": {
6994
- "version": "0.4.0",
6995
- "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
6996
- "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
6997
- "dev": true,
6998
- "license": "MIT",
6999
- "dependencies": {
7000
- "prelude-ls": "^1.2.1"
7001
- },
7002
- "engines": {
7003
- "node": ">= 0.8.0"
7004
- }
7005
- },
7006
  "node_modules/typescript": {
7007
  "version": "5.6.3",
7008
  "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz",
@@ -7017,33 +6033,6 @@
7017
  "node": ">=14.17"
7018
  }
7019
  },
7020
- "node_modules/typescript-eslint": {
7021
- "version": "8.17.0",
7022
- "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.17.0.tgz",
7023
- "integrity": "sha512-409VXvFd/f1br1DCbuKNFqQpXICoTB+V51afcwG1pn1a3Cp92MqAUges3YjwEdQ0cMUoCIodjVDAYzyD8h3SYA==",
7024
- "dev": true,
7025
- "license": "MIT",
7026
- "dependencies": {
7027
- "@typescript-eslint/eslint-plugin": "8.17.0",
7028
- "@typescript-eslint/parser": "8.17.0",
7029
- "@typescript-eslint/utils": "8.17.0"
7030
- },
7031
- "engines": {
7032
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
7033
- },
7034
- "funding": {
7035
- "type": "opencollective",
7036
- "url": "https://opencollective.com/typescript-eslint"
7037
- },
7038
- "peerDependencies": {
7039
- "eslint": "^8.57.0 || ^9.0.0"
7040
- },
7041
- "peerDependenciesMeta": {
7042
- "typescript": {
7043
- "optional": true
7044
- }
7045
- }
7046
- },
7047
  "node_modules/ufo": {
7048
  "version": "1.5.4",
7049
  "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz",
@@ -7248,16 +6237,6 @@
7248
  "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
7249
  "license": "(MIT AND Zlib)"
7250
  },
7251
- "node_modules/uri-js": {
7252
- "version": "4.4.1",
7253
- "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
7254
- "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
7255
- "dev": true,
7256
- "license": "BSD-2-Clause",
7257
- "dependencies": {
7258
- "punycode": "^2.1.0"
7259
- }
7260
- },
7261
  "node_modules/use-sync-external-store": {
7262
  "version": "1.2.2",
7263
  "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz",
@@ -7427,16 +6406,6 @@
7427
  "node": ">= 8"
7428
  }
7429
  },
7430
- "node_modules/word-wrap": {
7431
- "version": "1.2.5",
7432
- "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
7433
- "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
7434
- "dev": true,
7435
- "license": "MIT",
7436
- "engines": {
7437
- "node": ">=0.10.0"
7438
- }
7439
- },
7440
  "node_modules/wrap-ansi": {
7441
  "version": "8.1.0",
7442
  "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
@@ -7673,19 +6642,6 @@
7673
  "url": "https://github.com/sponsors/dmonad"
7674
  }
7675
  },
7676
- "node_modules/yocto-queue": {
7677
- "version": "0.1.0",
7678
- "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
7679
- "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
7680
- "dev": true,
7681
- "license": "MIT",
7682
- "engines": {
7683
- "node": ">=10"
7684
- },
7685
- "funding": {
7686
- "url": "https://github.com/sponsors/sindresorhus"
7687
- }
7688
- },
7689
  "node_modules/zrender": {
7690
  "version": "5.6.0",
7691
  "resolved": "https://registry.npmjs.org/zrender/-/zrender-5.6.0.tgz",
 
38
  "yjs": "^13.6.20"
39
  },
40
  "devDependencies": {
 
41
  "@playwright/test": "^1.50.1",
42
+ "@tailwindcss/typography": "^0.5.16",
43
  "@types/node": "^22.13.1",
44
  "@types/react": "^18.3.14",
45
  "@types/react-dom": "^18.3.2",
46
  "@vitejs/plugin-react-swc": "^3.5.0",
47
  "autoprefixer": "^10.4.20",
 
 
 
48
  "globals": "^15.12.0",
49
  "postcss": "^8.4.49",
50
  "tailwindcss": "^3.4.16",
51
  "typescript": "~5.6.2",
 
52
  "vite": "^6.3.4"
53
  },
54
  "optionalDependencies": {
 
797
  "node": ">=18"
798
  }
799
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
800
  "node_modules/@floating-ui/core": {
801
  "version": "1.6.9",
802
  "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.9.tgz",
 
822
  "integrity": "sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==",
823
  "license": "MIT"
824
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
825
  "node_modules/@iconify-json/tabler": {
826
  "version": "1.2.10",
827
  "resolved": "https://registry.npmjs.org/@iconify-json/tabler/-/tabler-1.2.10.tgz",
 
1782
  "yjs": "^13.5.13"
1783
  }
1784
  },
1785
+ "node_modules/@tailwindcss/typography": {
1786
+ "version": "0.5.16",
1787
+ "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.16.tgz",
1788
+ "integrity": "sha512-0wDLwCVF5V3x3b1SGXPCDcdsbDHMBe+lkFzBRaHeLvNi+nrrnZ1lA18u+OTWO8iSWU2GxUOCvlXtDuqftc1oiA==",
1789
+ "dev": true,
1790
+ "license": "MIT",
1791
+ "dependencies": {
1792
+ "lodash.castarray": "^4.4.0",
1793
+ "lodash.isplainobject": "^4.0.6",
1794
+ "lodash.merge": "^4.6.2",
1795
+ "postcss-selector-parser": "6.0.10"
1796
+ },
1797
+ "peerDependencies": {
1798
+ "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1"
1799
+ }
1800
+ },
1801
+ "node_modules/@tailwindcss/typography/node_modules/postcss-selector-parser": {
1802
+ "version": "6.0.10",
1803
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
1804
+ "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
1805
+ "dev": true,
1806
+ "license": "MIT",
1807
+ "dependencies": {
1808
+ "cssesc": "^3.0.0",
1809
+ "util-deprecate": "^1.0.2"
1810
+ },
1811
+ "engines": {
1812
+ "node": ">=4"
1813
+ }
1814
+ },
1815
  "node_modules/@types/d3-color": {
1816
  "version": "3.1.3",
1817
  "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
 
1962
  "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==",
1963
  "license": "MIT"
1964
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1965
  "node_modules/@ungap/structured-clone": {
1966
  "version": "1.2.1",
1967
  "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.1.tgz",
 
2057
  "node": ">=0.4.0"
2058
  }
2059
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2060
  "node_modules/ansi-regex": {
2061
  "version": "6.1.0",
2062
  "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
 
2233
  "url": "https://github.com/sponsors/sindresorhus"
2234
  }
2235
  },
 
 
 
 
 
 
 
 
 
 
 
2236
  "node_modules/braces": {
2237
  "version": "3.0.3",
2238
  "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
 
2376
  "url": "https://github.com/sponsors/wooorm"
2377
  }
2378
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2379
  "node_modules/character-entities": {
2380
  "version": "2.0.2",
2381
  "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz",
 
2524
  "node": ">= 6"
2525
  }
2526
  },
 
 
 
 
 
 
 
2527
  "node_modules/confbox": {
2528
  "version": "0.1.8",
2529
  "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz",
 
2777
  "url": "https://github.com/sponsors/wooorm"
2778
  }
2779
  },
 
 
 
 
 
 
 
2780
  "node_modules/deferred-leveldown": {
2781
  "version": "5.3.0",
2782
  "resolved": "https://registry.npmjs.org/deferred-leveldown/-/deferred-leveldown-5.3.0.tgz",
 
3036
  "node": ">=6"
3037
  }
3038
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3039
  "node_modules/estree-util-is-identifier-name": {
3040
  "version": "3.0.0",
3041
  "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz",
 
3043
  "license": "MIT",
3044
  "funding": {
3045
  "type": "opencollective",
3046
+ "url": "https://opencollective.com/unified"
 
 
 
 
 
 
 
 
 
 
3047
  }
3048
  },
3049
  "node_modules/extend": {
 
3052
  "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
3053
  "license": "MIT"
3054
  },
 
 
 
 
 
 
 
3055
  "node_modules/fast-glob": {
3056
  "version": "3.3.2",
3057
  "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
 
3082
  "node": ">= 6"
3083
  }
3084
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3085
  "node_modules/fastparse": {
3086
  "version": "1.1.2",
3087
  "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz",
 
3098
  "reusify": "^1.0.4"
3099
  }
3100
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
3101
  "node_modules/fill-range": {
3102
  "version": "7.1.1",
3103
  "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
 
3111
  "node": ">=8"
3112
  }
3113
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3114
  "node_modules/follow-redirects": {
3115
  "version": "1.15.9",
3116
  "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
 
3340
  "url": "https://github.com/sponsors/ljharb"
3341
  }
3342
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3343
  "node_modules/has-symbols": {
3344
  "version": "1.1.0",
3345
  "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
 
3450
  "license": "BSD-3-Clause",
3451
  "optional": true
3452
  },
 
 
 
 
 
 
 
 
 
 
3453
  "node_modules/immediate": {
3454
  "version": "3.3.0",
3455
  "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.3.0.tgz",
 
3473
  "url": "https://github.com/sponsors/sindresorhus"
3474
  }
3475
  },
 
 
 
 
 
 
 
 
 
 
3476
  "node_modules/inherits": {
3477
  "version": "2.0.4",
3478
  "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
 
3697
  "node": ">=6"
3698
  }
3699
  },
 
 
 
 
 
 
 
3700
  "node_modules/json-parse-even-better-errors": {
3701
  "version": "2.3.1",
3702
  "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
 
3726
  "node": ">=16.0.0"
3727
  }
3728
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3729
  "node_modules/json5": {
3730
  "version": "2.2.3",
3731
  "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
 
3738
  "node": ">=6"
3739
  }
3740
  },
 
 
 
 
 
 
 
 
 
 
3741
  "node_modules/kolorist": {
3742
  "version": "1.8.0",
3743
  "resolved": "https://registry.npmjs.org/kolorist/-/kolorist-1.8.0.tgz",
 
3890
  "node": ">=6"
3891
  }
3892
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3893
  "node_modules/lib0": {
3894
  "version": "0.2.99",
3895
  "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.99.tgz",
 
3946
  "url": "https://github.com/sponsors/antfu"
3947
  }
3948
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3949
  "node_modules/lodash": {
3950
  "version": "4.17.21",
3951
  "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
3952
  "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
3953
  "license": "MIT"
3954
  },
3955
+ "node_modules/lodash.castarray": {
3956
+ "version": "4.4.0",
3957
+ "resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz",
3958
+ "integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==",
3959
+ "dev": true,
3960
+ "license": "MIT"
3961
+ },
3962
  "node_modules/lodash.debounce": {
3963
  "version": "4.0.8",
3964
  "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
3965
  "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
3966
  "license": "MIT"
3967
  },
3968
+ "node_modules/lodash.isplainobject": {
3969
+ "version": "4.0.6",
3970
+ "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
3971
+ "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
3972
+ "dev": true,
3973
+ "license": "MIT"
3974
+ },
3975
  "node_modules/lodash.merge": {
3976
  "version": "4.6.2",
3977
  "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
 
4675
  "node": ">= 0.6"
4676
  }
4677
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
4678
  "node_modules/minimist": {
4679
  "version": "1.2.8",
4680
  "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
 
4755
  "license": "MIT",
4756
  "optional": true
4757
  },
 
 
 
 
 
 
 
4758
  "node_modules/netcdfjs": {
4759
  "version": "3.0.0",
4760
  "resolved": "https://registry.npmjs.org/netcdfjs/-/netcdfjs-3.0.0.tgz",
 
4832
  "node": ">= 6"
4833
  }
4834
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4835
  "node_modules/package-json-from-dist": {
4836
  "version": "1.0.1",
4837
  "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
 
4907
  "url": "https://github.com/sponsors/sindresorhus"
4908
  }
4909
  },
 
 
 
 
 
 
 
 
 
 
4910
  "node_modules/path-key": {
4911
  "version": "3.1.1",
4912
  "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
 
5205
  "dev": true,
5206
  "license": "MIT"
5207
  },
 
 
 
 
 
 
 
 
 
 
5208
  "node_modules/prettier": {
5209
  "version": "3.4.2",
5210
  "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz",
 
5243
  "license": "MIT",
5244
  "optional": true
5245
  },
 
 
 
 
 
 
 
 
 
 
5246
  "node_modules/queue-microtask": {
5247
  "version": "1.2.3",
5248
  "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
 
5589
  "loose-envify": "^1.1.0"
5590
  }
5591
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
5592
  "node_modules/set-cookie-parser": {
5593
  "version": "2.7.1",
5594
  "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz",
 
5794
  "node": ">=8"
5795
  }
5796
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
5797
  "node_modules/style-to-object": {
5798
  "version": "1.0.8",
5799
  "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.8.tgz",
 
5826
  "node": ">=16 || 14 >=14.17"
5827
  }
5828
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
5829
  "node_modules/supports-preserve-symlinks-flag": {
5830
  "version": "1.0.0",
5831
  "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
 
6000
  "url": "https://github.com/sponsors/wooorm"
6001
  }
6002
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
6003
  "node_modules/ts-interface-checker": {
6004
  "version": "0.1.13",
6005
  "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
 
6019
  "integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==",
6020
  "license": "ISC"
6021
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
6022
  "node_modules/typescript": {
6023
  "version": "5.6.3",
6024
  "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz",
 
6033
  "node": ">=14.17"
6034
  }
6035
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6036
  "node_modules/ufo": {
6037
  "version": "1.5.4",
6038
  "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.5.4.tgz",
 
6237
  "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
6238
  "license": "(MIT AND Zlib)"
6239
  },
 
 
 
 
 
 
 
 
 
 
6240
  "node_modules/use-sync-external-store": {
6241
  "version": "1.2.2",
6242
  "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz",
 
6406
  "node": ">= 8"
6407
  }
6408
  },
 
 
 
 
 
 
 
 
 
 
6409
  "node_modules/wrap-ansi": {
6410
  "version": "8.1.0",
6411
  "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
 
6642
  "url": "https://github.com/sponsors/dmonad"
6643
  }
6644
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
6645
  "node_modules/zrender": {
6646
  "version": "5.6.0",
6647
  "resolved": "https://registry.npmjs.org/zrender/-/zrender-5.6.0.tgz",
lynxkite-app/web/package.json CHANGED
@@ -41,6 +41,7 @@
41
  },
42
  "devDependencies": {
43
  "@playwright/test": "^1.50.1",
 
44
  "@types/node": "^22.13.1",
45
  "@types/react": "^18.3.14",
46
  "@types/react-dom": "^18.3.2",
 
41
  },
42
  "devDependencies": {
43
  "@playwright/test": "^1.50.1",
44
+ "@tailwindcss/typography": "^0.5.16",
45
  "@types/node": "^22.13.1",
46
  "@types/react": "^18.3.14",
47
  "@types/react-dom": "^18.3.2",
lynxkite-app/web/src/Tooltip.tsx CHANGED
@@ -10,7 +10,7 @@ export default function Tooltip(props: any) {
10
  <a data-tooltip-id={id} tabIndex={0}>
11
  {props.children}
12
  </a>
13
- <ReactTooltip id={id} className="tooltip" place="top-end">
14
  {props.doc.map?.(
15
  (section: any, i: number) =>
16
  section.kind === "text" && <Markdown key={i}>{section.value}</Markdown>,
 
10
  <a data-tooltip-id={id} tabIndex={0}>
11
  {props.children}
12
  </a>
13
+ <ReactTooltip id={id} className="tooltip prose" place="top-end">
14
  {props.doc.map?.(
15
  (section: any, i: number) =>
16
  section.kind === "text" && <Markdown key={i}>{section.value}</Markdown>,
lynxkite-app/web/src/index.css CHANGED
@@ -100,15 +100,6 @@ body {
100
  font-size: 16px;
101
  font-weight: initial;
102
  max-width: 300px;
103
-
104
- p {
105
- margin-top: 1em;
106
- line-height: normal;
107
- }
108
-
109
- p:first-child {
110
- margin-top: 0;
111
- }
112
  }
113
 
114
  .expanded .lynxkite-node {
@@ -293,6 +284,26 @@ body {
293
  }
294
  }
295
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
296
  .env-select {
297
  background: transparent;
298
  color: #39bcf3;
@@ -515,6 +526,7 @@ body {
515
  z-index: -10 !important;
516
  }
517
 
 
518
  .selected .lynxkite-node {
519
  outline: var(--xy-selection-border, var(--xy-selection-border-default));
520
  outline-offset: 7.5px;
 
100
  font-size: 16px;
101
  font-weight: initial;
102
  max-width: 300px;
 
 
 
 
 
 
 
 
 
103
  }
104
 
105
  .expanded .lynxkite-node {
 
284
  }
285
  }
286
 
287
+ .react-flow__node-comment {
288
+ width: auto !important;
289
+ height: auto !important;
290
+ max-width: 400px;
291
+
292
+ .comment-view {
293
+ border-radius: 4px;
294
+ padding: 5px 10px;
295
+ }
296
+
297
+ .comment-editor {
298
+ width: 400px;
299
+ box-shadow: 0px 5px 20px 0px rgba(0, 0, 0, 0.3);
300
+ border-radius: 4px;
301
+ padding: 5px 10px;
302
+ border: 1px solid #ccc;
303
+ overflow-y: hidden;
304
+ }
305
+ }
306
+
307
  .env-select {
308
  background: transparent;
309
  color: #39bcf3;
 
526
  z-index: -10 !important;
527
  }
528
 
529
+ .selected .comment-view,
530
  .selected .lynxkite-node {
531
  outline: var(--xy-selection-border, var(--xy-selection-border-default));
532
  outline-offset: 7.5px;
lynxkite-app/web/src/workspace/Workspace.tsx CHANGED
@@ -37,6 +37,7 @@ import LynxKiteEdge from "./LynxKiteEdge.tsx";
37
  import { LynxKiteState } from "./LynxKiteState";
38
  import NodeSearch, { type OpsOp, type Catalog, type Catalogs } from "./NodeSearch.tsx";
39
  import NodeWithGraphCreationView from "./nodes/GraphCreationNode.tsx";
 
40
  import NodeWithImage from "./nodes/NodeWithImage.tsx";
41
  import NodeWithMolecule from "./nodes/NodeWithMolecule.tsx";
42
  import NodeWithParams from "./nodes/NodeWithParams";
@@ -77,8 +78,8 @@ function LynxKiteFlow() {
77
  if (!state.workspace.nodes) return;
78
  if (!state.workspace.edges) return;
79
  for (const n of state.workspace.nodes) {
80
- if (n.dragHandle !== ".bg-primary") {
81
- n.dragHandle = ".bg-primary";
82
  }
83
  }
84
  const nodes = reactFlow.getNodes();
@@ -183,6 +184,7 @@ function LynxKiteFlow() {
183
  table_view: NodeWithTableView,
184
  graph_creation_view: NodeWithGraphCreationView,
185
  molecule: NodeWithMolecule,
 
186
  }),
187
  [],
188
  );
 
37
  import { LynxKiteState } from "./LynxKiteState";
38
  import NodeSearch, { type OpsOp, type Catalog, type Catalogs } from "./NodeSearch.tsx";
39
  import NodeWithGraphCreationView from "./nodes/GraphCreationNode.tsx";
40
+ import NodeWithComment from "./nodes/NodeWithComment.tsx";
41
  import NodeWithImage from "./nodes/NodeWithImage.tsx";
42
  import NodeWithMolecule from "./nodes/NodeWithMolecule.tsx";
43
  import NodeWithParams from "./nodes/NodeWithParams";
 
78
  if (!state.workspace.nodes) return;
79
  if (!state.workspace.edges) return;
80
  for (const n of state.workspace.nodes) {
81
+ if (n.dragHandle !== ".drag-handle") {
82
+ n.dragHandle = ".drag-handle";
83
  }
84
  }
85
  const nodes = reactFlow.getNodes();
 
184
  table_view: NodeWithTableView,
185
  graph_creation_view: NodeWithGraphCreationView,
186
  molecule: NodeWithMolecule,
187
+ comment: NodeWithComment,
188
  }),
189
  [],
190
  );
lynxkite-app/web/src/workspace/nodes/LynxKiteNode.tsx CHANGED
@@ -90,7 +90,7 @@ function LynxKiteNodeComponent(props: LynxKiteNodeProps) {
90
  >
91
  <div className="lynxkite-node" style={props.nodeStyle}>
92
  <div
93
- className={`title bg-primary ${data.status}`}
94
  style={titleStyle}
95
  onClick={titleClicked}
96
  >
 
90
  >
91
  <div className="lynxkite-node" style={props.nodeStyle}>
92
  <div
93
+ className={`title bg-primary drag-handle ${data.status}`}
94
  style={titleStyle}
95
  onClick={titleClicked}
96
  >
lynxkite-app/web/src/workspace/nodes/NodeWithComment.tsx ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useReactFlow } from "@xyflow/react";
2
+ import { useState } from "react";
3
+ import Markdown from "react-markdown";
4
+ import type { UpdateOptions } from "./NodeParameter";
5
+
6
+ export default function NodeWithComment(props: any) {
7
+ const reactFlow = useReactFlow();
8
+ const [editing, setEditing] = useState(false);
9
+ function setComment(newValue: string, opts?: UpdateOptions) {
10
+ reactFlow.updateNodeData(props.id, (prevData: any) => ({
11
+ ...prevData,
12
+ params: { text: newValue },
13
+ __execution_delay: opts?.delay || 0,
14
+ }));
15
+ }
16
+ function onClick(e: React.MouseEvent<HTMLDivElement, MouseEvent>) {
17
+ // Start editing on double-click.
18
+ if (e.detail === 2) {
19
+ setEditing(true);
20
+ }
21
+ }
22
+ function finishEditing(el: HTMLTextAreaElement) {
23
+ setComment(el.value);
24
+ setEditing(false);
25
+ }
26
+ function onKeyDown(e: React.KeyboardEvent<HTMLTextAreaElement>) {
27
+ if (e.key === "Escape") {
28
+ finishEditing(e.currentTarget);
29
+ }
30
+ }
31
+ function onInput(el: HTMLTextAreaElement | null) {
32
+ if (!el) return;
33
+ el.focus();
34
+ // Resize the textarea to the content.
35
+ el.style.height = "auto";
36
+ el.style.height = `${el.scrollHeight}px`;
37
+ }
38
+ if (editing) {
39
+ return (
40
+ <textarea
41
+ className="comment-editor"
42
+ onBlur={(e) => finishEditing(e.currentTarget)}
43
+ onKeyDown={onKeyDown}
44
+ onInput={(e) => onInput(e.currentTarget)}
45
+ ref={(el) => onInput(el)}
46
+ defaultValue={props.data.params.text}
47
+ onClick={(e) => e.stopPropagation()}
48
+ placeholder="Enter workspace comment"
49
+ />
50
+ );
51
+ }
52
+ const text = props.data.params.text || "_double-click to edit_";
53
+ return (
54
+ <div className="comment-view drag-handle prose" onClick={onClick}>
55
+ <Markdown>{text}</Markdown>
56
+ </div>
57
+ );
58
+ }
lynxkite-app/web/tailwind.config.js CHANGED
@@ -5,7 +5,7 @@ export default {
5
  theme: {
6
  extend: {},
7
  },
8
- plugins: [require("daisyui")],
9
  daisyui: {
10
  logs: false,
11
  themes: [
 
5
  theme: {
6
  extend: {},
7
  },
8
+ plugins: [require("daisyui"), require("@tailwindcss/typography")],
9
  daisyui: {
10
  logs: false,
11
  themes: [
lynxkite-app/web/tests/basic.spec.ts CHANGED
@@ -21,7 +21,7 @@ test("Box creation & deletion per env", async () => {
21
  const envs = await workspace.getEnvs();
22
  for (const env of envs) {
23
  await workspace.setEnv(env);
24
- const catalog = await workspace.getCatalog();
25
  expect(catalog).not.toHaveLength(0);
26
  const op = catalog[0];
27
  await workspace.addBox(op);
 
21
  const envs = await workspace.getEnvs();
22
  for (const env of envs) {
23
  await workspace.setEnv(env);
24
+ const catalog = (await workspace.getCatalog()).filter((box) => box !== "Comment");
25
  expect(catalog).not.toHaveLength(0);
26
  const op = catalog[0];
27
  await workspace.addBox(op);
lynxkite-app/web/tests/errors.spec.ts CHANGED
@@ -33,9 +33,9 @@ test("unknown operation", async () => {
33
  await workspace.addBox("NX › Scale-Free Graph");
34
  const graphBox = workspace.getBox("NX › Scale-Free Graph 1");
35
  await graphBox.getByLabel("n", { exact: true }).fill("10");
36
- await workspace.setEnv("LynxScribe");
37
  const csvBox = workspace.getBox("NX › Scale-Free Graph 1");
38
- await expect(csvBox.locator(".error")).toHaveText('Operation "NX › Scale-Free Graph" not found.');
39
  await workspace.setEnv("LynxKite Graph Analytics");
40
  await expect(csvBox.locator(".error")).not.toBeVisible();
41
  });
 
33
  await workspace.addBox("NX › Scale-Free Graph");
34
  const graphBox = workspace.getBox("NX › Scale-Free Graph 1");
35
  await graphBox.getByLabel("n", { exact: true }).fill("10");
36
+ await workspace.setEnv("Pillow");
37
  const csvBox = workspace.getBox("NX › Scale-Free Graph 1");
38
+ await expect(csvBox.locator(".error")).toHaveText("Unknown operation.");
39
  await workspace.setEnv("LynxKite Graph Analytics");
40
  await expect(csvBox.locator(".error")).not.toBeVisible();
41
  });
lynxkite-app/web/tests/examples.spec.ts CHANGED
@@ -3,12 +3,9 @@ import { expect, test } from "@playwright/test";
3
  import { Workspace } from "./lynxkite";
4
 
5
  const WORKSPACES = [
6
- // "AIMO",
7
  "Airlines demo",
8
  "Bio Cypher demo",
9
- // "Graph RAG",
10
  "Image processing",
11
- // "LynxScribe demo",
12
  "NetworkX demo",
13
  "Model use",
14
  ];
 
3
  import { Workspace } from "./lynxkite";
4
 
5
  const WORKSPACES = [
 
6
  "Airlines demo",
7
  "Bio Cypher demo",
 
8
  "Image processing",
 
9
  "NetworkX demo",
10
  "Model use",
11
  ];
lynxkite-core/pyproject.toml CHANGED
@@ -6,6 +6,10 @@ readme = "README.md"
6
  requires-python = ">=3.11"
7
  dependencies = [
8
  ]
 
 
 
 
9
 
10
  [project.optional-dependencies]
11
  dev = [
 
6
  requires-python = ">=3.11"
7
  dependencies = [
8
  ]
9
+ classifiers = ["Private :: Do Not Upload"]
10
+
11
+ [project.urls]
12
+ Homepage = "https://github.com/lynxkite/lynxkite-2000/"
13
 
14
  [project.optional-dependencies]
15
  dev = [
lynxkite-core/src/lynxkite/core/executors/one_by_one.py CHANGED
@@ -1,4 +1,6 @@
1
- """A LynxKite executor that assumes most operations operate on their input one by one."""
 
 
2
 
3
  from .. import ops
4
  from .. import workspace
@@ -11,24 +13,24 @@ import typing
11
 
12
 
13
  class Context(ops.BaseConfig):
14
- """Passed to operation functions as "_ctx" if they have such a parameter."""
 
 
 
 
 
 
 
15
 
16
  node: workspace.WorkspaceNode
17
  last_result: typing.Any = None
18
 
19
 
20
- class Output(ops.BaseConfig):
21
- """Return this to send values to specific outputs of a node."""
22
-
23
- output_handle: str
24
- value: dict
25
-
26
-
27
- def df_to_list(df):
28
  return df.to_dict(orient="records")
29
 
30
 
31
- def has_ctx(op):
32
  sig = inspect.signature(op.func)
33
  return "_ctx" in sig.parameters
34
 
@@ -37,16 +39,22 @@ CACHES = {}
37
 
38
 
39
  def register(env: str, cache: bool = True):
40
- """Registers the one-by-one executor."""
 
 
 
 
 
 
41
  if cache:
42
  CACHES[env] = {}
43
  cache = CACHES[env]
44
  else:
45
  cache = None
46
- ops.EXECUTORS[env] = lambda ws: execute(ws, ops.CATALOGS[env], cache=cache)
47
 
48
 
49
- def get_stages(ws, catalog: ops.Catalog):
50
  """Inputs on top/bottom are batch inputs. We decompose the graph into a DAG of components along these edges."""
51
  nodes = {n.id: n for n in ws.nodes}
52
  batch_inputs = {}
@@ -81,20 +89,20 @@ def _default_serializer(obj):
81
  return {"__nonserializable__": id(obj)}
82
 
83
 
84
- def make_cache_key(obj):
85
  return orjson.dumps(obj, default=_default_serializer)
86
 
87
 
88
  EXECUTOR_OUTPUT_CACHE = {}
89
 
90
 
91
- async def await_if_needed(obj):
92
  if inspect.isawaitable(obj):
93
  return await obj
94
  return obj
95
 
96
 
97
- async def execute(ws: workspace.Workspace, catalog: ops.Catalog, cache=None):
98
  nodes = {n.id: n for n in ws.nodes}
99
  contexts = {n.id: Context(node=n) for n in ws.nodes}
100
  edges = {n.id: [] for n in ws.nodes}
@@ -113,7 +121,7 @@ async def execute(ws: workspace.Workspace, catalog: ops.Catalog, cache=None):
113
  tasks[node.id] = [NO_INPUT]
114
  batch_inputs = {}
115
  # Run the rest until we run out of tasks.
116
- stages = get_stages(ws, catalog)
117
  for stage in stages:
118
  next_stage = {}
119
  while tasks:
@@ -124,7 +132,7 @@ async def execute(ws: workspace.Workspace, catalog: ops.Catalog, cache=None):
124
  node = nodes[n]
125
  op = catalog[node.data.title]
126
  params = {**node.data.params}
127
- if has_ctx(op):
128
  params["_ctx"] = contexts[node.id]
129
  results = []
130
  node.publish_started()
@@ -148,15 +156,15 @@ async def execute(ws: workspace.Workspace, catalog: ops.Catalog, cache=None):
148
  node.publish_error(f"Missing input: {', '.join(missing)}")
149
  break
150
  if cache is not None:
151
- key = make_cache_key((inputs, params))
152
  if key not in cache:
153
  result: ops.Result = op(*inputs, **params)
154
- result.output = await await_if_needed(result.output)
155
  cache[key] = result
156
  result = cache[key]
157
  else:
158
  result = op(*inputs, **params)
159
- output = await await_if_needed(result.output)
160
  except Exception as e:
161
  traceback.print_exc()
162
  node.publish_error(e)
@@ -164,13 +172,13 @@ async def execute(ws: workspace.Workspace, catalog: ops.Catalog, cache=None):
164
  contexts[node.id].last_result = output
165
  # Returned lists and DataFrames are considered multiple tasks.
166
  if isinstance(output, pd.DataFrame):
167
- output = df_to_list(output)
168
  elif not isinstance(output, list):
169
  output = [output]
170
  results.extend(output)
171
  else: # Finished all tasks without errors.
172
  if result.display:
173
- result.display = await await_if_needed(result.display)
174
  for edge in edges[node.id]:
175
  t = nodes[edge.target]
176
  op = catalog[t.data.title]
 
1
+ """
2
+ A LynxKite executor that assumes most operations operate on their input one by one.
3
+ """
4
 
5
  from .. import ops
6
  from .. import workspace
 
13
 
14
 
15
  class Context(ops.BaseConfig):
16
+ """Passed to operation functions as "_ctx" if they have such a parameter.
17
+
18
+ Attributes:
19
+ node: The workspace node that this context is associated with.
20
+ last_result: The last result produced by the operation.
21
+ This can be used to incrementally build a result, when the operation
22
+ is executed for multiple items.
23
+ """
24
 
25
  node: workspace.WorkspaceNode
26
  last_result: typing.Any = None
27
 
28
 
29
+ def _df_to_list(df):
 
 
 
 
 
 
 
30
  return df.to_dict(orient="records")
31
 
32
 
33
+ def _has_ctx(op):
34
  sig = inspect.signature(op.func)
35
  return "_ctx" in sig.parameters
36
 
 
39
 
40
 
41
  def register(env: str, cache: bool = True):
42
+ """Registers the one-by-one executor.
43
+
44
+ Usage:
45
+
46
+ from lynxkite.core.executors import one_by_one
47
+ one_by_one.register("My Environment")
48
+ """
49
  if cache:
50
  CACHES[env] = {}
51
  cache = CACHES[env]
52
  else:
53
  cache = None
54
+ ops.EXECUTORS[env] = lambda ws: _execute(ws, ops.CATALOGS[env], cache=cache)
55
 
56
 
57
+ def _get_stages(ws, catalog: ops.Catalog):
58
  """Inputs on top/bottom are batch inputs. We decompose the graph into a DAG of components along these edges."""
59
  nodes = {n.id: n for n in ws.nodes}
60
  batch_inputs = {}
 
89
  return {"__nonserializable__": id(obj)}
90
 
91
 
92
+ def _make_cache_key(obj):
93
  return orjson.dumps(obj, default=_default_serializer)
94
 
95
 
96
  EXECUTOR_OUTPUT_CACHE = {}
97
 
98
 
99
+ async def _await_if_needed(obj):
100
  if inspect.isawaitable(obj):
101
  return await obj
102
  return obj
103
 
104
 
105
+ async def _execute(ws: workspace.Workspace, catalog: ops.Catalog, cache=None):
106
  nodes = {n.id: n for n in ws.nodes}
107
  contexts = {n.id: Context(node=n) for n in ws.nodes}
108
  edges = {n.id: [] for n in ws.nodes}
 
121
  tasks[node.id] = [NO_INPUT]
122
  batch_inputs = {}
123
  # Run the rest until we run out of tasks.
124
+ stages = _get_stages(ws, catalog)
125
  for stage in stages:
126
  next_stage = {}
127
  while tasks:
 
132
  node = nodes[n]
133
  op = catalog[node.data.title]
134
  params = {**node.data.params}
135
+ if _has_ctx(op):
136
  params["_ctx"] = contexts[node.id]
137
  results = []
138
  node.publish_started()
 
156
  node.publish_error(f"Missing input: {', '.join(missing)}")
157
  break
158
  if cache is not None:
159
+ key = _make_cache_key((inputs, params))
160
  if key not in cache:
161
  result: ops.Result = op(*inputs, **params)
162
+ result.output = await _await_if_needed(result.output)
163
  cache[key] = result
164
  result = cache[key]
165
  else:
166
  result = op(*inputs, **params)
167
+ output = await _await_if_needed(result.output)
168
  except Exception as e:
169
  traceback.print_exc()
170
  node.publish_error(e)
 
172
  contexts[node.id].last_result = output
173
  # Returned lists and DataFrames are considered multiple tasks.
174
  if isinstance(output, pd.DataFrame):
175
+ output = _df_to_list(output)
176
  elif not isinstance(output, list):
177
  output = [output]
178
  results.extend(output)
179
  else: # Finished all tasks without errors.
180
  if result.display:
181
+ result.display = await _await_if_needed(result.display)
182
  for edge in edges[node.id]:
183
  t = nodes[edge.target]
184
  op = catalog[t.data.title]
lynxkite-core/src/lynxkite/core/executors/simple.py CHANGED
@@ -9,7 +9,13 @@ import graphlib
9
 
10
 
11
  def register(env: str):
12
- """Registers the one-by-one executor."""
 
 
 
 
 
 
13
  ops.EXECUTORS[env] = lambda ws: execute(ws, ops.CATALOGS[env])
14
 
15
 
 
9
 
10
 
11
  def register(env: str):
12
+ """Registers the simple executor.
13
+
14
+ Usage:
15
+
16
+ from lynxkite.core.executors import simple
17
+ simple.register("My Environment")
18
+ """
19
  ops.EXECUTORS[env] = lambda ws: execute(ws, ops.CATALOGS[env])
20
 
21
 
lynxkite-core/src/lynxkite/core/ops.py CHANGED
@@ -41,6 +41,7 @@ def type_to_json(t):
41
 
42
  Type = Annotated[typing.Any, pydantic.PlainSerializer(type_to_json, return_type=dict)]
43
  LongStr = Annotated[str, {"format": "textarea"}]
 
44
  PathStr = Annotated[str, {"format": "path"}]
45
  CollapsedStr = Annotated[str, {"format": "collapsed"}]
46
  NodeAttribute = Annotated[str, {"format": "node attribute"}]
@@ -268,7 +269,7 @@ def op(
268
  _params.append(Parameter.basic(n, param.default, param.annotation))
269
  if params:
270
  _params.extend(params)
271
- if outputs:
272
  _outputs = [Output(name=name, type=None) for name in outputs]
273
  else:
274
  _outputs = [Output(name="output", type=None)] if view == "basic" else []
@@ -314,24 +315,41 @@ def matplotlib_to_image(func):
314
  return wrapper
315
 
316
 
317
- def input_position(**kwargs):
318
- """Decorator for specifying unusual positions for the inputs."""
 
 
 
 
 
 
 
 
 
319
 
320
  def decorator(func):
321
  op = func.__op__
322
- for k, v in kwargs.items():
323
  op.get_input(k).position = Position(v)
324
  return func
325
 
326
  return decorator
327
 
328
 
329
- def output_position(**kwargs):
330
- """Decorator for specifying unusual positions for the outputs."""
 
 
 
 
 
 
 
 
331
 
332
  def decorator(func):
333
  op = func.__op__
334
- for k, v in kwargs.items():
335
  op.get_output(k).position = Position(v)
336
  return func
337
 
@@ -359,6 +377,16 @@ def register_passive_op(env: str, name: str, inputs=[], outputs=["output"], para
359
  return op
360
 
361
 
 
 
 
 
 
 
 
 
 
 
362
  def register_executor(env: str):
363
  """Decorator for registering an executor.
364
 
 
41
 
42
  Type = Annotated[typing.Any, pydantic.PlainSerializer(type_to_json, return_type=dict)]
43
  LongStr = Annotated[str, {"format": "textarea"}]
44
+ """LongStr is a string type for parameters that will be displayed as a multiline text area in the UI."""
45
  PathStr = Annotated[str, {"format": "path"}]
46
  CollapsedStr = Annotated[str, {"format": "collapsed"}]
47
  NodeAttribute = Annotated[str, {"format": "node attribute"}]
 
269
  _params.append(Parameter.basic(n, param.default, param.annotation))
270
  if params:
271
  _params.extend(params)
272
+ if outputs is not None:
273
  _outputs = [Output(name=name, type=None) for name in outputs]
274
  else:
275
  _outputs = [Output(name="output", type=None)] if view == "basic" else []
 
315
  return wrapper
316
 
317
 
318
+ def input_position(**positions):
319
+ """
320
+ Decorator for specifying unusual positions for the inputs.
321
+
322
+ Example usage:
323
+
324
+ @input_position(a="bottom", b="bottom")
325
+ @op("test", "maybe add")
326
+ def maybe_add(a: list[int], b: list[int] | None = None):
327
+ return [a + b for a, b in zip(a, b)] if b else a
328
+ """
329
 
330
  def decorator(func):
331
  op = func.__op__
332
+ for k, v in positions.items():
333
  op.get_input(k).position = Position(v)
334
  return func
335
 
336
  return decorator
337
 
338
 
339
+ def output_position(**positions):
340
+ """Decorator for specifying unusual positions for the outputs.
341
+
342
+ Example usage:
343
+
344
+ @output_position(output="top")
345
+ @op("test", "maybe add")
346
+ def maybe_add(a: list[int], b: list[int] | None = None):
347
+ return [a + b for a, b in zip(a, b)] if b else a
348
+ """
349
 
350
  def decorator(func):
351
  op = func.__op__
352
+ for k, v in positions.items():
353
  op.get_output(k).position = Position(v)
354
  return func
355
 
 
377
  return op
378
 
379
 
380
+ COMMENT_OP = Op(
381
+ func=no_op,
382
+ name="Comment",
383
+ params=[Parameter.basic("text", "", LongStr)],
384
+ inputs=[],
385
+ outputs=[],
386
+ type="comment",
387
+ )
388
+
389
+
390
  def register_executor(env: str):
391
  """Decorator for registering an executor.
392
 
lynxkite-graph-analytics/pyproject.toml CHANGED
@@ -14,6 +14,7 @@ dependencies = [
14
  "osmnx>=2.0.2",
15
  "pandas>=2.2.3",
16
  "polars>=1.25.2",
 
17
  "torch>=2.7.0",
18
  "torch-geometric>=2.6.1",
19
  "umap-learn>=0.5.7",
 
14
  "osmnx>=2.0.2",
15
  "pandas>=2.2.3",
16
  "polars>=1.25.2",
17
+ "pyarrow>=19.0.1",
18
  "torch>=2.7.0",
19
  "torch-geometric>=2.6.1",
20
  "umap-learn>=0.5.7",
lynxkite-graph-analytics/src/lynxkite_graph_analytics/core.py CHANGED
@@ -17,16 +17,28 @@ ENV = "LynxKite Graph Analytics"
17
 
18
  @dataclasses.dataclass
19
  class RelationDefinition:
20
- """Defines a set of edges."""
 
 
 
 
 
 
 
 
 
 
 
 
21
 
22
- df: str # The DataFrame that contains the edges.
23
- source_column: str # The column in the edge DataFrame that contains the source node ID.
24
- target_column: str # The column in the edge DataFrame that contains the target node ID.
25
- source_table: str # The DataFrame that contains the source nodes.
26
- target_table: str # The DataFrame that contains the target nodes.
27
- source_key: str # The column in the source table that contains the node ID.
28
- target_key: str # The column in the target table that contains the node ID.
29
- name: str | None = None # Descriptive name for the relation.
30
 
31
 
32
  @dataclasses.dataclass
@@ -34,7 +46,16 @@ class Bundle:
34
  """A collection of DataFrames and other data.
35
 
36
  Can efficiently represent a knowledge graph (homogeneous or heterogeneous) or tabular data.
37
- It can also carry other data, such as a trained model.
 
 
 
 
 
 
 
 
 
38
  """
39
 
40
  dfs: dict[str, pd.DataFrame] = dataclasses.field(default_factory=dict)
@@ -91,7 +112,10 @@ class Bundle:
91
  return graph
92
 
93
  def copy(self):
94
- """Returns a medium depth copy of the bundle. The Bundle is completely new, but the DataFrames and RelationDefinitions are shared."""
 
 
 
95
  return Bundle(
96
  dfs=dict(self.dfs),
97
  relations=list(self.relations),
 
17
 
18
  @dataclasses.dataclass
19
  class RelationDefinition:
20
+ """
21
+ Defines a set of edges.
22
+
23
+ Attributes:
24
+ df: The name of the DataFrame that contains the edges.
25
+ source_column: The column in the edge DataFrame that contains the source node ID.
26
+ target_column: The column in the edge DataFrame that contains the target node ID.
27
+ source_table: The name of the DataFrame that contains the source nodes.
28
+ target_table: The name of the DataFrame that contains the target nodes.
29
+ source_key: The column in the source table that contains the node ID.
30
+ target_key: The column in the target table that contains the node ID.
31
+ name: Descriptive name for the relation.
32
+ """
33
 
34
+ df: str
35
+ source_column: str
36
+ target_column: str
37
+ source_table: str
38
+ target_table: str
39
+ source_key: str
40
+ target_key: str
41
+ name: str | None = None
42
 
43
 
44
  @dataclasses.dataclass
 
46
  """A collection of DataFrames and other data.
47
 
48
  Can efficiently represent a knowledge graph (homogeneous or heterogeneous) or tabular data.
49
+
50
+ By convention, if it contains a single DataFrame, it is called `df`.
51
+ If it contains a homogeneous graph, it is represented as two DataFrames called `nodes` and
52
+ `edges`.
53
+
54
+ Attributes:
55
+ dfs: Named DataFrames.
56
+ relations: Metadata that describes the roles of each DataFrame.
57
+ Can be empty, if the bundle is just one or more DataFrames.
58
+ other: Other data, such as a trained model.
59
  """
60
 
61
  dfs: dict[str, pd.DataFrame] = dataclasses.field(default_factory=dict)
 
112
  return graph
113
 
114
  def copy(self):
115
+ """
116
+ Returns a shallow copy of the bundle. The Bundle and its containers are new, but
117
+ the DataFrames and RelationDefinitions are shared. (The contents of `other` are also shared.)
118
+ """
119
  return Bundle(
120
  dfs=dict(self.dfs),
121
  relations=list(self.relations),
lynxkite-pillow-example/pyproject.toml CHANGED
@@ -11,6 +11,10 @@ dependencies = [
11
  "requests>=2.32.3",
12
  "aiohttp>=3.11.11",
13
  ]
 
 
 
 
14
 
15
  [tool.uv.sources]
16
  lynxkite-core = { path = "../lynxkite-core" }
 
11
  "requests>=2.32.3",
12
  "aiohttp>=3.11.11",
13
  ]
14
+ classifiers = ["Private :: Do Not Upload"]
15
+
16
+ [project.urls]
17
+ Homepage = "https://github.com/lynxkite/lynxkite-2000/"
18
 
19
  [tool.uv.sources]
20
  lynxkite-core = { path = "../lynxkite-core" }
mkdocs.yml CHANGED
@@ -1,6 +1,25 @@
1
- site_name: "LynxKite"
2
- repo_url: https://github.com/lynxkite/lynxkite
3
- repo_name: lynxkite/lynxkite
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
 
5
  theme:
6
  name: "material"
@@ -13,13 +32,35 @@ theme:
13
  - navigation.path
14
  - navigation.instant
15
  - navigation.instant.prefetch
 
 
 
16
 
17
  extra_css:
18
  - stylesheets/extra.css
19
 
20
  plugins:
21
  - search
 
22
  - mkdocstrings:
23
  handlers:
24
  python:
25
- paths: ["./lynxkite-app/src", "./lynxkite-core/src", "./lynxkite-graph-analytics/src", "./lynxkite-lynxscribe/src"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ site_name: "LynxKite 2000:MM"
2
+ repo_url: https://github.com/lynxkite/lynxkite-2000
3
+ repo_name: lynxkite/lynxkite-2000
4
+ watch: [mkdocs.yml, README.md, lynxkite-core, lynxkite-graph-analytics, lynxkite-app]
5
+
6
+ nav:
7
+ - Home:
8
+ - Overview: index.md
9
+ - License: license.md
10
+ - Usage:
11
+ - usage/quickstart.md
12
+ - usage/plugins.md
13
+ - API reference:
14
+ - LynxKite Core:
15
+ - reference/lynxkite-core/ops.md
16
+ - reference/lynxkite-core/workspace.md
17
+ - Executors:
18
+ - reference/lynxkite-core/executors/simple.md
19
+ - reference/lynxkite-core/executors/one_by_one.md
20
+ - LynxKite Graph Analytics:
21
+ - reference/lynxkite-graph-analytics/core.md
22
+ - reference/lynxkite-graph-analytics/operations.md
23
 
24
  theme:
25
  name: "material"
 
32
  - navigation.path
33
  - navigation.instant
34
  - navigation.instant.prefetch
35
+ - navigation.footer
36
+ - content.code.annotate
37
+ - content.code.copy
38
 
39
  extra_css:
40
  - stylesheets/extra.css
41
 
42
  plugins:
43
  - search
44
+ - autorefs
45
  - mkdocstrings:
46
  handlers:
47
  python:
48
+ paths: ["./lynxkite-app/src", "./lynxkite-core/src", "./lynxkite-graph-analytics/src"]
49
+ options:
50
+ show_source: false
51
+ show_symbol_type_heading: true
52
+ show_symbol_type_toc: true
53
+ docstring_section_style: spacy
54
+ separate_signature: true
55
+ show_signature_annotations: true
56
+ signature_crossrefs: true
57
+ markdown_extensions:
58
+ - pymdownx.highlight:
59
+ anchor_linenums: true
60
+ line_spans: __span
61
+ pygments_lang_class: true
62
+ - pymdownx.inlinehilite
63
+ - pymdownx.snippets
64
+ - pymdownx.superfences
65
+ - toc:
66
+ permalink: "¤"