mirror of
https://github.com/langgenius/dify.git
synced 2025-12-22 07:17:26 +00:00
Compare commits
22 Commits
feat/detec
...
0.15.6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bfdce78ca5 | ||
|
|
00c2258352 | ||
|
|
a1b3d41712 | ||
|
|
b26e20fe34 | ||
|
|
161ff432f1 | ||
|
|
99a9def623 | ||
|
|
fe1846c437 | ||
|
|
8e75eb5c63 | ||
|
|
970508fcb6 | ||
|
|
9283a5414f | ||
|
|
2a2a0e9be9 | ||
|
|
061a765b7d | ||
|
|
acd7fead87 | ||
|
|
bbb080d5b2 | ||
|
|
c01d8a70f3 | ||
|
|
1ca15989e0 | ||
|
|
8b5a3a9424 | ||
|
|
42ddcf1edd | ||
|
|
21561df10f | ||
|
|
0e33a3aa5f | ||
|
|
d3895bcd6b | ||
|
|
eeb390650b |
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 }}
|
||||||
|
|||||||
18
CHANGELOG.md
Normal file
18
CHANGELOG.md
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to Dify will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [0.15.6] - 2025-04-22
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
|
- Fixed clickjacking vulnerability (#18552)
|
||||||
|
- Fixed reset password security issue (#18366)
|
||||||
|
- Updated reset password token when email code verification succeeds (#18362)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed Vertex AI Gemini 2.0 Flash 001 schema (#18405)
|
||||||
@@ -430,4 +430,7 @@ CREATE_TIDB_SERVICE_JOB_ENABLED=false
|
|||||||
# Maximum number of submitted thread count in a ThreadPool for parallel node execution
|
# Maximum number of submitted thread count in a ThreadPool for parallel node execution
|
||||||
MAX_SUBMIT_COUNT=100
|
MAX_SUBMIT_COUNT=100
|
||||||
# Lockout duration in seconds
|
# Lockout duration in seconds
|
||||||
LOGIN_LOCKOUT_DURATION=86400
|
LOGIN_LOCKOUT_DURATION=86400
|
||||||
|
|
||||||
|
# Prevent Clickjacking
|
||||||
|
ALLOW_EMBED=false
|
||||||
@@ -9,7 +9,7 @@ class PackagingInfo(BaseSettings):
|
|||||||
|
|
||||||
CURRENT_VERSION: str = Field(
|
CURRENT_VERSION: str = Field(
|
||||||
description="Dify version",
|
description="Dify version",
|
||||||
default="0.15.3",
|
default="0.15.6",
|
||||||
)
|
)
|
||||||
|
|
||||||
COMMIT_SHA: str = Field(
|
COMMIT_SHA: str = Field(
|
||||||
|
|||||||
@@ -6,9 +6,13 @@ from flask_restful import Resource, reqparse # type: ignore
|
|||||||
|
|
||||||
from constants.languages import languages
|
from constants.languages import languages
|
||||||
from controllers.console import api
|
from controllers.console import api
|
||||||
from controllers.console.auth.error import EmailCodeError, InvalidEmailError, InvalidTokenError, PasswordMismatchError
|
from controllers.console.auth.error import (EmailCodeError, InvalidEmailError,
|
||||||
from controllers.console.error import AccountInFreezeError, AccountNotFound, EmailSendIpLimitError
|
InvalidTokenError,
|
||||||
from controllers.console.wraps import setup_required
|
PasswordMismatchError)
|
||||||
|
from controllers.console.error import (AccountInFreezeError, AccountNotFound,
|
||||||
|
EmailSendIpLimitError)
|
||||||
|
from controllers.console.wraps import (email_password_login_enabled,
|
||||||
|
setup_required)
|
||||||
from events.tenant_event import tenant_was_created
|
from events.tenant_event import tenant_was_created
|
||||||
from extensions.ext_database import db
|
from extensions.ext_database import db
|
||||||
from libs.helper import email, extract_remote_ip
|
from libs.helper import email, extract_remote_ip
|
||||||
@@ -22,6 +26,7 @@ from services.feature_service import FeatureService
|
|||||||
|
|
||||||
class ForgotPasswordSendEmailApi(Resource):
|
class ForgotPasswordSendEmailApi(Resource):
|
||||||
@setup_required
|
@setup_required
|
||||||
|
@email_password_login_enabled
|
||||||
def post(self):
|
def post(self):
|
||||||
parser = reqparse.RequestParser()
|
parser = reqparse.RequestParser()
|
||||||
parser.add_argument("email", type=email, required=True, location="json")
|
parser.add_argument("email", type=email, required=True, location="json")
|
||||||
@@ -53,6 +58,7 @@ class ForgotPasswordSendEmailApi(Resource):
|
|||||||
|
|
||||||
class ForgotPasswordCheckApi(Resource):
|
class ForgotPasswordCheckApi(Resource):
|
||||||
@setup_required
|
@setup_required
|
||||||
|
@email_password_login_enabled
|
||||||
def post(self):
|
def post(self):
|
||||||
parser = reqparse.RequestParser()
|
parser = reqparse.RequestParser()
|
||||||
parser.add_argument("email", type=str, required=True, location="json")
|
parser.add_argument("email", type=str, required=True, location="json")
|
||||||
@@ -72,11 +78,20 @@ class ForgotPasswordCheckApi(Resource):
|
|||||||
if args["code"] != token_data.get("code"):
|
if args["code"] != token_data.get("code"):
|
||||||
raise EmailCodeError()
|
raise EmailCodeError()
|
||||||
|
|
||||||
return {"is_valid": True, "email": token_data.get("email")}
|
# Verified, revoke the first token
|
||||||
|
AccountService.revoke_reset_password_token(args["token"])
|
||||||
|
|
||||||
|
# Refresh token data by generating a new token
|
||||||
|
_, new_token = AccountService.generate_reset_password_token(
|
||||||
|
user_email, code=args["code"], additional_data={"phase": "reset"}
|
||||||
|
)
|
||||||
|
|
||||||
|
return {"is_valid": True, "email": token_data.get("email"), "token": new_token}
|
||||||
|
|
||||||
|
|
||||||
class ForgotPasswordResetApi(Resource):
|
class ForgotPasswordResetApi(Resource):
|
||||||
@setup_required
|
@setup_required
|
||||||
|
@email_password_login_enabled
|
||||||
def post(self):
|
def post(self):
|
||||||
parser = reqparse.RequestParser()
|
parser = reqparse.RequestParser()
|
||||||
parser.add_argument("token", type=str, required=True, nullable=False, location="json")
|
parser.add_argument("token", type=str, required=True, nullable=False, location="json")
|
||||||
@@ -95,6 +110,9 @@ class ForgotPasswordResetApi(Resource):
|
|||||||
|
|
||||||
if reset_data is None:
|
if reset_data is None:
|
||||||
raise InvalidTokenError()
|
raise InvalidTokenError()
|
||||||
|
# Must use token in reset phase
|
||||||
|
if reset_data.get("phase", "") != "reset":
|
||||||
|
raise InvalidTokenError()
|
||||||
|
|
||||||
AccountService.revoke_reset_password_token(token)
|
AccountService.revoke_reset_password_token(token)
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ from controllers.console.error import (
|
|||||||
EmailSendIpLimitError,
|
EmailSendIpLimitError,
|
||||||
NotAllowedCreateWorkspace,
|
NotAllowedCreateWorkspace,
|
||||||
)
|
)
|
||||||
from controllers.console.wraps import setup_required
|
from controllers.console.wraps import email_password_login_enabled, setup_required
|
||||||
from events.tenant_event import tenant_was_created
|
from events.tenant_event import tenant_was_created
|
||||||
from libs.helper import email, extract_remote_ip
|
from libs.helper import email, extract_remote_ip
|
||||||
from libs.password import valid_password
|
from libs.password import valid_password
|
||||||
@@ -38,6 +38,7 @@ class LoginApi(Resource):
|
|||||||
"""Resource for user login."""
|
"""Resource for user login."""
|
||||||
|
|
||||||
@setup_required
|
@setup_required
|
||||||
|
@email_password_login_enabled
|
||||||
def post(self):
|
def post(self):
|
||||||
"""Authenticate user and login."""
|
"""Authenticate user and login."""
|
||||||
parser = reqparse.RequestParser()
|
parser = reqparse.RequestParser()
|
||||||
@@ -110,6 +111,7 @@ class LogoutApi(Resource):
|
|||||||
|
|
||||||
class ResetPasswordSendEmailApi(Resource):
|
class ResetPasswordSendEmailApi(Resource):
|
||||||
@setup_required
|
@setup_required
|
||||||
|
@email_password_login_enabled
|
||||||
def post(self):
|
def post(self):
|
||||||
parser = reqparse.RequestParser()
|
parser = reqparse.RequestParser()
|
||||||
parser.add_argument("email", type=email, required=True, location="json")
|
parser.add_argument("email", type=email, required=True, location="json")
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ from models.model import DifySetup
|
|||||||
from services.feature_service import FeatureService, LicenseStatus
|
from services.feature_service import FeatureService, LicenseStatus
|
||||||
from services.operation_service import OperationService
|
from services.operation_service import OperationService
|
||||||
|
|
||||||
from .error import NotInitValidateError, NotSetupError, UnauthorizedAndForceLogout
|
from .error import (NotInitValidateError, NotSetupError,
|
||||||
|
UnauthorizedAndForceLogout)
|
||||||
|
|
||||||
|
|
||||||
def account_initialization_required(view):
|
def account_initialization_required(view):
|
||||||
@@ -154,3 +155,16 @@ def enterprise_license_required(view):
|
|||||||
return view(*args, **kwargs)
|
return view(*args, **kwargs)
|
||||||
|
|
||||||
return decorated
|
return decorated
|
||||||
|
|
||||||
|
|
||||||
|
def email_password_login_enabled(view):
|
||||||
|
@wraps(view)
|
||||||
|
def decorated(*args, **kwargs):
|
||||||
|
features = FeatureService.get_system_features()
|
||||||
|
if features.enable_email_password_login:
|
||||||
|
return view(*args, **kwargs)
|
||||||
|
|
||||||
|
# otherwise, return 403
|
||||||
|
abort(403)
|
||||||
|
|
||||||
|
return decorated
|
||||||
|
|||||||
@@ -19,8 +19,8 @@ class GoogleProvider(ModelProvider):
|
|||||||
try:
|
try:
|
||||||
model_instance = self.get_model_instance(ModelType.LLM)
|
model_instance = self.get_model_instance(ModelType.LLM)
|
||||||
|
|
||||||
# Use `gemini-pro` model for validate,
|
# Use `gemini-2.0-flash` model for validate,
|
||||||
model_instance.validate_credentials(model="gemini-pro", credentials=credentials)
|
model_instance.validate_credentials(model="gemini-2.0-flash", credentials=credentials)
|
||||||
except CredentialsValidateFailedError as ex:
|
except CredentialsValidateFailedError as ex:
|
||||||
raise ex
|
raise ex
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
|
|||||||
@@ -19,5 +19,3 @@
|
|||||||
- gemini-exp-1206
|
- gemini-exp-1206
|
||||||
- gemini-exp-1121
|
- gemini-exp-1121
|
||||||
- gemini-exp-1114
|
- gemini-exp-1114
|
||||||
- gemini-pro
|
|
||||||
- gemini-pro-vision
|
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
model: gemini-pro-vision
|
|
||||||
label:
|
|
||||||
en_US: Gemini Pro Vision
|
|
||||||
model_type: llm
|
|
||||||
features:
|
|
||||||
- vision
|
|
||||||
model_properties:
|
|
||||||
mode: chat
|
|
||||||
context_size: 12288
|
|
||||||
parameter_rules:
|
|
||||||
- name: temperature
|
|
||||||
use_template: temperature
|
|
||||||
- name: top_p
|
|
||||||
use_template: top_p
|
|
||||||
- name: top_k
|
|
||||||
label:
|
|
||||||
zh_Hans: 取样数量
|
|
||||||
en_US: Top k
|
|
||||||
type: int
|
|
||||||
help:
|
|
||||||
zh_Hans: 仅从每个后续标记的前 K 个选项中采样。
|
|
||||||
en_US: Only sample from the top K options for each subsequent token.
|
|
||||||
required: false
|
|
||||||
- name: max_tokens_to_sample
|
|
||||||
use_template: max_tokens
|
|
||||||
required: true
|
|
||||||
default: 4096
|
|
||||||
min: 1
|
|
||||||
max: 4096
|
|
||||||
pricing:
|
|
||||||
input: '0.00'
|
|
||||||
output: '0.00'
|
|
||||||
unit: '0.000001'
|
|
||||||
currency: USD
|
|
||||||
deprecated: true
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
model: gemini-pro
|
|
||||||
label:
|
|
||||||
en_US: Gemini Pro
|
|
||||||
model_type: llm
|
|
||||||
features:
|
|
||||||
- agent-thought
|
|
||||||
- tool-call
|
|
||||||
- stream-tool-call
|
|
||||||
model_properties:
|
|
||||||
mode: chat
|
|
||||||
context_size: 30720
|
|
||||||
parameter_rules:
|
|
||||||
- name: temperature
|
|
||||||
use_template: temperature
|
|
||||||
- name: top_p
|
|
||||||
use_template: top_p
|
|
||||||
- name: top_k
|
|
||||||
label:
|
|
||||||
zh_Hans: 取样数量
|
|
||||||
en_US: Top k
|
|
||||||
type: int
|
|
||||||
help:
|
|
||||||
zh_Hans: 仅从每个后续标记的前 K 个选项中采样。
|
|
||||||
en_US: Only sample from the top K options for each subsequent token.
|
|
||||||
required: false
|
|
||||||
- name: max_tokens_to_sample
|
|
||||||
use_template: max_tokens
|
|
||||||
required: true
|
|
||||||
default: 2048
|
|
||||||
min: 1
|
|
||||||
max: 2048
|
|
||||||
- name: response_format
|
|
||||||
use_template: response_format
|
|
||||||
pricing:
|
|
||||||
input: '0.00'
|
|
||||||
output: '0.00'
|
|
||||||
unit: '0.000001'
|
|
||||||
currency: USD
|
|
||||||
deprecated: true
|
|
||||||
@@ -5,11 +5,6 @@ model_type: llm
|
|||||||
features:
|
features:
|
||||||
- agent-thought
|
- agent-thought
|
||||||
- vision
|
- vision
|
||||||
- tool-call
|
|
||||||
- stream-tool-call
|
|
||||||
- document
|
|
||||||
- video
|
|
||||||
- audio
|
|
||||||
model_properties:
|
model_properties:
|
||||||
mode: chat
|
mode: chat
|
||||||
context_size: 1048576
|
context_size: 1048576
|
||||||
@@ -20,20 +15,21 @@ parameter_rules:
|
|||||||
use_template: top_p
|
use_template: top_p
|
||||||
- name: top_k
|
- name: top_k
|
||||||
label:
|
label:
|
||||||
zh_Hans: 取样数量
|
|
||||||
en_US: Top k
|
en_US: Top k
|
||||||
type: int
|
type: int
|
||||||
help:
|
help:
|
||||||
zh_Hans: 仅从每个后续标记的前 K 个选项中采样。
|
|
||||||
en_US: Only sample from the top K options for each subsequent token.
|
en_US: Only sample from the top K options for each subsequent token.
|
||||||
required: false
|
required: false
|
||||||
|
- name: presence_penalty
|
||||||
|
use_template: presence_penalty
|
||||||
|
- name: frequency_penalty
|
||||||
|
use_template: frequency_penalty
|
||||||
- name: max_output_tokens
|
- name: max_output_tokens
|
||||||
use_template: max_tokens
|
use_template: max_tokens
|
||||||
|
required: true
|
||||||
default: 8192
|
default: 8192
|
||||||
min: 1
|
min: 1
|
||||||
max: 8192
|
max: 8192
|
||||||
- name: json_schema
|
|
||||||
use_template: json_schema
|
|
||||||
pricing:
|
pricing:
|
||||||
input: '0.00'
|
input: '0.00'
|
||||||
output: '0.00'
|
output: '0.00'
|
||||||
|
|||||||
@@ -77,5 +77,4 @@
|
|||||||
- onebot
|
- onebot
|
||||||
- regex
|
- regex
|
||||||
- trello
|
- trello
|
||||||
- vanna
|
|
||||||
- fal
|
- fal
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 4.5 KiB |
@@ -1,134 +0,0 @@
|
|||||||
from typing import Any, Union
|
|
||||||
|
|
||||||
from vanna.remote import VannaDefault # type: ignore
|
|
||||||
|
|
||||||
from core.tools.entities.tool_entities import ToolInvokeMessage
|
|
||||||
from core.tools.errors import ToolProviderCredentialValidationError
|
|
||||||
from core.tools.tool.builtin_tool import BuiltinTool
|
|
||||||
|
|
||||||
|
|
||||||
class VannaTool(BuiltinTool):
|
|
||||||
def _invoke(
|
|
||||||
self, user_id: str, tool_parameters: dict[str, Any]
|
|
||||||
) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
|
|
||||||
"""
|
|
||||||
invoke tools
|
|
||||||
"""
|
|
||||||
# Ensure runtime and credentials
|
|
||||||
if not self.runtime or not self.runtime.credentials:
|
|
||||||
raise ToolProviderCredentialValidationError("Tool runtime or credentials are missing")
|
|
||||||
api_key = self.runtime.credentials.get("api_key", None)
|
|
||||||
if not api_key:
|
|
||||||
raise ToolProviderCredentialValidationError("Please input api key")
|
|
||||||
|
|
||||||
model = tool_parameters.get("model", "")
|
|
||||||
if not model:
|
|
||||||
return self.create_text_message("Please input RAG model")
|
|
||||||
|
|
||||||
prompt = tool_parameters.get("prompt", "")
|
|
||||||
if not prompt:
|
|
||||||
return self.create_text_message("Please input prompt")
|
|
||||||
|
|
||||||
url = tool_parameters.get("url", "")
|
|
||||||
if not url:
|
|
||||||
return self.create_text_message("Please input URL/Host/DSN")
|
|
||||||
|
|
||||||
db_name = tool_parameters.get("db_name", "")
|
|
||||||
username = tool_parameters.get("username", "")
|
|
||||||
password = tool_parameters.get("password", "")
|
|
||||||
port = tool_parameters.get("port", 0)
|
|
||||||
|
|
||||||
base_url = self.runtime.credentials.get("base_url", None)
|
|
||||||
vn = VannaDefault(model=model, api_key=api_key, config={"endpoint": base_url})
|
|
||||||
|
|
||||||
db_type = tool_parameters.get("db_type", "")
|
|
||||||
if db_type in {"Postgres", "MySQL", "Hive", "ClickHouse"}:
|
|
||||||
if not db_name:
|
|
||||||
return self.create_text_message("Please input database name")
|
|
||||||
if not username:
|
|
||||||
return self.create_text_message("Please input username")
|
|
||||||
if port < 1:
|
|
||||||
return self.create_text_message("Please input port")
|
|
||||||
|
|
||||||
schema_sql = "SELECT * FROM INFORMATION_SCHEMA.COLUMNS"
|
|
||||||
match db_type:
|
|
||||||
case "SQLite":
|
|
||||||
schema_sql = "SELECT type, sql FROM sqlite_master WHERE sql is not null"
|
|
||||||
vn.connect_to_sqlite(url)
|
|
||||||
case "Postgres":
|
|
||||||
vn.connect_to_postgres(host=url, dbname=db_name, user=username, password=password, port=port)
|
|
||||||
case "DuckDB":
|
|
||||||
vn.connect_to_duckdb(url=url)
|
|
||||||
case "SQLServer":
|
|
||||||
vn.connect_to_mssql(url)
|
|
||||||
case "MySQL":
|
|
||||||
vn.connect_to_mysql(host=url, dbname=db_name, user=username, password=password, port=port)
|
|
||||||
case "Oracle":
|
|
||||||
vn.connect_to_oracle(user=username, password=password, dsn=url)
|
|
||||||
case "Hive":
|
|
||||||
vn.connect_to_hive(host=url, dbname=db_name, user=username, password=password, port=port)
|
|
||||||
case "ClickHouse":
|
|
||||||
vn.connect_to_clickhouse(host=url, dbname=db_name, user=username, password=password, port=port)
|
|
||||||
|
|
||||||
enable_training = tool_parameters.get("enable_training", False)
|
|
||||||
reset_training_data = tool_parameters.get("reset_training_data", False)
|
|
||||||
if enable_training:
|
|
||||||
if reset_training_data:
|
|
||||||
existing_training_data = vn.get_training_data()
|
|
||||||
if len(existing_training_data) > 0:
|
|
||||||
for _, training_data in existing_training_data.iterrows():
|
|
||||||
vn.remove_training_data(training_data["id"])
|
|
||||||
|
|
||||||
ddl = tool_parameters.get("ddl", "")
|
|
||||||
question = tool_parameters.get("question", "")
|
|
||||||
sql = tool_parameters.get("sql", "")
|
|
||||||
memos = tool_parameters.get("memos", "")
|
|
||||||
training_metadata = tool_parameters.get("training_metadata", False)
|
|
||||||
|
|
||||||
if training_metadata:
|
|
||||||
if db_type == "SQLite":
|
|
||||||
df_ddl = vn.run_sql(schema_sql)
|
|
||||||
for ddl in df_ddl["sql"].to_list():
|
|
||||||
vn.train(ddl=ddl)
|
|
||||||
else:
|
|
||||||
df_information_schema = vn.run_sql(schema_sql)
|
|
||||||
plan = vn.get_training_plan_generic(df_information_schema)
|
|
||||||
vn.train(plan=plan)
|
|
||||||
|
|
||||||
if ddl:
|
|
||||||
vn.train(ddl=ddl)
|
|
||||||
|
|
||||||
if sql:
|
|
||||||
if question:
|
|
||||||
vn.train(question=question, sql=sql)
|
|
||||||
else:
|
|
||||||
vn.train(sql=sql)
|
|
||||||
if memos:
|
|
||||||
vn.train(documentation=memos)
|
|
||||||
|
|
||||||
#########################################################################################
|
|
||||||
# Due to CVE-2024-5565, we have to disable the chart generation feature
|
|
||||||
# The Vanna library uses a prompt function to present the user with visualized results,
|
|
||||||
# it is possible to alter the prompt using prompt injection and run arbitrary Python code
|
|
||||||
# instead of the intended visualization code.
|
|
||||||
# Specifically - allowing external input to the library’s “ask” method
|
|
||||||
# with "visualize" set to True (default behavior) leads to remote code execution.
|
|
||||||
# Affected versions: <= 0.5.5
|
|
||||||
#########################################################################################
|
|
||||||
allow_llm_to_see_data = tool_parameters.get("allow_llm_to_see_data", False)
|
|
||||||
res = vn.ask(
|
|
||||||
prompt, print_results=False, auto_train=True, visualize=False, allow_llm_to_see_data=allow_llm_to_see_data
|
|
||||||
)
|
|
||||||
|
|
||||||
result = []
|
|
||||||
|
|
||||||
if res is not None:
|
|
||||||
result.append(self.create_text_message(res[0]))
|
|
||||||
if len(res) > 1 and res[1] is not None:
|
|
||||||
result.append(self.create_text_message(res[1].to_markdown()))
|
|
||||||
if len(res) > 2 and res[2] is not None:
|
|
||||||
result.append(
|
|
||||||
self.create_blob_message(blob=res[2].to_image(format="svg"), meta={"mime_type": "image/svg+xml"})
|
|
||||||
)
|
|
||||||
|
|
||||||
return result
|
|
||||||
@@ -1,213 +0,0 @@
|
|||||||
identity:
|
|
||||||
name: vanna
|
|
||||||
author: QCTC
|
|
||||||
label:
|
|
||||||
en_US: Vanna.AI
|
|
||||||
zh_Hans: Vanna.AI
|
|
||||||
description:
|
|
||||||
human:
|
|
||||||
en_US: The fastest way to get actionable insights from your database just by asking questions.
|
|
||||||
zh_Hans: 一个基于大模型和RAG的Text2SQL工具。
|
|
||||||
llm: A tool for converting text to SQL.
|
|
||||||
parameters:
|
|
||||||
- name: prompt
|
|
||||||
type: string
|
|
||||||
required: true
|
|
||||||
label:
|
|
||||||
en_US: Prompt
|
|
||||||
zh_Hans: 提示词
|
|
||||||
pt_BR: Prompt
|
|
||||||
human_description:
|
|
||||||
en_US: used for generating SQL
|
|
||||||
zh_Hans: 用于生成SQL
|
|
||||||
llm_description: key words for generating SQL
|
|
||||||
form: llm
|
|
||||||
- name: model
|
|
||||||
type: string
|
|
||||||
required: true
|
|
||||||
label:
|
|
||||||
en_US: RAG Model
|
|
||||||
zh_Hans: RAG模型
|
|
||||||
human_description:
|
|
||||||
en_US: RAG Model for your database DDL
|
|
||||||
zh_Hans: 存储数据库训练数据的RAG模型
|
|
||||||
llm_description: RAG Model for generating SQL
|
|
||||||
form: llm
|
|
||||||
- name: db_type
|
|
||||||
type: select
|
|
||||||
required: true
|
|
||||||
options:
|
|
||||||
- value: SQLite
|
|
||||||
label:
|
|
||||||
en_US: SQLite
|
|
||||||
zh_Hans: SQLite
|
|
||||||
- value: Postgres
|
|
||||||
label:
|
|
||||||
en_US: Postgres
|
|
||||||
zh_Hans: Postgres
|
|
||||||
- value: DuckDB
|
|
||||||
label:
|
|
||||||
en_US: DuckDB
|
|
||||||
zh_Hans: DuckDB
|
|
||||||
- value: SQLServer
|
|
||||||
label:
|
|
||||||
en_US: Microsoft SQL Server
|
|
||||||
zh_Hans: 微软 SQL Server
|
|
||||||
- value: MySQL
|
|
||||||
label:
|
|
||||||
en_US: MySQL
|
|
||||||
zh_Hans: MySQL
|
|
||||||
- value: Oracle
|
|
||||||
label:
|
|
||||||
en_US: Oracle
|
|
||||||
zh_Hans: Oracle
|
|
||||||
- value: Hive
|
|
||||||
label:
|
|
||||||
en_US: Hive
|
|
||||||
zh_Hans: Hive
|
|
||||||
- value: ClickHouse
|
|
||||||
label:
|
|
||||||
en_US: ClickHouse
|
|
||||||
zh_Hans: ClickHouse
|
|
||||||
default: SQLite
|
|
||||||
label:
|
|
||||||
en_US: DB Type
|
|
||||||
zh_Hans: 数据库类型
|
|
||||||
human_description:
|
|
||||||
en_US: Database type.
|
|
||||||
zh_Hans: 选择要链接的数据库类型。
|
|
||||||
form: form
|
|
||||||
- name: url
|
|
||||||
type: string
|
|
||||||
required: true
|
|
||||||
label:
|
|
||||||
en_US: URL/Host/DSN
|
|
||||||
zh_Hans: URL/Host/DSN
|
|
||||||
human_description:
|
|
||||||
en_US: Please input depending on DB type, visit https://vanna.ai/docs/ for more specification
|
|
||||||
zh_Hans: 请根据数据库类型,填入对应值,详情参考https://vanna.ai/docs/
|
|
||||||
form: form
|
|
||||||
- name: db_name
|
|
||||||
type: string
|
|
||||||
required: false
|
|
||||||
label:
|
|
||||||
en_US: DB name
|
|
||||||
zh_Hans: 数据库名
|
|
||||||
human_description:
|
|
||||||
en_US: Database name
|
|
||||||
zh_Hans: 数据库名
|
|
||||||
form: form
|
|
||||||
- name: username
|
|
||||||
type: string
|
|
||||||
required: false
|
|
||||||
label:
|
|
||||||
en_US: Username
|
|
||||||
zh_Hans: 用户名
|
|
||||||
human_description:
|
|
||||||
en_US: Username
|
|
||||||
zh_Hans: 用户名
|
|
||||||
form: form
|
|
||||||
- name: password
|
|
||||||
type: secret-input
|
|
||||||
required: false
|
|
||||||
label:
|
|
||||||
en_US: Password
|
|
||||||
zh_Hans: 密码
|
|
||||||
human_description:
|
|
||||||
en_US: Password
|
|
||||||
zh_Hans: 密码
|
|
||||||
form: form
|
|
||||||
- name: port
|
|
||||||
type: number
|
|
||||||
required: false
|
|
||||||
label:
|
|
||||||
en_US: Port
|
|
||||||
zh_Hans: 端口
|
|
||||||
human_description:
|
|
||||||
en_US: Port
|
|
||||||
zh_Hans: 端口
|
|
||||||
form: form
|
|
||||||
- name: ddl
|
|
||||||
type: string
|
|
||||||
required: false
|
|
||||||
label:
|
|
||||||
en_US: Training DDL
|
|
||||||
zh_Hans: 训练DDL
|
|
||||||
human_description:
|
|
||||||
en_US: DDL statements for training data
|
|
||||||
zh_Hans: 用于训练RAG Model的建表语句
|
|
||||||
form: llm
|
|
||||||
- name: question
|
|
||||||
type: string
|
|
||||||
required: false
|
|
||||||
label:
|
|
||||||
en_US: Training Question
|
|
||||||
zh_Hans: 训练问题
|
|
||||||
human_description:
|
|
||||||
en_US: Question-SQL Pairs
|
|
||||||
zh_Hans: Question-SQL中的问题
|
|
||||||
form: llm
|
|
||||||
- name: sql
|
|
||||||
type: string
|
|
||||||
required: false
|
|
||||||
label:
|
|
||||||
en_US: Training SQL
|
|
||||||
zh_Hans: 训练SQL
|
|
||||||
human_description:
|
|
||||||
en_US: SQL queries to your training data
|
|
||||||
zh_Hans: 用于训练RAG Model的SQL语句
|
|
||||||
form: llm
|
|
||||||
- name: memos
|
|
||||||
type: string
|
|
||||||
required: false
|
|
||||||
label:
|
|
||||||
en_US: Training Memos
|
|
||||||
zh_Hans: 训练说明
|
|
||||||
human_description:
|
|
||||||
en_US: Sometimes you may want to add documentation about your business terminology or definitions
|
|
||||||
zh_Hans: 添加更多关于数据库的业务说明
|
|
||||||
form: llm
|
|
||||||
- name: enable_training
|
|
||||||
type: boolean
|
|
||||||
required: false
|
|
||||||
default: false
|
|
||||||
label:
|
|
||||||
en_US: Training Data
|
|
||||||
zh_Hans: 训练数据
|
|
||||||
human_description:
|
|
||||||
en_US: You only need to train once. Do not train again unless you want to add more training data
|
|
||||||
zh_Hans: 训练数据无更新时,训练一次即可
|
|
||||||
form: form
|
|
||||||
- name: reset_training_data
|
|
||||||
type: boolean
|
|
||||||
required: false
|
|
||||||
default: false
|
|
||||||
label:
|
|
||||||
en_US: Reset Training Data
|
|
||||||
zh_Hans: 重置训练数据
|
|
||||||
human_description:
|
|
||||||
en_US: Remove all training data in the current RAG Model
|
|
||||||
zh_Hans: 删除当前RAG Model中的所有训练数据
|
|
||||||
form: form
|
|
||||||
- name: training_metadata
|
|
||||||
type: boolean
|
|
||||||
required: false
|
|
||||||
default: false
|
|
||||||
label:
|
|
||||||
en_US: Training Metadata
|
|
||||||
zh_Hans: 训练元数据
|
|
||||||
human_description:
|
|
||||||
en_US: If enabled, it will attempt to train on the metadata of that database
|
|
||||||
zh_Hans: 是否自动从数据库获取元数据来训练
|
|
||||||
form: form
|
|
||||||
- name: allow_llm_to_see_data
|
|
||||||
type: boolean
|
|
||||||
required: false
|
|
||||||
default: false
|
|
||||||
label:
|
|
||||||
en_US: Whether to allow the LLM to see the data
|
|
||||||
zh_Hans: 是否允许LLM查看数据
|
|
||||||
human_description:
|
|
||||||
en_US: Whether to allow the LLM to see the data
|
|
||||||
zh_Hans: 是否允许LLM查看数据
|
|
||||||
form: form
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
import re
|
|
||||||
from typing import Any
|
|
||||||
from urllib.parse import urlparse
|
|
||||||
|
|
||||||
from core.tools.errors import ToolProviderCredentialValidationError
|
|
||||||
from core.tools.provider.builtin.vanna.tools.vanna import VannaTool
|
|
||||||
from core.tools.provider.builtin_tool_provider import BuiltinToolProviderController
|
|
||||||
|
|
||||||
|
|
||||||
class VannaProvider(BuiltinToolProviderController):
|
|
||||||
def _get_protocol_and_main_domain(self, url):
|
|
||||||
parsed_url = urlparse(url)
|
|
||||||
protocol = parsed_url.scheme
|
|
||||||
hostname = parsed_url.hostname
|
|
||||||
port = f":{parsed_url.port}" if parsed_url.port else ""
|
|
||||||
|
|
||||||
# Check if the hostname is an IP address
|
|
||||||
is_ip = re.match(r"^\d{1,3}(\.\d{1,3}){3}$", hostname) is not None
|
|
||||||
|
|
||||||
# Return the full hostname (with port if present) for IP addresses, otherwise return the main domain
|
|
||||||
main_domain = f"{hostname}{port}" if is_ip else ".".join(hostname.split(".")[-2:]) + port
|
|
||||||
return f"{protocol}://{main_domain}"
|
|
||||||
|
|
||||||
def _validate_credentials(self, credentials: dict[str, Any]) -> None:
|
|
||||||
base_url = credentials.get("base_url")
|
|
||||||
if not base_url:
|
|
||||||
base_url = "https://ask.vanna.ai/rpc"
|
|
||||||
else:
|
|
||||||
base_url = base_url.removesuffix("/")
|
|
||||||
credentials["base_url"] = base_url
|
|
||||||
try:
|
|
||||||
VannaTool().fork_tool_runtime(
|
|
||||||
runtime={
|
|
||||||
"credentials": credentials,
|
|
||||||
}
|
|
||||||
).invoke(
|
|
||||||
user_id="",
|
|
||||||
tool_parameters={
|
|
||||||
"model": "chinook",
|
|
||||||
"db_type": "SQLite",
|
|
||||||
"url": f"{self._get_protocol_and_main_domain(credentials['base_url'])}/Chinook.sqlite",
|
|
||||||
"query": "What are the top 10 customers by sales?",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
raise ToolProviderCredentialValidationError(str(e))
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
identity:
|
|
||||||
author: QCTC
|
|
||||||
name: vanna
|
|
||||||
label:
|
|
||||||
en_US: Vanna.AI
|
|
||||||
zh_Hans: Vanna.AI
|
|
||||||
description:
|
|
||||||
en_US: The fastest way to get actionable insights from your database just by asking questions.
|
|
||||||
zh_Hans: 一个基于大模型和RAG的Text2SQL工具。
|
|
||||||
icon: icon.png
|
|
||||||
tags:
|
|
||||||
- utilities
|
|
||||||
- productivity
|
|
||||||
credentials_for_provider:
|
|
||||||
api_key:
|
|
||||||
type: secret-input
|
|
||||||
required: true
|
|
||||||
label:
|
|
||||||
en_US: API key
|
|
||||||
zh_Hans: API key
|
|
||||||
placeholder:
|
|
||||||
en_US: Please input your API key
|
|
||||||
zh_Hans: 请输入你的 API key
|
|
||||||
pt_BR: Please input your API key
|
|
||||||
help:
|
|
||||||
en_US: Get your API key from Vanna.AI
|
|
||||||
zh_Hans: 从 Vanna.AI 获取你的 API key
|
|
||||||
url: https://vanna.ai/account/profile
|
|
||||||
base_url:
|
|
||||||
type: text-input
|
|
||||||
required: false
|
|
||||||
label:
|
|
||||||
en_US: Vanna.AI Endpoint Base URL
|
|
||||||
placeholder:
|
|
||||||
en_US: https://ask.vanna.ai/rpc
|
|
||||||
@@ -406,10 +406,8 @@ class AccountService:
|
|||||||
|
|
||||||
raise PasswordResetRateLimitExceededError()
|
raise PasswordResetRateLimitExceededError()
|
||||||
|
|
||||||
code = "".join([str(random.randint(0, 9)) for _ in range(6)])
|
code, token = cls.generate_reset_password_token(account_email, account)
|
||||||
token = TokenManager.generate_token(
|
|
||||||
account=account, email=email, token_type="reset_password", additional_data={"code": code}
|
|
||||||
)
|
|
||||||
send_reset_password_mail_task.delay(
|
send_reset_password_mail_task.delay(
|
||||||
language=language,
|
language=language,
|
||||||
to=account_email,
|
to=account_email,
|
||||||
@@ -418,6 +416,22 @@ class AccountService:
|
|||||||
cls.reset_password_rate_limiter.increment_rate_limit(account_email)
|
cls.reset_password_rate_limiter.increment_rate_limit(account_email)
|
||||||
return token
|
return token
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def generate_reset_password_token(
|
||||||
|
cls,
|
||||||
|
email: str,
|
||||||
|
account: Optional[Account] = None,
|
||||||
|
code: Optional[str] = None,
|
||||||
|
additional_data: dict[str, Any] = {},
|
||||||
|
):
|
||||||
|
if not code:
|
||||||
|
code = "".join([str(random.randint(0, 9)) for _ in range(6)])
|
||||||
|
additional_data["code"] = code
|
||||||
|
token = TokenManager.generate_token(
|
||||||
|
account=account, email=email, token_type="reset_password", additional_data=additional_data
|
||||||
|
)
|
||||||
|
return code, token
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def revoke_reset_password_token(cls, token: str):
|
def revoke_reset_password_token(cls, token: str):
|
||||||
TokenManager.revoke_token(token, "reset_password")
|
TokenManager.revoke_token(token, "reset_password")
|
||||||
|
|||||||
@@ -932,3 +932,6 @@ MAX_SUBMIT_COUNT=100
|
|||||||
|
|
||||||
# The maximum number of top-k value for RAG.
|
# The maximum number of top-k value for RAG.
|
||||||
TOP_K_MAX_VALUE=10
|
TOP_K_MAX_VALUE=10
|
||||||
|
|
||||||
|
# Prevent Clickjacking
|
||||||
|
ALLOW_EMBED=false
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
x-shared-env: &shared-api-worker-env
|
x-shared-env: &shared-api-worker-env
|
||||||
services:
|
services:
|
||||||
# API service
|
# API service
|
||||||
api:
|
api:
|
||||||
image: langgenius/dify-api:0.15.3
|
image: langgenius/dify-api:0.15.6
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
# Use the shared environment variables.
|
# Use the shared environment variables.
|
||||||
@@ -25,7 +25,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.15.3
|
image: langgenius/dify-api:0.15.6
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
# Use the shared environment variables.
|
# Use the shared environment variables.
|
||||||
@@ -47,7 +47,7 @@ services:
|
|||||||
|
|
||||||
# Frontend web application.
|
# Frontend web application.
|
||||||
web:
|
web:
|
||||||
image: langgenius/dify-web:0.15.3
|
image: langgenius/dify-web:0.15.6
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
CONSOLE_API_URL: ${CONSOLE_API_URL:-}
|
CONSOLE_API_URL: ${CONSOLE_API_URL:-}
|
||||||
@@ -56,6 +56,7 @@ services:
|
|||||||
NEXT_TELEMETRY_DISABLED: ${NEXT_TELEMETRY_DISABLED:-0}
|
NEXT_TELEMETRY_DISABLED: ${NEXT_TELEMETRY_DISABLED:-0}
|
||||||
TEXT_GENERATION_TIMEOUT_MS: ${TEXT_GENERATION_TIMEOUT_MS:-60000}
|
TEXT_GENERATION_TIMEOUT_MS: ${TEXT_GENERATION_TIMEOUT_MS:-60000}
|
||||||
CSP_WHITELIST: ${CSP_WHITELIST:-}
|
CSP_WHITELIST: ${CSP_WHITELIST:-}
|
||||||
|
ALLOW_EMBED: ${ALLOW_EMBED:-false}
|
||||||
TOP_K_MAX_VALUE: ${TOP_K_MAX_VALUE:-}
|
TOP_K_MAX_VALUE: ${TOP_K_MAX_VALUE:-}
|
||||||
INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH: ${INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH:-}
|
INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH: ${INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH:-}
|
||||||
|
|
||||||
@@ -98,7 +99,7 @@ services:
|
|||||||
|
|
||||||
# The DifySandbox
|
# The DifySandbox
|
||||||
sandbox:
|
sandbox:
|
||||||
image: langgenius/dify-sandbox:0.2.10
|
image: langgenius/dify-sandbox:0.2.11
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
# The DifySandbox configurations
|
# The DifySandbox configurations
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ services:
|
|||||||
|
|
||||||
# The DifySandbox
|
# The DifySandbox
|
||||||
sandbox:
|
sandbox:
|
||||||
image: langgenius/dify-sandbox:0.2.10
|
image: langgenius/dify-sandbox:0.2.11
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
# The DifySandbox configurations
|
# The DifySandbox configurations
|
||||||
|
|||||||
@@ -389,11 +389,12 @@ x-shared-env: &shared-api-worker-env
|
|||||||
CREATE_TIDB_SERVICE_JOB_ENABLED: ${CREATE_TIDB_SERVICE_JOB_ENABLED:-false}
|
CREATE_TIDB_SERVICE_JOB_ENABLED: ${CREATE_TIDB_SERVICE_JOB_ENABLED:-false}
|
||||||
MAX_SUBMIT_COUNT: ${MAX_SUBMIT_COUNT:-100}
|
MAX_SUBMIT_COUNT: ${MAX_SUBMIT_COUNT:-100}
|
||||||
TOP_K_MAX_VALUE: ${TOP_K_MAX_VALUE:-10}
|
TOP_K_MAX_VALUE: ${TOP_K_MAX_VALUE:-10}
|
||||||
|
ALLOW_EMBED: ${ALLOW_EMBED:-false}
|
||||||
|
|
||||||
services:
|
services:
|
||||||
# API service
|
# API service
|
||||||
api:
|
api:
|
||||||
image: langgenius/dify-api:0.15.3
|
image: langgenius/dify-api:0.15.6
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
# Use the shared environment variables.
|
# Use the shared environment variables.
|
||||||
@@ -416,7 +417,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.15.3
|
image: langgenius/dify-api:0.15.6
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
# Use the shared environment variables.
|
# Use the shared environment variables.
|
||||||
@@ -438,7 +439,7 @@ services:
|
|||||||
|
|
||||||
# Frontend web application.
|
# Frontend web application.
|
||||||
web:
|
web:
|
||||||
image: langgenius/dify-web:0.15.3
|
image: langgenius/dify-web:0.15.6
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
CONSOLE_API_URL: ${CONSOLE_API_URL:-}
|
CONSOLE_API_URL: ${CONSOLE_API_URL:-}
|
||||||
@@ -447,6 +448,7 @@ services:
|
|||||||
NEXT_TELEMETRY_DISABLED: ${NEXT_TELEMETRY_DISABLED:-0}
|
NEXT_TELEMETRY_DISABLED: ${NEXT_TELEMETRY_DISABLED:-0}
|
||||||
TEXT_GENERATION_TIMEOUT_MS: ${TEXT_GENERATION_TIMEOUT_MS:-60000}
|
TEXT_GENERATION_TIMEOUT_MS: ${TEXT_GENERATION_TIMEOUT_MS:-60000}
|
||||||
CSP_WHITELIST: ${CSP_WHITELIST:-}
|
CSP_WHITELIST: ${CSP_WHITELIST:-}
|
||||||
|
ALLOW_EMBED: ${ALLOW_EMBED:-false}
|
||||||
TOP_K_MAX_VALUE: ${TOP_K_MAX_VALUE:-}
|
TOP_K_MAX_VALUE: ${TOP_K_MAX_VALUE:-}
|
||||||
INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH: ${INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH:-}
|
INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH: ${INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH:-}
|
||||||
|
|
||||||
@@ -489,7 +491,7 @@ services:
|
|||||||
|
|
||||||
# The DifySandbox
|
# The DifySandbox
|
||||||
sandbox:
|
sandbox:
|
||||||
image: langgenius/dify-sandbox:0.2.10
|
image: langgenius/dify-sandbox:0.2.11
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
environment:
|
||||||
# The DifySandbox configurations
|
# The DifySandbox configurations
|
||||||
|
|||||||
@@ -31,3 +31,6 @@ NEXT_PUBLIC_TOP_K_MAX_VALUE=10
|
|||||||
|
|
||||||
# The maximum number of tokens for segmentation
|
# The maximum number of tokens for segmentation
|
||||||
NEXT_PUBLIC_INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH=4000
|
NEXT_PUBLIC_INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH=4000
|
||||||
|
|
||||||
|
# Default is not allow to embed into iframe to prevent Clickjacking: https://owasp.org/www-community/attacks/Clickjacking
|
||||||
|
NEXT_PUBLIC_ALLOW_EMBED=
|
||||||
|
|||||||
@@ -312,7 +312,7 @@ function AppPreview({ mode }: { mode: AppMode }) {
|
|||||||
'chat': {
|
'chat': {
|
||||||
title: t('app.types.chatbot'),
|
title: t('app.types.chatbot'),
|
||||||
description: t('app.newApp.chatbotUserDescription'),
|
description: t('app.newApp.chatbotUserDescription'),
|
||||||
link: 'https://docs.dify.ai/guides/application-orchestrate/conversation-application?fallback=true',
|
link: 'https://docs.dify.ai/guides/application-orchestrate#application_type',
|
||||||
},
|
},
|
||||||
'advanced-chat': {
|
'advanced-chat': {
|
||||||
title: t('app.types.advanced'),
|
title: t('app.types.advanced'),
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ const OPTION_MAP = {
|
|||||||
iframe: {
|
iframe: {
|
||||||
getContent: (url: string, token: string) =>
|
getContent: (url: string, token: string) =>
|
||||||
`<iframe
|
`<iframe
|
||||||
src="${url}/chatbot/${token}"
|
src="${url}/chat/${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">
|
||||||
@@ -35,12 +35,12 @@ const OPTION_MAP = {
|
|||||||
`<script>
|
`<script>
|
||||||
window.difyChatbotConfig = {
|
window.difyChatbotConfig = {
|
||||||
token: '${token}'${isTestEnv
|
token: '${token}'${isTestEnv
|
||||||
? `,
|
? `,
|
||||||
isDev: true`
|
isDev: true`
|
||||||
: ''}${IS_CE_EDITION
|
: ''}${IS_CE_EDITION
|
||||||
? `,
|
? `,
|
||||||
baseUrl: '${url}'`
|
baseUrl: '${url}'`
|
||||||
: ''}
|
: ''}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<script
|
<script
|
||||||
|
|||||||
@@ -11,10 +11,12 @@ import { useLocalStorageState } from 'ahooks'
|
|||||||
import produce from 'immer'
|
import produce from 'immer'
|
||||||
import type {
|
import type {
|
||||||
ChatConfig,
|
ChatConfig,
|
||||||
|
ChatItem,
|
||||||
Feedback,
|
Feedback,
|
||||||
} from '../types'
|
} from '../types'
|
||||||
import { CONVERSATION_ID_INFO } from '../constants'
|
import { CONVERSATION_ID_INFO } from '../constants'
|
||||||
import { getPrevChatList, getProcessedInputsFromUrlParams } from '../utils'
|
import { buildChatItemTree, getProcessedInputsFromUrlParams } from '../utils'
|
||||||
|
import { getProcessedFilesFromResponse } from '../../file-uploader/utils'
|
||||||
import {
|
import {
|
||||||
fetchAppInfo,
|
fetchAppInfo,
|
||||||
fetchAppMeta,
|
fetchAppMeta,
|
||||||
@@ -32,6 +34,33 @@ import { useToastContext } from '@/app/components/base/toast'
|
|||||||
import { changeLanguage } from '@/i18n/i18next-config'
|
import { changeLanguage } from '@/i18n/i18next-config'
|
||||||
import { InputVarType } from '@/app/components/workflow/types'
|
import { InputVarType } from '@/app/components/workflow/types'
|
||||||
import { TransferMethod } from '@/types/app'
|
import { TransferMethod } from '@/types/app'
|
||||||
|
import { addFileInfos, sortAgentSorts } from '@/app/components/tools/utils'
|
||||||
|
|
||||||
|
function getFormattedChatList(messages: any[]) {
|
||||||
|
const newChatList: ChatItem[] = []
|
||||||
|
messages.forEach((item) => {
|
||||||
|
const questionFiles = item.message_files?.filter((file: any) => file.belongs_to === 'user') || []
|
||||||
|
newChatList.push({
|
||||||
|
id: `question-${item.id}`,
|
||||||
|
content: item.query,
|
||||||
|
isAnswer: false,
|
||||||
|
message_files: getProcessedFilesFromResponse(questionFiles.map((item: any) => ({ ...item, related_id: item.id }))),
|
||||||
|
parentMessageId: item.parent_message_id || undefined,
|
||||||
|
})
|
||||||
|
const answerFiles = item.message_files?.filter((file: any) => file.belongs_to === 'assistant') || []
|
||||||
|
newChatList.push({
|
||||||
|
id: item.id,
|
||||||
|
content: item.answer,
|
||||||
|
agent_thoughts: addFileInfos(item.agent_thoughts ? sortAgentSorts(item.agent_thoughts) : item.agent_thoughts, item.message_files),
|
||||||
|
feedback: item.feedback,
|
||||||
|
isAnswer: true,
|
||||||
|
citation: item.retriever_resources,
|
||||||
|
message_files: getProcessedFilesFromResponse(answerFiles.map((item: any) => ({ ...item, related_id: item.id }))),
|
||||||
|
parentMessageId: `question-${item.id}`,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return newChatList
|
||||||
|
}
|
||||||
|
|
||||||
export const useEmbeddedChatbot = () => {
|
export const useEmbeddedChatbot = () => {
|
||||||
const isInstalledApp = false
|
const isInstalledApp = false
|
||||||
@@ -77,7 +106,7 @@ export const useEmbeddedChatbot = () => {
|
|||||||
|
|
||||||
const appPrevChatList = useMemo(
|
const appPrevChatList = useMemo(
|
||||||
() => (currentConversationId && appChatListData?.data.length)
|
() => (currentConversationId && appChatListData?.data.length)
|
||||||
? getPrevChatList(appChatListData.data)
|
? buildChatItemTree(getFormattedChatList(appChatListData.data))
|
||||||
: [],
|
: [],
|
||||||
[appChatListData, currentConversationId],
|
[appChatListData, currentConversationId],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { UUID_NIL } from './constants'
|
import { UUID_NIL } from './constants'
|
||||||
import type { IChatItem } from './chat/type'
|
import type { IChatItem } from './chat/type'
|
||||||
import type { ChatItem, ChatItemInTree } from './types'
|
import type { ChatItem, ChatItemInTree } from './types'
|
||||||
|
import { addFileInfos, sortAgentSorts } from '../../tools/utils'
|
||||||
|
import { getProcessedFilesFromResponse } from '../file-uploader/utils'
|
||||||
|
|
||||||
async function decodeBase64AndDecompress(base64String: string) {
|
async function decodeBase64AndDecompress(base64String: string) {
|
||||||
const binaryString = atob(base64String)
|
const binaryString = atob(base64String)
|
||||||
@@ -19,6 +21,60 @@ function getProcessedInputsFromUrlParams(): Record<string, any> {
|
|||||||
return inputs
|
return inputs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function appendQAToChatList(chatList: ChatItem[], item: any) {
|
||||||
|
// we append answer first and then question since will reverse the whole chatList later
|
||||||
|
const answerFiles = item.message_files?.filter((file: any) => file.belongs_to === 'assistant') || []
|
||||||
|
chatList.push({
|
||||||
|
id: item.id,
|
||||||
|
content: item.answer,
|
||||||
|
agent_thoughts: addFileInfos(item.agent_thoughts ? sortAgentSorts(item.agent_thoughts) : item.agent_thoughts, item.message_files),
|
||||||
|
feedback: item.feedback,
|
||||||
|
isAnswer: true,
|
||||||
|
citation: item.retriever_resources,
|
||||||
|
message_files: getProcessedFilesFromResponse(answerFiles.map((item: any) => ({ ...item, related_id: item.id }))),
|
||||||
|
})
|
||||||
|
const questionFiles = item.message_files?.filter((file: any) => file.belongs_to === 'user') || []
|
||||||
|
chatList.push({
|
||||||
|
id: `question-${item.id}`,
|
||||||
|
content: item.query,
|
||||||
|
isAnswer: false,
|
||||||
|
message_files: getProcessedFilesFromResponse(questionFiles.map((item: any) => ({ ...item, related_id: item.id }))),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes the latest thread messages from all messages of the conversation.
|
||||||
|
* Same logic as backend codebase `api/core/prompt/utils/extract_thread_messages.py`
|
||||||
|
*
|
||||||
|
* @param fetchedMessages - The history chat list data from the backend, sorted by created_at in descending order. This includes all flattened history messages of the conversation.
|
||||||
|
* @returns An array of ChatItems representing the latest thread.
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
function getPrevChatList(fetchedMessages: any[]) {
|
||||||
|
const ret: ChatItem[] = []
|
||||||
|
let nextMessageId = null
|
||||||
|
|
||||||
|
for (const item of fetchedMessages) {
|
||||||
|
if (!item.parent_message_id) {
|
||||||
|
appendQAToChatList(ret, item)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!nextMessageId) {
|
||||||
|
appendQAToChatList(ret, item)
|
||||||
|
nextMessageId = item.parent_message_id
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (item.id === nextMessageId || nextMessageId === UUID_NIL) {
|
||||||
|
appendQAToChatList(ret, item)
|
||||||
|
nextMessageId = item.parent_message_id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret.reverse()
|
||||||
|
}
|
||||||
|
|
||||||
function isValidGeneratedAnswer(item?: ChatItem | ChatItemInTree): boolean {
|
function isValidGeneratedAnswer(item?: ChatItem | ChatItemInTree): boolean {
|
||||||
return !!item && item.isAnswer && !item.id.startsWith('answer-placeholder-') && !item.isOpeningStatement
|
return !!item && item.isAnswer && !item.id.startsWith('answer-placeholder-') && !item.isOpeningStatement
|
||||||
}
|
}
|
||||||
@@ -164,6 +220,7 @@ function getThreadMessages(tree: ChatItemInTree[], targetMessageId?: string): Ch
|
|||||||
export {
|
export {
|
||||||
getProcessedInputsFromUrlParams,
|
getProcessedInputsFromUrlParams,
|
||||||
isValidGeneratedAnswer,
|
isValidGeneratedAnswer,
|
||||||
|
getPrevChatList,
|
||||||
getLastAnswer,
|
getLastAnswer,
|
||||||
buildChatItemTree,
|
buildChatItemTree,
|
||||||
getThreadMessages,
|
getThreadMessages,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import SyntaxHighlighter from 'react-syntax-highlighter'
|
|||||||
import { atelierHeathLight } from 'react-syntax-highlighter/dist/esm/styles/hljs'
|
import { atelierHeathLight } from 'react-syntax-highlighter/dist/esm/styles/hljs'
|
||||||
import { Component, memo, useMemo, useRef, useState } from 'react'
|
import { Component, memo, useMemo, useRef, useState } from 'react'
|
||||||
import type { CodeComponent } from 'react-markdown/lib/ast-to-react'
|
import type { CodeComponent } from 'react-markdown/lib/ast-to-react'
|
||||||
|
import SVGRenderer from './svg-gallery'
|
||||||
import cn from '@/utils/classnames'
|
import cn from '@/utils/classnames'
|
||||||
import CopyBtn from '@/app/components/base/copy-btn'
|
import CopyBtn from '@/app/components/base/copy-btn'
|
||||||
import SVGBtn from '@/app/components/base/svg'
|
import SVGBtn from '@/app/components/base/svg'
|
||||||
@@ -18,7 +19,7 @@ import ImageGallery from '@/app/components/base/image-gallery'
|
|||||||
import { useChatContext } from '@/app/components/base/chat/chat/context'
|
import { useChatContext } from '@/app/components/base/chat/chat/context'
|
||||||
import VideoGallery from '@/app/components/base/video-gallery'
|
import VideoGallery from '@/app/components/base/video-gallery'
|
||||||
import AudioGallery from '@/app/components/base/audio-gallery'
|
import AudioGallery from '@/app/components/base/audio-gallery'
|
||||||
import SVGRenderer from '@/app/components/base/svg-gallery'
|
// import SVGRenderer from '@/app/components/base/svg-gallery'
|
||||||
import MarkdownButton from '@/app/components/base/markdown-blocks/button'
|
import MarkdownButton from '@/app/components/base/markdown-blocks/button'
|
||||||
import MarkdownForm from '@/app/components/base/markdown-blocks/form'
|
import MarkdownForm from '@/app/components/base/markdown-blocks/form'
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import { SVG } from '@svgdotjs/svg.js'
|
import { SVG } from '@svgdotjs/svg.js'
|
||||||
|
import DOMPurify from 'dompurify'
|
||||||
import ImagePreview from '@/app/components/base/image-uploader/image-preview'
|
import ImagePreview from '@/app/components/base/image-uploader/image-preview'
|
||||||
|
|
||||||
export const SVGRenderer = ({ content }: { content: string }) => {
|
export const SVGRenderer = ({ content }: { content: string }) => {
|
||||||
@@ -44,7 +45,7 @@ export const SVGRenderer = ({ content }: { content: string }) => {
|
|||||||
|
|
||||||
svgRef.current.style.width = `${Math.min(originalWidth, 298)}px`
|
svgRef.current.style.width = `${Math.min(originalWidth, 298)}px`
|
||||||
|
|
||||||
const rootElement = draw.svg(content)
|
const rootElement = draw.svg(DOMPurify.sanitize(content))
|
||||||
|
|
||||||
rootElement.click(() => {
|
rootElement.click(() => {
|
||||||
setImagePreview(svgToDataURL(svgElement as Element))
|
setImagePreview(svgToDataURL(svgElement as Element))
|
||||||
|
|||||||
@@ -39,7 +39,11 @@ export default function CheckCode() {
|
|||||||
}
|
}
|
||||||
setIsLoading(true)
|
setIsLoading(true)
|
||||||
const ret = await verifyResetPasswordCode({ email, code, token })
|
const ret = await verifyResetPasswordCode({ email, code, token })
|
||||||
ret.is_valid && router.push(`/reset-password/set-password?${searchParams.toString()}`)
|
if (ret.is_valid) {
|
||||||
|
const params = new URLSearchParams(searchParams)
|
||||||
|
params.set('token', encodeURIComponent(ret.token))
|
||||||
|
router.push(`/reset-password/set-password?${params.toString()}`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (error) { console.error(error) }
|
catch (error) { console.error(error) }
|
||||||
finally {
|
finally {
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export NEXT_TELEMETRY_DISABLED=${NEXT_TELEMETRY_DISABLED}
|
|||||||
|
|
||||||
export NEXT_PUBLIC_TEXT_GENERATION_TIMEOUT_MS=${TEXT_GENERATION_TIMEOUT_MS}
|
export NEXT_PUBLIC_TEXT_GENERATION_TIMEOUT_MS=${TEXT_GENERATION_TIMEOUT_MS}
|
||||||
export NEXT_PUBLIC_CSP_WHITELIST=${CSP_WHITELIST}
|
export NEXT_PUBLIC_CSP_WHITELIST=${CSP_WHITELIST}
|
||||||
|
export NEXT_PUBLIC_ALLOW_EMBED=${ALLOW_EMBED}
|
||||||
export NEXT_PUBLIC_TOP_K_MAX_VALUE=${TOP_K_MAX_VALUE}
|
export NEXT_PUBLIC_TOP_K_MAX_VALUE=${TOP_K_MAX_VALUE}
|
||||||
export NEXT_PUBLIC_INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH=${INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH}
|
export NEXT_PUBLIC_INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH=${INDEXING_MAX_SEGMENTATION_TOKENS_LENGTH}
|
||||||
|
|
||||||
|
|||||||
@@ -3,10 +3,26 @@ import { NextResponse } from 'next/server'
|
|||||||
|
|
||||||
const NECESSARY_DOMAIN = '*.sentry.io http://localhost:* http://127.0.0.1:* https://analytics.google.com googletagmanager.com *.googletagmanager.com https://www.google-analytics.com https://api.github.com'
|
const NECESSARY_DOMAIN = '*.sentry.io http://localhost:* http://127.0.0.1:* https://analytics.google.com googletagmanager.com *.googletagmanager.com https://www.google-analytics.com https://api.github.com'
|
||||||
|
|
||||||
|
const wrapResponseWithXFrameOptions = (response: NextResponse, pathname: string) => {
|
||||||
|
// prevent clickjacking: https://owasp.org/www-community/attacks/Clickjacking
|
||||||
|
// Chatbot page should be allowed to be embedded in iframe. It's a feature
|
||||||
|
if (process.env.NEXT_PUBLIC_ALLOW_EMBED !== 'true' && !pathname.startsWith('/chat'))
|
||||||
|
response.headers.set('X-Frame-Options', 'DENY')
|
||||||
|
|
||||||
|
return response
|
||||||
|
}
|
||||||
export function middleware(request: NextRequest) {
|
export function middleware(request: NextRequest) {
|
||||||
|
const { pathname } = request.nextUrl
|
||||||
|
const requestHeaders = new Headers(request.headers)
|
||||||
|
const response = NextResponse.next({
|
||||||
|
request: {
|
||||||
|
headers: requestHeaders,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
const isWhiteListEnabled = !!process.env.NEXT_PUBLIC_CSP_WHITELIST && process.env.NODE_ENV === 'production'
|
const isWhiteListEnabled = !!process.env.NEXT_PUBLIC_CSP_WHITELIST && process.env.NODE_ENV === 'production'
|
||||||
if (!isWhiteListEnabled)
|
if (!isWhiteListEnabled)
|
||||||
return NextResponse.next()
|
return wrapResponseWithXFrameOptions(response, pathname)
|
||||||
|
|
||||||
const whiteList = `${process.env.NEXT_PUBLIC_CSP_WHITELIST} ${NECESSARY_DOMAIN}`
|
const whiteList = `${process.env.NEXT_PUBLIC_CSP_WHITELIST} ${NECESSARY_DOMAIN}`
|
||||||
const nonce = Buffer.from(crypto.randomUUID()).toString('base64')
|
const nonce = Buffer.from(crypto.randomUUID()).toString('base64')
|
||||||
@@ -33,7 +49,6 @@ export function middleware(request: NextRequest) {
|
|||||||
.replace(/\s{2,}/g, ' ')
|
.replace(/\s{2,}/g, ' ')
|
||||||
.trim()
|
.trim()
|
||||||
|
|
||||||
const requestHeaders = new Headers(request.headers)
|
|
||||||
requestHeaders.set('x-nonce', nonce)
|
requestHeaders.set('x-nonce', nonce)
|
||||||
|
|
||||||
requestHeaders.set(
|
requestHeaders.set(
|
||||||
@@ -41,17 +56,12 @@ export function middleware(request: NextRequest) {
|
|||||||
contentSecurityPolicyHeaderValue,
|
contentSecurityPolicyHeaderValue,
|
||||||
)
|
)
|
||||||
|
|
||||||
const response = NextResponse.next({
|
|
||||||
request: {
|
|
||||||
headers: requestHeaders,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
response.headers.set(
|
response.headers.set(
|
||||||
'Content-Security-Policy',
|
'Content-Security-Policy',
|
||||||
contentSecurityPolicyHeaderValue,
|
contentSecurityPolicyHeaderValue,
|
||||||
)
|
)
|
||||||
|
|
||||||
return response
|
return wrapResponseWithXFrameOptions(response, pathname)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
@@ -73,4 +83,4 @@ export const config = {
|
|||||||
// ],
|
// ],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "dify-web",
|
"name": "dify-web",
|
||||||
"version": "0.15.3",
|
"version": "0.15.6",
|
||||||
"private": true,
|
"private": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18.17.0"
|
"node": ">=18.17.0"
|
||||||
@@ -51,6 +51,7 @@
|
|||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
"dayjs": "^1.11.7",
|
"dayjs": "^1.11.7",
|
||||||
"decimal.js": "^10.4.3",
|
"decimal.js": "^10.4.3",
|
||||||
|
"dompurify": "^3.2.4",
|
||||||
"echarts": "^5.5.1",
|
"echarts": "^5.5.1",
|
||||||
"echarts-for-react": "^3.0.2",
|
"echarts-for-react": "^3.0.2",
|
||||||
"elkjs": "^0.9.3",
|
"elkjs": "^0.9.3",
|
||||||
@@ -70,7 +71,7 @@
|
|||||||
"mermaid": "11.4.1",
|
"mermaid": "11.4.1",
|
||||||
"mime": "^4.0.4",
|
"mime": "^4.0.4",
|
||||||
"negotiator": "^0.6.3",
|
"negotiator": "^0.6.3",
|
||||||
"next": "^14.2.10",
|
"next": "^14.2.25",
|
||||||
"pinyin-pro": "^3.23.0",
|
"pinyin-pro": "^3.23.0",
|
||||||
"qrcode.react": "^3.1.0",
|
"qrcode.react": "^3.1.0",
|
||||||
"qs": "^6.11.1",
|
"qs": "^6.11.1",
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ import type { SystemFeatures } from '@/types/feature'
|
|||||||
|
|
||||||
type LoginSuccess = {
|
type LoginSuccess = {
|
||||||
result: 'success'
|
result: 'success'
|
||||||
data: { access_token: string;refresh_token: string }
|
data: { access_token: string; refresh_token: string }
|
||||||
}
|
}
|
||||||
type LoginFail = {
|
type LoginFail = {
|
||||||
result: 'fail'
|
result: 'fail'
|
||||||
@@ -331,20 +331,20 @@ export const uploadRemoteFileInfo = (url: string, isPublic?: boolean) => {
|
|||||||
export const sendEMailLoginCode = (email: string, language = 'en-US') =>
|
export const sendEMailLoginCode = (email: string, language = 'en-US') =>
|
||||||
post<CommonResponse & { data: string }>('/email-code-login', { body: { email, language } })
|
post<CommonResponse & { data: string }>('/email-code-login', { body: { email, language } })
|
||||||
|
|
||||||
export const emailLoginWithCode = (data: { email: string;code: string;token: string }) =>
|
export const emailLoginWithCode = (data: { email: string; code: string; token: string }) =>
|
||||||
post<LoginResponse>('/email-code-login/validity', { body: data })
|
post<LoginResponse>('/email-code-login/validity', { body: data })
|
||||||
|
|
||||||
export const sendResetPasswordCode = (email: string, language = 'en-US') =>
|
export const sendResetPasswordCode = (email: string, language = 'en-US') =>
|
||||||
post<CommonResponse & { data: string;message?: string ;code?: string }>('/forgot-password', { body: { email, language } })
|
post<CommonResponse & { data: string; message?: string; code?: string }>('/forgot-password', { body: { email, language } })
|
||||||
|
|
||||||
export const verifyResetPasswordCode = (body: { email: string;code: string;token: string }) =>
|
export const verifyResetPasswordCode = (body: { email: string; code: string; token: string }) =>
|
||||||
post<CommonResponse & { is_valid: boolean }>('/forgot-password/validity', { body })
|
post<CommonResponse & { is_valid: boolean; token: string }>('/forgot-password/validity', { body })
|
||||||
|
|
||||||
export const sendDeleteAccountCode = () =>
|
export const sendDeleteAccountCode = () =>
|
||||||
get<CommonResponse & { data: string }>('/account/delete/verify')
|
get<CommonResponse & { data: string }>('/account/delete/verify')
|
||||||
|
|
||||||
export const verifyDeleteAccountCode = (body: { code: string;token: string }) =>
|
export const verifyDeleteAccountCode = (body: { code: string; token: string }) =>
|
||||||
post<CommonResponse & { is_valid: boolean }>('/account/delete', { body })
|
post<CommonResponse & { is_valid: boolean }>('/account/delete', { body })
|
||||||
|
|
||||||
export const submitDeleteAccountFeedback = (body: { feedback: string;email: string }) =>
|
export const submitDeleteAccountFeedback = (body: { feedback: string; email: string }) =>
|
||||||
post<CommonResponse>('/account/delete/feedback', { body })
|
post<CommonResponse>('/account/delete/feedback', { body })
|
||||||
|
|||||||
115
web/yarn.lock
115
web/yarn.lock
@@ -2066,10 +2066,10 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@monaco-editor/loader" "^1.4.0"
|
"@monaco-editor/loader" "^1.4.0"
|
||||||
|
|
||||||
"@next/env@14.2.17":
|
"@next/env@14.2.25":
|
||||||
version "14.2.17"
|
version "14.2.25"
|
||||||
resolved "https://registry.npmjs.org/@next/env/-/env-14.2.17.tgz"
|
resolved "https://registry.yarnpkg.com/@next/env/-/env-14.2.25.tgz#936d10b967e103e49a4bcea1e97292d5605278dd"
|
||||||
integrity sha512-MCgO7VHxXo8sYR/0z+sk9fGyJJU636JyRmkjc7ZJY8Hurl8df35qG5hoAh5KMs75FLjhlEo9bb2LGe89Y/scDA==
|
integrity sha512-JnzQ2cExDeG7FxJwqAksZ3aqVJrHjFwZQAEJ9gQZSoEhIow7SNoKZzju/AwQ+PLIR4NY8V0rhcVozx/2izDO0w==
|
||||||
|
|
||||||
"@next/eslint-plugin-next@14.0.4":
|
"@next/eslint-plugin-next@14.0.4":
|
||||||
version "14.0.4"
|
version "14.0.4"
|
||||||
@@ -2085,50 +2085,50 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
source-map "^0.7.0"
|
source-map "^0.7.0"
|
||||||
|
|
||||||
"@next/swc-darwin-arm64@14.2.17":
|
"@next/swc-darwin-arm64@14.2.25":
|
||||||
version "14.2.17"
|
version "14.2.25"
|
||||||
resolved "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.17.tgz"
|
resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.25.tgz#7bcccfda0c0ff045c45fbe34c491b7368e373e3d"
|
||||||
integrity sha512-WiOf5nElPknrhRMTipXYTJcUz7+8IAjOYw3vXzj3BYRcVY0hRHKWgTgQ5439EvzQyHEko77XK+yN9x9OJ0oOog==
|
integrity sha512-09clWInF1YRd6le00vt750s3m7SEYNehz9C4PUcSu3bAdCTpjIV4aTYQZ25Ehrr83VR1rZeqtKUPWSI7GfuKZQ==
|
||||||
|
|
||||||
"@next/swc-darwin-x64@14.2.17":
|
"@next/swc-darwin-x64@14.2.25":
|
||||||
version "14.2.17"
|
version "14.2.25"
|
||||||
resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.17.tgz#e29a17ef28d97c347c7d021f391e13b6c8e4c813"
|
resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.25.tgz#b489e209d7b405260b73f69a38186ed150fb7a08"
|
||||||
integrity sha512-29y425wYnL17cvtxrDQWC3CkXe/oRrdt8ie61S03VrpwpPRI0XsnTvtKO06XCisK4alaMnZlf8riwZIbJTaSHQ==
|
integrity sha512-V+iYM/QR+aYeJl3/FWWU/7Ix4b07ovsQ5IbkwgUK29pTHmq+5UxeDr7/dphvtXEq5pLB/PucfcBNh9KZ8vWbug==
|
||||||
|
|
||||||
"@next/swc-linux-arm64-gnu@14.2.17":
|
"@next/swc-linux-arm64-gnu@14.2.25":
|
||||||
version "14.2.17"
|
version "14.2.25"
|
||||||
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.17.tgz#10e99c7aa60cc33f8b7633e045f74be9a43e7b0c"
|
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.25.tgz#ba064fabfdce0190d9859493d8232fffa84ef2e2"
|
||||||
integrity sha512-SSHLZls3ZwNEHsc+d0ynKS+7Af0Nr8+KTUBAy9pm6xz9SHkJ/TeuEg6W3cbbcMSh6j4ITvrjv3Oi8n27VR+IPw==
|
integrity sha512-LFnV2899PJZAIEHQ4IMmZIgL0FBieh5keMnriMY1cK7ompR+JUd24xeTtKkcaw8QmxmEdhoE5Mu9dPSuDBgtTg==
|
||||||
|
|
||||||
"@next/swc-linux-arm64-musl@14.2.17":
|
"@next/swc-linux-arm64-musl@14.2.25":
|
||||||
version "14.2.17"
|
version "14.2.25"
|
||||||
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.17.tgz#9a5bb809d3c6aef96c409959aedae28b4e5db53d"
|
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.25.tgz#bf0018267e4e0fbfa1524750321f8cae855144a3"
|
||||||
integrity sha512-VFge37us5LNPatB4F7iYeuGs9Dprqe4ZkW7lOEJM91r+Wf8EIdViWHLpIwfdDXinvCdLl6b4VyLpEBwpkctJHA==
|
integrity sha512-QC5y5PPTmtqFExcKWKYgUNkHeHE/z3lUsu83di488nyP0ZzQ3Yse2G6TCxz6nNsQwgAx1BehAJTZez+UQxzLfw==
|
||||||
|
|
||||||
"@next/swc-linux-x64-gnu@14.2.17":
|
"@next/swc-linux-x64-gnu@14.2.25":
|
||||||
version "14.2.17"
|
version "14.2.25"
|
||||||
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.17.tgz#64e0ce01870e6dc45ae48f676d7cce82aedcdc62"
|
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.25.tgz#64f5a6016a7148297ee80542e0fd788418a32472"
|
||||||
integrity sha512-aaQlpxUVb9RZ41adlTYVQ3xvYEfBPUC8+6rDgmQ/0l7SvK8S1YNJzPmDPX6a4t0jLtIoNk7j+nroS/pB4nx7vQ==
|
integrity sha512-y6/ML4b9eQ2D/56wqatTJN5/JR8/xdObU2Fb1RBidnrr450HLCKr6IJZbPqbv7NXmje61UyxjF5kvSajvjye5w==
|
||||||
|
|
||||||
"@next/swc-linux-x64-musl@14.2.17":
|
"@next/swc-linux-x64-musl@14.2.25":
|
||||||
version "14.2.17"
|
version "14.2.25"
|
||||||
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.17.tgz#93114164b6ccfc533908193ab9065f0c3970abc3"
|
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.25.tgz#58dc636d7c55828478159546f7b95ab1e902301c"
|
||||||
integrity sha512-HSyEiFaEY3ay5iATDqEup5WAfrhMATNJm8dYx3ZxL+e9eKv10XKZCwtZByDoLST7CyBmyDz+OFJL1wigyXeaoA==
|
integrity sha512-sPX0TSXHGUOZFvv96GoBXpB3w4emMqKeMgemrSxI7A6l55VBJp/RKYLwZIB9JxSqYPApqiREaIIap+wWq0RU8w==
|
||||||
|
|
||||||
"@next/swc-win32-arm64-msvc@14.2.17":
|
"@next/swc-win32-arm64-msvc@14.2.25":
|
||||||
version "14.2.17"
|
version "14.2.25"
|
||||||
resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.17.tgz#4b99dea02178c112e5c33c742f9ff2a49b3b2939"
|
resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.25.tgz#93562d447c799bded1e89c1a62d5195a2a8c6c0d"
|
||||||
integrity sha512-h5qM9Btqv87eYH8ArrnLoAHLyi79oPTP2vlGNSg4CDvUiXgi7l0+5KuEGp5pJoMhjuv9ChRdm7mRlUUACeBt4w==
|
integrity sha512-ReO9S5hkA1DU2cFCsGoOEp7WJkhFzNbU/3VUF6XxNGUCQChyug6hZdYL/istQgfT/GWE6PNIg9cm784OI4ddxQ==
|
||||||
|
|
||||||
"@next/swc-win32-ia32-msvc@14.2.17":
|
"@next/swc-win32-ia32-msvc@14.2.25":
|
||||||
version "14.2.17"
|
version "14.2.25"
|
||||||
resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.17.tgz#f1c23955405a259b6d45c65f918575b01bcf0106"
|
resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.25.tgz#ad85a33466be1f41d083211ea21adc0d2c6e6554"
|
||||||
integrity sha512-BD/G++GKSLexQjdyoEUgyo5nClU7er5rK0sE+HlEqnldJSm96CIr/+YOTT063LVTT/dUOeQsNgp5DXr86/K7/A==
|
integrity sha512-DZ/gc0o9neuCDyD5IumyTGHVun2dCox5TfPQI/BJTYwpSNYM3CZDI4i6TOdjeq1JMo+Ug4kPSMuZdwsycwFbAw==
|
||||||
|
|
||||||
"@next/swc-win32-x64-msvc@14.2.17":
|
"@next/swc-win32-x64-msvc@14.2.25":
|
||||||
version "14.2.17"
|
version "14.2.25"
|
||||||
resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.17.tgz#44f5a4fcd8df1396a8d4326510ca2d92fb809cb3"
|
resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.25.tgz#3969c66609e683ec63a6a9f320a855f7be686a08"
|
||||||
integrity sha512-vkQfN1+4V4KqDibkW2q0sJ6CxQuXq5l2ma3z0BRcfIqkAMZiiW67T9yCpwqJKP68QghBtPEFjPAlaqe38O6frw==
|
integrity sha512-KSznmS6eFjQ9RJ1nEc66kJvtGIL1iZMYmGEXsZPh2YtnLtqrgdVvKXJY2ScjjoFnG6nGLyPFR0UiEvDwVah4Tw==
|
||||||
|
|
||||||
"@nodelib/fs.scandir@2.1.5":
|
"@nodelib/fs.scandir@2.1.5":
|
||||||
version "2.1.5"
|
version "2.1.5"
|
||||||
@@ -5864,6 +5864,13 @@ dompurify@^3.2.1:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
"@types/trusted-types" "^2.0.7"
|
"@types/trusted-types" "^2.0.7"
|
||||||
|
|
||||||
|
dompurify@^3.2.4:
|
||||||
|
version "3.2.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-3.2.4.tgz#af5a5a11407524431456cf18836c55d13441cd8e"
|
||||||
|
integrity sha512-ysFSFEDVduQpyhzAob/kkuJjf5zWkZD8/A9ywSp1byueyuCfHamrCBa14/Oc2iiB0e51B+NpxSl5gmzn+Ms/mg==
|
||||||
|
optionalDependencies:
|
||||||
|
"@types/trusted-types" "^2.0.7"
|
||||||
|
|
||||||
domutils@^2.5.2, domutils@^2.8.0:
|
domutils@^2.5.2, domutils@^2.8.0:
|
||||||
version "2.8.0"
|
version "2.8.0"
|
||||||
resolved "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz"
|
resolved "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz"
|
||||||
@@ -9911,12 +9918,12 @@ neo-async@^2.6.2:
|
|||||||
resolved "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz"
|
resolved "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz"
|
||||||
integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
|
integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
|
||||||
|
|
||||||
next@^14.2.10:
|
next@^14.2.25:
|
||||||
version "14.2.17"
|
version "14.2.25"
|
||||||
resolved "https://registry.npmjs.org/next/-/next-14.2.17.tgz"
|
resolved "https://registry.yarnpkg.com/next/-/next-14.2.25.tgz#0657551fde6a97f697cf9870e9ccbdaa465c6008"
|
||||||
integrity sha512-hNo/Zy701DDO3nzKkPmsLRlDfNCtb1OJxFUvjGEl04u7SFa3zwC6hqsOUzMajcaEOEV8ey1GjvByvrg0Qr5AiQ==
|
integrity sha512-N5M7xMc4wSb4IkPvEV5X2BRRXUmhVHNyaXwEM86+voXthSZz8ZiRyQW4p9mwAoAPIm6OzuVZtn7idgEJeAJN3Q==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@next/env" "14.2.17"
|
"@next/env" "14.2.25"
|
||||||
"@swc/helpers" "0.5.5"
|
"@swc/helpers" "0.5.5"
|
||||||
busboy "1.6.0"
|
busboy "1.6.0"
|
||||||
caniuse-lite "^1.0.30001579"
|
caniuse-lite "^1.0.30001579"
|
||||||
@@ -9924,15 +9931,15 @@ next@^14.2.10:
|
|||||||
postcss "8.4.31"
|
postcss "8.4.31"
|
||||||
styled-jsx "5.1.1"
|
styled-jsx "5.1.1"
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
"@next/swc-darwin-arm64" "14.2.17"
|
"@next/swc-darwin-arm64" "14.2.25"
|
||||||
"@next/swc-darwin-x64" "14.2.17"
|
"@next/swc-darwin-x64" "14.2.25"
|
||||||
"@next/swc-linux-arm64-gnu" "14.2.17"
|
"@next/swc-linux-arm64-gnu" "14.2.25"
|
||||||
"@next/swc-linux-arm64-musl" "14.2.17"
|
"@next/swc-linux-arm64-musl" "14.2.25"
|
||||||
"@next/swc-linux-x64-gnu" "14.2.17"
|
"@next/swc-linux-x64-gnu" "14.2.25"
|
||||||
"@next/swc-linux-x64-musl" "14.2.17"
|
"@next/swc-linux-x64-musl" "14.2.25"
|
||||||
"@next/swc-win32-arm64-msvc" "14.2.17"
|
"@next/swc-win32-arm64-msvc" "14.2.25"
|
||||||
"@next/swc-win32-ia32-msvc" "14.2.17"
|
"@next/swc-win32-ia32-msvc" "14.2.25"
|
||||||
"@next/swc-win32-x64-msvc" "14.2.17"
|
"@next/swc-win32-x64-msvc" "14.2.25"
|
||||||
|
|
||||||
no-case@^3.0.4:
|
no-case@^3.0.4:
|
||||||
version "3.0.4"
|
version "3.0.4"
|
||||||
|
|||||||
Reference in New Issue
Block a user