Compare commits

...

46 Commits

Author SHA1 Message Date
Erick Friis aa3d7bb0a7 path bugfix 2023-10-23 16:35:32 -07:00
Erick Friis 26a4495a53 Add package routes (#87)
Co-authored-by: Eugene Yurtsev <eyurtsev@gmail.com>
2023-10-23 15:16:00 -07:00
Nuno Campos ff6f20d9e1 Remove usage of locals from example (#86)
Co-authored-by: Eugene Yurtsev <eyurtsev@gmail.com>
2023-10-23 13:33:52 -04:00
Eugene Yurtsev c448042d3d Start handling errors on stream/astream/astream_log (#83)
This PR introduces error handling for stream/astream/astream_log
endpoints.

The error handling is incomplete as client side errors aren't handled
yet.

1) When an exception is encountered while streaming the server sents an
error event in the stream with data:

- status code: int
- message: str

2) Client code is updated to handle this

In addition this PR, proposes that the client with log an error instead
of raising when encountering an unsupported event. This will allow us to
ship additional events without breaking older clients. Useful for sending
over callback events.
2023-10-20 15:43:58 -04:00
Eugene Yurtsev 0db97cf565 Update readme (#81)
Update readme
2023-10-20 09:16:31 -04:00
Eugene Yurtsev ea6b3867f6 Account for custom input and output types (#56)
Account for custom input and output types
2023-10-19 22:23:09 -04:00
Nuno Campos 3027ecab3d 0.0.15 (#77) 2023-10-19 20:07:13 +01:00
Nuno Campos 80108338db 0.0.14 (#74) 2023-10-19 20:03:04 +01:00
Nuno Campos a4497911fd Refetch input schema on config change aka. support configurable prompts (#66) 2023-10-19 20:01:41 +01:00
Nuno Campos b93ccf9b90 Add documentation for playground (#73) 2023-10-19 18:30:14 +01:00
Nuno Campos 9f7d672d20 Add back char (#72) 2023-10-19 17:59:47 +01:00
Nuno Campos 4ba226e40a Revert example (#71) 2023-10-19 17:43:46 +01:00
Nuno Campos c3f553bc3b 0.0.13 (#69) 2023-10-19 17:30:54 +01:00
Nuno Campos 4cbc20f6c4 Fix non-dict input (#70) 2023-10-19 17:30:41 +01:00
David Duong 5e00bfee38 Playground Cleanup (#63)
- Autoexpanding textareas
- Add missing chevrons
- Align dark mode styles, spacing for nested JSON

---------

Co-authored-by: Nuno Campos <nuno@boringbits.io>
2023-10-19 17:11:11 +01:00
Nuno Campos c4565e85b0 0.0.12 (#68) 2023-10-19 17:04:08 +01:00
Nuno Campos 65709a7966 Rebuild files (#67) 2023-10-19 17:01:34 +01:00
Jacob Lee 05afa7fb07 Lightmode fix, add borders to nested inputs (#64) 2023-10-19 16:59:05 +01:00
Eugene Yurtsev 23827b3f5c Version 0.0.11 (#62) 2023-10-19 11:01:50 -04:00
Nuno Campos c50ec01632 Merge pull request #19 from langchain-ai/nc/playground
Nc/playground
2023-10-19 15:58:36 +01:00
Nuno Campos 174f47f4f7 Update built files 2023-10-19 15:36:01 +01:00
Nuno Campos 6ef8a25f71 Update built files 2023-10-19 15:34:47 +01:00
Eugene Yurtsev db1016da62 Allow prerelease from any branch (#61)
Allow prerelease to test pypi from any branch
2023-10-19 10:32:03 -04:00
Eugene Yurtsev b0935f0266 Add publish to test pypi (#59)
Add release to test pypi
2023-10-19 10:22:27 -04:00
Tat Dat Duong 20f04581cc Fix dark mode for icons 2023-10-19 13:47:37 +02:00
Tat Dat Duong 5ee72a5f24 Initial work on cleaning up styles 2023-10-19 13:11:11 +02:00
Nuno Campos 14b9c5f745 Lint 2023-10-19 11:55:45 +01:00
Nuno Campos 020f4e7ae7 Add json cell 2023-10-19 11:52:04 +01:00
Nuno Campos 33cf1f322f Resolve all refs before passing to jsonforms 2023-10-19 11:26:00 +01:00
Nuno Campos d800c100bf Copy changes 2023-10-19 10:39:00 +01:00
Nuno Campos 063e10f755 Resolve maximum call stack size exceeded for schemas with root $ref 2023-10-19 10:31:16 +01:00
Tat Dat Duong 001c924e4e Cleanup styles 2023-10-19 10:40:38 +02:00
jacoblee93 37f4f3c599 Update test case 2023-10-19 10:40:38 +02:00
jacoblee93 a54d7df974 Adds custom array and text controls 2023-10-19 10:40:38 +02:00
Eugene Yurtsev 4878d832cf Minor fix in contribution guidelines (#55)
Minor fix
2023-10-19 10:40:38 +02:00
Eugene Yurtsev 24d96b7cac Add CONTRIBUTING.md guidelines (#54)
Add contributing.md. Inserting this in the root of the project, so it's
easier to find. If folks feel strongly can put in in .github.
2023-10-19 10:40:38 +02:00
Nuno Campos 05f11af6e9 Update default to match langchain (#51)
Co-authored-by: jacoblee93 <jacoblee93@gmail.com>
Co-authored-by: Eugene Yurtsev <eyurtsev@gmail.com>
2023-10-19 10:40:38 +02:00
Eugene Yurtsev 144eb0717e Version 0.0.10 bump (#50)
Bump
2023-10-19 10:40:38 +02:00
Tat Dat Duong bd5d8cda86 Fix dark theme for errors, move validation stuff to the top-right 2023-10-19 09:04:38 +02:00
Tat Dat Duong ca06ab2e32 Refresh check icon 2023-10-19 08:45:06 +02:00
Tat Dat Duong 3df9fe8f28 Add better validation error styles 2023-10-19 08:35:57 +02:00
Tat Dat Duong d37fce3f57 Add separator 2023-10-19 08:09:30 +02:00
Eugene Yurtsev 6eeb274875 Minor fix in contribution guidelines (#55)
Minor fix
2023-10-17 21:46:54 -04:00
Eugene Yurtsev 779b8e1cc9 Add CONTRIBUTING.md guidelines (#54)
Add contributing.md. Inserting this in the root of the project, so it's
easier to find. If folks feel strongly can put in in .github.
2023-10-17 21:45:10 -04:00
Nuno Campos ada5d103b4 Update default to match langchain (#51)
Co-authored-by: jacoblee93 <jacoblee93@gmail.com>
Co-authored-by: Eugene Yurtsev <eyurtsev@gmail.com>
2023-10-17 21:20:44 -04:00
Eugene Yurtsev fb0f9a0dc3 Version 0.0.10 bump (#50)
Bump
2023-10-17 14:42:27 -04:00
43 changed files with 14550 additions and 837 deletions
+49
View File
@@ -0,0 +1,49 @@
name: test-release
on:
workflow_call:
inputs:
working-directory:
required: true
type: string
description: "From which folder this pipeline executes"
env:
POETRY_VERSION: "1.5.1"
jobs:
publish_to_test_pypi:
runs-on: ubuntu-latest
permissions:
# This permission is used for trusted publishing:
# https://blog.pypi.org/posts/2023-04-20-introducing-trusted-publishers/
#
# Trusted publishing has to also be configured on PyPI for each package:
# https://docs.pypi.org/trusted-publishers/adding-a-publisher/
id-token: write
defaults:
run:
working-directory: ${{ inputs.working-directory }}
steps:
- uses: actions/checkout@v3
- name: Set up Python + Poetry ${{ env.POETRY_VERSION }}
uses: "./.github/actions/poetry_setup"
with:
python-version: "3.10"
poetry-version: ${{ env.POETRY_VERSION }}
working-directory: ${{ inputs.working-directory }}
cache-key: release
- name: Build project for distribution
run: poetry build
- name: Check Version
id: check-version
run: |
echo version=$(poetry version --short) >> $GITHUB_OUTPUT
- name: Publish package to TestPyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
repository-url: https://test.pypi.org/legacy/
verbose: true
print-hash: true
@@ -0,0 +1,13 @@
---
name: Test Release
on:
workflow_dispatch: # Allows to trigger the workflow manually in GitHub UI
jobs:
release:
uses:
./.github/workflows/_test_release.yml
with:
working-directory: .
secrets: inherit
+51
View File
@@ -0,0 +1,51 @@
# Contributing
## Contributor License Agreement
We are grateful to the contributors who help evolve LangServe and dedicate their time to the project. As the primary sponsor of LangServe, LangChain, Inc. aims to build products in the open that benefit thousands of developers while allowing us to build a sustainable business. For all code contributions to LangServe, we ask that contributors complete and sign a Contributor License Agreement (“CLA”). The agreement between contributors and the project is explicit, so LangServe users can be confident in the legal status of the source code and their right to use it.The CLA does not change the terms of the underlying license, LangServe License, used by our software.
Before you can contribute to LangServe, a bot will comment on the PR asking you to agree to the CLA if you haven't already. Agreeing to the CLA is required before code can be merged and only needs to happen on the first contribution to the project. All subsequent contributions will fall under the same CLA.
## 🗺️ Guidelines
### Dependency Management: Poetry and other env/dependency managers
This project uses [Poetry](https://python-poetry.org/) v1.6.1+ as a dependency manager.
### Local Development Dependencies
Install langserve development requirements (for running langchain, running examples, linting, formatting, tests, and coverage):
```sh
poetry install --with test,dev
```
Then verify that tests pass:
```sh
make test
```
### Formatting and Linting
Run these locally before submitting a PR; the CI system will check also.
#### Code Formatting
Formatting for this project is done via a combination of [Black](https://black.readthedocs.io/en/stable/) and [ruff](https://docs.astral.sh/ruff/rules/).
To run formatting for this project:
```sh
make format
```
#### Linting
Linting for this project is done via a combination of [Black](https://black.readthedocs.io/en/stable/), [ruff](https://docs.astral.sh/ruff/rules/), and [mypy](http://mypy-lang.org/).
To run linting for this project:
```sh
make lint
```
+24 -12
View File
@@ -1,4 +1,4 @@
# LangServe 🦜️🔗
# LangServe 🦜️🔗
## Overview
@@ -15,6 +15,7 @@ A javascript client is available in [LangChainJS](https://js.langchain.com/docs/
- API docs page with JSONSchema and Swagger (insert example link)
- Efficient `/invoke`, `/batch` and `/stream` endpoints with support for many concurrent requests on a single server
- `/stream_log` endpoint for streaming all (or some) intermediate steps from your chain/agent
- Playground page at `/playground` with streaming output and intermediate steps
- Built-in (optional) tracing to [LangSmith](https://www.langchain.com/langsmith), just add your API key (see [Instructions](https://docs.smith.langchain.com/)])
- All built with battle-tested open-source Python libraries like FastAPI, Pydantic, uvloop and asyncio.
- Use the client SDK to call a LangServe server as if it was a Runnable running locally (or call the HTTP API directly)
@@ -24,7 +25,7 @@ A javascript client is available in [LangChainJS](https://js.langchain.com/docs/
- Client callbacks are not yet supported for events that originate on the server
- Does not work with [pydantic v2 yet](https://github.com/tiangolo/fastapi/issues/10360)
## LangChain CLI 🛠️
## LangChain CLI 🛠️
Use the `LangChain` CLI to bootstrap a `LangServe` project quickly.
@@ -41,7 +42,6 @@ And follow the instructions...
For more examples, see the [examples](./examples) directory.
### Server
Here's a server that deploys an OpenAI chat model, an Anthropic chat model, and a chain that uses
@@ -95,6 +95,14 @@ If you've deployed the server above, you can view the generated OpenAPI docs usi
curl localhost:8000/docs
```
make sure to **add** the `/docs` suffix.
Below will return a 404 until you define a `@app.get("/")`
```sh
localhost:8000
```
### Client
Python SDK
@@ -116,18 +124,18 @@ joke_chain.invoke({"topic": "parrots"})
await joke_chain.ainvoke({"topic": "parrots"})
prompt = [
SystemMessage(content='Act like either a cat or a parrot.'),
SystemMessage(content='Act like either a cat or a parrot.'),
HumanMessage(content='Hello!')
]
# Supports astream
async for msg in anthropic.astream(prompt):
print(msg, end="", flush=True)
prompt = ChatPromptTemplate.from_messages(
[("system", "Tell me a long story about {topic}")]
)
# Can define custom chains
chain = prompt | RunnableMap({
"openai": openai,
@@ -142,9 +150,11 @@ In TypeScript (requires LangChain.js version 0.0.166 or later):
```typescript
import { RemoteRunnable } from "langchain/runnables/remote";
const chain = new RemoteRunnable({ url: `http://localhost:8000/chain/invoke/` });
const chain = new RemoteRunnable({
url: `http://localhost:8000/chain/invoke/`,
});
const result = await chain.invoke({
"topic": "cats",
topic: "cats",
});
```
@@ -171,8 +181,7 @@ curl --location --request POST 'http://localhost:8000/chain/invoke/' \
}'
```
## Endpoints
## Endpoints
The following code:
@@ -195,6 +204,10 @@ adds of these endpoints to the server:
- `GET /my_runnable/output_schema` - json schema for output of the runnable
- `GET /my_runnable/config_schema` - json schema for config of the runnable
## Playground
You can find a playground page for your runnable at `/my_runnable/playground`. This exposes a simple UI to [configure](https://python.langchain.com/docs/expression_language/how_to/configure) and invoke your runnable with streaming output and intermediate steps.
## Installation
For both client and server:
@@ -212,10 +225,9 @@ However, some of the input schemas for legacy chains may be incomplete/incorrect
This can be fixed by updating the `input_schema` property of those chains in LangChain.
If you encounter any errors, please open an issue on THIS repo, and we will work to address it.
## Handling Authentication
If you need to add authentication to your server,
If you need to add authentication to your server,
please reference FastAPI's [security documentation](https://fastapi.tiangolo.com/tutorial/security/)
and [middleware documentation](https://fastapi.tiangolo.com/tutorial/middleware/).
+5 -4
View File
@@ -4,9 +4,11 @@ from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from langchain.chat_models import ChatOpenAI
from langchain.prompts import PromptTemplate
# from typing_extensions import TypedDict
from langchain.pydantic_v1 import BaseModel
from langchain.schema.output_parser import StrOutputParser
from langchain.schema.runnable import ConfigurableField
from typing_extensions import TypedDict
from langserve import add_routes
@@ -17,7 +19,7 @@ model = ChatOpenAI(temperature=0.5).configurable_alternatives(
default_key="medium_temp",
)
prompt = PromptTemplate.from_template(
"tell me a joke about {topic}"
"tell me a joke about {topic}."
).configurable_fields(
template=ConfigurableField(
id="prompt",
@@ -47,11 +49,10 @@ app.add_middleware(
# The input type is automatically inferred from the runnable
# interface; however, if you want to override it, you can do so
# by passing in the input_type argument to add_routes.
class ChainInput(TypedDict):
class ChainInput(BaseModel):
"""The input to the chain."""
topic: str
"""The topic of the joke."""
add_routes(app, chain, input_type=ChainInput, config_keys=["configurable"])
+3 -8
View File
@@ -229,7 +229,7 @@
"metadata": {},
"outputs": [],
"source": [
"from langchain.schema.runnable import PutLocalVar, GetLocalVar"
"from langchain.schema.runnable import RunnablePassthrough"
]
},
{
@@ -266,13 +266,8 @@
")\n",
"\n",
"\n",
"chain = (\n",
" comedian_chain\n",
" | PutLocalVar(\"joke\")\n",
" | {\"joke\": GetLocalVar(\"joke\")}\n",
" | joke_classifier_chain\n",
" | PutLocalVar(\"classification\")\n",
" | {\"joke\": GetLocalVar(\"joke\"), \"classification\": GetLocalVar(\"classification\")}\n",
"chain = {\"joke\": comedian_chain} | RunnablePassthrough.assign(\n",
" classification=joke_classifier_chain\n",
")"
]
},
+68 -18
View File
@@ -1,8 +1,11 @@
from __future__ import annotations
import asyncio
import json
import logging
import weakref
from concurrent.futures import ThreadPoolExecutor
from functools import lru_cache
from typing import (
Any,
AsyncIterator,
@@ -17,7 +20,7 @@ from urllib.parse import urljoin
import httpx
from httpx._types import AuthTypes, CertTypes, CookieTypes, HeaderTypes, VerifyTypes
from langchain.callbacks.tracers.log_stream import RunLog, RunLogPatch
from langchain.callbacks.tracers.log_stream import RunLogPatch
from langchain.load.dump import dumpd
from langchain.schema.runnable import Runnable
from langchain.schema.runnable.config import (
@@ -30,6 +33,8 @@ from langchain.schema.runnable.utils import Input, Output
from langserve.serialization import simple_dumpd, simple_loads
logger = logging.getLogger(__name__)
def _without_callbacks(config: Optional[RunnableConfig]) -> RunnableConfig:
"""Evict callbacks from the config since those are definitely not supported."""
@@ -37,6 +42,12 @@ def _without_callbacks(config: Optional[RunnableConfig]) -> RunnableConfig:
return {k: v for k, v in _config.items() if k != "callbacks"}
@lru_cache(maxsize=1_000) # Will accommodate up to 100 different error messages
def _log_error_message_once(error_message: str) -> None:
"""Log an error message once."""
logger.error(error_message)
def _raise_for_status(response: httpx.Response) -> None:
"""Re-raise with a more informative message.
@@ -94,6 +105,26 @@ def _close_clients(sync_client: httpx.Client, async_client: httpx.AsyncClient) -
asyncio.run(async_client.aclose())
def _raise_exception_from_data(data: str, request: httpx.Request) -> None:
"""Raise an httpx exception from the given error event data."""
try:
decoded_data = json.loads(data)
except json.JSONDecodeError:
raise httpx.HTTPStatusError(
message="invalid json in error event sent from server",
request=request,
response=httpx.Response(status_code=500, text=data),
)
raise httpx.HTTPStatusError(
message=decoded_data["message"],
request=request,
response=httpx.Response(
status_code=decoded_data["status_code"],
text=decoded_data["message"],
),
)
class RemoteRunnable(Runnable[Input, Output]):
"""A RemoteRunnable is a runnable that is executed on a remote server.
@@ -334,10 +365,19 @@ class RemoteRunnable(Runnable[Input, Output]):
final_output += chunk
else:
final_output = chunk
elif sse.event == "error":
# This can only be a server side error
_raise_exception_from_data(
sse.data, httpx.Request(method="POST", url=endpoint)
)
elif sse.event == "end":
break
else:
raise NotImplementedError(f"Unknown event {sse.event}")
logger.error(
f"Encountered an unsupported event type: `{sse.event}`. "
f"Try upgrading the remote client to the latest version."
f"Ignoring events of type `{sse.event}`."
)
except BaseException as e:
run_manager.on_chain_error(e)
raise
@@ -385,11 +425,20 @@ class RemoteRunnable(Runnable[Input, Output]):
final_output += chunk
else:
final_output = chunk
elif sse.event == "error":
# This can only be a server side error
_raise_exception_from_data(
sse.data, httpx.Request(method="POST", url=endpoint)
)
elif sse.event == "end":
break
else:
raise NotImplementedError(f"Unknown event {sse.event}")
logger.error(
f"Encountered an unsupported event type: `{sse.event}`. "
f"Try upgrading the remote client to the latest version."
f"Ignoring events of type `{sse.event}`."
)
except BaseException as e:
await run_manager.on_chain_error(e)
raise
@@ -401,7 +450,6 @@ class RemoteRunnable(Runnable[Input, Output]):
input: Input,
config: Optional[RunnableConfig] = None,
*,
diff: bool = False,
include_names: Optional[Sequence[str]] = None,
include_types: Optional[Sequence[str]] = None,
include_tags: Optional[Sequence[str]] = None,
@@ -409,7 +457,7 @@ class RemoteRunnable(Runnable[Input, Output]):
exclude_types: Optional[Sequence[str]] = None,
exclude_tags: Optional[Sequence[str]] = None,
**kwargs: Optional[Any],
) -> Union[AsyncIterator[RunLogPatch], AsyncIterator[RunLog]]:
) -> AsyncIterator[RunLogPatch]:
"""Stream all output from a runnable, as reported to the callback system.
This includes all inner runs of LLMs, Retrievers, Tools, etc.
@@ -436,7 +484,7 @@ class RemoteRunnable(Runnable[Input, Output]):
"input": simple_dumpd(input),
"config": _without_callbacks(config),
"kwargs": kwargs,
"diff": diff,
"diff": True,
"include_names": include_names,
"include_types": include_types,
"include_tags": include_tags,
@@ -458,24 +506,26 @@ class RemoteRunnable(Runnable[Input, Output]):
async for sse in event_source.aiter_sse():
if sse.event == "data":
data = simple_loads(sse.data)
if diff:
chunk = RunLogPatch(*data["ops"])
else:
chunk = RunLog(*data["ops"], state=data["state"])
chunk = RunLogPatch(*data["ops"])
yield chunk
if diff:
if final_output:
final_output += chunk
else:
final_output = chunk
if final_output:
final_output += chunk
else:
final_output = chunk
elif sse.event == "error":
# This can only be a server side error
_raise_exception_from_data(
sse.data, httpx.Request(method="POST", url=endpoint)
)
elif sse.event == "end":
break
else:
raise NotImplementedError(f"Unknown event {sse.event}")
_log_error_message_once(
f"Encountered an unsupported event type: `{sse.event}`. "
f"Try upgrading the remote client to the latest version."
f"Ignoring events of type `{sse.event}`."
)
except BaseException as e:
await run_manager.on_chain_error(e)
raise
+104
View File
@@ -0,0 +1,104 @@
import importlib
import logging
from pathlib import Path
from typing import Generator, TypedDict, Union
from fastapi import APIRouter, FastAPI
from tomli import load
from langserve.server import add_routes
class LangServeExport(TypedDict):
"""
Fields from pyproject.toml that are relevant to LangServe
Attributes:
module: The module to import from, tool.langserve.export_module
attr: The attribute to import from the module, tool.langserve.export_attr
package_name: The name of the package, tool.poetry.name
"""
module: str
attr: str
package_name: str
def get_langserve_export(filepath: Path) -> LangServeExport:
with open(filepath, "rb") as f:
data = load(f)
try:
module = data["tool"]["langserve"]["export_module"]
attr = data["tool"]["langserve"]["export_attr"]
package_name = data["tool"]["poetry"]["name"]
except KeyError as e:
raise KeyError("Invalid LangServe PyProject.toml") from e
return LangServeExport(module=module, attr=attr, package_name=package_name)
EXCLUDE_PATHS = set(["__pycache__", ".venv", ".git", ".github"])
def _include_path(path: Path) -> bool:
"""
Skip paths that are in EXCLUDE_PATHS or start with an underscore.
"""
for part in path.parts:
if part in EXCLUDE_PATHS:
return False
if part.startswith("_"):
return False
return True
def list_packages(path: str = "packages") -> Generator[Path, None, None]:
"""
Yields Path objects for each folder that contains a pyproject.toml file within a
path. Use this to find packages to add to the server.
See `add_package_routes` below for an example of how to use this.
"""
# traverse path for routes to host (any directory holding a pyproject.toml file)
package_root = Path(path)
for pyproject_path in package_root.glob("**/pyproject.toml"):
if not _include_path(pyproject_path):
continue
yield pyproject_path.parent
def add_package_route(
app: Union[FastAPI, APIRouter], package_path: Path, mount_path: str
) -> None:
try:
pyproject_path = package_path / "pyproject.toml"
# get langserve export
package = get_langserve_export(pyproject_path)
package_name = package["package_name"]
# import module
mod = importlib.import_module(package["module"])
except KeyError:
logging.warning(
f"Skipping {package_path} because it is not a valid LangServe "
"package (see pyproject.toml)"
)
return
except ImportError as e:
logging.warning(f"Error: {e}")
logging.warning(f"Try fixing with `poetry add --editable {package_path}`")
logging.warning(
"To remove packages, use `poe` instead of `poetry`: "
f"`poe remove {package_name}`"
)
return
# get attr
chain = getattr(mod, package["attr"])
add_routes(app, chain, path=mount_path)
def add_package_routes(app: Union[FastAPI, APIRouter], path: str = "packages") -> None:
# traverse path for routes to host (any directory holding a pyproject.toml file)
for package_path in list_packages(path):
mount_path_relative = package_path.relative_to(Path(path))
mount_path = f"/{mount_path_relative}"
add_package_route(app, package_path, mount_path)
+2
View File
@@ -22,3 +22,5 @@ dist-ssr
*.njsproj
*.sln
*.sw?
.yarn
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -5,8 +5,8 @@
<link rel="icon" href="/____LANGSERVE_BASE_URL/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Playground</title>
<script type="module" crossorigin src="/____LANGSERVE_BASE_URL/assets/index-38194dca.js"></script>
<link rel="stylesheet" href="/____LANGSERVE_BASE_URL/assets/index-d96a8751.css">
<script type="module" crossorigin src="/____LANGSERVE_BASE_URL/assets/index-9fe7f71c.js"></script>
<link rel="stylesheet" href="/____LANGSERVE_BASE_URL/assets/index-5008c8a8.css">
</head>
<body>
<div id="root"></div>
+5
View File
@@ -20,15 +20,20 @@
"@mui/icons-material": "^5.14.11",
"@mui/material": "^5.14.11",
"@mui/x-date-pickers": "^6.16.0",
"clsx": "^2.0.0",
"dayjs": "^1.11.10",
"fast-json-patch": "^3.1.1",
"json-schema-defaults": "^0.4.0",
"lodash": "^4.17.21",
"lz-string": "^1.5.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"tailwind-merge": "^1.14.0",
"use-debounce": "^9.0.4",
"vaul": "^0.7.3"
},
"devDependencies": {
"@types/lodash": "^4.14.200",
"@types/react": "^18.2.15",
"@types/react-dom": "^18.2.7",
"@typescript-eslint/eslint-plugin": "^6.0.0",
+12 -2
View File
@@ -55,15 +55,21 @@
@apply focus-within:border-ls-blue focus-within:outline focus-within:outline-4 focus-within:outline-ls-blue/20;
}
.control > label {
.control > label,
.control h6 {
@apply text-xs uppercase font-semibold text-ls-gray-100;
}
.control div .MuiGrid-item {
@apply pt-0;
}
.control > select {
@apply -ml-1;
}
.control .input-description {
.control > .input-description,
.control > .validation {
@apply absolute right-3 top-3 text-xs;
}
@@ -78,3 +84,7 @@
.no-scrollbar::-webkit-scrollbar {
display: none;
}
.vertical-layout {
@apply flex flex-col gap-4;
}
+87 -197
View File
@@ -1,7 +1,6 @@
import "./App.css";
import { Drawer } from "vaul";
import React, { ReactNode, useEffect, useMemo, useRef, useState } from "react";
import React, { useEffect, useRef, useState } from "react";
import defaults from "json-schema-defaults";
import { JsonForms } from "@jsonforms/react";
import {
@@ -9,7 +8,6 @@ import {
MaterialAllOfRenderer,
materialAnyOfControlTester,
MaterialAnyOfRenderer,
materialObjectControlTester,
MaterialObjectRenderer,
materialOneOfControlTester,
MaterialOneOfRenderer,
@@ -19,12 +17,8 @@ import utc from "dayjs/plugin/utc";
import relativeDate from "dayjs/plugin/relativeTime";
import SendIcon from "./assets/SendIcon.svg?react";
import ShareIcon from "./assets/ShareIcon.svg?react";
import CodeIcon from "./assets/CodeIcon.svg?react";
import PadlockIcon from "./assets/PadlockIcon.svg?react";
import {
compressToEncodedURIComponent,
decompressFromEncodedURIComponent,
} from "lz-string";
import ChevronRight from "./assets/ChevronRight.svg?react";
import { compressToEncodedURIComponent } from "lz-string";
import {
BooleanCell,
@@ -34,7 +28,6 @@ import {
IntegerCell,
NumberCell,
SliderCell,
TextAreaCell,
TimeCell,
booleanCellTester,
dateCellTester,
@@ -47,34 +40,70 @@ import {
textCellTester,
timeCellTester,
vanillaRenderers,
InputControl,
} from "@jsonforms/vanilla-renderers";
import { useSchemas } from "./useSchemas";
import { RunState, useStreamLog } from "./useStreamLog";
import { JsonFormsCore } from "@jsonforms/core";
import {
JsonFormsCore,
RankedTester,
rankWith,
and,
uiTypeIs,
schemaMatches,
schemaTypeIs,
} from "@jsonforms/core";
import CustomArrayControlRenderer, {
materialArrayControlTester,
} from "./components/CustomArrayControlRenderer";
import CustomTextAreaCell from "./components/CustomTextAreaCell";
import JsonTextAreaCell from "./components/JsonTextAreaCell";
import { cn } from "./utils/cn";
import { getStateFromUrl, ShareDialog } from "./components/ShareDialog";
dayjs.extend(relativeDate);
dayjs.extend(utc);
const URL_LENGTH_LIMIT = 2000;
function str(o: unknown): React.ReactNode {
return typeof o === "object"
? JSON.stringify(o, null, 2)
: (o as React.ReactNode);
}
const isObjectWithPropertiesControl = rankWith(
2,
and(
uiTypeIs("Control"),
schemaTypeIs("object"),
schemaMatches((schema) =>
Object.prototype.hasOwnProperty.call(schema, "properties")
)
)
);
const isObject = rankWith(1, and(uiTypeIs("Control"), schemaTypeIs("object")));
const isElse = rankWith(1, and(uiTypeIs("Control")));
const renderers = [
...vanillaRenderers,
// use material renderers to handle objects and json schema references
// they should yield the rendering to simpler cells
{ tester: materialObjectControlTester, renderer: MaterialObjectRenderer },
{ tester: isObjectWithPropertiesControl, renderer: MaterialObjectRenderer },
{ tester: materialAllOfControlTester, renderer: MaterialAllOfRenderer },
{ tester: materialAnyOfControlTester, renderer: MaterialAnyOfRenderer },
{ tester: materialOneOfControlTester, renderer: MaterialOneOfRenderer },
// custom renderers
{ tester: materialArrayControlTester, renderer: CustomArrayControlRenderer },
{ tester: isObject, renderer: InputControl },
];
const nestedArrayControlTester: RankedTester = rankWith(1, (_, jsonSchema) => {
return jsonSchema.type === "array";
});
const cells = [
{ tester: booleanCellTester, cell: BooleanCell },
{ tester: dateCellTester, cell: DateCell },
@@ -83,9 +112,11 @@ const cells = [
{ tester: integerCellTester, cell: IntegerCell },
{ tester: numberCellTester, cell: NumberCell },
{ tester: sliderCellTester, cell: SliderCell },
{ tester: textAreaCellTester, cell: TextAreaCell },
{ tester: textCellTester, cell: TextAreaCell },
{ tester: textAreaCellTester, cell: CustomTextAreaCell },
{ tester: textCellTester, cell: CustomTextAreaCell },
{ tester: timeCellTester, cell: TimeCell },
{ tester: nestedArrayControlTester, cell: CustomArrayControlRenderer },
{ tester: isElse, cell: JsonTextAreaCell },
];
function IntermediateSteps(props: { latest: RunState }) {
@@ -93,10 +124,13 @@ function IntermediateSteps(props: { latest: RunState }) {
return (
<div className="flex flex-col border border-divider-700 rounded-2xl bg-background">
<button
className="font-medium text-left p-4"
className="font-medium text-left p-4 flex items-center justify-between"
onClick={() => setExpanded((open) => !open)}
>
Intermediate steps
<span>Intermediate steps</span>
<ChevronRight
className={cn("transition-all", expanded && "rotate-90")}
/>
</button>
{expanded && (
<div className="flex flex-col gap-5 p-4 pt-0 divide-solid divide-y divide-divider-700 rounded-b-xl">
@@ -120,158 +154,6 @@ function IntermediateSteps(props: { latest: RunState }) {
);
}
function getStateFromUrl(path: string) {
let configFromUrl = null;
let basePath = path;
if (basePath.endsWith("/")) {
basePath = basePath.slice(0, -1);
}
if (basePath.endsWith("/playground")) {
basePath = basePath.slice(0, -"/playground".length);
}
// check if we can omit the last segment
const [configHash, c, ...rest] = basePath.split("/").reverse();
if (c === "c") {
basePath = rest.reverse().join("/");
try {
configFromUrl = JSON.parse(decompressFromEncodedURIComponent(configHash));
} catch (error) {
console.error(error);
}
}
return { basePath, configFromUrl };
}
function CopyButton(props: { value: string }) {
const [copied, setCopied] = useState(false);
const cbRef = useRef<number | null>(null);
function toggleCopied() {
setCopied(true);
if (cbRef.current != null) window.clearTimeout(cbRef.current);
cbRef.current = window.setTimeout(() => setCopied(false), 1500);
}
useEffect(() => {
return () => {
if (cbRef.current != null) {
window.clearTimeout(cbRef.current);
}
};
}, []);
return (
<button
className="px-3 py-1"
onClick={() => {
navigator.clipboard.writeText(props.value).then(toggleCopied);
}}
>
{copied ? "Copied" : "Copy"}
</button>
);
}
function ShareDialog(props: { config: unknown; children: ReactNode }) {
const hash = useMemo(() => {
return compressToEncodedURIComponent(JSON.stringify(props.config));
}, [props.config]);
const state = getStateFromUrl(window.location.href);
// get base URL
const targetUrl = `${state.basePath}/c/${hash}`;
// .../c/[hash]/playground
const playgroundUrl = `${targetUrl}/playground`;
// cURL, JS: .../c/[hash]/invoke
// Python: .../c/[hash]
const invokeUrl = `${targetUrl}/invoke`;
const pythonSnippet = `
from langserve import RemoteRunnable
chain = RemoteRunnable("${targetUrl}")
chain.invoke({ ... })
`;
const typescriptSnippet = `
import { RemoteRunnable } from "langchain/runnables/remote";
const chain = new RemoteRunnable({ url: \`${invokeUrl}\` });
const result = await chain.invoke({ ... });
`;
return (
<Drawer.Root>
<Drawer.Trigger asChild>{props.children}</Drawer.Trigger>
<Drawer.Portal>
<Drawer.Overlay className="fixed inset-0 bg-black/40" />
<Drawer.Content className="flex justify-center items-center mt-24 fixed bottom-0 left-0 right-0 text-ls-black !pointer-events-none after:!bg-background">
<div className="p-4 bg-background max-w-[calc(800px-2rem)] rounded-t-2xl border border-divider-500 border-b-background pointer-events-auto">
<h3 className="text-xl font-medium">Share</h3>
<hr className="border-divider-500 my-4 -mx-4" />
<div className="flex flex-col gap-3">
{playgroundUrl.length < URL_LENGTH_LIMIT && (
<div className="flex flex-col gap-2 p-3 rounded-2xl dark:bg-[#2C2C2E] bg-gray-100">
<div className="flex items-center gap-3">
<div className="w-10 h-10 flex items-center justify-center text-center text-sm bg-background rounded-xl">
🦜
</div>
<span className="font-semibold">Playground</span>
</div>
<div className="grid grid-cols-[auto,1fr,auto] dark:bg-[#111111] bg-white rounded-xl text-sm items-center">
<PadlockIcon className="mx-3" />
<div className="overflow-auto whitespace-nowrap py-3 no-scrollbar text-ls-gray-100">
{playgroundUrl.split("://")[1]}
PadlockIcon
</div>
<CopyButton value={playgroundUrl} />
</div>
</div>
)}
<div className="flex flex-col gap-2 p-3 rounded-2xl dark:bg-[#2C2C2E] bg-gray-100">
<div className="flex items-center gap-3">
<div className="w-10 h-10 flex items-center justify-center text-center text-sm bg-background rounded-xl">
<CodeIcon />
</div>
<span className="font-semibold">Get the code</span>
</div>
{targetUrl.length < URL_LENGTH_LIMIT && (
<div className="grid grid-cols-[1fr,auto] dark:bg-[#111111] bg-white rounded-xl text-sm items-center">
<div className="overflow-auto whitespace-nowrap px-3 py-3 no-scrollbar text-ls-gray-100">
Python SDK
</div>
<CopyButton value={pythonSnippet.trim()} />
</div>
)}
{invokeUrl.length < URL_LENGTH_LIMIT && (
<div className="grid grid-cols-[1fr,auto] dark:bg-[#111111] bg-white rounded-xl text-sm items-center">
<div className="overflow-auto whitespace-nowrap px-3 py-3 no-scrollbar text-ls-gray-100">
TypeScript SDK
</div>
<CopyButton value={typescriptSnippet.trim()} />
</div>
)}
</div>
</div>
</div>
</Drawer.Content>
</Drawer.Portal>
</Drawer.Root>
);
}
function App() {
const [isIframe] = useState(() => window.self !== window.top);
@@ -282,14 +164,14 @@ function App() {
// store form state
const [configData, setConfigData] = useState<
Pick<JsonFormsCore, "data" | "errors">
>({ data: {}, errors: [] });
Pick<JsonFormsCore, "data" | "errors"> & { defaults: boolean }
>({ data: {}, errors: [], defaults: true });
const [inputData, setInputData] = useState<
Pick<JsonFormsCore, "data" | "errors">
>({ data: {}, errors: [] });
>({ data: null, errors: [] });
// fetch input and config schemas from the server
const schemas = useSchemas();
const schemas = useSchemas(configData);
// apply defaults defined in each schema
useEffect(() => {
if (schemas.config) {
@@ -300,8 +182,9 @@ function App() {
initConfigData.current ??
defaults(schemas.config),
errors: [],
defaults: true,
});
setInputData({ data: {}, errors: [] });
setInputData({ data: null, errors: [] });
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [schemas.config]);
@@ -322,7 +205,11 @@ function App() {
const value: { config: JsonFormsCore["data"] } = message.value;
if (Object.keys(value.config).length > 0) {
initConfigData.current = value.config;
setConfigData({ data: value.config, errors: [] });
setConfigData({
data: value.config,
errors: [],
defaults: false,
});
break;
}
}
@@ -350,23 +237,28 @@ function App() {
renderers={renderers}
cells={cells}
onChange={({ data, errors }) =>
data ? setConfigData({ data, errors }) : undefined
data
? setConfigData({ data, errors, defaults: false })
: undefined
}
/>
{!!configData.errors?.length && (
<>
<h3>Validation Errors</h3>
{configData.errors?.map((e, i) => (
<p key={i}>{e.message}</p>
))}
</>
{!!configData.errors?.length && configData.data && (
<div className="bg-background rounded-xl">
<div className="bg-red-500/10 text-red-700 dark:text-red-300 rounded-xl p-3">
<strong className="font-bold">Validation Errors</strong>
<ul className="list-disc pl-5">
{configData.errors?.map((e, i) => (
<li key={i}>{e.message}</li>
))}
</ul>
</div>
</div>
)}
</div>
{!isIframe && (
<div className="flex flex-col gap-3">
<h2 className="text-xl font-semibold">Customize</h2>
<h2 className="text-xl font-semibold">Try it</h2>
<div className="p-4 border border-divider-700 flex flex-col gap-3 rounded-2xl bg-background">
<h3 className="font-medium">Inputs</h3>
@@ -378,13 +270,15 @@ function App() {
cells={cells}
onChange={({ data, errors }) => setInputData({ data, errors })}
/>
{!!inputData.errors?.length && (
<>
<h3>Validation Errors</h3>
{inputData.errors?.map((e, i) => (
<p key={i}>{e.message}</p>
))}
</>
{!!inputData.errors?.length && inputData.data && (
<div className="bg-red-500/10 text-red-700 dark:text-red-300 rounded-xl p-3">
<strong className="font-bold">Validation Errors</strong>
<ul className="list-disc pl-5">
{inputData.errors?.map((e, i) => (
<li key={i}>{e.message}</li>
))}
</ul>
</div>
)}
</div>
@@ -400,10 +294,6 @@ function App() {
</div>
)}
<div className="text-center text-sm">
<strong>🦜 LangServe</strong>. An Open Source project by LangChain
</div>
<div className="flex-grow md:hidden" />
<div className="gap-4 grid grid-cols-2 sticky -mx-4 px-4 py-4 bottom-0 bg-background md:static md:bg-transparent">
@@ -0,0 +1,6 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M12 5.45455C8.38505 5.45455 5.45455 8.38505 5.45455 12C5.45455 15.615 8.38505 18.5455 12 18.5455C15.615 18.5455 18.5455 15.615 18.5455 12C18.5455 8.38505 15.615 5.45455 12 5.45455ZM4 12C4 7.58172 7.58172 4 12 4C16.4183 4 20 7.58172 20 12C20 16.4183 16.4183 20 12 20C7.58172 20 4 16.4183 4 12ZM15.787 9.30392C16.071 9.58794 16.071 10.0484 15.787 10.3324L11.4233 14.6961C11.1393 14.9801 10.6788 14.9801 10.3948 14.6961L8.21301 12.5143C7.929 12.2303 7.929 11.7697 8.21301 11.4857C8.49703 11.2017 8.95751 11.2017 9.24153 11.4857L10.9091 13.1533L14.7585 9.30392C15.0425 9.01991 15.503 9.01991 15.787 9.30392Z"
fill="currentColor" />
</svg>

After

Width:  |  Height:  |  Size: 792 B

@@ -0,0 +1,6 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M8.29289 5.29289C8.68342 4.90237 9.31658 4.90237 9.70711 5.29289L15.7071 11.2929C16.0976 11.6834 16.0976 12.3166 15.7071 12.7071L9.70711 18.7071C9.31658 19.0976 8.68342 19.0976 8.29289 18.7071C7.90237 18.3166 7.90237 17.6834 8.29289 17.2929L13.5858 12L8.29289 6.70711C7.90237 6.31658 7.90237 5.68342 8.29289 5.29289Z"
fill="currentColor" />
</svg>

After

Width:  |  Height:  |  Size: 505 B

@@ -0,0 +1,6 @@
<svg width="25" height="25" viewBox="0 0 25 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M4.5 19.7783C4.5 21.5132 5.35498 22.3848 7.07324 22.3848H14.876C16.5942 22.3848 17.4492 21.5049 17.4492 19.7783V18.2427H18.9019C20.6118 18.2427 21.4751 17.3628 21.4751 15.6362V8.896C21.4751 7.875 21.2676 7.22754 20.645 6.58838L16.4531 2.33008C15.8638 1.72412 15.1665 1.5 14.2783 1.5H11.0991C9.38916 1.5 8.52588 2.37988 8.52588 4.10645V5.64209H7.07324C5.36328 5.64209 4.5 6.51367 4.5 8.24854V19.7783ZM16.6606 11.0874L12.0869 6.43066C11.4561 5.7832 10.9331 5.64209 10.0034 5.64209H9.8623V4.13135C9.8623 3.30957 10.3022 2.83643 11.1655 2.83643H14.8345V7.09473C14.8345 8.05762 15.2993 8.51416 16.2539 8.51416H20.1387V15.6113C20.1387 16.4414 19.6904 16.9062 18.8271 16.9062H17.4492V13.2954C17.4492 12.2329 17.3247 11.7681 16.6606 11.0874ZM16.0381 6.89551V3.49219L19.79 7.31055H16.4448C16.1543 7.31055 16.0381 7.18604 16.0381 6.89551ZM5.83643 19.7534V8.26514C5.83643 7.45166 6.27637 6.97852 7.13965 6.97852H9.8623V11.793C9.8623 12.8389 10.3936 13.3618 11.4229 13.3618H16.1128V19.7534C16.1128 20.5835 15.6646 21.0483 14.8096 21.0483H7.13135C6.27637 21.0483 5.83643 20.5835 5.83643 19.7534ZM11.5806 12.1084C11.2485 12.1084 11.1157 11.9756 11.1157 11.6436V7.28564L15.8555 12.1084H11.5806Z"
fill="currentColor" />
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@@ -0,0 +1,6 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M11.9999 2.51489C12.5522 2.51489 12.9999 2.96261 12.9999 3.51489V11.0002L20.4852 11.0002C21.0375 11.0002 21.4852 11.4479 21.4852 12.0002C21.4852 12.5525 21.0375 13.0002 20.4852 13.0002H12.9999V20.4855C12.9999 21.0377 12.5522 21.4855 11.9999 21.4855C11.4476 21.4855 10.9999 21.0377 10.9999 20.4855V13.0002H3.51465C2.96236 13.0002 2.51465 12.5525 2.51465 12.0002C2.51465 11.4479 2.96236 11.0002 3.51465 11.0002L10.9999 11.0002V3.51489C10.9999 2.96261 11.4476 2.51489 11.9999 2.51489Z"
fill="currentColor" />
</svg>

After

Width:  |  Height:  |  Size: 670 B

@@ -0,0 +1,6 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd"
d="M8 3C8 2.44772 8.44772 2 9 2H15C15.5523 2 16 2.44772 16 3C16 3.55228 15.5523 4 15 4H9C8.44772 4 8 3.55228 8 3ZM4.99224 5H3C2.44772 5 2 5.44772 2 6C2 6.55228 2.44772 7 3 7H4.06445L4.70614 16.6254C4.75649 17.3809 4.79816 18.006 4.87287 18.5149C4.95066 19.0447 5.07405 19.5288 5.33109 19.98C5.73123 20.6824 6.33479 21.247 7.06223 21.5996C7.52952 21.826 8.0208 21.917 8.55459 21.9593C9.06728 22 9.69383 22 10.4509 22H13.5491C14.3062 22 14.9327 22 15.4454 21.9593C15.9792 21.917 16.4705 21.826 16.9378 21.5996C17.6652 21.247 18.2688 20.6824 18.6689 19.98C18.926 19.5288 19.0493 19.0447 19.1271 18.5149C19.2018 18.006 19.2435 17.3808 19.2939 16.6253L19.9356 7H21C21.5523 7 22 6.55228 22 6C22 5.44772 21.5523 5 21 5H19.0078C19.0019 4.99995 18.9961 4.99995 18.9903 5H5.00974C5.00392 4.99995 4.99809 4.99995 4.99224 5ZM17.9311 7H6.06889L6.69907 16.4528C6.75274 17.2578 6.78984 17.8034 6.85166 18.2243C6.9117 18.6333 6.98505 18.8429 7.06888 18.99C7.26895 19.3412 7.57072 19.6235 7.93444 19.7998C8.08684 19.8736 8.30086 19.9329 8.71286 19.9656C9.13703 19.9993 9.68385 20 10.4907 20H13.5093C14.3161 20 14.863 19.9993 15.2871 19.9656C15.6991 19.9329 15.9132 19.8736 16.0656 19.7998C16.4293 19.6235 16.7311 19.3412 16.9311 18.99C17.015 18.8429 17.0883 18.6333 17.1483 18.2243C17.2102 17.8034 17.2473 17.2578 17.3009 16.4528L17.9311 7Z"
fill="currentColor" />
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@@ -0,0 +1,51 @@
import { cn } from "../utils/cn";
const COMMON_CLS = cn(
"text-lg col-[1] row-[1] m-0 resize-none overflow-hidden whitespace-pre-wrap break-words border-none bg-transparent p-0"
);
export function AutosizeTextarea(props: {
id?: string;
value?: string | null | undefined;
placeholder?: string;
className?: string;
onChange?: (e: string) => void;
onFocus?: () => void;
onBlur?: () => void;
onKeyDown?: (e: React.KeyboardEvent<HTMLTextAreaElement>) => void;
autoFocus?: boolean;
readOnly?: boolean;
cursorPointer?: boolean;
disabled?: boolean;
}) {
return (
<div className={cn("grid w-full", props.className)}>
<textarea
id={props.id}
className={cn(
COMMON_CLS,
"text-transparent caret-black dark:caret-slate-200"
)}
disabled={props.disabled}
value={props.value ?? ""}
rows={1}
onChange={(e) => {
const target = e.target as HTMLTextAreaElement;
props.onChange?.(target.value);
}}
onFocus={props.onFocus}
onBlur={props.onBlur}
placeholder={props.placeholder}
readOnly={props.readOnly}
autoFocus={props.autoFocus && !props.readOnly}
onKeyDown={props.onKeyDown}
/>
<div
aria-hidden
className={cn(COMMON_CLS, "pointer-events-none select-none")}
>
{props.value}{" "}
</div>
</div>
);
}
@@ -0,0 +1,92 @@
// From https://github.com/eclipsesource/jsonforms/blob/44070b325121ad7173082fdf33be079f42ef96c4/packages/material/src/complex/MaterialArrayControlRenderer.tsx
/*
The MIT License
Copyright (c) 2017-2019 EclipseSource Munich
https://github.com/eclipsesource/jsonforms
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
import { useCallback, useState } from "react";
import {
ArrayLayoutProps,
RankedTester,
isObjectArrayControl,
isObjectArrayWithNesting,
isPrimitiveArrayControl,
or,
rankWith,
} from "@jsonforms/core";
import { withJsonFormsArrayLayoutProps } from "@jsonforms/react";
import { MaterialTableControl } from "./CustomArrayControlRenderer/MaterialTableControl";
import { Hidden } from "@mui/material";
import { DeleteDialog } from "./CustomArrayControlRenderer/DeleteDialog";
export const MaterialArrayControlRenderer = (props: ArrayLayoutProps) => {
const [open, setOpen] = useState(false);
const [path, setPath] = useState<string | undefined>(undefined);
const [rowData, setRowData] = useState<number | undefined>(undefined);
const { removeItems, visible } = props;
const openDeleteDialog = useCallback(
(p: string, rowIndex: number) => {
setOpen(true);
setPath(p);
setRowData(rowIndex);
},
[setOpen, setPath, setRowData]
);
const deleteCancel = useCallback(() => setOpen(false), [setOpen]);
const deleteConfirm = useCallback(() => {
const p = path?.substring(0, path.lastIndexOf("."));
if (p != null && rowData != null) removeItems?.(p, [rowData])();
setOpen(false);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [setOpen, path, rowData]);
const deleteClose = useCallback(() => setOpen(false), [setOpen]);
return (
<div className="control">
<Hidden xsUp={!visible}>
<MaterialTableControl {...props} openDeleteDialog={openDeleteDialog} />
<DeleteDialog
open={open}
onCancel={deleteCancel}
onConfirm={deleteConfirm}
onClose={deleteClose}
acceptText={props.translations.deleteDialogAccept!}
declineText={props.translations.deleteDialogDecline!}
title={props.translations.deleteDialogTitle!}
message={props.translations.deleteDialogMessage!}
/>
</Hidden>
</div>
);
};
// eslint-disable-next-line react-refresh/only-export-components
export const materialArrayControlTester: RankedTester = rankWith(
999,
or(isObjectArrayControl, isPrimitiveArrayControl, isObjectArrayWithNesting)
);
// eslint-disable-next-line react-refresh/only-export-components
export default withJsonFormsArrayLayoutProps(MaterialArrayControlRenderer);
@@ -0,0 +1,84 @@
/*
The MIT License
Copyright (c) 2017-2019 EclipseSource Munich
https://github.com/eclipsesource/jsonforms
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
import React from "react";
import {
Button,
Dialog,
DialogActions,
DialogContent,
DialogContentText,
DialogTitle,
} from "@mui/material";
export interface DeleteDialogProps {
open: boolean;
onClose(): void;
onConfirm(): void;
onCancel(): void;
title: string;
message: string;
acceptText: string;
declineText: string;
}
export interface WithDeleteDialogSupport {
openDeleteDialog(path: string, data: number): void;
}
export const DeleteDialog = React.memo(function DeleteDialog({
open,
onClose,
onConfirm,
onCancel,
title,
message,
acceptText,
declineText,
}: DeleteDialogProps) {
return (
<Dialog
open={open}
keepMounted
onClose={onClose}
aria-labelledby="alert-dialog-confirmdelete-title"
aria-describedby="alert-dialog-confirmdelete-description"
>
<DialogTitle id="alert-dialog-confirmdelete-title">{title}</DialogTitle>
<DialogContent>
<DialogContentText id="alert-dialog-confirmdelete-description">
{message}
</DialogContentText>
</DialogContent>
<DialogActions>
<Button onClick={onCancel} color="primary">
{declineText}
</Button>
<Button onClick={onConfirm} color="primary">
{acceptText}
</Button>
</DialogActions>
</Dialog>
);
});
@@ -0,0 +1,502 @@
/*
The MIT License
Copyright (c) 2017-2019 EclipseSource Munich
https://github.com/eclipsesource/jsonforms
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
import isEmpty from "lodash/isEmpty";
import union from "lodash/union";
import {
DispatchCell,
JsonFormsStateContext,
useJsonForms,
} from "@jsonforms/react";
import startCase from "lodash/startCase";
import range from "lodash/range";
import React, { Fragment, useMemo } from "react";
import TrashIcon from "../../assets/TrashIcon.svg?react";
import {
FormHelperText,
Grid,
Hidden,
IconButton,
Table,
TableBody,
TableCell,
TableHead,
TableRow,
Typography,
} from "@mui/material";
import {
ArrayLayoutProps,
ControlElement,
errorsAt,
formatErrorMessage,
JsonSchema,
Paths,
Resolve,
JsonFormsRendererRegistryEntry,
JsonFormsCellRendererRegistryEntry,
encode,
ArrayTranslations,
} from "@jsonforms/core";
import ArrowDownward from "@mui/icons-material/ArrowDownward";
import ArrowUpward from "@mui/icons-material/ArrowUpward";
import { WithDeleteDialogSupport } from "./DeleteDialog";
import NoBorderTableCell from "./NoBorderTableCell";
import TableToolbar from "./TableToolbar";
import merge from "lodash/merge";
// we want a cell that doesn't automatically span
const styles = {
fixedCell: {
width: "150px",
height: "50px",
paddingLeft: 0,
paddingRight: 0,
textAlign: "center",
},
fixedCellSmall: {
width: "50px",
height: "50px",
paddingLeft: 0,
paddingRight: 0,
textAlign: "center",
},
};
const generateCells = (
Cell: React.ComponentType<OwnPropsOfNonEmptyCell | TableHeaderCellProps>,
schema: JsonSchema,
rowPath: string,
enabled: boolean,
cells?: JsonFormsCellRendererRegistryEntry[]
) => {
if (schema.type === "object") {
return getValidColumnProps(schema).map((prop) => {
const cellPath = Paths.compose(rowPath, prop);
const props = {
propName: prop,
schema,
title: schema.properties?.[prop]?.title ?? startCase(prop),
rowPath,
cellPath,
enabled,
cells,
};
return <Cell key={cellPath} {...props} />;
});
} else {
// primitives
const props = {
schema,
rowPath,
cellPath: rowPath,
enabled,
};
return <Cell key={rowPath} {...props} />;
}
};
const getValidColumnProps = (scopedSchema: JsonSchema) => {
if (
scopedSchema.type === "object" &&
typeof scopedSchema.properties === "object"
) {
return Object.keys(scopedSchema.properties).filter(
(prop) => scopedSchema.properties?.[prop].type !== "array"
);
}
// primitives
return [""];
};
export interface EmptyTableProps {
numColumns: number;
translations: ArrayTranslations;
}
const EmptyTable = ({ numColumns, translations }: EmptyTableProps) => (
<TableRow>
<NoBorderTableCell colSpan={numColumns}>
<Typography align="center">{translations.noDataMessage}</Typography>
</NoBorderTableCell>
</TableRow>
);
interface TableHeaderCellProps {
title: string;
}
const TableHeaderCell = React.memo(function TableHeaderCell({
title,
}: TableHeaderCellProps) {
return (
<TableCell
sx={{
color: "hsl(var(--ls-gray-100))",
borderBottomColor: "hsl(var(--divider-700))",
px: 0,
py: 1,
}}
>
{title}
</TableCell>
);
});
interface NonEmptyCellProps extends OwnPropsOfNonEmptyCell {
rootSchema: JsonSchema;
errors: string;
path: string;
enabled: boolean;
}
interface OwnPropsOfNonEmptyCell {
rowPath: string;
propName?: string;
schema: JsonSchema;
enabled: boolean;
renderers?: JsonFormsRendererRegistryEntry[];
cells?: JsonFormsCellRendererRegistryEntry[];
}
const ctxToNonEmptyCellProps = (
ctx: JsonFormsStateContext,
ownProps: OwnPropsOfNonEmptyCell
): NonEmptyCellProps => {
const path =
ownProps.rowPath +
(ownProps.schema.type === "object" ? "." + ownProps.propName : "");
const errors = formatErrorMessage(
union(
errorsAt(
path,
ownProps.schema,
(p) => p === path
)(ctx.core?.errors ?? []).map((error) => error.message) as string[]
)
);
return {
rowPath: ownProps.rowPath,
propName: ownProps.propName,
schema: ownProps.schema,
rootSchema: ctx.core?.schema ?? {},
errors,
path,
enabled: ownProps.enabled,
cells: ownProps.cells || ctx.cells,
renderers: ownProps.renderers || ctx.renderers,
};
};
const controlWithoutLabel = (scope: string): ControlElement => ({
type: "Control",
scope: scope,
label: false,
});
interface NonEmptyCellComponentProps {
path: string;
propName?: string;
schema: JsonSchema;
rootSchema: JsonSchema;
errors: string;
enabled: boolean;
renderers?: JsonFormsRendererRegistryEntry[];
cells?: JsonFormsCellRendererRegistryEntry[];
isValid: boolean;
}
const NonEmptyCellComponent = React.memo(function NonEmptyCellComponent({
path,
propName,
schema,
rootSchema,
errors,
enabled,
renderers,
cells,
isValid,
}: NonEmptyCellComponentProps) {
return (
<NoBorderTableCell sx={{ color: "hsl(var(--ls-black))" }}>
{schema.properties ? (
<DispatchCell
schema={Resolve.schema(
schema,
`#/properties/${encode(propName!)}`,
rootSchema
)}
uischema={controlWithoutLabel(`#/properties/${encode(propName!)}`)}
path={path}
enabled={enabled}
renderers={renderers}
cells={cells}
/>
) : (
<DispatchCell
schema={schema}
uischema={controlWithoutLabel("#")}
path={path}
enabled={enabled}
renderers={renderers}
cells={cells}
/>
)}
<FormHelperText error={!isValid}>{!isValid && errors}</FormHelperText>
</NoBorderTableCell>
);
});
const NonEmptyCell = (ownProps: OwnPropsOfNonEmptyCell) => {
const ctx = useJsonForms();
const emptyCellProps = ctxToNonEmptyCellProps(ctx, ownProps);
const isValid = isEmpty(emptyCellProps.errors);
return <NonEmptyCellComponent {...emptyCellProps} isValid={isValid} />;
};
interface NonEmptyRowProps {
childPath: string;
schema: JsonSchema;
rowIndex: number;
moveUpCreator: (path: string, position: number) => () => void;
moveDownCreator: (path: string, position: number) => () => void;
enableUp: boolean;
enableDown: boolean;
showSortButtons: boolean;
enabled: boolean;
cells?: JsonFormsCellRendererRegistryEntry[];
path: string;
translations: ArrayTranslations;
}
const NonEmptyRowComponent = ({
childPath,
schema,
rowIndex,
openDeleteDialog,
moveUpCreator,
moveDownCreator,
enableUp,
enableDown,
showSortButtons,
enabled,
cells,
path,
translations,
}: NonEmptyRowProps & WithDeleteDialogSupport) => {
const moveUp = useMemo(
() => moveUpCreator(path, rowIndex),
[moveUpCreator, path, rowIndex]
);
const moveDown = useMemo(
() => moveDownCreator(path, rowIndex),
[moveDownCreator, path, rowIndex]
);
return (
<TableRow key={childPath} hover>
{generateCells(NonEmptyCell as any, schema, childPath, enabled, cells)}
{enabled ? (
<NoBorderTableCell
style={showSortButtons ? styles.fixedCell : styles.fixedCellSmall}
>
<Grid
container
direction="row"
justifyContent="flex-end"
alignItems="center"
>
{showSortButtons ? (
<Fragment>
<Grid item>
<IconButton
aria-label={translations.upAriaLabel}
onClick={moveUp}
disabled={!enableUp}
size="large"
>
<ArrowUpward />
</IconButton>
</Grid>
<Grid item>
<IconButton
aria-label={translations.downAriaLabel}
onClick={moveDown}
disabled={!enableDown}
size="large"
>
<ArrowDownward />
</IconButton>
</Grid>
</Fragment>
) : null}
<Grid item>
<IconButton
aria-label={translations.removeAriaLabel}
onClick={() => openDeleteDialog(childPath, rowIndex)}
size="large"
sx={{ p: 1 }}
>
<TrashIcon className="text-ls-black" />
</IconButton>
</Grid>
</Grid>
</NoBorderTableCell>
) : null}
</TableRow>
);
};
export const NonEmptyRow = React.memo(NonEmptyRowComponent);
interface TableRowsProp {
data: number;
path: string;
schema: JsonSchema;
uischema: ControlElement;
config?: any;
enabled: boolean;
cells?: JsonFormsCellRendererRegistryEntry[];
moveUp?(path: string, toMove: number): () => void;
moveDown?(path: string, toMove: number): () => void;
translations: ArrayTranslations;
}
const TableRows = ({
data,
path,
schema,
openDeleteDialog,
moveUp,
moveDown,
uischema,
config,
enabled,
cells,
translations,
}: TableRowsProp & WithDeleteDialogSupport) => {
const isEmptyTable = data === 0;
if (isEmptyTable) {
return (
<EmptyTable
numColumns={getValidColumnProps(schema).length + 1}
translations={translations}
/>
);
}
const appliedUiSchemaOptions = merge({}, config, uischema.options);
return (
<React.Fragment>
{range(data).map((index: number) => {
const childPath = Paths.compose(path, `${index}`);
return (
<NonEmptyRow
key={childPath}
childPath={childPath}
rowIndex={index}
schema={schema}
openDeleteDialog={openDeleteDialog}
moveUpCreator={moveUp ?? (() => () => {})}
moveDownCreator={moveDown ?? (() => () => {})}
enableUp={index !== 0}
enableDown={index !== data - 1}
showSortButtons={
appliedUiSchemaOptions.showSortButtons ||
appliedUiSchemaOptions.showArrayTableSortButtons
}
enabled={enabled}
cells={cells}
path={path}
translations={translations}
/>
);
})}
</React.Fragment>
);
};
export class MaterialTableControl extends React.Component<
ArrayLayoutProps & WithDeleteDialogSupport,
any
> {
addItem = (path: string, value: any) => this.props.addItem(path, value);
render() {
const {
label,
path,
schema,
rootSchema,
uischema,
errors,
openDeleteDialog,
visible,
enabled,
cells,
translations,
} = this.props;
const controlElement = uischema as ControlElement;
const isObjectSchema = schema.type === "object";
const headerCells: any = isObjectSchema
? generateCells(TableHeaderCell as any, schema, path, enabled, cells)
: undefined;
return (
<Hidden xsUp={!visible}>
<Table>
<TableHead sx={{ borderBottomColor: "hsl(var(--divider-700))" }}>
<TableToolbar
errors={errors}
label={label}
addItem={this.addItem}
numColumns={isObjectSchema ? headerCells.length : 1}
path={path}
uischema={controlElement}
schema={schema}
rootSchema={rootSchema}
enabled={enabled}
translations={translations}
/>
{isObjectSchema && (
<TableRow>
{headerCells}
{enabled ? (
<TableCell
sx={{ borderBottomColor: "hsl(var(--divider-700))" }}
/>
) : null}
</TableRow>
)}
</TableHead>
<TableBody>
<TableRows
{...this.props}
openDeleteDialog={this.props.openDeleteDialog ?? openDeleteDialog}
translations={this.props.translations ?? translations}
/>
</TableBody>
</Table>
</Hidden>
);
}
}
@@ -0,0 +1,39 @@
/*
The MIT License
Copyright (c) 2017-2019 EclipseSource Munich
https://github.com/eclipsesource/jsonforms
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
import { styled } from "@mui/material/styles";
import { TableCell } from "@mui/material";
const StyledTableCell = styled(TableCell)({
borderBottom: "none",
fill: "white",
color: "inherit",
padding: 0,
});
const NoBorderTableCell = ({ children, ...otherProps }: any) => (
<StyledTableCell {...otherProps}>{children}</StyledTableCell>
);
export default NoBorderTableCell;
@@ -0,0 +1,101 @@
/*
The MIT License
Copyright (c) 2017-2019 EclipseSource Munich
https://github.com/eclipsesource/jsonforms
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
import React from "react";
import {
ControlElement,
createDefaultValue,
JsonSchema,
ArrayTranslations,
} from "@jsonforms/core";
import { IconButton, TableRow, Tooltip } from "@mui/material";
import PlusIcon from "../../assets/PlusIcon.svg?react";
import ValidationIcon from "./ValidationIcon";
import NoBorderTableCell from "./NoBorderTableCell";
export interface MaterialTableToolbarProps {
numColumns: number;
errors: string;
label: string;
path: string;
uischema: ControlElement;
schema: JsonSchema;
rootSchema: JsonSchema;
enabled: boolean;
translations: ArrayTranslations;
addItem(path: string, value: any): () => void;
}
const fixedCellSmall = {
paddingLeft: 0,
paddingRight: 0,
};
const TableToolbar = React.memo(function TableToolbar({
numColumns,
errors,
label,
path,
addItem,
schema,
enabled,
translations,
}: MaterialTableToolbarProps) {
return (
<TableRow>
<NoBorderTableCell colSpan={numColumns} sx={{ verticalAlign: "top" }}>
<div className="flex items-center gap-2">
{label && (
<span className="text-xs uppercase font-semibold text-ls-gray-100">
{label}
</span>
)}
{errors.length !== 0 && (
<ValidationIcon id="tooltip-validation" errorMessages={errors} />
)}
</div>
</NoBorderTableCell>
{enabled ? (
<NoBorderTableCell align="right" style={fixedCellSmall}>
<Tooltip
id="tooltip-add"
title={translations.addTooltip}
placement="bottom"
>
<IconButton
aria-label={translations.addAriaLabel}
onClick={addItem(path, createDefaultValue(schema))}
size="large"
sx={{ p: 1 }}
>
<PlusIcon className="text-ls-black" />
</IconButton>
</Tooltip>
</NoBorderTableCell>
) : null}
</TableRow>
);
});
export default TableToolbar;
@@ -0,0 +1,49 @@
/*
The MIT License
Copyright (c) 2017-2019 EclipseSource Munich
https://github.com/eclipsesource/jsonforms
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
import React from "react";
import ErrorOutlineIcon from "@mui/icons-material/ErrorOutline";
import { Badge, Tooltip, styled } from "@mui/material";
const StyledBadge = styled(Badge)(({ theme }: any) => ({
color: theme.palette.error.main,
}));
export interface ValidationProps {
errorMessages: string;
id: string;
}
const ValidationIcon: React.FC<ValidationProps> = ({ errorMessages, id }) => {
return (
<Tooltip id={id} title={errorMessages}>
<StyledBadge badgeContent={errorMessages.split("\n").length}>
<ErrorOutlineIcon color="inherit" />
</StyledBadge>
</Tooltip>
);
};
export default ValidationIcon;
@@ -0,0 +1,67 @@
// From https://github.com/eclipsesource/jsonforms/blob/master/packages/vanilla-renderers/src/cells/TextAreaCell.tsx
/*
The MIT License
Copyright (c) 2017-2019 EclipseSource Munich
https://github.com/eclipsesource/jsonforms
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
import {
CellProps,
isMultiLineControl,
RankedTester,
rankWith,
} from "@jsonforms/core";
import { withJsonFormsCellProps } from "@jsonforms/react";
import {
withVanillaCellProps,
type VanillaRendererProps,
} from "@jsonforms/vanilla-renderers";
import merge from "lodash/merge";
import { cn } from "../utils/cn";
import { AutosizeTextarea } from "./AutosizeTextarea";
export const TextAreaCell = (props: CellProps & VanillaRendererProps) => {
const { data, className, id, enabled, config, uischema, path, handleChange } =
props;
const appliedUiSchemaOptions = merge({}, config, uischema.options);
return (
<AutosizeTextarea
value={data || ""}
onChange={(value) => handleChange(path, value === "" ? undefined : value)}
className={cn("w-full text-lg", className)}
id={id}
disabled={!enabled}
autoFocus={appliedUiSchemaOptions.focus}
placeholder={appliedUiSchemaOptions.placeholder}
/>
);
};
/**
* Tester for a multi-line string control.
* @type {RankedTester}
*/
// eslint-disable-next-line react-refresh/only-export-components
export const textAreaCellTester: RankedTester = rankWith(2, isMultiLineControl);
// eslint-disable-next-line react-refresh/only-export-components
export default withJsonFormsCellProps(withVanillaCellProps(TextAreaCell));
@@ -0,0 +1,85 @@
// From https://github.com/eclipsesource/jsonforms/blob/master/packages/vanilla-renderers/src/cells/TextAreaCell.tsx
/*
The MIT License
Copyright (c) 2017-2019 EclipseSource Munich
https://github.com/eclipsesource/jsonforms
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/
import {
CellProps,
isMultiLineControl,
RankedTester,
rankWith,
} from "@jsonforms/core";
import { withJsonFormsCellProps } from "@jsonforms/react";
import {
withVanillaCellProps,
type VanillaRendererProps,
} from "@jsonforms/vanilla-renderers";
import merge from "lodash/merge";
import { AutosizeTextarea } from "./AutosizeTextarea";
import { cn } from "../utils/cn";
function tryJsonParse(str: string) {
try {
return JSON.parse(str);
} catch {
return str;
}
}
function tryJsonStringify(obj: unknown): string | unknown {
try {
return JSON.stringify(obj);
} catch {
return obj;
}
}
export const TextAreaCell = (props: CellProps & VanillaRendererProps) => {
const { data, className, id, enabled, config, uischema, path, handleChange } =
props;
const appliedUiSchemaOptions = merge({}, config, uischema.options);
return (
<AutosizeTextarea
value={typeof data === "object" ? tryJsonStringify(data) : data ?? ""}
onChange={(value) =>
handleChange(path, value === "" ? undefined : tryJsonParse(value))
}
className={cn("w-full text-lg", className)}
id={id}
disabled={!enabled}
autoFocus={appliedUiSchemaOptions.focus}
placeholder={appliedUiSchemaOptions.placeholder}
/>
);
};
/**
* Tester for a multi-line string control.
* @type {RankedTester}
*/
// eslint-disable-next-line react-refresh/only-export-components
export const textAreaCellTester: RankedTester = rankWith(2, isMultiLineControl);
// eslint-disable-next-line react-refresh/only-export-components
export default withJsonFormsCellProps(withVanillaCellProps(TextAreaCell));
@@ -0,0 +1,164 @@
import { Drawer } from "vaul";
import { ReactNode, useEffect, useMemo, useRef, useState } from "react";
import CodeIcon from "../assets/CodeIcon.svg?react";
import PadlockIcon from "../assets/PadlockIcon.svg?react";
import CopyIcon from "../assets/CopyIcon.svg?react";
import CheckCircleIcon from "../assets/CheckCircleIcon.svg?react";
import {
compressToEncodedURIComponent,
decompressFromEncodedURIComponent,
} from "lz-string";
const URL_LENGTH_LIMIT = 2000;
export function getStateFromUrl(path: string) {
let configFromUrl = null;
let basePath = path;
if (basePath.endsWith("/")) {
basePath = basePath.slice(0, -1);
}
if (basePath.endsWith("/playground")) {
basePath = basePath.slice(0, -"/playground".length);
}
// check if we can omit the last segment
const [configHash, c, ...rest] = basePath.split("/").reverse();
if (c === "c") {
basePath = rest.reverse().join("/");
try {
configFromUrl = JSON.parse(decompressFromEncodedURIComponent(configHash));
} catch (error) {
console.error(error);
}
}
return { basePath, configFromUrl };
}
function CopyButton(props: { value: string }) {
const [copied, setCopied] = useState(false);
const cbRef = useRef<number | null>(null);
function toggleCopied() {
setCopied(true);
if (cbRef.current != null) window.clearTimeout(cbRef.current);
cbRef.current = window.setTimeout(() => setCopied(false), 1500);
}
useEffect(() => {
return () => {
if (cbRef.current != null) {
window.clearTimeout(cbRef.current);
}
};
}, []);
return (
<button
className="px-3 py-1"
onClick={() => {
navigator.clipboard.writeText(props.value).then(toggleCopied);
}}
>
{copied ? <CheckCircleIcon /> : <CopyIcon />}
</button>
);
}
export function ShareDialog(props: { config: unknown; children: ReactNode }) {
const hash = useMemo(() => {
return compressToEncodedURIComponent(JSON.stringify(props.config));
}, [props.config]);
const state = getStateFromUrl(window.location.href);
// get base URL
const targetUrl = `${state.basePath}/c/${hash}`;
// .../c/[hash]/playground
const playgroundUrl = `${targetUrl}/playground`;
// cURL, JS: .../c/[hash]/invoke
// Python: .../c/[hash]
const invokeUrl = `${targetUrl}/invoke`;
const pythonSnippet = `
from langserve import RemoteRunnable
chain = RemoteRunnable("${targetUrl}")
chain.invoke({ ... })
`;
const typescriptSnippet = `
import { RemoteRunnable } from "langchain/runnables/remote";
const chain = new RemoteRunnable({ url: \`${invokeUrl}\` });
const result = await chain.invoke({ ... });
`;
return (
<Drawer.Root>
<Drawer.Trigger asChild>{props.children}</Drawer.Trigger>
<Drawer.Portal>
<Drawer.Overlay className="fixed inset-0 bg-black/40" />
<Drawer.Content className="flex justify-center items-center mt-24 fixed bottom-0 left-0 right-0 text-ls-black !pointer-events-none after:!bg-background">
<div className="p-4 bg-background max-w-[calc(800px-2rem)] rounded-t-2xl border border-divider-500 border-b-background pointer-events-auto">
<h3 className="text-xl font-medium">Share</h3>
<hr className="border-divider-500 my-4 -mx-4" />
<div className="flex flex-col gap-3">
{playgroundUrl.length < URL_LENGTH_LIMIT && (
<div className="flex flex-col gap-2 p-3 rounded-2xl dark:bg-[#2C2C2E] bg-gray-100">
<div className="flex items-center gap-3">
<div className="w-10 h-10 flex items-center justify-center text-center text-sm bg-background rounded-xl">
🦜
</div>
<span className="font-semibold">Playground</span>
</div>
<div className="grid grid-cols-[auto,1fr,auto] dark:bg-[#111111] bg-white rounded-xl text-sm items-center">
<PadlockIcon className="mx-3" />
<div className="overflow-auto whitespace-nowrap py-3 no-scrollbar text-ls-gray-100">
{playgroundUrl.split("://")[1]}
PadlockIcon
</div>
<CopyButton value={playgroundUrl} />
</div>
</div>
)}
<div className="flex flex-col gap-2 p-3 rounded-2xl dark:bg-[#2C2C2E] bg-gray-100">
<div className="flex items-center gap-3">
<div className="w-10 h-10 flex items-center justify-center text-center text-sm bg-background rounded-xl">
<CodeIcon />
</div>
<span className="font-semibold">Get the code</span>
</div>
{targetUrl.length < URL_LENGTH_LIMIT && (
<div className="grid grid-cols-[1fr,auto] dark:bg-[#111111] bg-white rounded-xl text-sm items-center">
<div className="overflow-auto whitespace-nowrap px-3 py-3 no-scrollbar text-ls-gray-100">
Python SDK
</div>
<CopyButton value={pythonSnippet.trim()} />
</div>
)}
{invokeUrl.length < URL_LENGTH_LIMIT && (
<div className="grid grid-cols-[1fr,auto] dark:bg-[#111111] bg-white rounded-xl text-sm items-center">
<div className="overflow-auto whitespace-nowrap px-3 py-3 no-scrollbar text-ls-gray-100">
TypeScript SDK
</div>
<CopyButton value={typescriptSnippet.trim()} />
</div>
)}
</div>
</div>
</div>
</Drawer.Content>
</Drawer.Portal>
</Drawer.Root>
);
}
+37 -5
View File
@@ -1,5 +1,9 @@
import { useEffect, useState } from "react";
import { resolveApiUrl } from "./utils/url";
import { simplifySchema } from "./utils/simplifySchema";
import { JsonFormsCore } from "@jsonforms/core";
import { compressToEncodedURIComponent } from "lz-string";
import { useDebounce } from "use-debounce";
declare global {
interface Window {
@@ -10,7 +14,9 @@ declare global {
}
}
export function useSchemas() {
export function useSchemas(
configData: Pick<JsonFormsCore, "data" | "errors"> & { defaults: boolean }
) {
const [schemas, setSchemas] = useState({
config: null,
input: null,
@@ -20,14 +26,22 @@ export function useSchemas() {
async function save() {
if (import.meta.env.DEV) {
const [config, input] = await Promise.all([
fetch(resolveApiUrl("/config_schema")).then((r) => r.json()),
fetch(resolveApiUrl("/input_schema")).then((r) => r.json()),
fetch(resolveApiUrl("/config_schema"))
.then((r) => r.json())
.then(simplifySchema),
fetch(resolveApiUrl("/input_schema"))
.then((r) => r.json())
.then(simplifySchema),
]);
setSchemas({ config, input });
} else {
setSchemas({
config: window.CONFIG_SCHEMA ?? null,
input: window.INPUT_SCHEMA ?? null,
config: window.CONFIG_SCHEMA
? await simplifySchema(window.CONFIG_SCHEMA)
: null,
input: window.INPUT_SCHEMA
? await simplifySchema(window.INPUT_SCHEMA)
: null,
});
}
}
@@ -35,5 +49,23 @@ export function useSchemas() {
save();
}, []);
const [debouncedConfigData] = useDebounce(configData, 500);
useEffect(() => {
if (!debouncedConfigData.defaults) {
fetch(
resolveApiUrl(
`c/${compressToEncodedURIComponent(
JSON.stringify(debouncedConfigData.data)
)}/input_schema`
)
)
.then((r) => r.json())
.then(simplifySchema)
.then((input) => setSchemas((current) => ({ ...current, input })))
.catch(() => {}); // ignore errors, eg. due to incomplete config
}
}, [debouncedConfigData]);
return schemas;
}
+7
View File
@@ -0,0 +1,7 @@
import clsx from "clsx";
import { ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
+4
View File
@@ -0,0 +1,4 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
export const JsonRefs: {
resolveRefs(schema: any): Promise<{ resolved: any }>;
};
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,8 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { JsonRefs } from "./json-refs";
// jsonforms doesn't support schemas with root $ref
// so we resolve root $ref and replace it with the actual schema
export function simplifySchema(schema: any) {
return JsonRefs.resolveRefs(schema).then((r: any) => r.resolved);
}
+219 -278
View File
@@ -29,11 +29,11 @@
chalk "^2.4.2"
"@babel/compat-data@^7.22.9":
version "7.22.20"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.20.tgz#8df6e96661209623f1975d66c35ffca66f3306d0"
integrity sha512-BQYjKbpXjoXwFW5jGqiizJQQT/aC7pFm9Ok1OWssonuguICi264lbgMzRp2ZMmRSlfkX6DsWDDcsrctK8Rwfiw==
version "7.23.2"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.23.2.tgz#6a12ced93455827037bfb5ed8492820d60fc32cc"
integrity sha512-0S9TQMmDHlqAZ2ITT95irXKfxN9bncq8ZCoJhun3nHL/lLUxd2NKBJYoNGWH7S0hz6fRQwWlAWn/ILM0C70KZQ==
"@babel/core@^7.21.3":
"@babel/core@^7.21.3", "@babel/core@^7.22.20":
version "7.23.2"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.23.2.tgz#ed10df0d580fff67c5f3ee70fd22e2e4c90a9f94"
integrity sha512-n7s51eWdaWZ3vGT2tD4T7J6eJs3QoBXydv7vkUM06Bf1cbVD2Kc2UrkzhiQwobfV7NwOnQXYL7UBJ5VPU+RGoQ==
@@ -54,27 +54,6 @@
json5 "^2.2.3"
semver "^6.3.1"
"@babel/core@^7.22.20":
version "7.23.0"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.23.0.tgz#f8259ae0e52a123eb40f552551e647b506a94d83"
integrity sha512-97z/ju/Jy1rZmDxybphrBuI+jtJjFVoz7Mr9yUQVVVi+DNZE333uFQeMOqcCIy1x3WYBIbWftUSLmbNXNT7qFQ==
dependencies:
"@ampproject/remapping" "^2.2.0"
"@babel/code-frame" "^7.22.13"
"@babel/generator" "^7.23.0"
"@babel/helper-compilation-targets" "^7.22.15"
"@babel/helper-module-transforms" "^7.23.0"
"@babel/helpers" "^7.23.0"
"@babel/parser" "^7.23.0"
"@babel/template" "^7.22.15"
"@babel/traverse" "^7.23.0"
"@babel/types" "^7.23.0"
convert-source-map "^2.0.0"
debug "^4.1.0"
gensync "^1.0.0-beta.2"
json5 "^2.2.3"
semver "^6.3.1"
"@babel/generator@^7.23.0":
version "7.23.0"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.0.tgz#df5c386e2218be505b34837acbcb874d7a983420"
@@ -168,15 +147,6 @@
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz#694c30dfa1d09a6534cdfcafbe56789d36aba040"
integrity sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==
"@babel/helpers@^7.23.0":
version "7.23.1"
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.23.1.tgz#44e981e8ce2b9e99f8f0b703f3326a4636c16d15"
integrity sha512-chNpneuK18yW5Oxsr+t553UZzzAs3aZnFm4bxhebsNTeshrC95yA7l5yl7GBAG+JG1rF0F7zzD2EixK9mWSDoA==
dependencies:
"@babel/template" "^7.22.15"
"@babel/traverse" "^7.23.0"
"@babel/types" "^7.23.0"
"@babel/helpers@^7.23.2":
version "7.23.2"
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.23.2.tgz#2832549a6e37d484286e15ba36a5330483cac767"
@@ -214,14 +184,7 @@
dependencies:
"@babel/helper-plugin-utils" "^7.22.5"
"@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.22.15", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7":
version "7.23.1"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.1.tgz#72741dc4d413338a91dcb044a86f3c0bc402646d"
integrity sha512-hC2v6p8ZSI/W0HUzh3V8C5g+NwSKzKPtJwSpTjwl0o297GP9+ZLQSkdvHz46CM3LqyoXxq+5G9komY+eSqSO0g==
dependencies:
regenerator-runtime "^0.14.0"
"@babel/runtime@^7.13.10":
"@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.18.3", "@babel/runtime@^7.23.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7":
version "7.23.2"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.2.tgz#062b0ac103261d68a966c4c7baf2ae3e62ec3885"
integrity sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==
@@ -237,22 +200,6 @@
"@babel/parser" "^7.22.15"
"@babel/types" "^7.22.15"
"@babel/traverse@^7.23.0":
version "7.23.0"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.0.tgz#18196ddfbcf4ccea324b7f6d3ada00d8c5a99c53"
integrity sha512-t/QaEvyIoIkwzpiZ7aoSKK8kObQYeF7T2v+dazAYCb8SXtp58zEVkWW7zAnju8FNKNdr4ScAOEDmMItbyOmEYw==
dependencies:
"@babel/code-frame" "^7.22.13"
"@babel/generator" "^7.23.0"
"@babel/helper-environment-visitor" "^7.22.20"
"@babel/helper-function-name" "^7.23.0"
"@babel/helper-hoist-variables" "^7.22.5"
"@babel/helper-split-export-declaration" "^7.22.6"
"@babel/parser" "^7.23.0"
"@babel/types" "^7.23.0"
debug "^4.1.0"
globals "^11.1.0"
"@babel/traverse@^7.23.2":
version "7.23.2"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.2.tgz#329c7a06735e144a506bdb2cad0268b7f46f4ad8"
@@ -534,10 +481,10 @@
minimatch "^3.1.2"
strip-json-comments "^3.1.1"
"@eslint/js@8.50.0":
version "8.50.0"
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.50.0.tgz#9e93b850f0f3fa35f5fa59adfd03adae8488e484"
integrity sha512-NCC3zz2+nvYd+Ckfh87rA47zfu2QsQpvc6k1yzTk+b9KzRj0wkGa8LSoGOXN6Zv4lRf/EIoZ80biDh9HOI+RNQ==
"@eslint/js@8.51.0":
version "8.51.0"
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.51.0.tgz#6d419c240cfb2b66da37df230f7e7eef801c32fa"
integrity sha512-HxjQ8Qn+4SI3/AFv6sOrDB+g6PpUTDwSJiQqOrnneEk8L71161srI9gjzzZvYVbzHiVg/BvcH95+cK/zfIt4pg==
"@floating-ui/core@^1.4.2":
version "1.5.0"
@@ -567,11 +514,11 @@
integrity sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==
"@humanwhocodes/config-array@^0.11.11":
version "0.11.11"
resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.11.tgz#88a04c570dbbc7dd943e4712429c3df09bc32844"
integrity sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==
version "0.11.12"
resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.12.tgz#549afec9bfce5232ac6325db12765f407e70e3a0"
integrity sha512-NlGesA1usRNn6ctHCZ21M4/dKPgW9Nn1FypRdIKKgZOKzkVV4T1FlK5mBiLhHBCDmEbdQG0idrcXlbZfksJ+RA==
dependencies:
"@humanwhocodes/object-schema" "^1.2.1"
"@humanwhocodes/object-schema" "^2.0.0"
debug "^4.1.1"
minimatch "^3.0.5"
@@ -580,10 +527,10 @@
resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c"
integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==
"@humanwhocodes/object-schema@^1.2.1":
version "1.2.1"
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45"
integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==
"@humanwhocodes/object-schema@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.0.tgz#04ad39d82176c7da1591c81e78b993cffd8348d8"
integrity sha512-9S9QrXY2K0L4AGDcSgTi9vgiCcG8VcBv4Mp7/1hDPYoswIy6Z6KO5blYto82BT8M0MZNRWmCFLpCs3HlpYGGdw==
"@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2":
version "0.3.3"
@@ -610,9 +557,9 @@
integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
"@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9":
version "0.3.19"
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz#f8a3249862f91be48d3127c3cfe992f79b4b8811"
integrity sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==
version "0.3.20"
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz#72e45707cf240fa6b081d0366f8265b0cd10197f"
integrity sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==
dependencies:
"@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14"
@@ -652,106 +599,106 @@
resolved "https://registry.yarnpkg.com/@microsoft/fetch-event-source/-/fetch-event-source-2.0.1.tgz#9ceecc94b49fbaa15666e38ae8587f64acce007d"
integrity sha512-W6CLUJ2eBMw3Rec70qrsEW0jOm/3twwJv21mrmj2yORiaVmVYGS4sSS5yUwvQc1ZlDLYGPnClVWmUUMagKNsfA==
"@mui/base@5.0.0-beta.17", "@mui/base@^5.0.0-beta.14":
version "5.0.0-beta.17"
resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-beta.17.tgz#98b7ef6a3176b7aaf59ac8862d3271acb6876bc0"
integrity sha512-xNbk7iOXrglNdIxFBN0k3ySsPIFLWCnFxqsAYl7CIcDkD9low4kJ7IUuy6ctwx/HAy2fenrT3KXHr1sGjAMgpQ==
"@mui/base@5.0.0-beta.20", "@mui/base@^5.0.0-beta.17":
version "5.0.0-beta.20"
resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-beta.20.tgz#14fcdfe0350f2aad06ab6c37c4c91dacaab8f600"
integrity sha512-CS2pUuqxST7ch9VNDCklRYDbJ3rru20Tx7na92QvVVKfu3RL4z/QLuVIc8jYGsdCnauMaeUSlFNLAJNb0yXe6w==
dependencies:
"@babel/runtime" "^7.22.15"
"@babel/runtime" "^7.23.1"
"@floating-ui/react-dom" "^2.0.2"
"@mui/types" "^7.2.4"
"@mui/utils" "^5.14.11"
"@mui/types" "^7.2.6"
"@mui/utils" "^5.14.13"
"@popperjs/core" "^2.11.8"
clsx "^2.0.0"
prop-types "^15.8.1"
"@mui/core-downloads-tracker@^5.14.11":
version "5.14.11"
resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.14.11.tgz#e829aceb5c0bbfc3383ed90a6a85445344dd65a7"
integrity sha512-uY8FLQURhXe3f3O4dS5OSGML9KDm9+IE226cBu78jarVIzdQGPlXwGIlSI9VJR8MvZDA6C0+6XfWDhWCHruC5Q==
"@mui/core-downloads-tracker@^5.14.14":
version "5.14.14"
resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.14.14.tgz#a54894e9b4dc908ab2d59eac543219d9018448e6"
integrity sha512-Rw/xKiTOUgXD8hdKqj60aC6QcGprMipG7ne2giK6Mz7b4PlhL/xog9xLeclY3BxsRLkZQ05egFnIEY1CSibTbw==
"@mui/icons-material@^5.14.11":
version "5.14.11"
resolved "https://registry.yarnpkg.com/@mui/icons-material/-/icons-material-5.14.11.tgz#ce563d1b6c7abc76f8a8048c970135601e7b49b5"
integrity sha512-aHReLasBuS/+hhPzbZCgZ0eTcZ2QRnoC2WNK7XvdAf3l+LjC1flzjh6GWw1tZJ5NHnZ+bivdwtLFQ8XTR96JkA==
version "5.14.14"
resolved "https://registry.yarnpkg.com/@mui/icons-material/-/icons-material-5.14.14.tgz#02d33f51f0b9de238d5c47b0a31ff330144393c4"
integrity sha512-vwuaMsKvI7AWTeYqR8wYbpXijuU8PzMAJWRAq2DDIuOZPxjKyHlr8WQ25+azZYkIXtJ7AqnVb1ZmHdEyB4/kug==
dependencies:
"@babel/runtime" "^7.22.15"
"@babel/runtime" "^7.23.1"
"@mui/material@^5.14.11":
version "5.14.11"
resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.14.11.tgz#7537f07c383a6cfa32a00fabc9959593478bc5c4"
integrity sha512-DnSdJzcR7lwG12JA5L2t8JF+RDzMygu5rCNW+logWb/KW2/TRzwLyVWO+CorHTBjBRd38DBxnwOCDiYkDd+N3A==
version "5.14.14"
resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.14.14.tgz#e47f3992b609002cd57a71f70e829dc2d286028c"
integrity sha512-cAmCwAHFQXxb44kWbVFkhKATN8tACgMsFwrXo8ro6WzYW73U/qsR5AcCiJIhCyYYg+gcftfkmNcpRaV3JjhHCg==
dependencies:
"@babel/runtime" "^7.22.15"
"@mui/base" "5.0.0-beta.17"
"@mui/core-downloads-tracker" "^5.14.11"
"@mui/system" "^5.14.11"
"@mui/types" "^7.2.4"
"@mui/utils" "^5.14.11"
"@types/react-transition-group" "^4.4.6"
"@babel/runtime" "^7.23.1"
"@mui/base" "5.0.0-beta.20"
"@mui/core-downloads-tracker" "^5.14.14"
"@mui/system" "^5.14.14"
"@mui/types" "^7.2.6"
"@mui/utils" "^5.14.13"
"@types/react-transition-group" "^4.4.7"
clsx "^2.0.0"
csstype "^3.1.2"
prop-types "^15.8.1"
react-is "^18.2.0"
react-transition-group "^4.4.5"
"@mui/private-theming@^5.14.11":
version "5.14.11"
resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-5.14.11.tgz#1543b4d13d5cb32018c5bd41b516db1c33f70344"
integrity sha512-MSnNNzTu9pfKLCKs1ZAKwOTgE4bz+fQA0fNr8Jm7NDmuWmw0CaN9Vq2/MHsatE7+S0A25IAKby46Uv1u53rKVQ==
"@mui/private-theming@^5.14.14":
version "5.14.14"
resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-5.14.14.tgz#035dde1eb30c896c69a12b7dee1dce3a323c66e9"
integrity sha512-n77au3CQj9uu16hak2Y+rvbGSBaJKxziG/gEbOLVGrAuqZ+ycVSkorCfN6Y/4XgYOpG/xvmuiY3JwhAEOzY3iA==
dependencies:
"@babel/runtime" "^7.22.15"
"@mui/utils" "^5.14.11"
"@babel/runtime" "^7.23.1"
"@mui/utils" "^5.14.13"
prop-types "^15.8.1"
"@mui/styled-engine@^5.14.11":
version "5.14.11"
resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-5.14.11.tgz#22cb0047f211be4dbc133a5d1015369293bdff00"
integrity sha512-jdUlqRgTYQ8RMtPX4MbRZqar6W2OiIb6J5KEFbIu4FqvPrk44Each4ppg/LAqp1qNlBYq5i+7Q10MYLMpDxX9A==
"@mui/styled-engine@^5.14.13":
version "5.14.14"
resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-5.14.14.tgz#b0ededf531fff1ef110f7b263c2d3d95a0b8ec9a"
integrity sha512-sF3DS2PVG+cFWvkVHQQaGFpL1h6gSwOW3L91pdxPLQDHDZ5mZ/X0SlXU5XA+WjypoysG4urdAQC7CH/BRvUiqg==
dependencies:
"@babel/runtime" "^7.22.15"
"@babel/runtime" "^7.23.1"
"@emotion/cache" "^11.11.0"
csstype "^3.1.2"
prop-types "^15.8.1"
"@mui/system@^5.14.11":
version "5.14.11"
resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.14.11.tgz#4f3aaf3e3d6d039e41a60f939056aa5fd371d291"
integrity sha512-yl8xV+y0k7j6dzBsHabKwoShmjqLa8kTxrhUI3JpqLG358VRVMJRW/ES0HhvfcCi4IVXde+Tc2P3K1akGL8zoA==
"@mui/system@^5.14.14":
version "5.14.14"
resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.14.14.tgz#f33327e74230523169107ace960e8bb51cbdbab7"
integrity sha512-y4InFmCgGGWXnz+iK4jRTWVikY0HgYnABjz4wgiUgEa2W1H8M4ow+27BegExUWPkj4TWthQ2qG9FOGSMtI+PKA==
dependencies:
"@babel/runtime" "^7.22.15"
"@mui/private-theming" "^5.14.11"
"@mui/styled-engine" "^5.14.11"
"@mui/types" "^7.2.4"
"@mui/utils" "^5.14.11"
"@babel/runtime" "^7.23.1"
"@mui/private-theming" "^5.14.14"
"@mui/styled-engine" "^5.14.13"
"@mui/types" "^7.2.6"
"@mui/utils" "^5.14.13"
clsx "^2.0.0"
csstype "^3.1.2"
prop-types "^15.8.1"
"@mui/types@^7.2.4":
version "7.2.4"
resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.2.4.tgz#b6fade19323b754c5c6de679a38f068fd50b9328"
integrity sha512-LBcwa8rN84bKF+f5sDyku42w1NTxaPgPyYKODsh01U1fVstTClbUoSA96oyRBnSNyEiAVjKm6Gwx9vjR+xyqHA==
"@mui/types@^7.2.6":
version "7.2.6"
resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.2.6.tgz#d72b9e9eb0032e107e76033932d65c3f731d2608"
integrity sha512-7sjLQrUmBwufm/M7jw/quNiPK/oor2+pGUQP2CULRcFCArYTq78oJ3D5esTaL0UMkXKJvDqXn6Ike69yAOBQng==
"@mui/utils@^5.14.11", "@mui/utils@^5.14.8":
version "5.14.11"
resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.14.11.tgz#d19a1d8725ffd16c6c6817f00b5172931958fb9a"
integrity sha512-fmkIiCPKyDssYrJ5qk+dime1nlO3dmWfCtaPY/uVBqCRMBZ11JhddB9m8sjI2mgqQQwRJG5bq3biaosNdU/s4Q==
"@mui/utils@^5.14.11", "@mui/utils@^5.14.13":
version "5.14.14"
resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.14.14.tgz#7b2a0bcfb44c3376fc81f85500f9bd01706682ac"
integrity sha512-3AKp8uksje5sRfVrtgG9Q/2TBsHWVBUtA0NaXliZqGcXo8J+A+Agp0qUW2rJ+ivgPWTCCubz9FZVT2IQZ3bGsw==
dependencies:
"@babel/runtime" "^7.22.15"
"@types/prop-types" "^15.7.5"
"@babel/runtime" "^7.23.1"
"@types/prop-types" "^15.7.7"
prop-types "^15.8.1"
react-is "^18.2.0"
"@mui/x-date-pickers@^6.16.0":
version "6.16.0"
resolved "https://registry.yarnpkg.com/@mui/x-date-pickers/-/x-date-pickers-6.16.0.tgz#944440fabd9cdc5c704ccab7b8c44ebac75f15db"
integrity sha512-5xN/OuVtcy/bX/Yx4cJrGWLmHTMTZkufhQMtInFOM8u93TJJSPwA7X86vQdl6OztYNyYKnEpxNKxSrxcDHfKFQ==
version "6.16.2"
resolved "https://registry.yarnpkg.com/@mui/x-date-pickers/-/x-date-pickers-6.16.2.tgz#a21e9891d797287bd8aebb822eb26e70a81923f8"
integrity sha512-JFrDUeBkiKtfJ0WqwyPBICEP1U+Ujfsily3ZQ/Hv4zAOleG/5769EgS7TOO4cVgnuhtvQ/pqx2gmuCn8/gcC5w==
dependencies:
"@babel/runtime" "^7.22.15"
"@mui/base" "^5.0.0-beta.14"
"@mui/utils" "^5.14.8"
"@types/react-transition-group" "^4.4.6"
"@babel/runtime" "^7.23.1"
"@mui/base" "^5.0.0-beta.17"
"@mui/utils" "^5.14.11"
"@types/react-transition-group" "^4.4.7"
clsx "^2.0.0"
prop-types "^15.8.1"
react-transition-group "^4.4.5"
@@ -1017,9 +964,9 @@
svg-parser "^2.0.4"
"@types/babel__core@^7.20.2":
version "7.20.2"
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.2.tgz#215db4f4a35d710256579784a548907237728756"
integrity sha512-pNpr1T1xLUc2l3xJKuPtsEky3ybxN3m4fJkknfIpTCTfIZCDW57oAg+EfCgIIp2rvCe0Wn++/FfodDS4YXxBwA==
version "7.20.3"
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.3.tgz#d5625a50b6f18244425a1359a858c73d70340778"
integrity sha512-54fjTSeSHwfan8AyHWrKbfBWiEUrNTZsUwPTDSNaaP1QDQIZbeNUg3a59E9D+375MzUw/x1vx2/0F5LBz+AeYA==
dependencies:
"@babel/parser" "^7.20.7"
"@babel/types" "^7.20.7"
@@ -1028,90 +975,95 @@
"@types/babel__traverse" "*"
"@types/babel__generator@*":
version "7.6.5"
resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.5.tgz#281f4764bcbbbc51fdded0f25aa587b4ce14da95"
integrity sha512-h9yIuWbJKdOPLJTbmSpPzkF67e659PbQDba7ifWm5BJ8xTv+sDmS7rFmywkWOvXedGTivCdeGSIIX8WLcRTz8w==
version "7.6.6"
resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.6.tgz#676f89f67dc8ddaae923f70ebc5f1fa800c031a8"
integrity sha512-66BXMKb/sUWbMdBNdMvajU7i/44RkrA3z/Yt1c7R5xejt8qh84iU54yUWCtm0QwGJlDcf/gg4zd/x4mpLAlb/w==
dependencies:
"@babel/types" "^7.0.0"
"@types/babel__template@*":
version "7.4.2"
resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.2.tgz#843e9f1f47c957553b0c374481dc4772921d6a6b"
integrity sha512-/AVzPICMhMOMYoSx9MoKpGDKdBRsIXMNByh1PXSZoa+v6ZoLa8xxtsT/uLQ/NJm0XVAWl/BvId4MlDeXJaeIZQ==
version "7.4.3"
resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.3.tgz#db9ac539a2fe05cfe9e168b24f360701bde41f5f"
integrity sha512-ciwyCLeuRfxboZ4isgdNZi/tkt06m8Tw6uGbBSBgWrnnZGNXiEyM27xc/PjXGQLqlZ6ylbgHMnm7ccF9tCkOeQ==
dependencies:
"@babel/parser" "^7.1.0"
"@babel/types" "^7.0.0"
"@types/babel__traverse@*":
version "7.20.2"
resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.2.tgz#4ddf99d95cfdd946ff35d2b65c978d9c9bf2645d"
integrity sha512-ojlGK1Hsfce93J0+kn3H5R73elidKUaZonirN33GSmgTUMpzI/MIFfSpF3haANe3G1bEBS9/9/QEqwTzwqFsKw==
version "7.20.3"
resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.3.tgz#a971aa47441b28ef17884ff945d0551265a2d058"
integrity sha512-Lsh766rGEFbaxMIDH7Qa+Yha8cMVI3qAK6CHt3OR0YfxOIn5Z54iHiyDRycHrBqeIiqGa20Kpsv1cavfBKkRSw==
dependencies:
"@babel/types" "^7.20.7"
"@types/estree@^1.0.0":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.2.tgz#ff02bc3dc8317cd668dfec247b750ba1f1d62453"
integrity sha512-VeiPZ9MMwXjO32/Xu7+OwflfmeoRwkE/qzndw42gGtgJwZopBnzy2gD//NN1+go1mADzkDcqf/KnFRSjTJ8xJA==
version "1.0.3"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.3.tgz#2be19e759a3dd18c79f9f436bd7363556c1a73dd"
integrity sha512-CS2rOaoQ/eAgAfcTfq6amKG7bsN+EMcgGY4FAFQdvSj2y1ixvOZTUA9mOtCai7E1SYu283XNw7urKK30nP3wkQ==
"@types/json-schema@^7.0.12", "@types/json-schema@^7.0.3":
version "7.0.13"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.13.tgz#02c24f4363176d2d18fc8b70b9f3c54aba178a85"
integrity sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ==
version "7.0.14"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.14.tgz#74a97a5573980802f32c8e47b663530ab3b6b7d1"
integrity sha512-U3PUjAudAdJBeC2pgN8uTIKgxrb4nlDF3SF0++EldXQvQBGkpFZMSnwQiIoDU77tv45VgNkl/L4ouD+rEomujw==
"@types/lodash@^4.14.200":
version "4.14.200"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.200.tgz#435b6035c7eba9cdf1e039af8212c9e9281e7149"
integrity sha512-YI/M/4HRImtNf3pJgbF+W6FrXovqj+T+/HpENLTooK9PnkacBsDpeP3IpHab40CClUfhNmdM2WTNP2sa2dni5Q==
"@types/parse-json@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==
version "4.0.1"
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.1.tgz#27f7559836ad796cea31acb63163b203756a5b4e"
integrity sha512-3YmXzzPAdOTVljVMkTMBdBEvlOLg2cDQaDhnnhT3nT9uDbnJzjWhKlzb+desT12Y7tGqaN6d+AbozcKzyL36Ng==
"@types/prop-types@*", "@types/prop-types@^15.7.5":
version "15.7.8"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.8.tgz#805eae6e8f41bd19e88917d2ea200dc992f405d3"
integrity sha512-kMpQpfZKSCBqltAJwskgePRaYRFukDkm1oItcAbC3gNELR20XIBcN9VRgg4+m8DKsTfkWeA4m4Imp4DDuWy7FQ==
"@types/prop-types@*", "@types/prop-types@^15.7.7":
version "15.7.9"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.9.tgz#b6f785caa7ea1fe4414d9df42ee0ab67f23d8a6d"
integrity sha512-n1yyPsugYNSmHgxDFjicaI2+gCNjsBck8UX9kuofAKlc0h1bL+20oSF72KeNaW2DUlesbEVCFgyV2dPGTiY42g==
"@types/react-dom@^18.2.7":
version "18.2.8"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.8.tgz#338f1b0a646c9f10e0a97208c1d26b9f473dffd6"
integrity sha512-bAIvO5lN/U8sPGvs1Xm61rlRHHaq5rp5N3kp9C+NJ/Q41P8iqjkXSu0+/qu8POsjH9pNWb0OYabFez7taP7omw==
version "18.2.14"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.14.tgz#c01ba40e5bb57fc1dc41569bb3ccdb19eab1c539"
integrity sha512-V835xgdSVmyQmI1KLV2BEIUgqEuinxp9O4G6g3FqO/SqLac049E53aysv0oEFD2kHfejeKU+ZqL2bcFWj9gLAQ==
dependencies:
"@types/react" "*"
"@types/react-transition-group@^4.4.6":
version "4.4.7"
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.7.tgz#bf69f269d74aa78b99097673ca6dd6824a68ef1c"
integrity sha512-ICCyBl5mvyqYp8Qeq9B5G/fyBSRC0zx3XM3sCC6KkcMsNeAHqXBKkmat4GqdJET5jtYUpZXrxI5flve5qhi2Eg==
"@types/react-transition-group@^4.4.7":
version "4.4.8"
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.8.tgz#46f87d80512959cac793ecc610a93d80ef241ccf"
integrity sha512-QmQ22q+Pb+HQSn04NL3HtrqHwYMf4h3QKArOy5F8U5nEVMaihBs3SR10WiOM1iwPz5jIo8x/u11al+iEGZZrvg==
dependencies:
"@types/react" "*"
"@types/react@*", "@types/react@^18.2.15":
version "18.2.24"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.24.tgz#3c7d68c02e0205a472f04abe4a0c1df35d995c05"
integrity sha512-Ee0Jt4sbJxMu1iDcetZEIKQr99J1Zfb6D4F3qfUWoR1JpInkY1Wdg4WwCyBjL257D0+jGqSl1twBjV8iCaC0Aw==
version "18.2.29"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.29.tgz#88b48a287e00f6fdcd6f95662878fb701ae18b27"
integrity sha512-Z+ZrIRocWtdD70j45izShRwDuiB4JZqDegqMFW/I8aG5DxxLKOzVNoq62UIO82v9bdgi+DO1jvsb9sTEZUSm+Q==
dependencies:
"@types/prop-types" "*"
"@types/scheduler" "*"
csstype "^3.0.2"
"@types/scheduler@*":
version "0.16.4"
resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.4.tgz#fedc3e5b15c26dc18faae96bf1317487cb3658cf"
integrity sha512-2L9ifAGl7wmXwP4v3pN4p2FLhD0O1qsJpvKmNin5VA8+UvNVb447UDaAEV6UdrkA+m/Xs58U1RFps44x6TFsVQ==
version "0.16.5"
resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.5.tgz#4751153abbf8d6199babb345a52e1eb4167d64af"
integrity sha512-s/FPdYRmZR8SjLWGMCuax7r3qCWQw9QKHzXVukAuuIJkXkDRwp+Pu5LMIVFi0Fxbav35WURicYr8u1QsoybnQw==
"@types/semver@^7.5.0":
version "7.5.3"
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.3.tgz#9a726e116beb26c24f1ccd6850201e1246122e04"
integrity sha512-OxepLK9EuNEIPxWNME+C6WwbRAOOI2o2BaQEGzz5Lu2e4Z5eDnEo+/aVEDMIXywoJitJ7xWd641wrGLZdtwRyw==
version "7.5.4"
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.4.tgz#0a41252ad431c473158b22f9bfb9a63df7541cff"
integrity sha512-MMzuxN3GdFwskAnb6fz0orFvhfqi752yjaXylr0Rp4oDg5H0Zn1IuyRhDVvYOwAXoJirx2xuS16I3WjxnAIHiQ==
"@typescript-eslint/eslint-plugin@^6.0.0":
version "6.7.4"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.7.4.tgz#057338df21b6062c2f2fc5999fbea8af9973ac6d"
integrity sha512-DAbgDXwtX+pDkAHwiGhqP3zWUGpW49B7eqmgpPtg+BKJXwdct79ut9+ifqOFPJGClGKSHXn2PTBatCnldJRUoA==
version "6.8.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.8.0.tgz#06abe4265e7c82f20ade2dcc0e3403c32d4f148b"
integrity sha512-GosF4238Tkes2SHPQ1i8f6rMtG6zlKwMEB0abqSJ3Npvos+doIlc/ATG+vX1G9coDF3Ex78zM3heXHLyWEwLUw==
dependencies:
"@eslint-community/regexpp" "^4.5.1"
"@typescript-eslint/scope-manager" "6.7.4"
"@typescript-eslint/type-utils" "6.7.4"
"@typescript-eslint/utils" "6.7.4"
"@typescript-eslint/visitor-keys" "6.7.4"
"@typescript-eslint/scope-manager" "6.8.0"
"@typescript-eslint/type-utils" "6.8.0"
"@typescript-eslint/utils" "6.8.0"
"@typescript-eslint/visitor-keys" "6.8.0"
debug "^4.3.4"
graphemer "^1.4.0"
ignore "^5.2.4"
@@ -1120,71 +1072,71 @@
ts-api-utils "^1.0.1"
"@typescript-eslint/parser@^6.0.0":
version "6.7.4"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.7.4.tgz#23d1dd4fe5d295c7fa2ab651f5406cd9ad0bd435"
integrity sha512-I5zVZFY+cw4IMZUeNCU7Sh2PO5O57F7Lr0uyhgCJmhN/BuTlnc55KxPonR4+EM3GBdfiCyGZye6DgMjtubQkmA==
version "6.8.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.8.0.tgz#bb2a969d583db242f1ee64467542f8b05c2e28cb"
integrity sha512-5tNs6Bw0j6BdWuP8Fx+VH4G9fEPDxnVI7yH1IAPkQH5RUtvKwRoqdecAPdQXv4rSOADAaz1LFBZvZG7VbXivSg==
dependencies:
"@typescript-eslint/scope-manager" "6.7.4"
"@typescript-eslint/types" "6.7.4"
"@typescript-eslint/typescript-estree" "6.7.4"
"@typescript-eslint/visitor-keys" "6.7.4"
"@typescript-eslint/scope-manager" "6.8.0"
"@typescript-eslint/types" "6.8.0"
"@typescript-eslint/typescript-estree" "6.8.0"
"@typescript-eslint/visitor-keys" "6.8.0"
debug "^4.3.4"
"@typescript-eslint/scope-manager@6.7.4":
version "6.7.4"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.7.4.tgz#a484a17aa219e96044db40813429eb7214d7b386"
integrity sha512-SdGqSLUPTXAXi7c3Ob7peAGVnmMoGzZ361VswK2Mqf8UOYcODiYvs8rs5ILqEdfvX1lE7wEZbLyELCW+Yrql1A==
"@typescript-eslint/scope-manager@6.8.0":
version "6.8.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.8.0.tgz#5cac7977385cde068ab30686889dd59879811efd"
integrity sha512-xe0HNBVwCph7rak+ZHcFD6A+q50SMsFwcmfdjs9Kz4qDh5hWhaPhFjRs/SODEhroBI5Ruyvyz9LfwUJ624O40g==
dependencies:
"@typescript-eslint/types" "6.7.4"
"@typescript-eslint/visitor-keys" "6.7.4"
"@typescript-eslint/types" "6.8.0"
"@typescript-eslint/visitor-keys" "6.8.0"
"@typescript-eslint/type-utils@6.7.4":
version "6.7.4"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.7.4.tgz#847cd3b59baf948984499be3e0a12ff07373e321"
integrity sha512-n+g3zi1QzpcAdHFP9KQF+rEFxMb2KxtnJGID3teA/nxKHOVi3ylKovaqEzGBbVY2pBttU6z85gp0D00ufLzViQ==
"@typescript-eslint/type-utils@6.8.0":
version "6.8.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.8.0.tgz#50365e44918ca0fd159844b5d6ea96789731e11f"
integrity sha512-RYOJdlkTJIXW7GSldUIHqc/Hkto8E+fZN96dMIFhuTJcQwdRoGN2rEWA8U6oXbLo0qufH7NPElUb+MceHtz54g==
dependencies:
"@typescript-eslint/typescript-estree" "6.7.4"
"@typescript-eslint/utils" "6.7.4"
"@typescript-eslint/typescript-estree" "6.8.0"
"@typescript-eslint/utils" "6.8.0"
debug "^4.3.4"
ts-api-utils "^1.0.1"
"@typescript-eslint/types@6.7.4":
version "6.7.4"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.7.4.tgz#5d358484d2be986980c039de68e9f1eb62ea7897"
integrity sha512-o9XWK2FLW6eSS/0r/tgjAGsYasLAnOWg7hvZ/dGYSSNjCh+49k5ocPN8OmG5aZcSJ8pclSOyVKP2x03Sj+RrCA==
"@typescript-eslint/types@6.8.0":
version "6.8.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.8.0.tgz#1ab5d4fe1d613e3f65f6684026ade6b94f7e3ded"
integrity sha512-p5qOxSum7W3k+llc7owEStXlGmSl8FcGvhYt8Vjy7FqEnmkCVlM3P57XQEGj58oqaBWDQXbJDZxwUWMS/EAPNQ==
"@typescript-eslint/typescript-estree@6.7.4":
version "6.7.4"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.7.4.tgz#f2baece09f7bb1df9296e32638b2e1130014ef1a"
integrity sha512-ty8b5qHKatlNYd9vmpHooQz3Vki3gG+3PchmtsA4TgrZBKWHNjWfkQid7K7xQogBqqc7/BhGazxMD5vr6Ha+iQ==
"@typescript-eslint/typescript-estree@6.8.0":
version "6.8.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.8.0.tgz#9565f15e0cd12f55cf5aa0dfb130a6cb0d436ba1"
integrity sha512-ISgV0lQ8XgW+mvv5My/+iTUdRmGspducmQcDw5JxznasXNnZn3SKNrTRuMsEXv+V/O+Lw9AGcQCfVaOPCAk/Zg==
dependencies:
"@typescript-eslint/types" "6.7.4"
"@typescript-eslint/visitor-keys" "6.7.4"
"@typescript-eslint/types" "6.8.0"
"@typescript-eslint/visitor-keys" "6.8.0"
debug "^4.3.4"
globby "^11.1.0"
is-glob "^4.0.3"
semver "^7.5.4"
ts-api-utils "^1.0.1"
"@typescript-eslint/utils@6.7.4":
version "6.7.4"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.7.4.tgz#2236f72b10e38277ee05ef06142522e1de470ff2"
integrity sha512-PRQAs+HUn85Qdk+khAxsVV+oULy3VkbH3hQ8hxLRJXWBEd7iI+GbQxH5SEUSH7kbEoTp6oT1bOwyga24ELALTA==
"@typescript-eslint/utils@6.8.0":
version "6.8.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.8.0.tgz#d42939c2074c6b59844d0982ce26a51d136c4029"
integrity sha512-dKs1itdE2qFG4jr0dlYLQVppqTE+Itt7GmIf/vX6CSvsW+3ov8PbWauVKyyfNngokhIO9sKZeRGCUo1+N7U98Q==
dependencies:
"@eslint-community/eslint-utils" "^4.4.0"
"@types/json-schema" "^7.0.12"
"@types/semver" "^7.5.0"
"@typescript-eslint/scope-manager" "6.7.4"
"@typescript-eslint/types" "6.7.4"
"@typescript-eslint/typescript-estree" "6.7.4"
"@typescript-eslint/scope-manager" "6.8.0"
"@typescript-eslint/types" "6.8.0"
"@typescript-eslint/typescript-estree" "6.8.0"
semver "^7.5.4"
"@typescript-eslint/visitor-keys@6.7.4":
version "6.7.4"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.7.4.tgz#80dfecf820fc67574012375859085f91a4dff043"
integrity sha512-pOW37DUhlTZbvph50x5zZCkFn3xzwkGtNoJHzIM3svpiSkJzwOYr/kVBaXmf+RAQiUDs1AHEZVNPg6UJCJpwRA==
"@typescript-eslint/visitor-keys@6.8.0":
version "6.8.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.8.0.tgz#cffebed56ae99c45eba901c378a6447b06be58b8"
integrity sha512-oqAnbA7c+pgOhW2OhGvxm0t1BULX5peQI/rLsNDpGM78EebV3C9IGbX5HNZabuZ6UQrYveCLjKo8Iy/lLlBkkg==
dependencies:
"@typescript-eslint/types" "6.7.4"
"@typescript-eslint/types" "6.8.0"
eslint-visitor-keys "^3.4.1"
"@vitejs/plugin-react@^4.0.3":
@@ -1367,15 +1319,10 @@ camelcase@^6.2.0:
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a"
integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==
caniuse-lite@^1.0.30001538:
version "1.0.30001549"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001549.tgz#7d1a3dce7ea78c06ed72c32c2743ea364b3615aa"
integrity sha512-qRp48dPYSCYaP+KurZLhDYdVE+yEyht/3NlmcJgVQ2VMGt6JL36ndQ/7rgspdZsJuxDPFIo/OzBT2+GmIJ53BA==
caniuse-lite@^1.0.30001541:
version "1.0.30001543"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001543.tgz#478a3e9dddbb353c5ab214b0ecb0dbed529ed1d8"
integrity sha512-qxdO8KPWPQ+Zk6bvNpPeQIOH47qZSYdFZd6dXQzb2KzhnSXju4Kd7H1PkSJx6NICSMgo/IhRZRhhfPTHYpJUCA==
caniuse-lite@^1.0.30001538, caniuse-lite@^1.0.30001541:
version "1.0.30001551"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001551.tgz#1f2cfa8820bd97c971a57349d7fd8f6e08664a3e"
integrity sha512-vtBAez47BoGMMzlbYhfXrMV1kvRF2WP/lqiMuDu1Sb4EE4LKEgjopFDSRtZfdVnslNRpOqV/woE+Xgrwj6VQlg==
chalk@^2.4.2:
version "2.4.2"
@@ -1566,9 +1513,9 @@ dot-case@^3.0.4:
tslib "^2.0.3"
electron-to-chromium@^1.4.535:
version "1.4.539"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.539.tgz#5ce6b161e252132cc84501bc35d084995a2a9840"
integrity sha512-wRmWJ8F7rgmINuI32S6r2SLrw/h/bJQsDSvBiq9GBfvc2Lh73qTOwn73r3Cf67mjVgFGJYcYtmERzySa5jIWlg==
version "1.4.559"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.559.tgz#050483c22c5eb2345017a8976a67b060559a33f4"
integrity sha512-iS7KhLYCSJbdo3rUSkhDTVuFNCV34RKs2UaB9Ecr7VlqzjjWW//0nfsFF5dtDmyXlZQaDYYtID5fjtC/6lpRug==
entities@^4.4.0:
version "4.5.0"
@@ -1649,14 +1596,14 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4
integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==
eslint@^8.45.0:
version "8.50.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.50.0.tgz#2ae6015fee0240fcd3f83e1e25df0287f487d6b2"
integrity sha512-FOnOGSuFuFLv/Sa+FDVRZl4GGVAAFFi8LecRsI5a1tMO5HIE8nCm4ivAlzt4dT3ol/PaaGC0rJEEXQmHJBGoOg==
version "8.51.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.51.0.tgz#4a82dae60d209ac89a5cff1604fea978ba4950f3"
integrity sha512-2WuxRZBrlwnXi+/vFSJyjMqrNjtJqiasMzehF0shoLaW7DzS3/9Yvrmq5JiT66+pNjiX4UBnLDiKHcWAr/OInA==
dependencies:
"@eslint-community/eslint-utils" "^4.2.0"
"@eslint-community/regexpp" "^4.6.1"
"@eslint/eslintrc" "^2.1.2"
"@eslint/js" "8.50.0"
"@eslint/js" "8.51.0"
"@humanwhocodes/config-array" "^0.11.11"
"@humanwhocodes/module-importer" "^1.0.1"
"@nodelib/fs.walk" "^1.2.8"
@@ -1795,15 +1742,15 @@ find-up@^5.0.0:
path-exists "^4.0.0"
flat-cache@^3.0.4:
version "3.1.0"
resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.1.0.tgz#0e54ab4a1a60fe87e2946b6b00657f1c99e1af3f"
integrity sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew==
version "3.1.1"
resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.1.1.tgz#a02a15fdec25a8f844ff7cc658f03dd99eb4609b"
integrity sha512-/qM2b3LUIaIgviBQovTLvijfyOQXPtSRnRK26ksj2J7rzPIecePUIpJsZ4T02Qg+xiAEKIs5K8dsHEd+VaKa/Q==
dependencies:
flatted "^3.2.7"
flatted "^3.2.9"
keyv "^4.5.3"
rimraf "^3.0.2"
flatted@^3.2.7:
flatted@^3.2.9:
version "3.2.9"
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.9.tgz#7eb4c67ca1ba34232ca9d2d93e9886e611ad7daf"
integrity sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==
@@ -1823,11 +1770,6 @@ fsevents@~2.3.2:
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
function-bind@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
gensync@^1.0.0-beta.2:
version "1.0.0-beta.2"
resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
@@ -1882,9 +1824,9 @@ globals@^11.1.0:
integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
globals@^13.19.0:
version "13.22.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-13.22.0.tgz#0c9fcb9c48a2494fbb5edbfee644285543eba9d8"
integrity sha512-H1Ddc/PbZHTDVJSnj8kWptIRSD6AM3pK+mKytuIVF4uoBV7rshFlhhvA58ceJ5wp3Er58w6zj7bykMpYXt3ETw==
version "13.23.0"
resolved "https://registry.yarnpkg.com/globals/-/globals-13.23.0.tgz#ef31673c926a0976e1f61dab4dca57e0c0a8af02"
integrity sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==
dependencies:
type-fest "^0.20.2"
@@ -1916,11 +1858,9 @@ has-flag@^4.0.0:
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
has@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
dependencies:
function-bind "^1.1.1"
version "1.0.4"
resolved "https://registry.yarnpkg.com/has/-/has-1.0.4.tgz#2eb2860e000011dae4f1406a86fe80e530fb2ec6"
integrity sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==
hoist-non-react-statics@^3.3.1:
version "3.3.2"
@@ -2073,9 +2013,9 @@ json5@^2.2.3:
integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
keyv@^4.5.3:
version "4.5.3"
resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.3.tgz#00873d2b046df737963157bd04f294ca818c9c25"
integrity sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==
version "4.5.4"
resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93"
integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==
dependencies:
json-buffer "3.0.1"
@@ -2109,7 +2049,7 @@ lodash.merge@^4.6.2:
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
lodash@^4.17.15:
lodash@^4.17.15, lodash@^4.17.21:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@@ -2493,7 +2433,7 @@ resolve-from@^4.0.0:
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
resolve@^1.1.7, resolve@^1.22.2:
resolve@^1.1.7, resolve@^1.19.0, resolve@^1.22.2:
version "1.22.8"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d"
integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==
@@ -2502,15 +2442,6 @@ resolve@^1.1.7, resolve@^1.22.2:
path-parse "^1.0.7"
supports-preserve-symlinks-flag "^1.0.0"
resolve@^1.19.0:
version "1.22.6"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.6.tgz#dd209739eca3aef739c626fea1b4f3c506195362"
integrity sha512-njhxM7mV12JfufShqGy3Rz8j11RPdLy4xi15UurGJeoHLfJpVXKdh3ueuOqbYUcDZnffr6X739JBo5LzyahEsw==
dependencies:
is-core-module "^2.13.0"
path-parse "^1.0.7"
supports-preserve-symlinks-flag "^1.0.0"
reusify@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
@@ -2650,6 +2581,11 @@ svg-parser@^2.0.4:
resolved "https://registry.yarnpkg.com/svg-parser/-/svg-parser-2.0.4.tgz#fdc2e29e13951736140b76cb122c8ee6630eb6b5"
integrity sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==
tailwind-merge@^1.14.0:
version "1.14.0"
resolved "https://registry.yarnpkg.com/tailwind-merge/-/tailwind-merge-1.14.0.tgz#e677f55d864edc6794562c63f5001f45093cdb8b"
integrity sha512-3mFKyCo/MBcgyOTlrY8T7odzZFx+w+qKSMAmdFzRvqBfLlSigU6TZnlFHK0lkMwj9Bj8OYU+9yW9lmGuS0QEnQ==
tailwindcss@^3.3.3:
version "3.3.3"
resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.3.3.tgz#90da807393a2859189e48e9e7000e6880a736daf"
@@ -2763,6 +2699,11 @@ use-callback-ref@^1.3.0:
dependencies:
tslib "^2.0.0"
use-debounce@^9.0.4:
version "9.0.4"
resolved "https://registry.yarnpkg.com/use-debounce/-/use-debounce-9.0.4.tgz#51d25d856fbdfeb537553972ce3943b897f1ac85"
integrity sha512-6X8H/mikbrt0XE8e+JXRtZ8yYVvKkdYRfmIhWZYsP8rcNs9hk3APV8Ua2mFkKRLcJKVdnX2/Vwrmg2GWKUQEaQ==
use-sidecar@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.1.2.tgz#2f43126ba2d7d7e117aa5855e5d8f0276dfe73c2"
@@ -2777,9 +2718,9 @@ util-deprecate@^1.0.2:
integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
vaul@^0.7.3:
version "0.7.3"
resolved "https://registry.yarnpkg.com/vaul/-/vaul-0.7.3.tgz#13808352c7c56b1f908e258fe8caf15dafdca3ae"
integrity sha512-B0d2Unc1qiTrUCt4kLMon7NtPyhiWxzXemDH42XD8n57mttN2PMgGQmThk0NJAJeVRx3UXKApuNPCVU2+N2P8g==
version "0.7.5"
resolved "https://registry.yarnpkg.com/vaul/-/vaul-0.7.5.tgz#7e53d3dccfb6d965f44b18de9296cb86133df3f5"
integrity sha512-tfihsrhkVNfIrSioiw66CqiqFAjBQ26mlbbPuypfHQV0zvQetpXgpSDLVqIz9GPi92iwWFbh1jNA/AmVmdy9GQ==
dependencies:
"@radix-ui/react-dialog" "^1.0.4"
@@ -2793,9 +2734,9 @@ vite-plugin-svgr@^4.1.0:
"@svgr/plugin-jsx" "^8.1.0"
vite@^4.4.5:
version "4.4.9"
resolved "https://registry.yarnpkg.com/vite/-/vite-4.4.9.tgz#1402423f1a2f8d66fd8d15e351127c7236d29d3d"
integrity sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==
version "4.5.0"
resolved "https://registry.yarnpkg.com/vite/-/vite-4.5.0.tgz#ec406295b4167ac3bc23e26f9c8ff559287cff26"
integrity sha512-ulr8rNLA6rkyFAlVWw2q5YJ91v098AFQ2R0PRFwPzREXOUJQPtFUG0t+/ZikhaOCDqFoDhN6/v8Sq0o4araFAw==
dependencies:
esbuild "^0.18.10"
postcss "^8.4.27"
+73 -36
View File
@@ -21,7 +21,7 @@ from typing import (
)
from fastapi import HTTPException, Request
from langchain.callbacks.tracers.log_stream import RunLog, RunLogPatch
from langchain.callbacks.tracers.log_stream import RunLogPatch
from langchain.load.serializable import Serializable
from langchain.schema.runnable import Runnable
from langchain.schema.runnable.config import merge_configs
@@ -382,6 +382,16 @@ def add_routes(
}
}
* error - for signaling an error in the stream, also ends the stream.
{
"event": "error",
"data": {
"status_code": 500,
"message": "Internal Server Error"
}
}
* end - for signaling the end of the stream.
This helps the client to know when to stop listening for events and
@@ -402,12 +412,24 @@ def add_routes(
async def _stream() -> AsyncIterator[dict]:
"""Stream the output of the runnable."""
async for chunk in runnable.astream(
input_,
config=config,
):
yield {"data": simple_dumps(chunk), "event": "data"}
yield {"event": "end"}
try:
async for chunk in runnable.astream(
input_,
config=config,
):
yield {"data": simple_dumps(chunk), "event": "data"}
yield {"event": "end"}
except BaseException:
yield {
"event": "error",
# Do not expose the error message to the client since
# the message may contain sensitive information.
# We'll add client side errors for validation as well.
"data": json.dumps(
{"status_code": 500, "message": "Internal Server Error"}
),
}
raise
return EventSourceResponse(_stream())
@@ -441,6 +463,16 @@ def add_routes(
}
}
* error - for signaling an error in the stream, also ends the stream.
{
"event": "error",
"data": {
"status_code": 500,
"message": "Internal Server Error"
}
}
* end - for signaling the end of the stream.
This helps the client to know when to stop listening for events and
@@ -464,18 +496,18 @@ def add_routes(
async def _stream_log() -> AsyncIterator[dict]:
"""Stream the output of the runnable."""
async for chunk in runnable.astream_log(
input_,
config=config,
diff=stream_log_request.diff,
include_names=stream_log_request.include_names,
include_types=stream_log_request.include_types,
include_tags=stream_log_request.include_tags,
exclude_names=stream_log_request.exclude_names,
exclude_types=stream_log_request.exclude_types,
exclude_tags=stream_log_request.exclude_tags,
):
if stream_log_request.diff: # Run log patch
try:
async for chunk in runnable.astream_log(
input_,
config=config,
diff=True,
include_names=stream_log_request.include_names,
include_types=stream_log_request.include_types,
include_tags=stream_log_request.include_tags,
exclude_names=stream_log_request.exclude_names,
exclude_types=stream_log_request.exclude_types,
exclude_tags=stream_log_request.exclude_tags,
):
if not isinstance(chunk, RunLogPatch):
raise AssertionError(
f"Expected a RunLog instance got {type(chunk)}"
@@ -483,23 +515,24 @@ def add_routes(
data = {
"ops": chunk.ops,
}
else:
# Then it's a run log
if not isinstance(chunk, RunLog):
raise AssertionError(
f"Expected a RunLog instance got {type(chunk)}"
)
data = {
"state": chunk.state,
"ops": chunk.ops,
}
# Temporary adapter
# Temporary adapter
yield {
"data": simple_dumps(data),
"event": "data",
}
yield {"event": "end"}
except BaseException:
yield {
"data": simple_dumps(data),
"event": "data",
"event": "error",
# Do not expose the error message to the client since
# the message may contain sensitive information.
# We'll add client side errors for validation as well.
"data": json.dumps(
{"status_code": 500, "message": "Internal Server Error"}
),
}
yield {"event": "end"}
raise
return EventSourceResponse(_stream_log())
@@ -519,9 +552,13 @@ def add_routes(
@app.get(f"{namespace}/output_schema")
async def output_schema(config_hash: str = "") -> Any:
"""Return the output schema of the runnable."""
return runnable.with_config(
_unpack_config(config_hash, keys=config_keys, model=ConfigPayload)
).output_schema.schema()
return (
runnable.with_config(
_unpack_config(config_hash, keys=config_keys, model=ConfigPayload)
).output_schema.schema()
if output_type_ == "auto"
else output_type_.schema()
)
@app.get(namespace + "/c/{config_hash}/config_schema", tags=["config"])
@app.get(f"{namespace}/config_schema")
-1
View File
@@ -136,7 +136,6 @@ def create_stream_log_request_model(
f"{namespace}StreamLogRequest",
input=(input_type, ...),
config=(config, Field(default_factory=dict)),
diff=(Optional[bool], False),
include_names=(
Optional[Sequence[str]],
Field(
Generated
+19 -19
View File
@@ -827,20 +827,20 @@ tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipyth
[[package]]
name = "fastapi"
version = "0.103.2"
version = "0.104.0"
description = "FastAPI framework, high performance, easy to learn, fast to code, ready for production"
optional = false
python-versions = ">=3.7"
python-versions = ">=3.8"
files = [
{file = "fastapi-0.103.2-py3-none-any.whl", hash = "sha256:3270de872f0fe9ec809d4bd3d4d890c6d5cc7b9611d721d6438f9dacc8c4ef2e"},
{file = "fastapi-0.103.2.tar.gz", hash = "sha256:75a11f6bfb8fc4d2bec0bd710c2d5f2829659c0e8c0afd5560fdda6ce25ec653"},
{file = "fastapi-0.104.0-py3-none-any.whl", hash = "sha256:456482c1178fb7beb2814b88e1885bc49f9a81f079665016feffe3e1c6a7663e"},
{file = "fastapi-0.104.0.tar.gz", hash = "sha256:9c44de45693ae037b0c6914727a29c49a40668432b67c859a87851fc6a7b74c6"},
]
[package.dependencies]
anyio = ">=3.7.1,<4.0.0"
pydantic = ">=1.7.4,<1.8 || >1.8,<1.8.1 || >1.8.1,<2.0.0 || >2.0.0,<2.0.1 || >2.0.1,<2.1.0 || >2.1.0,<3.0.0"
starlette = ">=0.27.0,<0.28.0"
typing-extensions = ">=4.5.0"
typing-extensions = ">=4.8.0"
[package.extras]
all = ["email-validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.5)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"]
@@ -1654,13 +1654,13 @@ test = ["hatch", "ipykernel", "openapi-core (>=0.18.0,<0.19.0)", "openapi-spec-v
[[package]]
name = "langchain"
version = "0.0.316"
version = "0.0.319"
description = "Building applications with LLMs through composability"
optional = false
python-versions = ">=3.8.1,<4.0"
files = [
{file = "langchain-0.0.316-py3-none-any.whl", hash = "sha256:997d2ecdaf481517c90dcea86f93b98327546c357ac8f581f06d7d782a2eda2d"},
{file = "langchain-0.0.316.tar.gz", hash = "sha256:732c958d41e22a5bb55f8c0cb70914793f083d175739d476e5cbbc7715703153"},
{file = "langchain-0.0.319-py3-none-any.whl", hash = "sha256:a61448fd418ff9478f2be3477c9c92acbf6b6acd8163c58c994a6158d4d116f8"},
{file = "langchain-0.0.319.tar.gz", hash = "sha256:4fe5025e5fd48dcf8e02107fefe173ba997af3c8960871cc4a4467e24bb89375"},
]
[package.dependencies]
@@ -1685,7 +1685,7 @@ cli = ["typer (>=0.9.0,<0.10.0)"]
cohere = ["cohere (>=4,<5)"]
docarray = ["docarray[hnswlib] (>=0.32.0,<0.33.0)"]
embeddings = ["sentence-transformers (>=2,<3)"]
extended-testing = ["aiosqlite (>=0.19.0,<0.20.0)", "amazon-textract-caller (<2)", "anthropic (>=0.3.11,<0.4.0)", "arxiv (>=1.4,<2.0)", "assemblyai (>=0.17.0,<0.18.0)", "atlassian-python-api (>=3.36.0,<4.0.0)", "beautifulsoup4 (>=4,<5)", "bibtexparser (>=1.4.0,<2.0.0)", "cassio (>=0.1.0,<0.2.0)", "chardet (>=5.1.0,<6.0.0)", "dashvector (>=1.0.1,<2.0.0)", "esprima (>=4.0.1,<5.0.0)", "faiss-cpu (>=1,<2)", "feedparser (>=6.0.10,<7.0.0)", "geopandas (>=0.13.1,<0.14.0)", "gitpython (>=3.1.32,<4.0.0)", "gql (>=3.4.1,<4.0.0)", "html2text (>=2020.1.16,<2021.0.0)", "jinja2 (>=3,<4)", "jq (>=1.4.1,<2.0.0)", "lxml (>=4.9.2,<5.0.0)", "markdownify (>=0.11.6,<0.12.0)", "motor (>=3.3.1,<4.0.0)", "mwparserfromhell (>=0.6.4,<0.7.0)", "mwxml (>=0.3.3,<0.4.0)", "newspaper3k (>=0.2.8,<0.3.0)", "numexpr (>=2.8.6,<3.0.0)", "openai (>=0,<1)", "openapi-schema-pydantic (>=1.2,<2.0)", "pandas (>=2.0.1,<3.0.0)", "pdfminer-six (>=20221105,<20221106)", "pgvector (>=0.1.6,<0.2.0)", "psychicapi (>=0.8.0,<0.9.0)", "py-trello (>=0.19.0,<0.20.0)", "pymupdf (>=1.22.3,<2.0.0)", "pypdf (>=3.4.0,<4.0.0)", "pypdfium2 (>=4.10.0,<5.0.0)", "pyspark (>=3.4.0,<4.0.0)", "rank-bm25 (>=0.2.2,<0.3.0)", "rapidfuzz (>=3.1.1,<4.0.0)", "rapidocr-onnxruntime (>=1.3.2,<2.0.0)", "requests-toolbelt (>=1.0.0,<2.0.0)", "rspace_client (>=2.5.0,<3.0.0)", "scikit-learn (>=1.2.2,<2.0.0)", "sqlite-vss (>=0.1.2,<0.2.0)", "streamlit (>=1.18.0,<2.0.0)", "sympy (>=1.12,<2.0)", "telethon (>=1.28.5,<2.0.0)", "timescale-vector (>=0.0.1,<0.0.2)", "tqdm (>=4.48.0)", "upstash-redis (>=0.15.0,<0.16.0)", "xata (>=1.0.0a7,<2.0.0)", "xmltodict (>=0.13.0,<0.14.0)"]
extended-testing = ["aiosqlite (>=0.19.0,<0.20.0)", "amazon-textract-caller (<2)", "anthropic (>=0.3.11,<0.4.0)", "arxiv (>=1.4,<2.0)", "assemblyai (>=0.17.0,<0.18.0)", "atlassian-python-api (>=3.36.0,<4.0.0)", "beautifulsoup4 (>=4,<5)", "bibtexparser (>=1.4.0,<2.0.0)", "cassio (>=0.1.0,<0.2.0)", "chardet (>=5.1.0,<6.0.0)", "dashvector (>=1.0.1,<2.0.0)", "esprima (>=4.0.1,<5.0.0)", "faiss-cpu (>=1,<2)", "feedparser (>=6.0.10,<7.0.0)", "geopandas (>=0.13.1,<0.14.0)", "gitpython (>=3.1.32,<4.0.0)", "gql (>=3.4.1,<4.0.0)", "html2text (>=2020.1.16,<2021.0.0)", "jinja2 (>=3,<4)", "jq (>=1.4.1,<2.0.0)", "lxml (>=4.9.2,<5.0.0)", "markdownify (>=0.11.6,<0.12.0)", "motor (>=3.3.1,<4.0.0)", "mwparserfromhell (>=0.6.4,<0.7.0)", "mwxml (>=0.3.3,<0.4.0)", "newspaper3k (>=0.2.8,<0.3.0)", "numexpr (>=2.8.6,<3.0.0)", "openai (>=0,<1)", "openapi-pydantic (>=0.3.2,<0.4.0)", "pandas (>=2.0.1,<3.0.0)", "pdfminer-six (>=20221105,<20221106)", "pgvector (>=0.1.6,<0.2.0)", "psychicapi (>=0.8.0,<0.9.0)", "py-trello (>=0.19.0,<0.20.0)", "pymupdf (>=1.22.3,<2.0.0)", "pypdf (>=3.4.0,<4.0.0)", "pypdfium2 (>=4.10.0,<5.0.0)", "pyspark (>=3.4.0,<4.0.0)", "rank-bm25 (>=0.2.2,<0.3.0)", "rapidfuzz (>=3.1.1,<4.0.0)", "rapidocr-onnxruntime (>=1.3.2,<2.0.0)", "requests-toolbelt (>=1.0.0,<2.0.0)", "rspace_client (>=2.5.0,<3.0.0)", "scikit-learn (>=1.2.2,<2.0.0)", "sqlite-vss (>=0.1.2,<0.2.0)", "streamlit (>=1.18.0,<2.0.0)", "sympy (>=1.12,<2.0)", "telethon (>=1.28.5,<2.0.0)", "timescale-vector (>=0.0.1,<0.0.2)", "tqdm (>=4.48.0)", "upstash-redis (>=0.15.0,<0.16.0)", "xata (>=1.0.0a7,<2.0.0)", "xmltodict (>=0.13.0,<0.14.0)"]
javascript = ["esprima (>=4.0.1,<5.0.0)"]
llms = ["clarifai (>=9.1.0)", "cohere (>=4,<5)", "huggingface_hub (>=0,<1)", "manifest-ml (>=0.0.1,<0.0.2)", "nlpcloud (>=1,<2)", "openai (>=0,<1)", "openlm (>=0.0.5,<0.0.6)", "torch (>=1,<3)", "transformers (>=4,<5)"]
openai = ["openai (>=0,<1)", "tiktoken (>=0.3.2,<0.6.0)"]
@@ -1694,13 +1694,13 @@ text-helpers = ["chardet (>=5.1.0,<6.0.0)"]
[[package]]
name = "langsmith"
version = "0.0.44"
version = "0.0.47"
description = "Client library to connect to the LangSmith LLM Tracing and Evaluation Platform."
optional = false
python-versions = ">=3.8.1,<4.0"
files = [
{file = "langsmith-0.0.44-py3-none-any.whl", hash = "sha256:5e7e5b45360ce89a2d5d6066a3b9fdd31b1f874a0cf19b1666c9792fecef0a1b"},
{file = "langsmith-0.0.44.tar.gz", hash = "sha256:74a262ba23a958ca1a4863d5386c151be462e40ccfcb8b39d0a5d8c9eaa40164"},
{file = "langsmith-0.0.47-py3-none-any.whl", hash = "sha256:90279d4888e4513f83703becf38a6d92b7c7d696b120872fa1fb28c5c67bfd91"},
{file = "langsmith-0.0.47.tar.gz", hash = "sha256:04b8373cbfcf7b9c28ba53174206095c50dc09ae1131a99b501f92776c8ad0c8"},
]
[package.dependencies]
@@ -2496,13 +2496,13 @@ testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtuale
[[package]]
name = "pytest-mock"
version = "3.11.1"
version = "3.12.0"
description = "Thin-wrapper around the mock package for easier use with pytest"
optional = false
python-versions = ">=3.7"
python-versions = ">=3.8"
files = [
{file = "pytest-mock-3.11.1.tar.gz", hash = "sha256:7f6b125602ac6d743e523ae0bfa71e1a697a2f5534064528c6ff84c2f7c2fc7f"},
{file = "pytest_mock-3.11.1-py3-none-any.whl", hash = "sha256:21c279fff83d70763b05f8874cc9cfb3fcacd6d354247a976f9529d19f9acf39"},
{file = "pytest-mock-3.12.0.tar.gz", hash = "sha256:31a40f038c22cad32287bb43932054451ff5583ff094bca6f675df2f8bc1a6e9"},
{file = "pytest_mock-3.12.0-py3-none-any.whl", hash = "sha256:0972719a7263072da3a21c7f4773069bcc7486027d7e8e1f81d98a47e701bc4f"},
]
[package.dependencies]
@@ -3328,13 +3328,13 @@ dev = ["flake8", "flake8-annotations", "flake8-bandit", "flake8-bugbear", "flake
[[package]]
name = "urllib3"
version = "2.0.6"
version = "2.0.7"
description = "HTTP library with thread-safe connection pooling, file post, and more."
optional = false
python-versions = ">=3.7"
files = [
{file = "urllib3-2.0.6-py3-none-any.whl", hash = "sha256:7a7c7003b000adf9e7ca2a377c9688bbc54ed41b985789ed576570342a375cd2"},
{file = "urllib3-2.0.6.tar.gz", hash = "sha256:b19e1a85d206b56d7df1d5e683df4a7725252a964e3993648dd0fb5a1c157564"},
{file = "urllib3-2.0.7-py3-none-any.whl", hash = "sha256:fdb6d215c776278489906c2f8916e6e7d4f5a9b602ccbcfdf7f016fc8da0596e"},
{file = "urllib3-2.0.7.tar.gz", hash = "sha256:c97dfde1f7bd43a71c8d2a58e369e9b2bf692d1334ea9f9cae55add7d0dd0f84"},
]
[package.extras]
+1 -1
View File
@@ -1,6 +1,6 @@
[tool.poetry]
name = "langserve"
version = "0.0.9"
version = "0.0.15"
description = ""
readme = "README.md"
authors = ["LangChain"]
+269 -37
View File
@@ -2,8 +2,8 @@
import asyncio
import json
from asyncio import AbstractEventLoop
from contextlib import asynccontextmanager
from typing import Any, Dict, List, Optional, TypedDict, Union
from contextlib import asynccontextmanager, contextmanager
from typing import Any, Dict, Iterator, List, Optional, Sequence, Union
import httpx
import pytest
@@ -11,15 +11,17 @@ import pytest_asyncio
from fastapi import FastAPI
from fastapi.testclient import TestClient
from httpx import AsyncClient
from langchain.callbacks.tracers.log_stream import RunLog, RunLogPatch
from langchain.callbacks.tracers.log_stream import RunLogPatch
from langchain.prompts import PromptTemplate
from langchain.prompts.base import StringPromptValue
from langchain.schema.messages import HumanMessage, SystemMessage
from langchain.schema.runnable import RunnableConfig, RunnablePassthrough
from langchain.schema.runnable import Runnable, RunnableConfig, RunnablePassthrough
from langchain.schema.runnable.base import RunnableLambda
from langchain.schema.runnable.utils import ConfigurableField
from langchain.schema.runnable.utils import ConfigurableField, Input, Output
from pytest_mock import MockerFixture
from typing_extensions import TypedDict
from langserve import server
from langserve.client import RemoteRunnable
from langserve.lzstring import LZString
from langserve.server import (
@@ -102,14 +104,19 @@ def client(app: FastAPI) -> RemoteRunnable:
@asynccontextmanager
async def get_async_client(
server: FastAPI, path: Optional[str] = None
server: FastAPI, *, path: Optional[str] = None, raise_app_exceptions: bool = True
) -> RemoteRunnable:
"""Get an async client."""
url = "http://localhost:9999"
if path:
url += path
remote_runnable_client = RemoteRunnable(url=url)
async_client = AsyncClient(app=server, base_url=url)
transport = httpx.ASGITransport(
app=server,
raise_app_exceptions=raise_app_exceptions,
)
async_client = AsyncClient(app=server, base_url=url, transport=transport)
remote_runnable_client.async_client = async_client
try:
yield remote_runnable_client
@@ -117,6 +124,25 @@ async def get_async_client(
await async_client.aclose()
@contextmanager
def get_sync_client(
server: FastAPI, *, path: Optional[str] = None, raise_server_exceptions: bool = True
) -> RemoteRunnable:
"""Get an async client."""
url = "http://localhost:9999"
if path:
url += path
remote_runnable_client = RemoteRunnable(url=url)
sync_client = TestClient(
app=server, base_url=url, raise_server_exceptions=raise_server_exceptions
)
remote_runnable_client.sync_client = sync_client
try:
yield remote_runnable_client
finally:
sync_client.close()
@pytest_asyncio.fixture()
async def async_client(app: FastAPI) -> RemoteRunnable:
"""Create a FastAPI app that exposes the Runnable as an API."""
@@ -278,47 +304,33 @@ async def test_astream(async_client: RemoteRunnable) -> None:
@pytest.mark.asyncio
async def test_astream_log_no_diff(async_client: RemoteRunnable) -> None:
async def test_astream_log_diff_no_effect(async_client: RemoteRunnable) -> None:
"""Test async stream."""
run_logs = []
async for chunk in async_client.astream_log(1, diff=False):
run_logs.append(chunk)
assert len(run_logs) == 3
op = run_logs[0].ops[0]
uuid = op["value"]["id"]
for run_log in run_logs:
assert isinstance(run_log, RunLog)
states = [run_log.state for run_log in run_logs]
assert states == [
{
"final_output": None,
"id": uuid,
"logs": {},
"streamed_output": [],
},
{
"final_output": {"output": 2},
"id": uuid,
"logs": {},
"streamed_output": [],
},
{
"final_output": {"output": 2},
"id": uuid,
"logs": {},
"streamed_output": [2],
},
assert [run_log_patch.ops for run_log_patch in run_logs] == [
[
{
"op": "replace",
"path": "",
"value": {
"final_output": {"output": 2},
"id": uuid,
"logs": {},
"streamed_output": [],
},
}
],
[{"op": "replace", "path": "/final_output", "value": {"output": 2}}],
[{"op": "add", "path": "/streamed_output/-", "value": 2}],
]
# Check that we're picking up one extra op on each chunk
assert [len(run_log.ops) for run_log in run_logs] == [1, 2, 3]
@pytest.mark.asyncio
async def test_astream_log(async_client: RemoteRunnable) -> None:
@@ -762,6 +774,132 @@ def test_rename_pydantic_model() -> None:
assert Model.__name__ == "Bar"
@pytest.mark.asyncio
async def test_input_config_output_schemas(event_loop: AbstractEventLoop) -> None:
"""Test schemas returned for different configurations."""
# TODO(Fix me): need to fix handling of global state -- we get problems
# gives inconsistent results when running multiple tests / results
# depending on ordering
server._SEEN_NAMES = set()
server._MODEL_REGISTRY = {}
async def add_one(x: int) -> int:
"""Add one to simulate a valid function"""
return x + 1
async def add_two(y: int) -> int:
"""Add one to simulate a valid function"""
return y + 2
app = FastAPI()
add_routes(app, RunnableLambda(add_one), path="/add_one")
# Custom input type
add_routes(
app,
RunnableLambda(add_two),
path="/add_two_custom",
input_type=float,
output_type=Sequence[float],
config_keys=["tags", "configurable"],
)
add_routes(app, PromptTemplate.from_template("{question}"), path="/prompt_1")
template = PromptTemplate.from_template("say {name}").configurable_fields(
template=ConfigurableField(
id="template",
name="Template",
description="The template to use for the prompt",
)
)
add_routes(app, template, path="/prompt_2", config_keys=["tags", "configurable"])
async with AsyncClient(app=app, base_url="http://localhost:9999") as async_client:
# input schema
response = await async_client.get("/add_one/input_schema")
assert response.json() == {"title": "RunnableLambdaInput", "type": "integer"}
response = await async_client.get("/add_two_custom/input_schema")
assert response.json() == {"title": "Input", "type": "number"}
response = await async_client.get("/prompt_1/input_schema")
assert response.json() == {
"properties": {"question": {"title": "Question", "type": "string"}},
"title": "PromptInput",
"type": "object",
}
response = await async_client.get("/prompt_2/input_schema")
assert response.json() == {
"properties": {"name": {"title": "Name", "type": "string"}},
"title": "PromptInput",
"type": "object",
}
# output schema
response = await async_client.get("/add_one/output_schema")
assert response.json() == {
"title": "RunnableLambdaOutput",
"type": "integer",
}
response = await async_client.get("/add_two_custom/output_schema")
assert response.json() == {
"items": {"type": "number"},
"title": "Output",
"type": "array",
}
# Just verify that the schema is not empty (it's pretty long)
# and the actual value should be tested in LangChain
response = await async_client.get("/prompt_1/output_schema")
assert response.json() != {} # Long string
response = await async_client.get("/prompt_2/output_schema")
assert response.json() != {} # Long string
## Config schema
response = await async_client.get("/add_one/config_schema")
assert response.json() == {
"properties": {},
"title": "RunnableLambdaConfig",
"type": "object",
}
response = await async_client.get("/add_two_custom/config_schema")
assert response.json() == {
"properties": {
"tags": {"items": {"type": "string"}, "title": "Tags", "type": "array"}
},
"title": "RunnableLambdaConfig",
"type": "object",
}
response = await async_client.get("/prompt_2/config_schema")
assert response.json() == {
"definitions": {
"Configurable": {
"properties": {
"template": {
"default": "say {name}",
"description": "The template to use for the prompt",
"title": "Template",
"type": "string",
}
},
"title": "Configurable",
"type": "object",
}
},
"properties": {
"configurable": {"$ref": "#/definitions/Configurable"},
"tags": {"items": {"type": "string"}, "title": "Tags", "type": "array"},
},
"title": "RunnableConfigurableFieldsConfig",
"type": "object",
}
@pytest.mark.asyncio
async def test_input_schema_typed_dict() -> None:
class InputType(TypedDict):
@@ -796,3 +934,97 @@ async def test_input_schema_typed_dict() -> None:
}
},
}
class ErroringRunnable(Runnable):
"""A custom runnable for testing errors are raised server side."""
def invoke(self, input: Input, config: Optional[RunnableConfig] = None) -> Output:
"""Invoke the runnable."""
raise ValueError("Server side error")
def stream(
self,
input: Input,
config: Optional[RunnableConfig] = None,
**kwargs: Optional[Any],
) -> Iterator[Output]:
yield 1
yield 2
raise ValueError("An exception occurred")
async def astream(
self,
input: Iterator[Input],
config: Optional[RunnableConfig] = None,
**kwargs: Optional[Any],
) -> Iterator[Output]:
yield 1
yield 2
raise ValueError("An exception occurred")
@pytest.mark.asyncio
async def test_server_side_error() -> None:
"""Test server side error handling."""
app = FastAPI()
add_routes(app, ErroringRunnable())
# Invoke request
async with get_async_client(app, raise_app_exceptions=False) as runnable:
with pytest.raises(httpx.HTTPStatusError) as cm:
assert await runnable.ainvoke(1)
assert isinstance(cm.value, httpx.HTTPStatusError)
with pytest.raises(httpx.HTTPStatusError) as cm:
assert await runnable.abatch([1, 2])
assert isinstance(cm.value, httpx.HTTPStatusError)
# Test astream
chunks = []
try:
async for chunk in runnable.astream({"a": 1}):
chunks.append(chunk)
except httpx.HTTPStatusError as e:
assert chunks == [1, 2]
assert e.response.status_code == 500
assert e.response.text == "Internal Server Error"
# # Failing right now, can uncomment or add callbacks
# # Test astream_log
# chunks = []
# try:
# async for chunk in runnable.astream_log({"a": 1}):
# chunks.append(chunk)
# except httpx.HTTPStatusError as e:
# assert chunks == []
# assert e.response.status_code == 500
# assert e.response.text == "Internal Server Error"
def test_server_side_error_sync() -> None:
"""Test server side error handling."""
app = FastAPI()
add_routes(app, ErroringRunnable())
# Invoke request
with get_sync_client(app, raise_server_exceptions=False) as runnable:
with pytest.raises(httpx.HTTPStatusError) as cm:
assert runnable.invoke(1)
assert isinstance(cm.value, httpx.HTTPStatusError)
with pytest.raises(httpx.HTTPStatusError) as cm:
assert runnable.batch([1, 2])
assert isinstance(cm.value, httpx.HTTPStatusError)
# Test astream
chunks = []
try:
for chunk in runnable.stream({"a": 1}):
chunks.append(chunk)
except httpx.HTTPStatusError as e:
assert chunks == [1, 2]
assert e.response.status_code == 500
assert e.response.text == "Internal Server Error"