mirror of
https://github.com/langgenius/dify.git
synced 2025-12-21 15:02:26 +00:00
Compare commits
5 Commits
13604-when
...
0.10.2-fix
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f8c6eef170 | ||
|
|
737b2d5238 | ||
|
|
17ba978d1a | ||
|
|
fa6d2874b3 | ||
|
|
411b92893a |
4
.github/workflows/build-push.yml
vendored
4
.github/workflows/build-push.yml
vendored
@@ -5,8 +5,8 @@ on:
|
|||||||
branches:
|
branches:
|
||||||
- "main"
|
- "main"
|
||||||
- "deploy/dev"
|
- "deploy/dev"
|
||||||
release:
|
tags:
|
||||||
types: [published]
|
- "*"
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: build-push-${{ github.head_ref || github.run_id }}
|
group: build-push-${{ github.head_ref || github.run_id }}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ FROM python:3.10-slim-bookworm AS base
|
|||||||
WORKDIR /app/api
|
WORKDIR /app/api
|
||||||
|
|
||||||
# Install Poetry
|
# Install Poetry
|
||||||
ENV POETRY_VERSION=1.8.3
|
ENV POETRY_VERSION=1.8.4
|
||||||
|
|
||||||
# if you located in China, you can use aliyun mirror to speed up
|
# if you located in China, you can use aliyun mirror to speed up
|
||||||
# RUN pip install --no-cache-dir poetry==${POETRY_VERSION} -i https://mirrors.aliyun.com/pypi/simple/
|
# RUN pip install --no-cache-dir poetry==${POETRY_VERSION} -i https://mirrors.aliyun.com/pypi/simple/
|
||||||
@@ -55,7 +55,7 @@ RUN apt-get update \
|
|||||||
&& echo "deb http://deb.debian.org/debian testing main" > /etc/apt/sources.list \
|
&& echo "deb http://deb.debian.org/debian testing main" > /etc/apt/sources.list \
|
||||||
&& apt-get update \
|
&& apt-get update \
|
||||||
# For Security
|
# For Security
|
||||||
&& apt-get install -y --no-install-recommends zlib1g=1:1.3.dfsg+really1.3.1-1 expat=2.6.3-1 libldap-2.5-0=2.5.18+dfsg-3+b1 perl=5.40.0-6 libsqlite3-0=3.46.1-1 \
|
&& apt-get install -y --no-install-recommends expat=2.6.3-2 libldap-2.5-0=2.5.18+dfsg-3+b1 perl=5.40.0-6 libsqlite3-0=3.46.1-1 zlib1g=1:1.3.dfsg+really1.3.1-1+b1 \
|
||||||
# install a chinese font to support the use of tools like matplotlib
|
# install a chinese font to support the use of tools like matplotlib
|
||||||
&& apt-get install -y fonts-noto-cjk \
|
&& apt-get install -y fonts-noto-cjk \
|
||||||
&& apt-get autoremove -y \
|
&& apt-get autoremove -y \
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ class PackagingInfo(BaseSettings):
|
|||||||
|
|
||||||
CURRENT_VERSION: str = Field(
|
CURRENT_VERSION: str = Field(
|
||||||
description="Dify version",
|
description="Dify version",
|
||||||
default="0.10.2",
|
default="0.10.2-fix1",
|
||||||
)
|
)
|
||||||
|
|
||||||
COMMIT_SHA: str = Field(
|
COMMIT_SHA: str = Field(
|
||||||
|
|||||||
@@ -89,14 +89,13 @@ class RemoteFileInfoApi(Resource):
|
|||||||
@marshal_with(remote_file_info_fields)
|
@marshal_with(remote_file_info_fields)
|
||||||
def get(self, url):
|
def get(self, url):
|
||||||
decoded_url = urllib.parse.unquote(url)
|
decoded_url = urllib.parse.unquote(url)
|
||||||
try:
|
resp = ssrf_proxy.head(decoded_url)
|
||||||
response = ssrf_proxy.head(decoded_url)
|
if resp.status_code != 200:
|
||||||
return {
|
resp = ssrf_proxy.get(decoded_url, timeout=3)
|
||||||
"file_type": response.headers.get("Content-Type", "application/octet-stream"),
|
return {
|
||||||
"file_length": int(response.headers.get("Content-Length", 0)),
|
"file_type": resp.headers.get("Content-Type", "application/octet-stream"),
|
||||||
}
|
"file_length": int(resp.headers.get("Content-Length", 0)),
|
||||||
except Exception as e:
|
}
|
||||||
return {"error": str(e)}, 400
|
|
||||||
|
|
||||||
|
|
||||||
api.add_resource(FileApi, "/files/upload")
|
api.add_resource(FileApi, "/files/upload")
|
||||||
|
|||||||
@@ -42,14 +42,13 @@ class RemoteFileInfoApi(WebApiResource):
|
|||||||
@marshal_with(remote_file_info_fields)
|
@marshal_with(remote_file_info_fields)
|
||||||
def get(self, url):
|
def get(self, url):
|
||||||
decoded_url = urllib.parse.unquote(url)
|
decoded_url = urllib.parse.unquote(url)
|
||||||
try:
|
resp = ssrf_proxy.head(decoded_url)
|
||||||
response = ssrf_proxy.head(decoded_url)
|
if resp.status_code != 200:
|
||||||
return {
|
resp = ssrf_proxy.get(decoded_url, timeout=3)
|
||||||
"file_type": response.headers.get("Content-Type", "application/octet-stream"),
|
return {
|
||||||
"file_length": int(response.headers.get("Content-Length", -1)),
|
"file_type": resp.headers.get("Content-Type", "application/octet-stream"),
|
||||||
}
|
"file_length": int(resp.headers.get("Content-Length", -1)),
|
||||||
except Exception as e:
|
}
|
||||||
return {"error": str(e)}, 400
|
|
||||||
|
|
||||||
|
|
||||||
api.add_resource(FileApi, "/files/upload")
|
api.add_resource(FileApi, "/files/upload")
|
||||||
|
|||||||
@@ -89,15 +89,6 @@ class Executor:
|
|||||||
headers = self.variable_pool.convert_template(self.node_data.headers).text
|
headers = self.variable_pool.convert_template(self.node_data.headers).text
|
||||||
self.headers = _plain_text_to_dict(headers)
|
self.headers = _plain_text_to_dict(headers)
|
||||||
|
|
||||||
body = self.node_data.body
|
|
||||||
if body is None:
|
|
||||||
return
|
|
||||||
if "content-type" not in (k.lower() for k in self.headers) and body.type in BODY_TYPE_TO_CONTENT_TYPE:
|
|
||||||
self.headers["Content-Type"] = BODY_TYPE_TO_CONTENT_TYPE[body.type]
|
|
||||||
if body.type == "form-data":
|
|
||||||
self.boundary = f"----WebKitFormBoundary{_generate_random_string(16)}"
|
|
||||||
self.headers["Content-Type"] = f"multipart/form-data; boundary={self.boundary}"
|
|
||||||
|
|
||||||
def _init_body(self):
|
def _init_body(self):
|
||||||
body = self.node_data.body
|
body = self.node_data.body
|
||||||
if body is not None:
|
if body is not None:
|
||||||
@@ -146,9 +137,8 @@ class Executor:
|
|||||||
for k, v in files.items()
|
for k, v in files.items()
|
||||||
if v.related_id is not None
|
if v.related_id is not None
|
||||||
}
|
}
|
||||||
|
|
||||||
self.data = form_data
|
self.data = form_data
|
||||||
self.files = files
|
self.files = files or None
|
||||||
|
|
||||||
def _assembling_headers(self) -> dict[str, Any]:
|
def _assembling_headers(self) -> dict[str, Any]:
|
||||||
authorization = deepcopy(self.auth)
|
authorization = deepcopy(self.auth)
|
||||||
@@ -209,6 +199,7 @@ class Executor:
|
|||||||
"timeout": (self.timeout.connect, self.timeout.read, self.timeout.write),
|
"timeout": (self.timeout.connect, self.timeout.read, self.timeout.write),
|
||||||
"follow_redirects": True,
|
"follow_redirects": True,
|
||||||
}
|
}
|
||||||
|
# request_args = {k: v for k, v in request_args.items() if v is not None}
|
||||||
|
|
||||||
response = getattr(ssrf_proxy, self.method)(**request_args)
|
response = getattr(ssrf_proxy, self.method)(**request_args)
|
||||||
return response
|
return response
|
||||||
@@ -236,6 +227,13 @@ class Executor:
|
|||||||
raw += f"Host: {url_parts.netloc}\r\n"
|
raw += f"Host: {url_parts.netloc}\r\n"
|
||||||
|
|
||||||
headers = self._assembling_headers()
|
headers = self._assembling_headers()
|
||||||
|
body = self.node_data.body
|
||||||
|
boundary = f"----WebKitFormBoundary{_generate_random_string(16)}"
|
||||||
|
if body:
|
||||||
|
if "content-type" not in (k.lower() for k in self.headers) and body.type in BODY_TYPE_TO_CONTENT_TYPE:
|
||||||
|
headers["Content-Type"] = BODY_TYPE_TO_CONTENT_TYPE[body.type]
|
||||||
|
if body.type == "form-data":
|
||||||
|
headers["Content-Type"] = f"multipart/form-data; boundary={boundary}"
|
||||||
for k, v in headers.items():
|
for k, v in headers.items():
|
||||||
if self.auth.type == "api-key":
|
if self.auth.type == "api-key":
|
||||||
authorization_header = "Authorization"
|
authorization_header = "Authorization"
|
||||||
@@ -248,7 +246,6 @@ class Executor:
|
|||||||
|
|
||||||
body = ""
|
body = ""
|
||||||
if self.files:
|
if self.files:
|
||||||
boundary = self.boundary
|
|
||||||
for k, v in self.files.items():
|
for k, v in self.files.items():
|
||||||
body += f"--{boundary}\r\n"
|
body += f"--{boundary}\r\n"
|
||||||
body += f'Content-Disposition: form-data; name="{k}"\r\n\r\n'
|
body += f'Content-Disposition: form-data; name="{k}"\r\n\r\n'
|
||||||
@@ -263,7 +260,6 @@ class Executor:
|
|||||||
elif self.data and self.node_data.body.type == "x-www-form-urlencoded":
|
elif self.data and self.node_data.body.type == "x-www-form-urlencoded":
|
||||||
body = urlencode(self.data)
|
body = urlencode(self.data)
|
||||||
elif self.data and self.node_data.body.type == "form-data":
|
elif self.data and self.node_data.body.type == "form-data":
|
||||||
boundary = self.boundary
|
|
||||||
for key, value in self.data.items():
|
for key, value in self.data.items():
|
||||||
body += f"--{boundary}\r\n"
|
body += f"--{boundary}\r\n"
|
||||||
body += f'Content-Disposition: form-data; name="{key}"\r\n\r\n'
|
body += f'Content-Disposition: form-data; name="{key}"\r\n\r\n'
|
||||||
|
|||||||
@@ -184,6 +184,8 @@ def _build_from_remote_url(
|
|||||||
filename = url.split("/")[-1].split("?")[0] or "unknown_file"
|
filename = url.split("/")[-1].split("?")[0] or "unknown_file"
|
||||||
|
|
||||||
resp = ssrf_proxy.head(url, follow_redirects=True)
|
resp = ssrf_proxy.head(url, follow_redirects=True)
|
||||||
|
if resp.status_code != httpx.codes.OK:
|
||||||
|
resp = ssrf_proxy.get(url, follow_redirects=True, timeout=3)
|
||||||
if resp.status_code == httpx.codes.OK:
|
if resp.status_code == httpx.codes.OK:
|
||||||
if content_disposition := resp.headers.get("Content-Disposition"):
|
if content_disposition := resp.headers.get("Content-Disposition"):
|
||||||
filename = content_disposition.split("filename=")[-1].strip('"')
|
filename = content_disposition.split("filename=")[-1].strip('"')
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ v0_9_0_release_date= '2024-09-29 12:00:00'
|
|||||||
def upgrade():
|
def upgrade():
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
sql = f"""UPDATE
|
sql = f"""UPDATE
|
||||||
public.messages
|
messages
|
||||||
SET
|
SET
|
||||||
parent_message_id = '{UUID_NIL}'
|
parent_message_id = '{UUID_NIL}'
|
||||||
WHERE
|
WHERE
|
||||||
@@ -37,7 +37,7 @@ WHERE
|
|||||||
def downgrade():
|
def downgrade():
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
sql = f"""UPDATE
|
sql = f"""UPDATE
|
||||||
public.messages
|
messages
|
||||||
SET
|
SET
|
||||||
parent_message_id = NULL
|
parent_message_id = NULL
|
||||||
WHERE
|
WHERE
|
||||||
|
|||||||
@@ -367,3 +367,97 @@ def test_executor_with_json_body_and_nested_object_variable():
|
|||||||
assert '"name": "John Doe"' in raw_request
|
assert '"name": "John Doe"' in raw_request
|
||||||
assert '"age": 30' in raw_request
|
assert '"age": 30' in raw_request
|
||||||
assert '"email": "john@example.com"' in raw_request
|
assert '"email": "john@example.com"' in raw_request
|
||||||
|
|
||||||
|
|
||||||
|
def test_extract_selectors_from_template_with_newline():
|
||||||
|
variable_pool = VariablePool()
|
||||||
|
variable_pool.add(("node_id", "custom_query"), "line1\nline2")
|
||||||
|
node_data = HttpRequestNodeData(
|
||||||
|
title="Test JSON Body with Nested Object Variable",
|
||||||
|
method="post",
|
||||||
|
url="https://api.example.com/data",
|
||||||
|
authorization=HttpRequestNodeAuthorization(type="no-auth"),
|
||||||
|
headers="Content-Type: application/json",
|
||||||
|
params="test: {{#node_id.custom_query#}}",
|
||||||
|
body=HttpRequestNodeBody(
|
||||||
|
type="none",
|
||||||
|
data=[],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
executor = Executor(
|
||||||
|
node_data=node_data,
|
||||||
|
timeout=HttpRequestNodeTimeout(connect=10, read=30, write=30),
|
||||||
|
variable_pool=variable_pool,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert executor.params == {"test": "line1\nline2"}
|
||||||
|
|
||||||
|
|
||||||
|
def test_executor_with_form_data():
|
||||||
|
# Prepare the variable pool
|
||||||
|
variable_pool = VariablePool(
|
||||||
|
system_variables={},
|
||||||
|
user_inputs={},
|
||||||
|
)
|
||||||
|
variable_pool.add(["pre_node_id", "text_field"], "Hello, World!")
|
||||||
|
variable_pool.add(["pre_node_id", "number_field"], 42)
|
||||||
|
|
||||||
|
# Prepare the node data
|
||||||
|
node_data = HttpRequestNodeData(
|
||||||
|
title="Test Form Data",
|
||||||
|
method="post",
|
||||||
|
url="https://api.example.com/upload",
|
||||||
|
authorization=HttpRequestNodeAuthorization(type="no-auth"),
|
||||||
|
headers="Content-Type: multipart/form-data",
|
||||||
|
params="",
|
||||||
|
body=HttpRequestNodeBody(
|
||||||
|
type="form-data",
|
||||||
|
data=[
|
||||||
|
BodyData(
|
||||||
|
key="text_field",
|
||||||
|
type="text",
|
||||||
|
value="{{#pre_node_id.text_field#}}",
|
||||||
|
),
|
||||||
|
BodyData(
|
||||||
|
key="number_field",
|
||||||
|
type="text",
|
||||||
|
value="{{#pre_node_id.number_field#}}",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
# Initialize the Executor
|
||||||
|
executor = Executor(
|
||||||
|
node_data=node_data,
|
||||||
|
timeout=HttpRequestNodeTimeout(connect=10, read=30, write=30),
|
||||||
|
variable_pool=variable_pool,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Check the executor's data
|
||||||
|
assert executor.method == "post"
|
||||||
|
assert executor.url == "https://api.example.com/upload"
|
||||||
|
assert "Content-Type" in executor.headers
|
||||||
|
assert "multipart/form-data" in executor.headers["Content-Type"]
|
||||||
|
assert executor.params == {}
|
||||||
|
assert executor.json is None
|
||||||
|
assert executor.files is None
|
||||||
|
assert executor.content is None
|
||||||
|
|
||||||
|
# Check that the form data is correctly loaded in executor.data
|
||||||
|
assert isinstance(executor.data, dict)
|
||||||
|
assert "text_field" in executor.data
|
||||||
|
assert executor.data["text_field"] == "Hello, World!"
|
||||||
|
assert "number_field" in executor.data
|
||||||
|
assert executor.data["number_field"] == "42"
|
||||||
|
|
||||||
|
# Check the raw request (to_log method)
|
||||||
|
raw_request = executor.to_log()
|
||||||
|
assert "POST /upload HTTP/1.1" in raw_request
|
||||||
|
assert "Host: api.example.com" in raw_request
|
||||||
|
assert "Content-Type: multipart/form-data" in raw_request
|
||||||
|
assert "text_field" in raw_request
|
||||||
|
assert "Hello, World!" in raw_request
|
||||||
|
assert "number_field" in raw_request
|
||||||
|
assert "42" in raw_request
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ version: '3'
|
|||||||
services:
|
services:
|
||||||
# API service
|
# API service
|
||||||
api:
|
api:
|
||||||
image: langgenius/dify-api:0.10.2
|
image: langgenius/dify-api:0.10.2-fix1
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
# Startup mode, 'api' starts the API server.
|
# Startup mode, 'api' starts the API server.
|
||||||
@@ -227,7 +227,7 @@ services:
|
|||||||
# worker service
|
# worker service
|
||||||
# The Celery worker for processing the queue.
|
# The Celery worker for processing the queue.
|
||||||
worker:
|
worker:
|
||||||
image: langgenius/dify-api:0.10.2
|
image: langgenius/dify-api:0.10.2-fix1
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
CONSOLE_WEB_URL: ''
|
CONSOLE_WEB_URL: ''
|
||||||
@@ -396,7 +396,7 @@ services:
|
|||||||
|
|
||||||
# Frontend web application.
|
# Frontend web application.
|
||||||
web:
|
web:
|
||||||
image: langgenius/dify-web:0.10.2
|
image: langgenius/dify-web:0.10.2-fix1
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
# The base URL of console application api server, refers to the Console base URL of WEB service if console domain is
|
# The base URL of console application api server, refers to the Console base URL of WEB service if console domain is
|
||||||
|
|||||||
@@ -242,7 +242,7 @@ x-shared-env: &shared-api-worker-env
|
|||||||
services:
|
services:
|
||||||
# API service
|
# API service
|
||||||
api:
|
api:
|
||||||
image: langgenius/dify-api:0.10.2
|
image: langgenius/dify-api:0.10.2-fix1
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
# Use the shared environment variables.
|
# Use the shared environment variables.
|
||||||
@@ -262,7 +262,7 @@ services:
|
|||||||
# worker service
|
# worker service
|
||||||
# The Celery worker for processing the queue.
|
# The Celery worker for processing the queue.
|
||||||
worker:
|
worker:
|
||||||
image: langgenius/dify-api:0.10.2
|
image: langgenius/dify-api:0.10.2-fix1
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
# Use the shared environment variables.
|
# Use the shared environment variables.
|
||||||
@@ -281,7 +281,7 @@ services:
|
|||||||
|
|
||||||
# Frontend web application.
|
# Frontend web application.
|
||||||
web:
|
web:
|
||||||
image: langgenius/dify-web:0.10.2
|
image: langgenius/dify-web:0.10.2-fix1
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
CONSOLE_API_URL: ${CONSOLE_API_URL:-}
|
CONSOLE_API_URL: ${CONSOLE_API_URL:-}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "dify-web",
|
"name": "dify-web",
|
||||||
"version": "0.10.2",
|
"version": "0.10.2-fix1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.17.0"
|
"node": ">=18.17.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user