darabos commited on
Commit
03b7855
·
1 Parent(s): f484f08

Run Open WebUI against LynxScribe in LynxKite.

Browse files
README.md CHANGED
@@ -7,16 +7,13 @@ original LynxKite. The primary goals of this rewrite are:
7
  - More extensible backend. Make it easy to add new LynxKite boxes. Make it easy to use our frontend for other purposes,
8
  configuring and executing other pipelines.
9
 
10
- Current status: **PROTOTYPE**
11
-
12
  ## Installation
13
 
14
  To run the backend:
15
 
16
  ```bash
17
- pip install -r requirements.txt
18
- PYTHONPATH=. pydantic2ts --module server.workspace --output ./web/src/apiTypes.ts
19
- uvicorn server.main:app --reload
20
  ```
21
 
22
  To run the frontend:
@@ -26,3 +23,9 @@ cd web
26
  npm i
27
  npm run dev
28
  ```
 
 
 
 
 
 
 
7
  - More extensible backend. Make it easy to add new LynxKite boxes. Make it easy to use our frontend for other purposes,
8
  configuring and executing other pipelines.
9
 
 
 
10
  ## Installation
11
 
12
  To run the backend:
13
 
14
  ```bash
15
+ PYTHONPATH=. uv run pydantic2ts --module server.workspace --output ./web/src/apiTypes.ts --json2ts-cmd "npm exec --prefix web json2ts"
16
+ uv run fastapi run server/main.py --reload
 
17
  ```
18
 
19
  To run the frontend:
 
23
  npm i
24
  npm run dev
25
  ```
26
+
27
+ To run a chat UI for LynxScribe workspaces:
28
+
29
+ ```bash
30
+ WEBUI_AUTH=false OPENAI_API_BASE_URL=http://localhost:8000/api/service/server.lynxscribe_ops uvx open-webui serve
31
+ ```
data/LynxScribe demo CHANGED
@@ -580,8 +580,7 @@
580
  "data": {
581
  "title": "Truncate history",
582
  "params": {
583
- "max_tokens": 10000,
584
- "language": "English"
585
  },
586
  "display": null,
587
  "error": null,
@@ -594,13 +593,6 @@
594
  "type": {
595
  "type": "<class 'int'>"
596
  }
597
- },
598
- "language": {
599
- "name": "language",
600
- "default": "English",
601
- "type": {
602
- "type": "<class 'str'>"
603
- }
604
  }
605
  },
606
  "inputs": {},
@@ -957,4 +949,4 @@
957
  "targetHandle": "chat_api"
958
  }
959
  ]
960
- }
 
580
  "data": {
581
  "title": "Truncate history",
582
  "params": {
583
+ "max_tokens": 10000
 
584
  },
585
  "display": null,
586
  "error": null,
 
593
  "type": {
594
  "type": "<class 'int'>"
595
  }
 
 
 
 
 
 
 
596
  }
597
  },
598
  "inputs": {},
 
949
  "targetHandle": "chat_api"
950
  }
951
  ]
952
+ }
server/executors/one_by_one.py CHANGED
@@ -127,12 +127,13 @@ async def execute(ws, catalog, cache=None):
127
  results = []
128
  for task in ts:
129
  try:
130
- inputs = [
131
- batch_inputs[(n, i.name)]
132
- if i.position in "top or bottom"
133
- else task
134
- for i in op.inputs.values()
135
- ]
 
136
  if cache is not None:
137
  key = make_cache_key((inputs, params))
138
  if key not in cache:
 
127
  results = []
128
  for task in ts:
129
  try:
130
+ inputs = []
131
+ for i in op.inputs.values():
132
+ if i.position in "top or bottom":
133
+ assert (n, i.name) in batch_inputs, f"{i.name} is missing"
134
+ inputs.append(batch_inputs[(n, i.name)])
135
+ else:
136
+ inputs.append(task)
137
  if cache is not None:
138
  key = make_cache_key((inputs, params))
139
  if key not in cache:
server/llm_ops.py CHANGED
@@ -13,7 +13,7 @@ from .executors import one_by_one
13
  chat_client = openai.OpenAI(base_url="http://localhost:8080/v1")
14
  embedding_client = openai.OpenAI(base_url="http://localhost:7997/")
15
  jinja = jinja2.Environment()
16
- chroma_client = chromadb.Client()
17
  LLM_CACHE = {}
18
  ENV = "LLM logic"
19
  one_by_one.register(ENV)
@@ -178,12 +178,15 @@ def rag(
178
  num_matches: int = 10,
179
  _ctx: one_by_one.Context,
180
  ):
 
181
  if engine == RagEngine.Chroma:
182
  last = _ctx.last_result
183
  if last:
184
  collection = last["_collection"]
185
  else:
186
  collection_name = _ctx.node.id.replace(" ", "_")
 
 
187
  for c in chroma_client.list_collections():
188
  if c.name == collection_name:
189
  chroma_client.delete_collection(name=collection_name)
 
13
  chat_client = openai.OpenAI(base_url="http://localhost:8080/v1")
14
  embedding_client = openai.OpenAI(base_url="http://localhost:7997/")
15
  jinja = jinja2.Environment()
16
+ chroma_client = None
17
  LLM_CACHE = {}
18
  ENV = "LLM logic"
19
  one_by_one.register(ENV)
 
178
  num_matches: int = 10,
179
  _ctx: one_by_one.Context,
180
  ):
181
+ global chroma_client
182
  if engine == RagEngine.Chroma:
183
  last = _ctx.last_result
184
  if last:
185
  collection = last["_collection"]
186
  else:
187
  collection_name = _ctx.node.id.replace(" ", "_")
188
+ if chroma_client is None:
189
+ chroma_client = chromadb.Client()
190
  for c in chroma_client.list_collections():
191
  if c.name == collection_name:
192
  chroma_client.delete_collection(name=collection_name)
server/lynxkite_ops.py CHANGED
@@ -218,7 +218,7 @@ def sql(bundle: Bundle, *, query: ops.LongStr, save_as: str = "result"):
218
  # TODO: Currently `collect()` moves the data from cuDF to Polars. Then we convert it to Pandas,
219
  # which (hopefully) puts it back into cuDF. Hopefully we will be able to keep it in cuDF.
220
  else:
221
- res = pl.SQLContext(bundle.dfs).execute(query)
222
  bundle.dfs[save_as] = res
223
  return bundle
224
 
 
218
  # TODO: Currently `collect()` moves the data from cuDF to Polars. Then we convert it to Pandas,
219
  # which (hopefully) puts it back into cuDF. Hopefully we will be able to keep it in cuDF.
220
  else:
221
+ res = pl.SQLContext(bundle.dfs).execute(query).collect().to_pandas()
222
  bundle.dfs[save_as] = res
223
  return bundle
224
 
server/lynxscribe_ops.py CHANGED
@@ -5,19 +5,18 @@ LynxScribe configuration and testing in LynxKite.
5
  from lynxscribe.core.llm.base import get_llm_engine
6
  from lynxscribe.core.vector_store.base import get_vector_store
7
  from lynxscribe.common.config import load_config
8
- from lynxscribe.components.text_embedder import TextEmbedder
9
  from lynxscribe.components.rag.rag_graph import RAGGraph
10
  from lynxscribe.components.rag.knowledge_base_graph import PandasKnowledgeBaseGraph
11
  from lynxscribe.components.rag.rag_chatbot import Scenario, ScenarioSelector, RAGChatbot
12
- from lynxscribe.components.chat_processor.base import ChatProcessor
13
- from lynxscribe.components.chat_processor.processors import (
14
  MaskTemplate,
15
  TruncateHistory,
16
  )
17
- from lynxscribe.components.chat_api import ChatAPI, ChatAPIRequest, ChatAPIResponse
18
 
19
  from . import ops
20
- import asyncio
21
  import json
22
  from .executors import one_by_one
23
 
@@ -134,12 +133,8 @@ def chat_processor(processor, *, _ctx: one_by_one.Context):
134
 
135
  @output_on_top
136
  @op("Truncate history")
137
- def truncate_history(*, max_tokens=10000, language="English"):
138
- return {
139
- "question_processor": TruncateHistory(
140
- max_tokens=max_tokens, language=language.lower()
141
- )
142
- }
143
 
144
 
145
  @output_on_top
@@ -224,42 +219,102 @@ def view(input):
224
  return v
225
 
226
 
227
- async def api_service(request):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
228
  """
229
  Serves a chat endpoint that matches LynxScribe's interface.
230
  To access it you need to add the "module" and "workspace"
231
  parameters.
232
  The workspace must contain exactly one "Chat API" node.
233
 
234
- curl -X POST ${LYNXKITE_URL}/api/service \
235
  -H "Content-Type: application/json" \
236
  -d '{
237
- "module": "server.lynxscribe_ops",
238
- "workspace": "LynxScribe demo",
239
- "session_id": "b43215a0-428f-11ef-9454-0242ac120002",
240
- "question": "what does the fox say",
241
- "history": [],
242
- "user_id": "x",
243
- "meta_inputs": {}
244
  }'
245
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
246
  import pathlib
247
  from . import workspace
248
 
249
- DATA_PATH = pathlib.Path.cwd() / "data"
250
- path = DATA_PATH / request["workspace"]
251
- assert path.is_relative_to(DATA_PATH)
252
- assert path.exists(), f"Workspace {path} does not exist"
253
- ws = workspace.load(path)
254
- contexts = ops.EXECUTORS[ENV](ws)
255
- nodes = [op for op in ws.nodes if op.data.title == "Chat API"]
256
- [node] = nodes
257
- context = contexts[node.id]
258
- chat_api = context.last_result["chat_api"]
259
- request = ChatAPIRequest(
260
- session_id=request["session_id"],
261
- question=request["question"],
262
- history=request["history"],
263
- )
264
- response = await chat_api.answer(request)
265
- return response
 
5
  from lynxscribe.core.llm.base import get_llm_engine
6
  from lynxscribe.core.vector_store.base import get_vector_store
7
  from lynxscribe.common.config import load_config
8
+ from lynxscribe.components.text.embedder import TextEmbedder
9
  from lynxscribe.components.rag.rag_graph import RAGGraph
10
  from lynxscribe.components.rag.knowledge_base_graph import PandasKnowledgeBaseGraph
11
  from lynxscribe.components.rag.rag_chatbot import Scenario, ScenarioSelector, RAGChatbot
12
+ from lynxscribe.components.chat.processors import (
13
+ ChatProcessor,
14
  MaskTemplate,
15
  TruncateHistory,
16
  )
17
+ from lynxscribe.components.chat.api import ChatAPI, ChatAPIRequest, ChatAPIResponse
18
 
19
  from . import ops
 
20
  import json
21
  from .executors import one_by_one
22
 
 
133
 
134
  @output_on_top
135
  @op("Truncate history")
136
+ def truncate_history(*, max_tokens=10000):
137
+ return {"question_processor": TruncateHistory(max_tokens=max_tokens)}
 
 
 
 
138
 
139
 
140
  @output_on_top
 
219
  return v
220
 
221
 
222
+ async def get_chat_api(ws):
223
+ import pathlib
224
+ from . import workspace
225
+
226
+ DATA_PATH = pathlib.Path.cwd() / "data"
227
+ path = DATA_PATH / ws
228
+ assert path.is_relative_to(DATA_PATH)
229
+ assert path.exists(), f"Workspace {path} does not exist"
230
+ ws = workspace.load(path)
231
+ contexts = await ops.EXECUTORS[ENV](ws)
232
+ nodes = [op for op in ws.nodes if op.data.title == "Chat API"]
233
+ [node] = nodes
234
+ context = contexts[node.id]
235
+ return context.last_result["chat_api"]
236
+
237
+
238
+ async def stream_chat_api_response(request):
239
+ chat_api = await get_chat_api(request["model"])
240
+ chat_api_request = ChatAPIRequest(
241
+ session_id=request.get("session_id", "00000000-0000-0000-0000-000000000000"),
242
+ history=request["messages"][:-1],
243
+ question=request["messages"][-1]["content"],
244
+ )
245
+ response = await chat_api.answer(chat_api_request)
246
+ response = response.model_dump()
247
+ yield json.dumps(
248
+ {
249
+ **response,
250
+ "id": "asd",
251
+ "object": "chat.completion.chunk",
252
+ "model": request["model"],
253
+ "choices": [
254
+ {
255
+ "index": 0,
256
+ "delta": {"role": "assistant", "content": response["answer"]},
257
+ }
258
+ ],
259
+ }
260
+ )
261
+
262
+
263
+ async def api_service_post(request):
264
  """
265
  Serves a chat endpoint that matches LynxScribe's interface.
266
  To access it you need to add the "module" and "workspace"
267
  parameters.
268
  The workspace must contain exactly one "Chat API" node.
269
 
270
+ curl -X POST ${LYNXKITE_URL}/api/service/server.lynxkite_ops \
271
  -H "Content-Type: application/json" \
272
  -d '{
273
+ "model": "LynxScribe demo",
274
+ "messages": [{"role": "user", "content": "what does the fox say"}]
 
 
 
 
 
275
  }'
276
+ """
277
+ path = "/".join(request.url.path.split("/")[4:])
278
+ request = await request.json()
279
+ if path == "chat/completions":
280
+ from sse_starlette.sse import EventSourceResponse
281
+
282
+ return EventSourceResponse(stream_chat_api_response(request))
283
+ return {"error": "Not found"}
284
+
285
+
286
+ async def api_service_get(request):
287
+ path = "/".join(request.url.path.split("/")[4:])
288
+ if path == "models":
289
+ return {
290
+ "object": "list",
291
+ "data": [
292
+ {
293
+ "id": ws,
294
+ "object": "model",
295
+ "created": 0,
296
+ "owned_by": "lynxkite",
297
+ "meta": {"profile_image_url": "https://lynxkite.com/favicon.png"},
298
+ }
299
+ for ws in get_lynxscribe_workspaces()
300
+ ],
301
+ }
302
+ return {"error": "Not found"}
303
+
304
+
305
+ def get_lynxscribe_workspaces():
306
  import pathlib
307
  from . import workspace
308
 
309
+ DATA_DIR = pathlib.Path.cwd() / "data"
310
+ workspaces = []
311
+ for p in DATA_DIR.glob("**/*"):
312
+ if p.is_file():
313
+ try:
314
+ ws = workspace.load(p)
315
+ if ws.env == ENV:
316
+ workspaces.append(p.relative_to(DATA_DIR))
317
+ except Exception:
318
+ pass # Ignore files that are not valid workspaces.
319
+ workspaces.sort()
320
+ return workspaces
 
 
 
 
 
server/main.py CHANGED
@@ -1,3 +1,6 @@
 
 
 
1
  import dataclasses
2
  import fastapi
3
  import importlib
@@ -87,8 +90,15 @@ def make_dir(req: dict):
87
  return list_dir(path.parent)
88
 
89
 
90
- @app.post("/api/service")
91
- async def service(req: dict):
92
  """Executors can provide extra HTTP APIs through the /api/service endpoint."""
93
- module = lynxkite_modules[req["module"]]
94
- return await module.api_service(req)
 
 
 
 
 
 
 
 
1
+ # TODO: Make this conditional. Until then just comment/uncomment it to use cuDF Pandas.
2
+ # import cudf.pandas
3
+ # cudf.pandas.install()
4
  import dataclasses
5
  import fastapi
6
  import importlib
 
90
  return list_dir(path.parent)
91
 
92
 
93
+ @app.get("/api/service/{module_path:path}")
94
+ async def service_get(req: fastapi.Request, module_path: str):
95
  """Executors can provide extra HTTP APIs through the /api/service endpoint."""
96
+ module = lynxkite_modules[module_path.split("/")[0]]
97
+ return await module.api_service_get(req)
98
+
99
+
100
+ @app.post("/api/service/{module_path:path}")
101
+ async def service_post(req: fastapi.Request, module_path: str):
102
+ """Executors can provide extra HTTP APIs through the /api/service endpoint."""
103
+ module = lynxkite_modules[module_path.split("/")[0]]
104
+ return await module.api_service_post(req)
web/package-lock.json CHANGED
@@ -20,6 +20,7 @@
20
  "daisyui": "^4.12.20",
21
  "echarts": "^5.5.1",
22
  "fuse.js": "^7.0.0",
 
23
  "react": "^18.3.1",
24
  "react-dom": "^18.3.1",
25
  "react-markdown": "^9.0.1",
@@ -97,6 +98,23 @@
97
  "url": "https://github.com/sponsors/antfu"
98
  }
99
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
  "node_modules/@babel/code-frame": {
101
  "version": "7.26.2",
102
  "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz",
@@ -687,6 +705,12 @@
687
  "@jridgewell/sourcemap-codec": "^1.4.14"
688
  }
689
  },
 
 
 
 
 
 
690
  "node_modules/@nodelib/fs.scandir": {
691
  "version": "2.1.5",
692
  "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -1333,7 +1357,12 @@
1333
  "version": "7.0.15",
1334
  "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
1335
  "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
1336
- "dev": true,
 
 
 
 
 
1337
  "license": "MIT"
1338
  },
1339
  "node_modules/@types/mdast": {
@@ -3339,7 +3368,6 @@
3339
  "version": "2.1.1",
3340
  "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
3341
  "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
3342
- "dev": true,
3343
  "license": "MIT",
3344
  "engines": {
3345
  "node": ">=0.10.0"
@@ -3359,7 +3387,6 @@
3359
  "version": "4.0.3",
3360
  "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
3361
  "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
3362
- "dev": true,
3363
  "license": "MIT",
3364
  "dependencies": {
3365
  "is-extglob": "^2.1.1"
@@ -3486,6 +3513,29 @@
3486
  "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
3487
  "license": "MIT"
3488
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3489
  "node_modules/json-schema-traverse": {
3490
  "version": "0.4.1",
3491
  "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
@@ -3760,6 +3810,12 @@
3760
  "url": "https://github.com/sponsors/sindresorhus"
3761
  }
3762
  },
 
 
 
 
 
 
3763
  "node_modules/lodash.debounce": {
3764
  "version": "4.0.8",
3765
  "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
@@ -4452,6 +4508,15 @@
4452
  "node": "*"
4453
  }
4454
  },
 
 
 
 
 
 
 
 
 
4455
  "node_modules/minipass": {
4456
  "version": "7.1.2",
4457
  "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
@@ -4985,6 +5050,21 @@
4985
  "node": ">= 0.8.0"
4986
  }
4987
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4988
  "node_modules/property-information": {
4989
  "version": "6.5.0",
4990
  "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz",
@@ -5695,6 +5775,45 @@
5695
  "integrity": "sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==",
5696
  "license": "MIT"
5697
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5698
  "node_modules/to-regex-range": {
5699
  "version": "5.0.1",
5700
  "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
 
20
  "daisyui": "^4.12.20",
21
  "echarts": "^5.5.1",
22
  "fuse.js": "^7.0.0",
23
+ "json-schema-to-typescript": "^15.0.3",
24
  "react": "^18.3.1",
25
  "react-dom": "^18.3.1",
26
  "react-markdown": "^9.0.1",
 
98
  "url": "https://github.com/sponsors/antfu"
99
  }
100
  },
101
+ "node_modules/@apidevtools/json-schema-ref-parser": {
102
+ "version": "11.7.3",
103
+ "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-11.7.3.tgz",
104
+ "integrity": "sha512-WApSdLdXEBb/1FUPca2lteASewEfpjEYJ8oXZP+0gExK5qSfsEKBKcA+WjY6Q4wvXwyv0+W6Kvc372pSceib9w==",
105
+ "license": "MIT",
106
+ "dependencies": {
107
+ "@jsdevtools/ono": "^7.1.3",
108
+ "@types/json-schema": "^7.0.15",
109
+ "js-yaml": "^4.1.0"
110
+ },
111
+ "engines": {
112
+ "node": ">= 16"
113
+ },
114
+ "funding": {
115
+ "url": "https://github.com/sponsors/philsturgeon"
116
+ }
117
+ },
118
  "node_modules/@babel/code-frame": {
119
  "version": "7.26.2",
120
  "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz",
 
705
  "@jridgewell/sourcemap-codec": "^1.4.14"
706
  }
707
  },
708
+ "node_modules/@jsdevtools/ono": {
709
+ "version": "7.1.3",
710
+ "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz",
711
+ "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==",
712
+ "license": "MIT"
713
+ },
714
  "node_modules/@nodelib/fs.scandir": {
715
  "version": "2.1.5",
716
  "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
 
1357
  "version": "7.0.15",
1358
  "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
1359
  "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
1360
+ "license": "MIT"
1361
+ },
1362
+ "node_modules/@types/lodash": {
1363
+ "version": "4.17.14",
1364
+ "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.14.tgz",
1365
+ "integrity": "sha512-jsxagdikDiDBeIRaPYtArcT8my4tN1og7MtMRquFT3XNA6axxyHDRUemqDz/taRDdOUn0GnGHRCuff4q48sW9A==",
1366
  "license": "MIT"
1367
  },
1368
  "node_modules/@types/mdast": {
 
3368
  "version": "2.1.1",
3369
  "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
3370
  "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
 
3371
  "license": "MIT",
3372
  "engines": {
3373
  "node": ">=0.10.0"
 
3387
  "version": "4.0.3",
3388
  "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
3389
  "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
 
3390
  "license": "MIT",
3391
  "dependencies": {
3392
  "is-extglob": "^2.1.1"
 
3513
  "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
3514
  "license": "MIT"
3515
  },
3516
+ "node_modules/json-schema-to-typescript": {
3517
+ "version": "15.0.3",
3518
+ "resolved": "https://registry.npmjs.org/json-schema-to-typescript/-/json-schema-to-typescript-15.0.3.tgz",
3519
+ "integrity": "sha512-iOKdzTUWEVM4nlxpFudFsWyUiu/Jakkga4OZPEt7CGoSEsAsUgdOZqR6pcgx2STBek9Gm4hcarJpXSzIvZ/hKA==",
3520
+ "license": "MIT",
3521
+ "dependencies": {
3522
+ "@apidevtools/json-schema-ref-parser": "^11.5.5",
3523
+ "@types/json-schema": "^7.0.15",
3524
+ "@types/lodash": "^4.17.7",
3525
+ "is-glob": "^4.0.3",
3526
+ "js-yaml": "^4.1.0",
3527
+ "lodash": "^4.17.21",
3528
+ "minimist": "^1.2.8",
3529
+ "prettier": "^3.2.5",
3530
+ "tinyglobby": "^0.2.9"
3531
+ },
3532
+ "bin": {
3533
+ "json2ts": "dist/src/cli.js"
3534
+ },
3535
+ "engines": {
3536
+ "node": ">=16.0.0"
3537
+ }
3538
+ },
3539
  "node_modules/json-schema-traverse": {
3540
  "version": "0.4.1",
3541
  "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
 
3810
  "url": "https://github.com/sponsors/sindresorhus"
3811
  }
3812
  },
3813
+ "node_modules/lodash": {
3814
+ "version": "4.17.21",
3815
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
3816
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
3817
+ "license": "MIT"
3818
+ },
3819
  "node_modules/lodash.debounce": {
3820
  "version": "4.0.8",
3821
  "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
 
4508
  "node": "*"
4509
  }
4510
  },
4511
+ "node_modules/minimist": {
4512
+ "version": "1.2.8",
4513
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
4514
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
4515
+ "license": "MIT",
4516
+ "funding": {
4517
+ "url": "https://github.com/sponsors/ljharb"
4518
+ }
4519
+ },
4520
  "node_modules/minipass": {
4521
  "version": "7.1.2",
4522
  "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
 
5050
  "node": ">= 0.8.0"
5051
  }
5052
  },
5053
+ "node_modules/prettier": {
5054
+ "version": "3.4.2",
5055
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz",
5056
+ "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==",
5057
+ "license": "MIT",
5058
+ "bin": {
5059
+ "prettier": "bin/prettier.cjs"
5060
+ },
5061
+ "engines": {
5062
+ "node": ">=14"
5063
+ },
5064
+ "funding": {
5065
+ "url": "https://github.com/prettier/prettier?sponsor=1"
5066
+ }
5067
+ },
5068
  "node_modules/property-information": {
5069
  "version": "6.5.0",
5070
  "resolved": "https://registry.npmjs.org/property-information/-/property-information-6.5.0.tgz",
 
5775
  "integrity": "sha512-WiCJLEECkO18gwqIp6+hJg0//p23HXp4S+gGtAKu3mI2F2/sXC4FvHvXvB0zJVVaTPhx1/tOwdbRsa1sOBIKqQ==",
5776
  "license": "MIT"
5777
  },
5778
+ "node_modules/tinyglobby": {
5779
+ "version": "0.2.10",
5780
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.10.tgz",
5781
+ "integrity": "sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew==",
5782
+ "license": "MIT",
5783
+ "dependencies": {
5784
+ "fdir": "^6.4.2",
5785
+ "picomatch": "^4.0.2"
5786
+ },
5787
+ "engines": {
5788
+ "node": ">=12.0.0"
5789
+ }
5790
+ },
5791
+ "node_modules/tinyglobby/node_modules/fdir": {
5792
+ "version": "6.4.2",
5793
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.2.tgz",
5794
+ "integrity": "sha512-KnhMXsKSPZlAhp7+IjUkRZKPb4fUyccpDrdFXbi4QL1qkmFh9kVY09Yox+n4MaOb3lHZ1Tv829C3oaaXoMYPDQ==",
5795
+ "license": "MIT",
5796
+ "peerDependencies": {
5797
+ "picomatch": "^3 || ^4"
5798
+ },
5799
+ "peerDependenciesMeta": {
5800
+ "picomatch": {
5801
+ "optional": true
5802
+ }
5803
+ }
5804
+ },
5805
+ "node_modules/tinyglobby/node_modules/picomatch": {
5806
+ "version": "4.0.2",
5807
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
5808
+ "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
5809
+ "license": "MIT",
5810
+ "engines": {
5811
+ "node": ">=12"
5812
+ },
5813
+ "funding": {
5814
+ "url": "https://github.com/sponsors/jonschlinkert"
5815
+ }
5816
+ },
5817
  "node_modules/to-regex-range": {
5818
  "version": "5.0.1",
5819
  "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
web/package.json CHANGED
@@ -22,6 +22,7 @@
22
  "daisyui": "^4.12.20",
23
  "echarts": "^5.5.1",
24
  "fuse.js": "^7.0.0",
 
25
  "react": "^18.3.1",
26
  "react-dom": "^18.3.1",
27
  "react-markdown": "^9.0.1",
 
22
  "daisyui": "^4.12.20",
23
  "echarts": "^5.5.1",
24
  "fuse.js": "^7.0.0",
25
+ "json-schema-to-typescript": "^15.0.3",
26
  "react": "^18.3.1",
27
  "react-dom": "^18.3.1",
28
  "react-markdown": "^9.0.1",
web/src/apiTypes.ts CHANGED
@@ -5,6 +5,13 @@
5
  /* Do not modify it by hand - just update the pydantic models and then re-run the script
6
  */
7
 
 
 
 
 
 
 
 
8
  export interface BaseConfig {
9
  [k: string]: unknown;
10
  }
 
5
  /* Do not modify it by hand - just update the pydantic models and then re-run the script
6
  */
7
 
8
+ /* eslint-disable */
9
+ /**
10
+ * This file was automatically generated by json-schema-to-typescript.
11
+ * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file,
12
+ * and run json-schema-to-typescript to regenerate this file.
13
+ */
14
+
15
  export interface BaseConfig {
16
  [k: string]: unknown;
17
  }