Merge remote-tracking branch 'origin/main'

pull/18136/head
JF.Hsiong 1 year ago
commit 344e90c31c

@ -30,7 +30,6 @@ jobs:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
fetch-depth: 0
persist-credentials: false persist-credentials: false
- name: Setup UV and Python - name: Setup UV and Python
@ -43,7 +42,7 @@ jobs:
run: uv lock --project api --check run: uv lock --project api --check
- name: Install dependencies - name: Install dependencies
run: uv sync --project api --group dev run: uv sync --project api --dev
- name: Run Unit tests - name: Run Unit tests
run: uv run --project api bash dev/pytest/pytest_unit_tests.sh run: uv run --project api bash dev/pytest/pytest_unit_tests.sh

@ -18,7 +18,6 @@ jobs:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
fetch-depth: 0
persist-credentials: false persist-credentials: false
- name: Check changed files - name: Check changed files
@ -38,7 +37,7 @@ jobs:
- name: Install dependencies - name: Install dependencies
if: steps.changed-files.outputs.any_changed == 'true' if: steps.changed-files.outputs.any_changed == 'true'
run: uv sync --project api --only-group lint run: uv sync --project api --dev
- name: Ruff check - name: Ruff check
if: steps.changed-files.outputs.any_changed == 'true' if: steps.changed-files.outputs.any_changed == 'true'
@ -66,7 +65,6 @@ jobs:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
fetch-depth: 0
persist-credentials: false persist-credentials: false
- name: Check changed files - name: Check changed files
@ -105,7 +103,6 @@ jobs:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
fetch-depth: 0
persist-credentials: false persist-credentials: false
- name: Check changed files - name: Check changed files
@ -136,7 +133,6 @@ jobs:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
fetch-depth: 0
persist-credentials: false persist-credentials: false
- name: Check changed files - name: Check changed files

@ -27,7 +27,6 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
fetch-depth: 0
persist-credentials: false persist-credentials: false
- name: Use Node.js ${{ matrix.node-version }} - name: Use Node.js ${{ matrix.node-version }}

@ -29,7 +29,6 @@ jobs:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
fetch-depth: 0
persist-credentials: false persist-credentials: false
- name: Setup UV and Python - name: Setup UV and Python
@ -42,7 +41,7 @@ jobs:
run: uv lock --project api --check run: uv lock --project api --check
- name: Install dependencies - name: Install dependencies
run: uv sync --project api --group dev run: uv sync --project api --dev
- name: Set up dotenvs - name: Set up dotenvs
run: | run: |

@ -23,7 +23,6 @@ jobs:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
fetch-depth: 0
persist-credentials: false persist-credentials: false
- name: Check changed files - name: Check changed files

@ -52,7 +52,7 @@
5. Install dependencies 5. Install dependencies
```bash ```bash
uv sync --group lint --group dev uv sync --dev
``` ```
6. Run migrate 6. Run migrate
@ -82,7 +82,7 @@
1. Install dependencies for both the backend and the test environment 1. Install dependencies for both the backend and the test environment
```bash ```bash
uv sync --group lint --group dev uv sync --dev
``` ```
2. Run the tests locally with mocked system environment variables in `tool.pytest_env` section in `pyproject.toml` 2. Run the tests locally with mocked system environment variables in `tool.pytest_env` section in `pyproject.toml`

@ -82,7 +82,7 @@ class BasePluginManager:
Make a stream request to the plugin daemon inner API Make a stream request to the plugin daemon inner API
""" """
response = self._request(method, path, headers, data, params, files, stream=True) response = self._request(method, path, headers, data, params, files, stream=True)
for line in response.iter_lines(): for line in response.iter_lines(chunk_size=1024 * 8):
line = line.decode("utf-8").strip() line = line.decode("utf-8").strip()
if line.startswith("data:"): if line.startswith("data:"):
line = line[5:].strip() line = line[5:].strip()

@ -110,7 +110,62 @@ class PluginToolManager(BasePluginManager):
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
) )
return response
class FileChunk:
"""
Only used for internal processing.
"""
bytes_written: int
total_length: int
data: bytearray
def __init__(self, total_length: int):
self.bytes_written = 0
self.total_length = total_length
self.data = bytearray(total_length)
files: dict[str, FileChunk] = {}
for resp in response:
if resp.type == ToolInvokeMessage.MessageType.BLOB_CHUNK:
assert isinstance(resp.message, ToolInvokeMessage.BlobChunkMessage)
# Get blob chunk information
chunk_id = resp.message.id
total_length = resp.message.total_length
blob_data = resp.message.blob
is_end = resp.message.end
# Initialize buffer for this file if it doesn't exist
if chunk_id not in files:
files[chunk_id] = FileChunk(total_length)
# If this is the final chunk, yield a complete blob message
if is_end:
yield ToolInvokeMessage(
type=ToolInvokeMessage.MessageType.BLOB,
message=ToolInvokeMessage.BlobMessage(blob=files[chunk_id].data),
meta=resp.meta,
)
else:
# Check if file is too large (30MB limit)
if files[chunk_id].bytes_written + len(blob_data) > 30 * 1024 * 1024:
# Delete the file if it's too large
del files[chunk_id]
# Skip yielding this message
raise ValueError("File is too large which reached the limit of 30MB")
# Check if single chunk is too large (8KB limit)
if len(blob_data) > 8192:
# Skip yielding this message
raise ValueError("File chunk is too large which reached the limit of 8KB")
# Append the blob data to the buffer
files[chunk_id].data[
files[chunk_id].bytes_written : files[chunk_id].bytes_written + len(blob_data)
] = blob_data
files[chunk_id].bytes_written += len(blob_data)
else:
yield resp
def validate_provider_credentials( def validate_provider_credentials(
self, tenant_id: str, user_id: str, provider: str, credentials: dict[str, Any] self, tenant_id: str, user_id: str, provider: str, credentials: dict[str, Any]

@ -120,6 +120,13 @@ class ToolInvokeMessage(BaseModel):
class BlobMessage(BaseModel): class BlobMessage(BaseModel):
blob: bytes blob: bytes
class BlobChunkMessage(BaseModel):
id: str = Field(..., description="The id of the blob")
sequence: int = Field(..., description="The sequence of the chunk")
total_length: int = Field(..., description="The total length of the blob")
blob: bytes = Field(..., description="The blob data of the chunk")
end: bool = Field(..., description="Whether the chunk is the last chunk")
class FileMessage(BaseModel): class FileMessage(BaseModel):
pass pass
@ -180,12 +187,15 @@ class ToolInvokeMessage(BaseModel):
VARIABLE = "variable" VARIABLE = "variable"
FILE = "file" FILE = "file"
LOG = "log" LOG = "log"
BLOB_CHUNK = "blob_chunk"
type: MessageType = MessageType.TEXT type: MessageType = MessageType.TEXT
""" """
plain text, image url or link url plain text, image url or link url
""" """
message: JsonMessage | TextMessage | BlobMessage | LogMessage | FileMessage | None | VariableMessage message: (
JsonMessage | TextMessage | BlobChunkMessage | BlobMessage | LogMessage | FileMessage | None | VariableMessage
)
meta: dict[str, Any] | None = None meta: dict[str, Any] | None = None
@field_validator("message", mode="before") @field_validator("message", mode="before")

@ -42,6 +42,7 @@ message_file_fields = {
"size": fields.Integer, "size": fields.Integer,
"transfer_method": fields.String, "transfer_method": fields.String,
"belongs_to": fields.String(default="user"), "belongs_to": fields.String(default="user"),
"upload_file_id": fields.String(default=None),
} }
agent_thought_fields = { agent_thought_fields = {

@ -1155,7 +1155,7 @@ class Message(db.Model): # type: ignore[name-defined]
files.append(file) files.append(file)
result = [ result = [
{"belongs_to": message_file.belongs_to, **file.to_dict()} {"belongs_to": message_file.belongs_to, "upload_file_id": message_file.upload_file_id, **file.to_dict()}
for (file, message_file) in zip(files, message_files) for (file, message_file) in zip(files, message_files)
] ]

@ -90,16 +90,18 @@ dependencies = [
default-groups = ["storage", "tools", "vdb"] default-groups = ["storage", "tools", "vdb"]
[dependency-groups] [dependency-groups]
############################################################ ############################################################
# [ Dev ] dependency group # [ Dev ] dependency group
# Required for development and running tests # Required for development and running tests
############################################################ ############################################################
dev = [ dev = [
"coverage~=7.2.4", "coverage~=7.2.4",
"dotenv-linter~=0.5.0",
"faker~=32.1.0", "faker~=32.1.0",
"lxml-stubs~=0.5.1", "lxml-stubs~=0.5.1",
"mypy~=1.15.0", "mypy~=1.15.0",
"ruff~=0.11.5",
"pytest~=8.3.2", "pytest~=8.3.2",
"pytest-benchmark~=4.0.0", "pytest-benchmark~=4.0.0",
"pytest-env~=1.1.3", "pytest-env~=1.1.3",
@ -141,15 +143,6 @@ dev = [
"types-ujson~=5.10.0", "types-ujson~=5.10.0",
] ]
############################################################
# [ Lint ] dependency group
# Required for code style linting
############################################################
lint = [
"dotenv-linter~=0.5.0",
"ruff~=0.11.0",
]
############################################################ ############################################################
# [ Storage ] dependency group # [ Storage ] dependency group
# Required for storage clients # Required for storage clients

@ -1228,6 +1228,7 @@ dependencies = [
[package.dev-dependencies] [package.dev-dependencies]
dev = [ dev = [
{ name = "coverage" }, { name = "coverage" },
{ name = "dotenv-linter" },
{ name = "faker" }, { name = "faker" },
{ name = "lxml-stubs" }, { name = "lxml-stubs" },
{ name = "mypy" }, { name = "mypy" },
@ -1235,6 +1236,7 @@ dev = [
{ name = "pytest-benchmark" }, { name = "pytest-benchmark" },
{ name = "pytest-env" }, { name = "pytest-env" },
{ name = "pytest-mock" }, { name = "pytest-mock" },
{ name = "ruff" },
{ name = "types-aiofiles" }, { name = "types-aiofiles" },
{ name = "types-beautifulsoup4" }, { name = "types-beautifulsoup4" },
{ name = "types-cachetools" }, { name = "types-cachetools" },
@ -1271,10 +1273,6 @@ dev = [
{ name = "types-tqdm" }, { name = "types-tqdm" },
{ name = "types-ujson" }, { name = "types-ujson" },
] ]
lint = [
{ name = "dotenv-linter" },
{ name = "ruff" },
]
storage = [ storage = [
{ name = "azure-storage-blob" }, { name = "azure-storage-blob" },
{ name = "bce-python-sdk" }, { name = "bce-python-sdk" },
@ -1397,6 +1395,7 @@ requires-dist = [
[package.metadata.requires-dev] [package.metadata.requires-dev]
dev = [ dev = [
{ name = "coverage", specifier = "~=7.2.4" }, { name = "coverage", specifier = "~=7.2.4" },
{ name = "dotenv-linter", specifier = "~=0.5.0" },
{ name = "faker", specifier = "~=32.1.0" }, { name = "faker", specifier = "~=32.1.0" },
{ name = "lxml-stubs", specifier = "~=0.5.1" }, { name = "lxml-stubs", specifier = "~=0.5.1" },
{ name = "mypy", specifier = "~=1.15.0" }, { name = "mypy", specifier = "~=1.15.0" },
@ -1404,6 +1403,7 @@ dev = [
{ name = "pytest-benchmark", specifier = "~=4.0.0" }, { name = "pytest-benchmark", specifier = "~=4.0.0" },
{ name = "pytest-env", specifier = "~=1.1.3" }, { name = "pytest-env", specifier = "~=1.1.3" },
{ name = "pytest-mock", specifier = "~=3.14.0" }, { name = "pytest-mock", specifier = "~=3.14.0" },
{ name = "ruff", specifier = "~=0.11.5" },
{ name = "types-aiofiles", specifier = "~=24.1.0" }, { name = "types-aiofiles", specifier = "~=24.1.0" },
{ name = "types-beautifulsoup4", specifier = "~=4.12.0" }, { name = "types-beautifulsoup4", specifier = "~=4.12.0" },
{ name = "types-cachetools", specifier = "~=5.5.0" }, { name = "types-cachetools", specifier = "~=5.5.0" },
@ -1440,10 +1440,6 @@ dev = [
{ name = "types-tqdm", specifier = "~=4.67.0" }, { name = "types-tqdm", specifier = "~=4.67.0" },
{ name = "types-ujson", specifier = "~=5.10.0" }, { name = "types-ujson", specifier = "~=5.10.0" },
] ]
lint = [
{ name = "dotenv-linter", specifier = "~=0.5.0" },
{ name = "ruff", specifier = "~=0.11.0" },
]
storage = [ storage = [
{ name = "azure-storage-blob", specifier = "==12.13.0" }, { name = "azure-storage-blob", specifier = "==12.13.0" },
{ name = "bce-python-sdk", specifier = "~=0.9.23" }, { name = "bce-python-sdk", specifier = "~=0.9.23" },
@ -4834,27 +4830,27 @@ wheels = [
[[package]] [[package]]
name = "ruff" name = "ruff"
version = "0.11.4" version = "0.11.5"
source = { registry = "https://pypi.org/simple" } source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/e8/5b/3ae20f89777115944e89c2d8c2e795dcc5b9e04052f76d5347e35e0da66e/ruff-0.11.4.tar.gz", hash = "sha256:f45bd2fb1a56a5a85fae3b95add03fb185a0b30cf47f5edc92aa0355ca1d7407", size = 3933063 } sdist = { url = "https://files.pythonhosted.org/packages/45/71/5759b2a6b2279bb77fe15b1435b89473631c2cd6374d45ccdb6b785810be/ruff-0.11.5.tar.gz", hash = "sha256:cae2e2439cb88853e421901ec040a758960b576126dab520fa08e9de431d1bef", size = 3976488 }
wheels = [ wheels = [
{ url = "https://files.pythonhosted.org/packages/9c/db/baee59ac88f57527fcbaad3a7b309994e42329c6bc4d4d2b681a3d7b5426/ruff-0.11.4-py3-none-linux_armv6l.whl", hash = "sha256:d9f4a761ecbde448a2d3e12fb398647c7f0bf526dbc354a643ec505965824ed2", size = 10106493 }, { url = "https://files.pythonhosted.org/packages/23/db/6efda6381778eec7f35875b5cbefd194904832a1153d68d36d6b269d81a8/ruff-0.11.5-py3-none-linux_armv6l.whl", hash = "sha256:2561294e108eb648e50f210671cc56aee590fb6167b594144401532138c66c7b", size = 10103150 },
{ url = "https://files.pythonhosted.org/packages/c1/d6/9a0962cbb347f4ff98b33d699bf1193ff04ca93bed4b4222fd881b502154/ruff-0.11.4-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:8c1747d903447d45ca3d40c794d1a56458c51e5cc1bc77b7b64bd2cf0b1626cc", size = 10876382 }, { url = "https://files.pythonhosted.org/packages/44/f2/06cd9006077a8db61956768bc200a8e52515bf33a8f9b671ee527bb10d77/ruff-0.11.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ac12884b9e005c12d0bd121f56ccf8033e1614f736f766c118ad60780882a077", size = 10898637 },
{ url = "https://files.pythonhosted.org/packages/3a/8f/62bab0c7d7e1ae3707b69b157701b41c1ccab8f83e8501734d12ea8a839f/ruff-0.11.4-py3-none-macosx_11_0_arm64.whl", hash = "sha256:51a6494209cacca79e121e9b244dc30d3414dac8cc5afb93f852173a2ecfc906", size = 10237050 }, { url = "https://files.pythonhosted.org/packages/18/f5/af390a013c56022fe6f72b95c86eb7b2585c89cc25d63882d3bfe411ecf1/ruff-0.11.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:4bfd80a6ec559a5eeb96c33f832418bf0fb96752de0539905cf7b0cc1d31d779", size = 10236012 },
{ url = "https://files.pythonhosted.org/packages/09/96/e296965ae9705af19c265d4d441958ed65c0c58fc4ec340c27cc9d2a1f5b/ruff-0.11.4-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3f171605f65f4fc49c87f41b456e882cd0c89e4ac9d58e149a2b07930e1d466f", size = 10424984 }, { url = "https://files.pythonhosted.org/packages/b8/ca/b9bf954cfed165e1a0c24b86305d5c8ea75def256707f2448439ac5e0d8b/ruff-0.11.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0947c0a1afa75dcb5db4b34b070ec2bccee869d40e6cc8ab25aca11a7d527794", size = 10415338 },
{ url = "https://files.pythonhosted.org/packages/e5/56/644595eb57d855afed6e54b852e2df8cd5ca94c78043b2f29bdfb29882d5/ruff-0.11.4-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ebf99ea9af918878e6ce42098981fc8c1db3850fef2f1ada69fb1dcdb0f8e79e", size = 9957438 }, { url = "https://files.pythonhosted.org/packages/d9/4d/2522dde4e790f1b59885283f8786ab0046958dfd39959c81acc75d347467/ruff-0.11.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ad871ff74b5ec9caa66cb725b85d4ef89b53f8170f47c3406e32ef040400b038", size = 9965277 },
{ url = "https://files.pythonhosted.org/packages/86/83/9d3f3bed0118aef3e871ded9e5687fb8c5776bde233427fd9ce0a45db2d4/ruff-0.11.4-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edad2eac42279df12e176564a23fc6f4aaeeb09abba840627780b1bb11a9d223", size = 11547282 }, { url = "https://files.pythonhosted.org/packages/e5/7a/749f56f150eef71ce2f626a2f6988446c620af2f9ba2a7804295ca450397/ruff-0.11.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e6cf918390cfe46d240732d4d72fa6e18e528ca1f60e318a10835cf2fa3dc19f", size = 11541614 },
{ url = "https://files.pythonhosted.org/packages/40/e6/0c6e4f5ae72fac5ccb44d72c0111f294a5c2c8cc5024afcb38e6bda5f4b3/ruff-0.11.4-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f103a848be9ff379fc19b5d656c1f911d0a0b4e3e0424f9532ececf319a4296e", size = 12182020 }, { url = "https://files.pythonhosted.org/packages/89/b2/7d9b8435222485b6aac627d9c29793ba89be40b5de11584ca604b829e960/ruff-0.11.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:56145ee1478582f61c08f21076dc59153310d606ad663acc00ea3ab5b2125f82", size = 12198873 },
{ url = "https://files.pythonhosted.org/packages/b5/92/4aed0e460aeb1df5ea0c2fbe8d04f9725cccdb25d8da09a0d3f5b8764bf8/ruff-0.11.4-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:193e6fac6eb60cc97b9f728e953c21cc38a20077ed64f912e9d62b97487f3f2d", size = 11679154 }, { url = "https://files.pythonhosted.org/packages/00/e0/a1a69ef5ffb5c5f9c31554b27e030a9c468fc6f57055886d27d316dfbabd/ruff-0.11.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e5f66f8f1e8c9fc594cbd66fbc5f246a8d91f916cb9667e80208663ec3728304", size = 11670190 },
{ url = "https://files.pythonhosted.org/packages/1b/d3/7316aa2609f2c592038e2543483eafbc62a0e1a6a6965178e284808c095c/ruff-0.11.4-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7af4e5f69b7c138be8dcffa5b4a061bf6ba6a3301f632a6bce25d45daff9bc99", size = 13905985 }, { url = "https://files.pythonhosted.org/packages/05/61/c1c16df6e92975072c07f8b20dad35cd858e8462b8865bc856fe5d6ccb63/ruff-0.11.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:80b4df4d335a80315ab9afc81ed1cff62be112bd165e162b5eed8ac55bfc8470", size = 13902301 },
{ url = "https://files.pythonhosted.org/packages/63/80/734d3d17546e47ff99871f44ea7540ad2bbd7a480ed197fe8a1c8a261075/ruff-0.11.4-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:126b1bf13154aa18ae2d6c3c5efe144ec14b97c60844cfa6eb960c2a05188222", size = 11348343 }, { url = "https://files.pythonhosted.org/packages/79/89/0af10c8af4363304fd8cb833bd407a2850c760b71edf742c18d5a87bb3ad/ruff-0.11.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3068befab73620b8a0cc2431bd46b3cd619bc17d6f7695a3e1bb166b652c382a", size = 11350132 },
{ url = "https://files.pythonhosted.org/packages/04/7b/70fc7f09a0161dce9613a4671d198f609e653d6f4ff9eee14d64c4c240fb/ruff-0.11.4-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e8806daaf9dfa881a0ed603f8a0e364e4f11b6ed461b56cae2b1c0cab0645304", size = 10308487 }, { url = "https://files.pythonhosted.org/packages/b9/e1/ecb4c687cbf15164dd00e38cf62cbab238cad05dd8b6b0fc68b0c2785e15/ruff-0.11.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:f5da2e710a9641828e09aa98b92c9ebbc60518fdf3921241326ca3e8f8e55b8b", size = 10312937 },
{ url = "https://files.pythonhosted.org/packages/1a/22/1cdd62dabd678d75842bf4944fd889cf794dc9e58c18cc547f9eb28f95ed/ruff-0.11.4-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:5d94bb1cc2fc94a769b0eb975344f1b1f3d294da1da9ddbb5a77665feb3a3019", size = 9929091 }, { url = "https://files.pythonhosted.org/packages/cf/4f/0e53fe5e500b65934500949361e3cd290c5ba60f0324ed59d15f46479c06/ruff-0.11.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ef39f19cb8ec98cbc762344921e216f3857a06c47412030374fffd413fb8fd3a", size = 9936683 },
{ url = "https://files.pythonhosted.org/packages/9f/20/40e0563506332313148e783bbc1e4276d657962cc370657b2fff20e6e058/ruff-0.11.4-py3-none-musllinux_1_2_i686.whl", hash = "sha256:995071203d0fe2183fc7a268766fd7603afb9996785f086b0d76edee8755c896", size = 10924659 }, { url = "https://files.pythonhosted.org/packages/04/a8/8183c4da6d35794ae7f76f96261ef5960853cd3f899c2671961f97a27d8e/ruff-0.11.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:b2a7cedf47244f431fd11aa5a7e2806dda2e0c365873bda7834e8f7d785ae159", size = 10950217 },
{ url = "https://files.pythonhosted.org/packages/b5/41/eef9b7aac8819d9e942f617f9db296f13d2c4576806d604aba8db5a753f1/ruff-0.11.4-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:7a37ca937e307ea18156e775a6ac6e02f34b99e8c23fe63c1996185a4efe0751", size = 11428160 }, { url = "https://files.pythonhosted.org/packages/26/88/9b85a5a8af21e46a0639b107fcf9bfc31da4f1d263f2fc7fbe7199b47f0a/ruff-0.11.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:81be52e7519f3d1a0beadcf8e974715b2dfc808ae8ec729ecfc79bddf8dbb783", size = 11404521 },
{ url = "https://files.pythonhosted.org/packages/ff/61/c488943414fb2b8754c02f3879de003e26efdd20f38167ded3fb3fc1cda3/ruff-0.11.4-py3-none-win32.whl", hash = "sha256:0e9365a7dff9b93af933dab8aebce53b72d8f815e131796268709890b4a83270", size = 10311496 }, { url = "https://files.pythonhosted.org/packages/fc/52/047f35d3b20fd1ae9ccfe28791ef0f3ca0ef0b3e6c1a58badd97d450131b/ruff-0.11.5-py3-none-win32.whl", hash = "sha256:e268da7b40f56e3eca571508a7e567e794f9bfcc0f412c4b607931d3af9c4afe", size = 10320697 },
{ url = "https://files.pythonhosted.org/packages/b6/2b/2a1c8deb5f5dfa3871eb7daa41492c4d2b2824a74d2b38e788617612a66d/ruff-0.11.4-py3-none-win_amd64.whl", hash = "sha256:5a9fa1c69c7815e39fcfb3646bbfd7f528fa8e2d4bebdcf4c2bd0fa037a255fb", size = 11399146 }, { url = "https://files.pythonhosted.org/packages/b9/fe/00c78010e3332a6e92762424cf4c1919065707e962232797d0b57fd8267e/ruff-0.11.5-py3-none-win_amd64.whl", hash = "sha256:6c6dc38af3cfe2863213ea25b6dc616d679205732dc0fb673356c2d69608f800", size = 11378665 },
{ url = "https://files.pythonhosted.org/packages/4f/03/3aec4846226d54a37822e4c7ea39489e4abd6f88388fba74e3d4abe77300/ruff-0.11.4-py3-none-win_arm64.whl", hash = "sha256:d435db6b9b93d02934cf61ef332e66af82da6d8c69aefdea5994c89997c7a0fc", size = 10450306 }, { url = "https://files.pythonhosted.org/packages/43/7c/c83fe5cbb70ff017612ff36654edfebec4b1ef79b558b8e5fd933bab836b/ruff-0.11.5-py3-none-win_arm64.whl", hash = "sha256:67e241b4314f4eacf14a601d586026a962f4002a475aa702c69980a38087aa4e", size = 10460287 },
] ]
[[package]] [[package]]

@ -3,5 +3,5 @@
set -x set -x
# run mypy checks # run mypy checks
uv run --directory api --group dev \ uv run --directory api --dev \
python -m mypy --install-types --non-interactive . python -m mypy --install-types --non-interactive .

@ -3,13 +3,13 @@
set -x set -x
# run ruff linter # run ruff linter
uv run --directory api --group lint ruff check --fix ./ uv run --directory api --dev ruff check --fix ./
# run ruff formatter # run ruff formatter
uv run --directory api --group lint ruff format ./ uv run --directory api --dev ruff format ./
# run dotenv-linter linter # run dotenv-linter linter
uv run --project api --group lint dotenv-linter ./api/.env.example ./web/.env.example uv run --project api --dev dotenv-linter ./api/.env.example ./web/.env.example
# run mypy check # run mypy check
dev/mypy-check dev/mypy-check

@ -1,7 +0,0 @@
#!/bin/bash
set -x
# run mypy checks
uv run --directory api --group dev \
python -m mypy --install-types --non-interactive .

@ -17,8 +17,10 @@ services:
PLUGIN_MAX_PACKAGE_SIZE: ${PLUGIN_MAX_PACKAGE_SIZE:-52428800} PLUGIN_MAX_PACKAGE_SIZE: ${PLUGIN_MAX_PACKAGE_SIZE:-52428800}
INNER_API_KEY_FOR_PLUGIN: ${PLUGIN_DIFY_INNER_API_KEY:-QaHbTe77CtuXmsfyhR7+vRjI/+XbV1AaFy691iy+kGDv2Jvy0/eAh8Y1} INNER_API_KEY_FOR_PLUGIN: ${PLUGIN_DIFY_INNER_API_KEY:-QaHbTe77CtuXmsfyhR7+vRjI/+XbV1AaFy691iy+kGDv2Jvy0/eAh8Y1}
depends_on: depends_on:
- db db:
- redis condition: service_healthy
redis:
condition: service_started
volumes: volumes:
# Mount the storage directory to the container, for storing user files. # Mount the storage directory to the container, for storing user files.
- ./volumes/app/storage:/app/api/storage - ./volumes/app/storage:/app/api/storage
@ -42,8 +44,10 @@ services:
PLUGIN_MAX_PACKAGE_SIZE: ${PLUGIN_MAX_PACKAGE_SIZE:-52428800} PLUGIN_MAX_PACKAGE_SIZE: ${PLUGIN_MAX_PACKAGE_SIZE:-52428800}
INNER_API_KEY_FOR_PLUGIN: ${PLUGIN_DIFY_INNER_API_KEY:-QaHbTe77CtuXmsfyhR7+vRjI/+XbV1AaFy691iy+kGDv2Jvy0/eAh8Y1} INNER_API_KEY_FOR_PLUGIN: ${PLUGIN_DIFY_INNER_API_KEY:-QaHbTe77CtuXmsfyhR7+vRjI/+XbV1AaFy691iy+kGDv2Jvy0/eAh8Y1}
depends_on: depends_on:
- db db:
- redis condition: service_healthy
redis:
condition: service_started
volumes: volumes:
# Mount the storage directory to the container, for storing user files. # Mount the storage directory to the container, for storing user files.
- ./volumes/app/storage:/app/api/storage - ./volumes/app/storage:/app/api/storage

@ -485,8 +485,10 @@ services:
PLUGIN_MAX_PACKAGE_SIZE: ${PLUGIN_MAX_PACKAGE_SIZE:-52428800} PLUGIN_MAX_PACKAGE_SIZE: ${PLUGIN_MAX_PACKAGE_SIZE:-52428800}
INNER_API_KEY_FOR_PLUGIN: ${PLUGIN_DIFY_INNER_API_KEY:-QaHbTe77CtuXmsfyhR7+vRjI/+XbV1AaFy691iy+kGDv2Jvy0/eAh8Y1} INNER_API_KEY_FOR_PLUGIN: ${PLUGIN_DIFY_INNER_API_KEY:-QaHbTe77CtuXmsfyhR7+vRjI/+XbV1AaFy691iy+kGDv2Jvy0/eAh8Y1}
depends_on: depends_on:
- db db:
- redis condition: service_healthy
redis:
condition: service_started
volumes: volumes:
# Mount the storage directory to the container, for storing user files. # Mount the storage directory to the container, for storing user files.
- ./volumes/app/storage:/app/api/storage - ./volumes/app/storage:/app/api/storage
@ -510,8 +512,10 @@ services:
PLUGIN_MAX_PACKAGE_SIZE: ${PLUGIN_MAX_PACKAGE_SIZE:-52428800} PLUGIN_MAX_PACKAGE_SIZE: ${PLUGIN_MAX_PACKAGE_SIZE:-52428800}
INNER_API_KEY_FOR_PLUGIN: ${PLUGIN_DIFY_INNER_API_KEY:-QaHbTe77CtuXmsfyhR7+vRjI/+XbV1AaFy691iy+kGDv2Jvy0/eAh8Y1} INNER_API_KEY_FOR_PLUGIN: ${PLUGIN_DIFY_INNER_API_KEY:-QaHbTe77CtuXmsfyhR7+vRjI/+XbV1AaFy691iy+kGDv2Jvy0/eAh8Y1}
depends_on: depends_on:
- db db:
- redis condition: service_healthy
redis:
condition: service_started
volumes: volumes:
# Mount the storage directory to the container, for storing user files. # Mount the storage directory to the container, for storing user files.
- ./volumes/app/storage:/app/api/storage - ./volumes/app/storage:/app/api/storage

@ -27,17 +27,11 @@ done
if $api_modified; then if $api_modified; then
echo "Running Ruff linter on api module" echo "Running Ruff linter on api module"
# python style checks rely on `ruff` in path
if ! command -v ruff > /dev/null 2>&1; then
echo "Installing linting tools (Ruff, dotenv-linter ...) ..."
uv sync --project api --only-group lint
fi
# run Ruff linter auto-fixing # run Ruff linter auto-fixing
uv run --project api ruff check --fix ./api uv run --project api --dev ruff check --fix ./api
# run Ruff linter checks # run Ruff linter checks
uv run --project api ruff check ./api || status=$? uv run --project api --dev ruff check ./api || status=$?
status=${status:-0} status=${status:-0}

@ -16,6 +16,7 @@ import AppsContext, { useAppContext } from '@/context/app-context'
import type { HtmlContentProps } from '@/app/components/base/popover' import type { HtmlContentProps } from '@/app/components/base/popover'
import CustomPopover from '@/app/components/base/popover' import CustomPopover from '@/app/components/base/popover'
import Divider from '@/app/components/base/divider' import Divider from '@/app/components/base/divider'
import { basePath } from '@/utils/var'
import { getRedirection } from '@/utils/app-redirection' import { getRedirection } from '@/utils/app-redirection'
import { useProviderContext } from '@/context/provider-context' import { useProviderContext } from '@/context/provider-context'
import { NEED_REFRESH_APP_LIST_KEY } from '@/config' import { NEED_REFRESH_APP_LIST_KEY } from '@/config'
@ -216,7 +217,7 @@ const AppCard = ({ app, onRefresh }: AppCardProps) => {
try { try {
const { installed_apps }: any = await fetchInstalledAppList(app.id) || {} const { installed_apps }: any = await fetchInstalledAppList(app.id) || {}
if (installed_apps?.length > 0) if (installed_apps?.length > 0)
window.open(`/explore/installed/${installed_apps[0].id}`, '_blank') window.open(`${basePath}/explore/installed/${installed_apps[0].id}`, '_blank')
else else
throw new Error('No app found in Explore') throw new Error('No app found in Explore')
} }

@ -1,5 +1,6 @@
'use client' 'use client'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { basePath } from '@/utils/var'
import { import {
RiAddLine, RiAddLine,
RiArrowRightLine, RiArrowRightLine,
@ -17,7 +18,7 @@ const CreateAppCard = (
<div className='bg-background-default-dimm flex min-h-[160px] flex-col rounded-xl border-[0.5px] <div className='bg-background-default-dimm flex min-h-[160px] flex-col rounded-xl border-[0.5px]
border-components-panel-border transition-all duration-200 ease-in-out' border-components-panel-border transition-all duration-200 ease-in-out'
> >
<a ref={ref} className='group flex grow cursor-pointer items-start p-4' href='/datasets/create'> <a ref={ref} className='group flex grow cursor-pointer items-start p-4' href={`${basePath}/datasets/create`}>
<div className='flex items-center gap-3'> <div className='flex items-center gap-3'>
<div className='flex h-10 w-10 items-center justify-center rounded-lg border border-dashed border-divider-regular bg-background-default-lighter <div className='flex h-10 w-10 items-center justify-center rounded-lg border border-dashed border-divider-regular bg-background-default-lighter
p-2 group-hover:border-solid group-hover:border-effects-highlight group-hover:bg-background-default-dodge' p-2 group-hover:border-solid group-hover:border-effects-highlight group-hover:bg-background-default-dodge'
@ -28,7 +29,7 @@ const CreateAppCard = (
</div> </div>
</a> </a>
<div className='system-xs-regular p-4 pt-0 text-text-tertiary'>{t('dataset.createDatasetIntro')}</div> <div className='system-xs-regular p-4 pt-0 text-text-tertiary'>{t('dataset.createDatasetIntro')}</div>
<a className='group flex cursor-pointer items-center gap-1 rounded-b-xl border-t-[0.5px] border-divider-subtle p-4' href='/datasets/connect'> <a className='group flex cursor-pointer items-center gap-1 rounded-b-xl border-t-[0.5px] border-divider-subtle p-4' href={`${basePath}/datasets/connect`}>
<div className='system-xs-medium text-text-tertiary group-hover:text-text-accent'>{t('dataset.connectDataset')}</div> <div className='system-xs-medium text-text-tertiary group-hover:text-text-accent'>{t('dataset.connectDataset')}</div>
<RiArrowRightLine className='h-3.5 w-3.5 text-text-tertiary group-hover:text-text-accent' /> <RiArrowRightLine className='h-3.5 w-3.5 text-text-tertiary group-hover:text-text-accent' />
</a> </a>

@ -24,6 +24,7 @@ import {
PortalToFollowElemContent, PortalToFollowElemContent,
PortalToFollowElemTrigger, PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem' } from '@/app/components/base/portal-to-follow-elem'
import { basePath } from '@/utils/var'
import { fetchInstalledAppList } from '@/service/explore' import { fetchInstalledAppList } from '@/service/explore'
import EmbeddedModal from '@/app/components/app/overview/embedded' import EmbeddedModal from '@/app/components/app/overview/embedded'
import { useStore as useAppStore } from '@/app/components/app/store' import { useStore as useAppStore } from '@/app/components/app/store'
@ -75,7 +76,7 @@ const AppPublisher = ({
const appDetail = useAppStore(state => state.appDetail) const appDetail = useAppStore(state => state.appDetail)
const { app_base_url: appBaseURL = '', access_token: accessToken = '' } = appDetail?.site ?? {} const { app_base_url: appBaseURL = '', access_token: accessToken = '' } = appDetail?.site ?? {}
const appMode = (appDetail?.mode !== 'completion' && appDetail?.mode !== 'workflow') ? 'chat' : appDetail.mode const appMode = (appDetail?.mode !== 'completion' && appDetail?.mode !== 'workflow') ? 'chat' : appDetail.mode
const appURL = `${appBaseURL}/${appMode}/${accessToken}` const appURL = `${appBaseURL}/${basePath}/${appMode}/${accessToken}`
const isChatApp = ['chat', 'agent-chat', 'completion'].includes(appDetail?.mode || '') const isChatApp = ['chat', 'agent-chat', 'completion'].includes(appDetail?.mode || '')
const language = useGetLanguage() const language = useGetLanguage()
@ -120,7 +121,7 @@ const AppPublisher = ({
try { try {
const { installed_apps }: any = await fetchInstalledAppList(appDetail?.id) || {} const { installed_apps }: any = await fetchInstalledAppList(appDetail?.id) || {}
if (installed_apps?.length > 0) if (installed_apps?.length > 0)
window.open(`/explore/installed/${installed_apps[0].id}`, '_blank') window.open(`${basePath}/explore/installed/${installed_apps[0].id}`, '_blank')
else else
throw new Error('No app found in Explore') throw new Error('No app found in Explore')
} }

@ -2,6 +2,7 @@
import type { FC } from 'react' import type { FC } from 'react'
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react' import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import useSWR from 'swr' import useSWR from 'swr'
import { basePath } from '@/utils/var'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { useContext } from 'use-context-selector' import { useContext } from 'use-context-selector'
import { usePathname } from 'next/navigation' import { usePathname } from 'next/navigation'
@ -503,6 +504,12 @@ const Configuration: FC = () => {
useEffect(() => { useEffect(() => {
(async () => { (async () => {
const collectionList = await fetchCollectionList() const collectionList = await fetchCollectionList()
if (basePath) {
collectionList.forEach((item) => {
if (typeof item.icon == 'string' && !item.icon.includes(basePath))
item.icon = `${basePath}${item.icon}`
})
}
setCollectionList(collectionList) setCollectionList(collectionList)
fetchAppDetail({ url: '/apps', id: appId }).then(async (res: any) => { fetchAppDetail({ url: '/apps', id: appId }).then(async (res: any) => {
setMode(res.mode) setMode(res.mode)

@ -14,6 +14,7 @@ import type { AppIconSelection } from '../../base/app-icon-picker'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import Divider from '@/app/components/base/divider' import Divider from '@/app/components/base/divider'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
import { basePath } from '@/utils/var'
import AppsContext, { useAppContext } from '@/context/app-context' import AppsContext, { useAppContext } from '@/context/app-context'
import { useProviderContext } from '@/context/provider-context' import { useProviderContext } from '@/context/provider-context'
import { ToastContext } from '@/app/components/base/toast' import { ToastContext } from '@/app/components/base/toast'
@ -352,11 +353,11 @@ function AppScreenShot({ mode, show }: { mode: AppMode; show: boolean }) {
'workflow': 'Workflow', 'workflow': 'Workflow',
} }
return <picture> return <picture>
<source media="(resolution: 1x)" srcSet={`/screenshots/${theme}/${modeToImageMap[mode]}.png`} /> <source media="(resolution: 1x)" srcSet={`${basePath}/screenshots/${theme}/${modeToImageMap[mode]}.png`} />
<source media="(resolution: 2x)" srcSet={`/screenshots/${theme}/${modeToImageMap[mode]}@2x.png`} /> <source media="(resolution: 2x)" srcSet={`${basePath}/screenshots/${theme}/${modeToImageMap[mode]}@2x.png`} />
<source media="(resolution: 3x)" srcSet={`/screenshots/${theme}/${modeToImageMap[mode]}@3x.png`} /> <source media="(resolution: 3x)" srcSet={`${basePath}/screenshots/${theme}/${modeToImageMap[mode]}@3x.png`} />
<Image className={show ? '' : 'hidden'} <Image className={show ? '' : 'hidden'}
src={`/screenshots/${theme}/${modeToImageMap[mode]}.png`} src={`${basePath}/screenshots/${theme}/${modeToImageMap[mode]}.png`}
alt='App Screen Shot' alt='App Screen Shot'
width={664} height={448} /> width={664} height={448} />
</picture> </picture>

@ -7,6 +7,7 @@ import { usePathname } from 'next/navigation'
import { useDebounce } from 'ahooks' import { useDebounce } from 'ahooks'
import { omit } from 'lodash-es' import { omit } from 'lodash-es'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { basePath } from '@/utils/var'
import { Trans, useTranslation } from 'react-i18next' import { Trans, useTranslation } from 'react-i18next'
import List from './list' import List from './list'
import Filter, { TIME_PERIOD_MAPPING } from './filter' import Filter, { TIME_PERIOD_MAPPING } from './filter'
@ -109,7 +110,7 @@ const Logs: FC<ILogsProps> = ({ appDetail }) => {
? <Loading type='app' /> ? <Loading type='app' />
: total > 0 : total > 0
? <List logs={isChatMode ? chatConversations : completionConversations} appDetail={appDetail} onRefresh={isChatMode ? mutateChatList : mutateCompletionList} /> ? <List logs={isChatMode ? chatConversations : completionConversations} appDetail={appDetail} onRefresh={isChatMode ? mutateChatList : mutateCompletionList} />
: <EmptyElement appUrl={`${appDetail.site.app_base_url}/${getWebAppType(appDetail.mode)}/${appDetail.site.access_token}`} /> : <EmptyElement appUrl={`${appDetail.site.app_base_url}${basePath}/${getWebAppType(appDetail.mode)}/${appDetail.site.access_token}`} />
} }
{/* Show Pagination only if the total is more than the limit */} {/* Show Pagination only if the total is more than the limit */}
{(total && total > APP_PAGE_LIMIT) {(total && total > APP_PAGE_LIMIT)

@ -17,6 +17,7 @@ import type { ConfigParams } from './settings'
import Tooltip from '@/app/components/base/tooltip' import Tooltip from '@/app/components/base/tooltip'
import AppBasic from '@/app/components/app-sidebar/basic' import AppBasic from '@/app/components/app-sidebar/basic'
import { asyncRunSafe, randomString } from '@/utils' import { asyncRunSafe, randomString } from '@/utils'
import { basePath } from '@/utils/var'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import Switch from '@/app/components/base/switch' import Switch from '@/app/components/base/switch'
import Divider from '@/app/components/base/divider' import Divider from '@/app/components/base/divider'
@ -88,7 +89,7 @@ function AppCard({
const runningStatus = isApp ? appInfo.enable_site : appInfo.enable_api const runningStatus = isApp ? appInfo.enable_site : appInfo.enable_api
const { app_base_url, access_token } = appInfo.site ?? {} const { app_base_url, access_token } = appInfo.site ?? {}
const appMode = (appInfo.mode !== 'completion' && appInfo.mode !== 'workflow') ? 'chat' : appInfo.mode const appMode = (appInfo.mode !== 'completion' && appInfo.mode !== 'workflow') ? 'chat' : appInfo.mode
const appUrl = `${app_base_url}/${appMode}/${access_token}` const appUrl = `${app_base_url}${basePath}/${appMode}/${access_token}`
const apiUrl = appInfo?.api_base_url const apiUrl = appInfo?.api_base_url
const genClickFuncByName = (opName: string) => { const genClickFuncByName = (opName: string) => {

@ -13,6 +13,7 @@ import { IS_CE_EDITION } from '@/config'
import type { SiteInfo } from '@/models/share' import type { SiteInfo } from '@/models/share'
import { useThemeContext } from '@/app/components/base/chat/embedded-chatbot/theme/theme-context' import { useThemeContext } from '@/app/components/base/chat/embedded-chatbot/theme/theme-context'
import ActionButton from '@/app/components/base/action-button' import ActionButton from '@/app/components/base/action-button'
import { basePath } from '@/utils/var'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
type Props = { type Props = {
@ -28,7 +29,7 @@ const OPTION_MAP = {
iframe: { iframe: {
getContent: (url: string, token: string) => getContent: (url: string, token: string) =>
`<iframe `<iframe
src="${url}/chatbot/${token}" src="${url}${basePath}/chatbot/${token}"
style="width: 100%; height: 100%; min-height: 700px" style="width: 100%; height: 100%; min-height: 700px"
frameborder="0" frameborder="0"
allow="microphone"> allow="microphone">
@ -41,17 +42,17 @@ const OPTION_MAP = {
token: '${token}'${isTestEnv token: '${token}'${isTestEnv
? `, ? `,
isDev: true` isDev: true`
: ''}${IS_CE_EDITION : ''}${IS_CE_EDITION
? `, ? `,
baseUrl: '${url}'` baseUrl: '${url}${basePath}'`
: ''}, : ''},
systemVariables: { systemVariables: {
// user_id: 'YOU CAN DEFINE USER ID HERE', // user_id: 'YOU CAN DEFINE USER ID HERE',
}, },
} }
</script> </script>
<script <script
src="${url}/embed.min.js" src="${url}${basePath}/embed.min.js"
id="${token}" id="${token}"
defer> defer>
</script> </script>
@ -66,7 +67,7 @@ const OPTION_MAP = {
</style>`, </style>`,
}, },
chromePlugin: { chromePlugin: {
getContent: (url: string, token: string) => `ChatBot URL: ${url}/chatbot/${token}`, getContent: (url: string, token: string) => `ChatBot URL: ${url}${basePath}/chatbot/${token}`,
}, },
} }
const prefixEmbedded = 'appOverview.overview.appInfo.embedded' const prefixEmbedded = 'appOverview.overview.appInfo.embedded'

@ -11,6 +11,7 @@ import timezone from 'dayjs/plugin/timezone'
import { Trans, useTranslation } from 'react-i18next' import { Trans, useTranslation } from 'react-i18next'
import Link from 'next/link' import Link from 'next/link'
import List from './list' import List from './list'
import { basePath } from '@/utils/var'
import Filter, { TIME_PERIOD_MAPPING } from './filter' import Filter, { TIME_PERIOD_MAPPING } from './filter'
import Pagination from '@/app/components/base/pagination' import Pagination from '@/app/components/base/pagination'
import Loading from '@/app/components/base/loading' import Loading from '@/app/components/base/loading'
@ -100,7 +101,7 @@ const Logs: FC<ILogsProps> = ({ appDetail }) => {
? <Loading type='app' /> ? <Loading type='app' />
: total > 0 : total > 0
? <List logs={workflowLogs} appDetail={appDetail} onRefresh={mutate} /> ? <List logs={workflowLogs} appDetail={appDetail} onRefresh={mutate} />
: <EmptyElement appUrl={`${appDetail.site.app_base_url}/${getWebAppType(appDetail.mode)}/${appDetail.site.access_token}`} /> : <EmptyElement appUrl={`${appDetail.site.app_base_url}${basePath}/${getWebAppType(appDetail.mode)}/${appDetail.site.access_token}`} />
} }
{/* Show Pagination only if the total is more than the limit */} {/* Show Pagination only if the total is more than the limit */}
{(total && total > APP_PAGE_LIMIT) {(total && total > APP_PAGE_LIMIT)

@ -52,7 +52,7 @@ function getFormattedChatList(messages: any[]) {
id: `question-${item.id}`, id: `question-${item.id}`,
content: item.query, content: item.query,
isAnswer: false, isAnswer: false,
message_files: getProcessedFilesFromResponse(questionFiles.map((item: any) => ({ ...item, related_id: item.id }))), message_files: getProcessedFilesFromResponse(questionFiles.map((item: any) => ({ ...item, related_id: item.id, upload_file_id: item.upload_file_id }))),
parentMessageId: item.parent_message_id || undefined, parentMessageId: item.parent_message_id || undefined,
}) })
const answerFiles = item.message_files?.filter((file: any) => file.belongs_to === 'assistant') || [] const answerFiles = item.message_files?.filter((file: any) => file.belongs_to === 'assistant') || []
@ -63,7 +63,7 @@ function getFormattedChatList(messages: any[]) {
feedback: item.feedback, feedback: item.feedback,
isAnswer: true, isAnswer: true,
citation: item.retriever_resources, citation: item.retriever_resources,
message_files: getProcessedFilesFromResponse(answerFiles.map((item: any) => ({ ...item, related_id: item.id }))), message_files: getProcessedFilesFromResponse(answerFiles.map((item: any) => ({ ...item, related_id: item.id, upload_file_id: item.upload_file_id }))),
parentMessageId: `question-${item.id}`, parentMessageId: `question-${item.id}`,
}) })
}) })

@ -134,7 +134,7 @@ export const getProcessedFilesFromResponse = (files: FileResponse[]) => {
progress: 100, progress: 100,
transferMethod: fileItem.transfer_method, transferMethod: fileItem.transfer_method,
supportFileType: fileItem.type, supportFileType: fileItem.type,
uploadedId: fileItem.related_id, uploadedId: fileItem.upload_file_id || fileItem.related_id,
url: fileItem.url, url: fileItem.url,
} }
}) })

@ -1,5 +1,6 @@
import classNames from '@/utils/classnames' import classNames from '@/utils/classnames'
import type { FC } from 'react' import type { FC } from 'react'
import { basePath } from '@/utils/var'
type LogoEmbeddedChatHeaderProps = { type LogoEmbeddedChatHeaderProps = {
className?: string className?: string
@ -13,7 +14,7 @@ const LogoEmbeddedChatHeader: FC<LogoEmbeddedChatHeaderProps> = ({
<source media="(resolution: 2x)" srcSet='/logo/logo-embedded-chat-header@2x.png' /> <source media="(resolution: 2x)" srcSet='/logo/logo-embedded-chat-header@2x.png' />
<source media="(resolution: 3x)" srcSet='/logo/logo-embedded-chat-header@3x.png' /> <source media="(resolution: 3x)" srcSet='/logo/logo-embedded-chat-header@3x.png' />
<img <img
src='/logo/logo-embedded-chat-header.png' src={`${basePath}/logo/logo-embedded-chat-header.png`}
alt='logo' alt='logo'
className={classNames('block h-6 w-auto', className)} className={classNames('block h-6 w-auto', className)}
/> />

@ -1,5 +1,6 @@
'use client' 'use client'
import type { FC } from 'react' import type { FC } from 'react'
import { basePath } from '@/utils/var'
import classNames from '@/utils/classnames' import classNames from '@/utils/classnames'
type LogoSiteProps = { type LogoSiteProps = {
@ -11,7 +12,7 @@ const LogoSite: FC<LogoSiteProps> = ({
}) => { }) => {
return ( return (
<img <img
src={'/logo/logo.png'} src={`${basePath}/logo/logo.png`}
className={classNames('block w-[22.651px] h-[24.5px]', className)} className={classNames('block w-[22.651px] h-[24.5px]', className)}
alt='logo' alt='logo'
/> />

@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next'
import { Menu, MenuButton, MenuItems, Transition } from '@headlessui/react' import { Menu, MenuButton, MenuItems, Transition } from '@headlessui/react'
import { RiArrowDownSLine } from '@remixicon/react' import { RiArrowDownSLine } from '@remixicon/react'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
import { basePath } from '@/utils/var'
import PlanBadge from '@/app/components/header/plan-badge' import PlanBadge from '@/app/components/header/plan-badge'
import { switchWorkspace } from '@/service/common' import { switchWorkspace } from '@/service/common'
import { useWorkspacesContext } from '@/context/workspace-context' import { useWorkspacesContext } from '@/context/workspace-context'
@ -22,7 +23,7 @@ const WorkplaceSelector = () => {
return return
await switchWorkspace({ url: '/workspaces/switch', body: { tenant_id } }) await switchWorkspace({ url: '/workspaces/switch', body: { tenant_id } })
notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') }) notify({ type: 'success', message: t('common.actionMsg.modifiedSuccessfully') })
location.assign(`${location.origin}`) location.assign(`${location.origin}${basePath}`)
} }
catch { catch {
notify({ type: 'error', message: t('common.provider.saveFailed') }) notify({ type: 'error', message: t('common.provider.saveFailed') })

@ -1,5 +1,6 @@
'use client' 'use client'
import React, { useCallback, useEffect, useRef, useState } from 'react' import React, { useCallback, useEffect, useRef, useState } from 'react'
import { basePath } from '@/utils/var'
import { t } from 'i18next' import { t } from 'i18next'
import copy from 'copy-to-clipboard' import copy from 'copy-to-clipboard'
import s from './index.module.css' import s from './index.module.css'
@ -18,7 +19,7 @@ const InvitationLink = ({
const selector = useRef(`invite-link-${randomString(4)}`) const selector = useRef(`invite-link-${randomString(4)}`)
const copyHandle = useCallback(() => { const copyHandle = useCallback(() => {
copy(`${!value.url.startsWith('http') ? window.location.origin : ''}${value.url}`) copy(`${!value.url.startsWith('http') ? window.location.origin : ''}${basePath}${value.url}`)
setIsCopied(true) setIsCopied(true)
}, [value]) }, [value])
@ -41,7 +42,7 @@ const InvitationLink = ({
<Tooltip <Tooltip
popupContent={isCopied ? `${t('appApi.copied')}` : `${t('appApi.copy')}`} popupContent={isCopied ? `${t('appApi.copied')}` : `${t('appApi.copy')}`}
> >
<div className='r-0 absolute left-0 top-0 w-full cursor-pointer truncate pl-2 pr-2' onClick={copyHandle}>{value.url}</div> <div className='r-0 absolute left-0 top-0 w-full cursor-pointer truncate pl-2 pr-2' onClick={copyHandle}>{basePath + value.url}</div>
</Tooltip> </Tooltip>
</div> </div>
<div className="h-4 shrink-0 border bg-divider-regular" /> <div className="h-4 shrink-0 border bg-divider-regular" />

@ -3,6 +3,7 @@ import type {
Model, Model,
ModelProvider, ModelProvider,
} from '../declarations' } from '../declarations'
import { basePath } from '@/utils/var'
import { useLanguage } from '../hooks' import { useLanguage } from '../hooks'
import { Group } from '@/app/components/base/icons/src/vender/other' import { Group } from '@/app/components/base/icons/src/vender/other'
import { OpenaiBlue, OpenaiViolet } from '@/app/components/base/icons/src/public/llm' import { OpenaiBlue, OpenaiViolet } from '@/app/components/base/icons/src/public/llm'
@ -30,7 +31,7 @@ const ModelIcon: FC<ModelIconProps> = ({
if (provider?.icon_small) { if (provider?.icon_small) {
return ( return (
<div className={cn('flex h-5 w-5 items-center justify-center', isDeprecated && 'opacity-50', className)}> <div className={cn('flex h-5 w-5 items-center justify-center', isDeprecated && 'opacity-50', className)}>
<img alt='model-icon' src={renderI18nObject(provider.icon_small, language)}/> <img alt='model-icon' src={basePath + renderI18nObject(provider.icon_small, language)}/>
</div> </div>
) )
} }

@ -1,5 +1,6 @@
import type { FC } from 'react' import type { FC } from 'react'
import type { ModelProvider } from '../declarations' import type { ModelProvider } from '../declarations'
import { basePath } from '@/utils/var'
import { useLanguage } from '../hooks' import { useLanguage } from '../hooks'
import { Openai } from '@/app/components/base/icons/src/vender/other' import { Openai } from '@/app/components/base/icons/src/vender/other'
import { AnthropicDark, AnthropicLight } from '@/app/components/base/icons/src/public/llm' import { AnthropicDark, AnthropicLight } from '@/app/components/base/icons/src/public/llm'
@ -40,7 +41,7 @@ const ProviderIcon: FC<ProviderIconProps> = ({
<div className={cn('inline-flex items-center gap-2', className)}> <div className={cn('inline-flex items-center gap-2', className)}>
<img <img
alt='provider-icon' alt='provider-icon'
src={renderI18nObject(provider.icon_small, language)} src={basePath + renderI18nObject(provider.icon_small, language)}
className='h-6 w-6' className='h-6 w-6'
/> />
<div className='system-md-semibold text-text-primary'> <div className='system-md-semibold text-text-primary'>

@ -14,6 +14,7 @@ import Type from './type'
import Category from './category' import Category from './category'
import Tools from './tools' import Tools from './tools'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
import { basePath } from '@/utils/var'
import I18n from '@/context/i18n' import I18n from '@/context/i18n'
import Drawer from '@/app/components/base/drawer' import Drawer from '@/app/components/base/drawer'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
@ -57,6 +58,12 @@ const AddToolModal: FC<Props> = ({
const getAllTools = async () => { const getAllTools = async () => {
setListLoading(true) setListLoading(true)
const buildInTools = await fetchAllBuiltInTools() const buildInTools = await fetchAllBuiltInTools()
if (basePath) {
buildInTools.forEach((item) => {
if (typeof item.icon == 'string' && !item.icon.includes(basePath))
item.icon = `${basePath}${item.icon}`
})
}
const customTools = await fetchAllCustomTools() const customTools = await fetchAllCustomTools()
const workflowTools = await fetchAllWorkflowTools() const workflowTools = await fetchAllWorkflowTools()
const mergedToolList = [ const mergedToolList = [

@ -2,6 +2,7 @@ import {
memo, memo,
useCallback, useCallback,
} from 'react' } from 'react'
import { basePath } from '@/utils/var'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { import {
RiAddLine, RiAddLine,
@ -53,7 +54,7 @@ const Blocks = ({
> >
<div className='flex h-[22px] w-full items-center justify-between pl-3 pr-1 text-xs font-medium text-gray-500'> <div className='flex h-[22px] w-full items-center justify-between pl-3 pr-1 text-xs font-medium text-gray-500'>
{toolWithProvider.label[language]} {toolWithProvider.label[language]}
<a className='hidden cursor-pointer items-center group-hover:flex' href={`/tools?category=${toolWithProvider.type}`} target='_blank'>{t('tools.addToolModal.manageInTools')}<ArrowUpRight className='ml-0.5 h-3 w-3' /></a> <a className='hidden cursor-pointer items-center group-hover:flex' href={`${basePath}/tools?category=${toolWithProvider.type}`} target='_blank'>{t('tools.addToolModal.manageInTools')}<ArrowUpRight className='ml-0.5 h-3 w-3' /></a>
</div> </div>
{list.map((tool) => { {list.map((tool) => {
const labelContent = (() => { const labelContent = (() => {

@ -6,6 +6,7 @@ import {
RiCloseLine, RiCloseLine,
} from '@remixicon/react' } from '@remixicon/react'
import { AuthHeaderPrefix, AuthType, CollectionType } from '../types' import { AuthHeaderPrefix, AuthType, CollectionType } from '../types'
import { basePath } from '@/utils/var'
import type { Collection, CustomCollectionBackend, Tool, WorkflowToolProviderRequest, WorkflowToolProviderResponse } from '../types' import type { Collection, CustomCollectionBackend, Tool, WorkflowToolProviderRequest, WorkflowToolProviderResponse } from '../types'
import ToolItem from './tool-item' import ToolItem from './tool-item'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
@ -276,7 +277,7 @@ const ProviderDetail = ({
variant='primary' variant='primary'
className={cn('my-3 w-[183px] shrink-0')} className={cn('my-3 w-[183px] shrink-0')}
> >
<a className='flex items-center' href={`/app/${(customCollection as WorkflowToolProviderResponse).workflow_app_id}/workflow`} rel='noreferrer' target='_blank'> <a className='flex items-center' href={`${basePath}/app/${(customCollection as WorkflowToolProviderResponse).workflow_app_id}/workflow`} rel='noreferrer' target='_blank'>
<div className='system-sm-medium'>{t('tools.openInStudio')}</div> <div className='system-sm-medium'>{t('tools.openInStudio')}</div>
<LinkExternal02 className='ml-1 h-4 w-4' /> <LinkExternal02 className='ml-1 h-4 w-4' />
</a> </a>

@ -59,6 +59,7 @@ import { CollectionType } from '@/app/components/tools/types'
import { CUSTOM_ITERATION_START_NODE } from '@/app/components/workflow/nodes/iteration-start/constants' import { CUSTOM_ITERATION_START_NODE } from '@/app/components/workflow/nodes/iteration-start/constants'
import { CUSTOM_LOOP_START_NODE } from '@/app/components/workflow/nodes/loop-start/constants' import { CUSTOM_LOOP_START_NODE } from '@/app/components/workflow/nodes/loop-start/constants'
import { useWorkflowConfig } from '@/service/use-workflow' import { useWorkflowConfig } from '@/service/use-workflow'
import { basePath } from '@/utils/var'
import { canFindTool } from '@/utils' import { canFindTool } from '@/utils'
export const useIsChatMode = () => { export const useIsChatMode = () => {
@ -446,6 +447,12 @@ export const useFetchToolsData = () => {
if (type === 'builtin') { if (type === 'builtin') {
const buildInTools = await fetchAllBuiltInTools() const buildInTools = await fetchAllBuiltInTools()
if (basePath) {
buildInTools.forEach((item) => {
if (typeof item.icon == 'string' && !item.icon.includes(basePath))
item.icon = `${basePath}${item.icon}`
})
}
workflowStore.setState({ workflowStore.setState({
buildInTools: buildInTools || [], buildInTools: buildInTools || [],
}) })

@ -3,6 +3,7 @@ import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import useSWR from 'swr' import useSWR from 'swr'
import { useSearchParams } from 'next/navigation' import { useSearchParams } from 'next/navigation'
import { basePath } from '@/utils/var'
import cn from 'classnames' import cn from 'classnames'
import { CheckCircleIcon } from '@heroicons/react/24/solid' import { CheckCircleIcon } from '@heroicons/react/24/solid'
import Input from '../components/base/input' import Input from '../components/base/input'
@ -163,7 +164,7 @@ const ChangePasswordForm = () => {
</div> </div>
<div className="mx-auto mt-6 w-full"> <div className="mx-auto mt-6 w-full">
<Button variant='primary' className='w-full'> <Button variant='primary' className='w-full'>
<a href="/signin">{t('login.passwordChanged')}</a> <a href={`${basePath}/signin`}>{t('login.passwordChanged')}</a>
</Button> </Button>
</div> </div>
</div> </div>

@ -10,6 +10,7 @@ import { zodResolver } from '@hookform/resolvers/zod'
import Loading from '../components/base/loading' import Loading from '../components/base/loading'
import Input from '../components/base/input' import Input from '../components/base/input'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import { basePath } from '@/utils/var'
import { import {
fetchInitValidateStatus, fetchInitValidateStatus,
@ -70,7 +71,7 @@ const ForgotPasswordForm = () => {
fetchSetupStatus().then(() => { fetchSetupStatus().then(() => {
fetchInitValidateStatus().then((res: InitValidateStatusResponse) => { fetchInitValidateStatus().then((res: InitValidateStatusResponse) => {
if (res.status === 'not_started') if (res.status === 'not_started')
window.location.href = '/init' window.location.href = `${basePath}/init`
}) })
setLoading(false) setLoading(false)

@ -5,6 +5,7 @@ import { useRouter } from 'next/navigation'
import Toast from '../components/base/toast' import Toast from '../components/base/toast'
import Loading from '../components/base/loading' import Loading from '../components/base/loading'
import Button from '@/app/components/base/button' import Button from '@/app/components/base/button'
import { basePath } from '@/utils/var'
import { fetchInitValidateStatus, initValidate } from '@/service/common' import { fetchInitValidateStatus, initValidate } from '@/service/common'
import type { InitValidateStatusResponse } from '@/models/common' import type { InitValidateStatusResponse } from '@/models/common'
@ -41,7 +42,7 @@ const InitPasswordPopup = () => {
useEffect(() => { useEffect(() => {
fetchInitValidateStatus().then((res: InitValidateStatusResponse) => { fetchInitValidateStatus().then((res: InitValidateStatusResponse) => {
if (res.status === 'finished') if (res.status === 'finished')
window.location.href = '/install' window.location.href = `${basePath}/install`
else else
setLoading(false) setLoading(false)
}) })

@ -80,12 +80,12 @@ const InstallForm = () => {
fetchSetupStatus().then((res: SetupStatusResponse) => { fetchSetupStatus().then((res: SetupStatusResponse) => {
if (res.step === 'finished') { if (res.step === 'finished') {
localStorage.setItem('setup_status', 'finished') localStorage.setItem('setup_status', 'finished')
window.location.href = '/signin' router.push('/signin')
} }
else { else {
fetchInitValidateStatus().then((res: InitValidateStatusResponse) => { fetchInitValidateStatus().then((res: InitValidateStatusResponse) => {
if (res.status === 'not_started') if (res.status === 'not_started')
window.location.href = '/init' router.push('/init')
}) })
} }
setLoading(false) setLoading(false)

@ -1,4 +1,5 @@
import type { Viewport } from 'next' import type { Viewport } from 'next'
import RoutePrefixHandle from './routePrefixHandle'
import I18nServer from './components/i18n-server' import I18nServer from './components/i18n-server'
import BrowserInitor from './components/browser-initor' import BrowserInitor from './components/browser-initor'
import SentryInitor from './components/sentry-initor' import SentryInitor from './components/sentry-initor'
@ -71,6 +72,7 @@ const LocaleLayout = async ({
</TanstackQueryIniter> </TanstackQueryIniter>
</SentryInitor> </SentryInitor>
</BrowserInitor> </BrowserInitor>
<RoutePrefixHandle />
</body> </body>
</html> </html>
) )

@ -0,0 +1,53 @@
'use client'
import { basePath } from '@/utils/var'
import { useEffect } from 'react'
import { usePathname } from 'next/navigation'
export default function RoutePrefixHandle() {
const pathname = usePathname()
const handleRouteChange = () => {
const addPrefixToImg = (e: HTMLImageElement) => {
const url = new URL(e.src)
const prefix = url.pathname.substr(0, basePath.length)
if (prefix !== basePath) {
url.pathname = basePath + url.pathname
e.src = url.toString()
}
}
// create an observer instance
const observer = new MutationObserver((mutationsList) => {
for (const mutation of mutationsList) {
if (mutation.type === 'childList') {
// listen for newly added img tags
mutation.addedNodes.forEach((node) => {
if (((node as HTMLElement).tagName) === 'IMG')
addPrefixToImg(node as HTMLImageElement)
})
}
else if (mutation.type === 'attributes' && (mutation.target as HTMLElement).tagName === 'IMG') {
// if the src of an existing img tag changes, update the prefix
if (mutation.attributeName === 'src')
addPrefixToImg(mutation.target as HTMLImageElement)
}
}
})
// configure observation options
const config = {
childList: true,
attributes: true,
subtree: true,
attributeFilter: ['src'],
}
observer.observe(document.body, config)
}
useEffect(() => {
if (basePath)
handleRouteChange()
}, [pathname])
return null
}

@ -24,7 +24,8 @@ export const useTabSearchParams = ({
searchParamName = 'category', searchParamName = 'category',
disableSearchParams = false, disableSearchParams = false,
}: UseTabSearchParamsOptions) => { }: UseTabSearchParamsOptions) => {
const pathName = usePathname() const pathnameFromHook = usePathname()
const pathName = window?.location?.pathname || pathnameFromHook
const searchParams = useSearchParams() const searchParams = useSearchParams()
const [activeTab, setTab] = useState<string>( const [activeTab, setTab] = useState<string>(
!disableSearchParams !disableSearchParams

@ -1,3 +1,4 @@
const { basePath } = require('./utils/var-basePath')
const { codeInspectorPlugin } = require('code-inspector-plugin') const { codeInspectorPlugin } = require('code-inspector-plugin')
const withMDX = require('@next/mdx')({ const withMDX = require('@next/mdx')({
extension: /\.mdx?$/, extension: /\.mdx?$/,
@ -14,6 +15,7 @@ const withMDX = require('@next/mdx')({
/** @type {import('next').NextConfig} */ /** @type {import('next').NextConfig} */
const nextConfig = { const nextConfig = {
basePath,
webpack: (config, { dev, isServer }) => { webpack: (config, { dev, isServer }) => {
config.plugins.push(codeInspectorPlugin({ bundler: 'webpack' })) config.plugins.push(codeInspectorPlugin({ bundler: 'webpack' }))
return config return config

@ -1,6 +1,7 @@
import { API_PREFIX, IS_CE_EDITION, PUBLIC_API_PREFIX } from '@/config' import { API_PREFIX, IS_CE_EDITION, PUBLIC_API_PREFIX } from '@/config'
import { refreshAccessTokenOrRelogin } from './refresh-token' import { refreshAccessTokenOrRelogin } from './refresh-token'
import Toast from '@/app/components/base/toast' import Toast from '@/app/components/base/toast'
import { basePath } from '@/utils/var'
import type { AnnotationReply, MessageEnd, MessageReplace, ThoughtItem } from '@/app/components/base/chat/chat/type' import type { AnnotationReply, MessageEnd, MessageReplace, ThoughtItem } from '@/app/components/base/chat/chat/type'
import type { VisionFile } from '@/types/app' import type { VisionFile } from '@/types/app'
import type { import type {
@ -466,7 +467,7 @@ export const request = async<T>(url: string, options = {}, otherOptions?: IOther
const errResp: Response = err as any const errResp: Response = err as any
if (errResp.status === 401) { if (errResp.status === 401) {
const [parseErr, errRespData] = await asyncRunSafe<ResponseError>(errResp.json()) const [parseErr, errRespData] = await asyncRunSafe<ResponseError>(errResp.json())
const loginUrl = `${globalThis.location.origin}/signin` const loginUrl = `${globalThis.location.origin}${basePath}/signin`
if (parseErr) { if (parseErr) {
globalThis.location.href = loginUrl globalThis.location.href = loginUrl
return Promise.reject(err) return Promise.reject(err)
@ -498,11 +499,11 @@ export const request = async<T>(url: string, options = {}, otherOptions?: IOther
return Promise.reject(err) return Promise.reject(err)
} }
if (code === 'not_init_validated' && IS_CE_EDITION) { if (code === 'not_init_validated' && IS_CE_EDITION) {
globalThis.location.href = `${globalThis.location.origin}/init` globalThis.location.href = `${globalThis.location.origin}${basePath}/init`
return Promise.reject(err) return Promise.reject(err)
} }
if (code === 'not_setup' && IS_CE_EDITION) { if (code === 'not_setup' && IS_CE_EDITION) {
globalThis.location.href = `${globalThis.location.origin}/install` globalThis.location.href = `${globalThis.location.origin}${basePath}/install`
return Promise.reject(err) return Promise.reject(err)
} }
@ -510,7 +511,7 @@ export const request = async<T>(url: string, options = {}, otherOptions?: IOther
const [refreshErr] = await asyncRunSafe(refreshAccessTokenOrRelogin(TIME_OUT)) const [refreshErr] = await asyncRunSafe(refreshAccessTokenOrRelogin(TIME_OUT))
if (refreshErr === null) if (refreshErr === null)
return baseFetch<T>(url, options, otherOptionsForBaseFetch) return baseFetch<T>(url, options, otherOptionsForBaseFetch)
if (location.pathname !== '/signin' || !IS_CE_EDITION) { if (location.pathname !== `${basePath}/signin` || !IS_CE_EDITION) {
globalThis.location.href = loginUrl globalThis.location.href = loginUrl
return Promise.reject(err) return Promise.reject(err)
} }

@ -197,6 +197,7 @@ export type FileResponse = {
transfer_method: TransferMethod transfer_method: TransferMethod
type: string type: string
url: string url: string
upload_file_id: string
} }
export type NodeFinishedResponse = { export type NodeFinishedResponse = {

@ -0,0 +1,5 @@
// export basePath to next.config.js
// same as the one exported from var.ts
module.exports = {
basePath: '',
}

@ -104,3 +104,7 @@ export const getVars = (value: string) => {
}) })
return res return res
} }
// Set the value of basePath
// example: /dify
export const basePath = ''

Loading…
Cancel
Save