Compare commits

..

13 Commits

@ -1,7 +1,7 @@
![cover-v5-optimized](./images/GitHub_README_if.png)
<p align="center">
📌 <a href="https://dify.ai/blog/introducing-dify-workflow-file-upload-a-demo-on-ai-podcast">Introducing Dify Workflow File Upload: Recreate Google NotebookLM Podcast</a>
📌 <a href="https://dify.ai/blog/introducing-dify-workflow-file-upload-a-demo-on-ai-podcast">Introducing Dify Workflow File Upload: Recreate Google NotebookLM Podcast111</a>
</p>
<p align="center">

@ -113,3 +113,9 @@ class MemberNotInTenantError(BaseHTTPException):
error_code = "member_not_in_tenant"
description = "The member is not in the workspace."
code = 400
class AccountInFreezeError(BaseHTTPException):
error_code = "account_in_freeze"
description = "This email is temporarily unavailable."
code = 400

@ -9,6 +9,7 @@ from configs import dify_config
from constants.languages import supported_language
from controllers.console import api
from controllers.console.auth.error import (
AccountInFreezeError,
EmailAlreadyInUseError,
EmailChangeLimitError,
EmailCodeError,
@ -479,21 +480,28 @@ class ChangeEmailResetApi(Resource):
parser.add_argument("token", type=str, required=True, nullable=False, location="json")
args = parser.parse_args()
if AccountService.is_account_in_freeze(args["new_email"]):
raise AccountInFreezeError()
if not AccountService.check_email_unique(args["new_email"]):
raise EmailAlreadyInUseError()
reset_data = AccountService.get_change_email_data(args["token"])
if not reset_data:
raise InvalidTokenError()
AccountService.revoke_change_email_token(args["token"])
if not AccountService.check_email_unique(args["new_email"]):
raise EmailAlreadyInUseError()
old_email = reset_data.get("old_email", "")
if current_user.email != old_email:
raise AccountNotFound()
updated_account = AccountService.update_account(current_user, email=args["new_email"])
AccountService.send_change_email_completed_notify_email(
email=args["new_email"],
)
return updated_account

@ -1011,7 +1011,9 @@ class ToolManager:
if variable is None:
raise ToolParameterError(f"Variable {tool_input.value} does not exist")
parameter_value = variable.value
elif tool_input.type in {"mixed", "constant"}:
elif tool_input.type == "constant":
parameter_value = tool_input.value
elif tool_input.type == "mixed":
segment_group = variable_pool.convert_template(str(tool_input.value))
parameter_value = segment_group.text
else:

@ -54,7 +54,7 @@ class ToolNodeData(BaseNodeData, ToolEntity):
for val in value:
if not isinstance(val, str):
raise ValueError("value must be a list of strings")
elif typ == "constant" and not isinstance(value, str | int | float | bool):
elif typ == "constant" and not isinstance(value, str | int | float | bool | dict):
raise ValueError("value must be a string, int, float, or bool")
return typ

@ -25,6 +25,7 @@ class EmailType(Enum):
EMAIL_CODE_LOGIN = "email_code_login"
CHANGE_EMAIL_OLD = "change_email_old"
CHANGE_EMAIL_NEW = "change_email_new"
CHANGE_EMAIL_COMPLETED = "change_email_completed"
OWNER_TRANSFER_CONFIRM = "owner_transfer_confirm"
OWNER_TRANSFER_OLD_NOTIFY = "owner_transfer_old_notify"
OWNER_TRANSFER_NEW_NOTIFY = "owner_transfer_new_notify"
@ -344,6 +345,18 @@ def create_default_email_config() -> EmailI18nConfig:
branded_template_path="without-brand/change_mail_confirm_new_template_zh-CN.html",
),
},
EmailType.CHANGE_EMAIL_COMPLETED: {
EmailLanguage.EN_US: EmailTemplate(
subject="Your login email has been changed",
template_path="change_mail_completed_template_en-US.html",
branded_template_path="without-brand/change_mail_completed_template_en-US.html",
),
EmailLanguage.ZH_HANS: EmailTemplate(
subject="您的登录邮箱已更改",
template_path="change_mail_completed_template_zh-CN.html",
branded_template_path="without-brand/change_mail_completed_template_zh-CN.html",
),
},
EmailType.OWNER_TRANSFER_CONFIRM: {
EmailLanguage.EN_US: EmailTemplate(
subject="Verify Your Request to Transfer Workspace Ownership",

@ -54,7 +54,10 @@ from services.errors.workspace import WorkSpaceNotAllowedCreateError, Workspaces
from services.feature_service import FeatureService
from tasks.delete_account_task import delete_account_task
from tasks.mail_account_deletion_task import send_account_deletion_verification_code
from tasks.mail_change_mail_task import send_change_mail_task
from tasks.mail_change_mail_task import (
send_change_mail_completed_notification_task,
send_change_mail_task,
)
from tasks.mail_email_code_login import send_email_code_login_mail_task
from tasks.mail_invite_member_task import send_invite_member_mail_task
from tasks.mail_owner_transfer_task import (
@ -461,6 +464,22 @@ class AccountService:
cls.change_email_rate_limiter.increment_rate_limit(account_email)
return token
@classmethod
def send_change_email_completed_notify_email(
cls,
account: Optional[Account] = None,
email: Optional[str] = None,
language: Optional[str] = "en-US",
):
account_email = account.email if account else email
if account_email is None:
raise ValueError("Email must be provided.")
send_change_mail_completed_notification_task.delay(
language=language,
to=account_email,
)
@classmethod
def send_owner_transfer_email(
cls,
@ -652,6 +671,12 @@ class AccountService:
return account
@classmethod
def is_account_in_freeze(cls, email: str) -> bool:
if dify_config.BILLING_ENABLED and BillingService.is_email_in_freeze(email):
return True
return False
@staticmethod
@redis_fallback(default_return=None)
def add_login_error_rate_limit(email: str) -> None:

@ -5,7 +5,7 @@ import click
from celery import shared_task # type: ignore
from extensions.ext_mail import mail
from libs.email_i18n import get_email_i18n_service
from libs.email_i18n import EmailType, get_email_i18n_service
@shared_task(queue="mail")
@ -40,3 +40,41 @@ def send_change_mail_task(language: str, to: str, code: str, phase: str) -> None
)
except Exception:
logging.exception("Send change email mail to {} failed".format(to))
@shared_task(queue="mail")
def send_change_mail_completed_notification_task(language: str, to: str) -> None:
"""
Send change email completed notification with internationalization support.
Args:
language: Language code for email localization
to: Recipient email address
"""
if not mail.is_inited():
return
logging.info(click.style("Start change email completed notify mail to {}".format(to), fg="green"))
start_at = time.perf_counter()
try:
email_service = get_email_i18n_service()
email_service.send_email(
email_type=EmailType.CHANGE_EMAIL_COMPLETED,
language_code=language,
to=to,
template_context={
"to": to,
"email": to,
},
)
end_at = time.perf_counter()
logging.info(
click.style(
"Send change email completed mail to {} succeeded: latency: {}".format(to, end_at - start_at),
fg="green",
)
)
except Exception:
logging.exception("Send change email completed mail to {} failed".format(to))

@ -0,0 +1,135 @@
<!DOCTYPE html>
<html>
<head>
<style>
body {
font-family: 'Arial', sans-serif;
line-height: 16pt;
color: #101828;
background-color: #e9ebf0;
margin: 0;
padding: 0;
}
.container {
width: 504px;
min-height: 374px;
margin: 40px auto;
padding: 0 48px;
background-color: #fcfcfd;
border-radius: 16px;
border: 1px solid #ffffff;
box-shadow: 0px 3px 10px -2px rgba(9, 9, 11, 0.08), 0px 2px 4px -2px rgba(9, 9, 11, 0.06);
}
.header {
padding-top: 36px;
padding-bottom: 24px;
}
.header img {
max-width: 63px;
height: auto;
}
.title {
margin: 0;
padding-top: 8px;
padding-bottom: 16px;
color: #101828;
font-size: 24px;
font-family: Inter;
font-style: normal;
font-weight: 600;
line-height: 120%; /* 28.8px */
}
.description {
color: #354052;
font-size: 14px;
font-family: Inter;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
}
.content1 {
margin: 0;
padding-top: 16px;
padding-bottom: 12px;
}
.content2 {
margin: 0;
}
.content3 {
margin: 0;
padding-bottom: 12px;
}
.code-content {
margin-bottom: 8px;
padding: 16px 32px;
text-align: center;
border-radius: 16px;
background-color: #f2f4f7;
}
.code {
color: #101828;
font-family: Inter;
font-size: 30px;
font-style: normal;
font-weight: 700;
line-height: 36px;
}
.tips {
margin: 0;
padding-top: 12px;
padding-bottom: 16px;
color: #354052;
font-size: 14px;
font-family: Inter;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
}
.support {
color: #354052;
font-weight: 500;
text-decoration: none;
}
.support:hover {
color: #354052;
font-weight: 500;
text-decoration: underline;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<!-- Optional: Add a logo or a header image here -->
<img src="https://assets.dify.ai/images/logo.png" alt="Dify Logo" />
</div>
<p class="title">Your login email has been changed</p>
<div class="description">
<p class="content1">You can now log into Dify with your new email address:</p>
</div>
<div class="code-content">
<span class="code">{{email}}</span>
</div>
<p class="tips">If you did not make this change, email <a class="support" href="mailto:support@dify.ai">support@dify.ai</a>.</p>
</div>
</body>
</html>

@ -0,0 +1,135 @@
<!DOCTYPE html>
<html>
<head>
<style>
body {
font-family: 'Arial', sans-serif;
line-height: 16pt;
color: #101828;
background-color: #e9ebf0;
margin: 0;
padding: 0;
}
.container {
width: 504px;
min-height: 374px;
margin: 40px auto;
padding: 0 48px;
background-color: #fcfcfd;
border-radius: 16px;
border: 1px solid #ffffff;
box-shadow: 0px 3px 10px -2px rgba(9, 9, 11, 0.08), 0px 2px 4px -2px rgba(9, 9, 11, 0.06);
}
.header {
padding-top: 36px;
padding-bottom: 24px;
}
.header img {
max-width: 63px;
height: auto;
}
.title {
margin: 0;
padding-top: 8px;
padding-bottom: 16px;
color: #101828;
font-size: 24px;
font-family: Inter;
font-style: normal;
font-weight: 600;
line-height: 120%; /* 28.8px */
}
.description {
color: #354052;
font-size: 14px;
font-family: Inter;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
}
.content1 {
margin: 0;
padding-top: 16px;
padding-bottom: 12px;
}
.content2 {
margin: 0;
}
.content3 {
margin: 0;
padding-bottom: 12px;
}
.code-content {
margin-bottom: 8px;
padding: 16px 32px;
text-align: center;
border-radius: 16px;
background-color: #f2f4f7;
}
.code {
color: #101828;
font-family: Inter;
font-size: 30px;
font-style: normal;
font-weight: 700;
line-height: 36px;
}
.tips {
margin: 0;
padding-top: 12px;
padding-bottom: 16px;
color: #354052;
font-size: 14px;
font-family: Inter;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
}
.support {
color: #354052;
font-weight: 500;
text-decoration: none;
}
.support:hover {
color: #354052;
font-weight: 500;
text-decoration: underline;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<!-- Optional: Add a logo or a header image here -->
<img src="https://assets.dify.ai/images/logo.png" alt="Dify Logo" />
</div>
<p class="title">您的登录邮箱已更改</p>
<div class="description">
<p class="content1">您现在可以使用新的电子邮件地址登录 Dify</p>
</div>
<div class="code-content">
<span class="code">{{email}}</span>
</div>
<p class="tips">如果您没有进行此更改,请发送电子邮件至 <a class="support" href="mailto:support@dify.ai">support@dify.ai</a></p>
</div>
</body>
</html>

@ -0,0 +1,132 @@
<!DOCTYPE html>
<html>
<head>
<style>
body {
font-family: 'Arial', sans-serif;
line-height: 16pt;
color: #101828;
background-color: #e9ebf0;
margin: 0;
padding: 0;
}
.container {
width: 504px;
min-height: 374px;
margin: 40px auto;
padding: 0 48px;
background-color: #fcfcfd;
border-radius: 16px;
border: 1px solid #ffffff;
box-shadow: 0px 3px 10px -2px rgba(9, 9, 11, 0.08), 0px 2px 4px -2px rgba(9, 9, 11, 0.06);
}
.header {
padding-top: 36px;
padding-bottom: 24px;
}
.header img {
max-width: 63px;
height: auto;
}
.title {
margin: 0;
padding-top: 8px;
padding-bottom: 16px;
color: #101828;
font-size: 24px;
font-family: Inter;
font-style: normal;
font-weight: 600;
line-height: 120%; /* 28.8px */
}
.description {
color: #354052;
font-size: 14px;
font-family: Inter;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
}
.content1 {
margin: 0;
padding-top: 16px;
padding-bottom: 12px;
}
.content2 {
margin: 0;
}
.content3 {
margin: 0;
padding-bottom: 12px;
}
.code-content {
margin-bottom: 8px;
padding: 16px 32px;
text-align: center;
border-radius: 16px;
background-color: #f2f4f7;
}
.code {
color: #101828;
font-family: Inter;
font-size: 30px;
font-style: normal;
font-weight: 700;
line-height: 36px;
}
.tips {
margin: 0;
padding-top: 12px;
padding-bottom: 16px;
color: #354052;
font-size: 14px;
font-family: Inter;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
}
.support {
color: #354052;
font-weight: 500;
text-decoration: none;
}
.support:hover {
color: #354052;
font-weight: 500;
text-decoration: underline;
}
</style>
</head>
<body>
<div class="container">
<div class="header"></div>
<p class="title">Your login email has been changed</p>
<div class="description">
<p class="content1">You can now log into {{application_title}} with your new email address:</p>
</div>
<div class="code-content">
<span class="code">{{email}}</span>
</div>
<p class="tips">If you did not make this change, please ignore this email or contact support immediately.</p>
</div>
</body>
</html>

@ -0,0 +1,132 @@
<!DOCTYPE html>
<html>
<head>
<style>
body {
font-family: 'Arial', sans-serif;
line-height: 16pt;
color: #101828;
background-color: #e9ebf0;
margin: 0;
padding: 0;
}
.container {
width: 504px;
min-height: 374px;
margin: 40px auto;
padding: 0 48px;
background-color: #fcfcfd;
border-radius: 16px;
border: 1px solid #ffffff;
box-shadow: 0px 3px 10px -2px rgba(9, 9, 11, 0.08), 0px 2px 4px -2px rgba(9, 9, 11, 0.06);
}
.header {
padding-top: 36px;
padding-bottom: 24px;
}
.header img {
max-width: 63px;
height: auto;
}
.title {
margin: 0;
padding-top: 8px;
padding-bottom: 16px;
color: #101828;
font-size: 24px;
font-family: Inter;
font-style: normal;
font-weight: 600;
line-height: 120%; /* 28.8px */
}
.description {
color: #354052;
font-size: 14px;
font-family: Inter;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
}
.content1 {
margin: 0;
padding-top: 16px;
padding-bottom: 12px;
}
.content2 {
margin: 0;
}
.content3 {
margin: 0;
padding-bottom: 12px;
}
.code-content {
margin-bottom: 8px;
padding: 16px 32px;
text-align: center;
border-radius: 16px;
background-color: #f2f4f7;
}
.code {
color: #101828;
font-family: Inter;
font-size: 30px;
font-style: normal;
font-weight: 700;
line-height: 36px;
}
.tips {
margin: 0;
padding-top: 12px;
padding-bottom: 16px;
color: #354052;
font-size: 14px;
font-family: Inter;
font-style: normal;
font-weight: 400;
line-height: 20px; /* 142.857% */
letter-spacing: -0.07px;
}
.support {
color: #354052;
font-weight: 500;
text-decoration: none;
}
.support:hover {
color: #354052;
font-weight: 500;
text-decoration: underline;
}
</style>
</head>
<body>
<div class="container">
<div class="header"></div>
<p class="title">您的登录邮箱已更改</p>
<div class="description">
<p class="content1">您现在可以使用新的电子邮件地址登录 {{application_title}}</p>
</div>
<div class="code-content">
<span class="code">{{email}}</span>
</div>
<p class="tips">如果您没有进行此更改,请忽略此电子邮件或立即联系支持。</p>
</div>
</body>
</html>

@ -32,6 +32,35 @@ export type IAppDetailLayoutProps = {
appId: string
}
const useIframeHeader = () => {
const [show, setShow] = useState(true);
useEffect(() => {
// 监听父级指定操作
const handler = (event: MessageEvent) => {
// if (event.origin !== "https://gcgj.ngsk.tech:7001") return;
if (event.data.type === "HIDDEN") setShow(() => false);
};
// 初始化完成后提示iframe操作
window.parent.postMessage(
{
type: "SIDEBA",
},
"*"
);
window.addEventListener("message", handler);
return () => window.removeEventListener("message", handler);
}, []);
return {
show,
};
};
const AppDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
const {
children,
@ -56,6 +85,7 @@ const AppDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
icon: NavIcon
selectedIcon: NavIcon
}>>([])
const { show } = useIframeHeader()
const getNavigations = useCallback((appId: string, isCurrentWorkspaceEditor: boolean, mode: string) => {
const navs = [
@ -160,9 +190,9 @@ const AppDetailLayout: FC<IAppDetailLayoutProps> = (props) => {
return (
<div className={cn(s.app, 'relative flex', 'overflow-hidden')}>
{appDetail && (
{show ? appDetail && (
<AppSideBar title={appDetail.name} icon={appDetail.icon} icon_background={appDetail.icon_background as string} desc={appDetail.mode} navigation={navigation} />
)}
): ''}
<div className="grow overflow-hidden bg-components-panel-bg">
{children}
</div>

@ -1,5 +1,5 @@
'use client'
import React, { useState } from 'react'
import React, { useEffect, useState } from 'react'
import dayjs from 'dayjs'
import quarterOfYear from 'dayjs/plugin/quarterOfYear'
import { useTranslation } from 'react-i18next'
@ -9,6 +9,7 @@ import type { Item } from '@/app/components/base/select'
import { SimpleSelect } from '@/app/components/base/select'
import { TIME_PERIOD_MAPPING } from '@/app/components/app/log/filter'
import { useStore as useAppStore } from '@/app/components/app/store'
import { useRouter } from 'next/navigation'
dayjs.extend(quarterOfYear)
@ -21,12 +22,44 @@ export type IChartViewProps = {
headerRight: React.ReactNode
}
let state = 0
const useIframeRedirection = () => {
const router = useRouter()
useEffect(() => {
// 监听父级指定操作
const handler = (event: MessageEvent) => {
// if (event.origin !== "https://gcgj.ngsk.tech:7001") return;
if (
event.data.type === 'CHART_VIEW_REDIRECT'
&& event.data.redirectUrl
&& state < 3 // 防止无限重定向
) {
state++
router.replace(event.data.redirectUrl)
}
}
// 首页初始化完成后提示iframe操作
window.parent.postMessage(
{
type: 'CHART_VIEW',
},
'*',
)
window.addEventListener('message', handler)
return () => window.removeEventListener('message', handler)
}, [])
}
export default function ChartView({ appId, headerRight }: IChartViewProps) {
const { t } = useTranslation()
const appDetail = useAppStore(state => state.appDetail)
const isChatApp = appDetail?.mode !== 'completion' && appDetail?.mode !== 'workflow'
const isWorkflow = appDetail?.mode === 'workflow'
const [period, setPeriod] = useState<PeriodParams>({ name: t('appLog.filter.period.last7days'), query: { start: today.subtract(7, 'day').startOf('day').format(queryDateFormat), end: today.endOf('day').format(queryDateFormat) } })
useIframeRedirection()
const onSelect = (item: Item) => {
if (item.value === -1) {

@ -1,5 +1,5 @@
'use client'
import type { FC } from 'react'
import type { FC, JSX } from 'react'
import React, { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useBoolean } from 'ahooks'

@ -1,4 +1,4 @@
import type { ReactElement } from 'react'
import type { JSX } from 'react'
import { cloneElement, useCallback } from 'react'
import { useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
@ -7,7 +7,7 @@ import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigge
import { RiMoreLine } from '@remixicon/react'
export type Operation = {
id: string; title: string; icon: ReactElement; onClick: () => void
id: string; title: string; icon: JSX.Element; onClick: () => void
}
const AppOperations = ({ operations, gap }: {
@ -47,7 +47,7 @@ const AppOperations = ({ operations, gap }: {
updatedEntries[id] = true
width += gap + childWidth
}
else {
else {
if (i === childrens.length - 1 && width + childWidth <= containerWidth)
updatedEntries[id] = true
else

@ -7,6 +7,7 @@ import produce from 'immer'
import ModalFoot from '../modal-foot'
import ConfigSelect from '../config-select'
import ConfigString from '../config-string'
import SelectTypeItem from '../select-type-item'
import Field from './field'
import Input from '@/app/components/base/input'
import Toast from '@/app/components/base/toast'
@ -19,8 +20,6 @@ import FileUploadSetting from '@/app/components/workflow/nodes/_base/components/
import Checkbox from '@/app/components/base/checkbox'
import { DEFAULT_FILE_UPLOAD_SETTING } from '@/app/components/workflow/constants'
import { DEFAULT_VALUE_MAX_LEN } from '@/config'
import type { Item as SelectItem } from './type-select'
import TypeSelector from './type-select'
const TEXT_MAX_LENGTH = 256
@ -78,56 +77,23 @@ const ConfigModal: FC<IConfigModalProps> = ({
}
}, [])
const selectOptions: SelectItem[] = [
{
name: t('appDebug.variableConfig.text-input'),
value: InputVarType.textInput,
},
{
name: t('appDebug.variableConfig.paragraph'),
value: InputVarType.paragraph,
},
{
name: t('appDebug.variableConfig.select'),
value: InputVarType.select,
},
{
name: t('appDebug.variableConfig.number'),
value: InputVarType.number,
},
{
name: t('appDebug.variableConfig.boolean'),
value: InputVarType.boolean,
},
...(supportFile ? [
{
name: t('appDebug.variableConfig.single-file'),
value: InputVarType.singleFile,
},
{
name: t('appDebug.variableConfig.multi-files'),
value: InputVarType.multiFiles,
},
] : []),
]
const handleTypeChange = useCallback((item: SelectItem) => {
const type = item.value as InputVarType
const newPayload = produce(tempPayload, (draft) => {
draft.type = type
if ([InputVarType.singleFile, InputVarType.multiFiles].includes(type)) {
(Object.keys(DEFAULT_FILE_UPLOAD_SETTING)).forEach((key) => {
if (key !== 'max_length')
(draft as any)[key] = (DEFAULT_FILE_UPLOAD_SETTING as any)[key]
})
if (type === InputVarType.multiFiles)
draft.max_length = DEFAULT_FILE_UPLOAD_SETTING.max_length
}
if (type === InputVarType.paragraph)
draft.max_length = DEFAULT_VALUE_MAX_LEN
})
setTempPayload(newPayload)
const handleTypeChange = useCallback((type: InputVarType) => {
return () => {
const newPayload = produce(tempPayload, (draft) => {
draft.type = type
if ([InputVarType.singleFile, InputVarType.multiFiles].includes(type)) {
(Object.keys(DEFAULT_FILE_UPLOAD_SETTING)).forEach((key) => {
if (key !== 'max_length')
(draft as any)[key] = (DEFAULT_FILE_UPLOAD_SETTING as any)[key]
})
if (type === InputVarType.multiFiles)
draft.max_length = DEFAULT_FILE_UPLOAD_SETTING.max_length
}
if (type === InputVarType.paragraph)
draft.max_length = DEFAULT_VALUE_MAX_LEN
})
setTempPayload(newPayload)
}
}, [tempPayload])
const handleVarKeyBlur = useCallback((e: any) => {
@ -169,6 +135,15 @@ const ConfigModal: FC<IConfigModalProps> = ({
if (!isVariableNameValid)
return
// TODO: check if key already exists. should the consider the edit case
// if (varKeys.map(key => key?.trim()).includes(tempPayload.variable.trim())) {
// Toast.notify({
// type: 'error',
// message: t('appDebug.varKeyError.keyAlreadyExists', { key: tempPayload.variable }),
// })
// return
// }
if (!tempPayload.label) {
Toast.notify({ type: 'error', message: t('appDebug.variableConfig.errorMsg.labelNameRequired') })
return
@ -222,8 +197,18 @@ const ConfigModal: FC<IConfigModalProps> = ({
>
<div className='mb-8' ref={modalRef} tabIndex={-1}>
<div className='space-y-2'>
<Field title={t('appDebug.variableConfig.fieldType')}>
<TypeSelector value={type} items={selectOptions} onSelect={handleTypeChange} />
<div className='grid grid-cols-3 gap-2'>
<SelectTypeItem type={InputVarType.textInput} selected={type === InputVarType.textInput} onClick={handleTypeChange(InputVarType.textInput)} />
<SelectTypeItem type={InputVarType.paragraph} selected={type === InputVarType.paragraph} onClick={handleTypeChange(InputVarType.paragraph)} />
<SelectTypeItem type={InputVarType.select} selected={type === InputVarType.select} onClick={handleTypeChange(InputVarType.select)} />
<SelectTypeItem type={InputVarType.number} selected={type === InputVarType.number} onClick={handleTypeChange(InputVarType.number)} />
{supportFile && <>
<SelectTypeItem type={InputVarType.singleFile} selected={type === InputVarType.singleFile} onClick={handleTypeChange(InputVarType.singleFile)} />
<SelectTypeItem type={InputVarType.multiFiles} selected={type === InputVarType.multiFiles} onClick={handleTypeChange(InputVarType.multiFiles)} />
</>}
</div>
</Field>
<Field title={t('appDebug.variableConfig.varName')}>

@ -1,99 +0,0 @@
'use client'
import type { FC } from 'react'
import React, { useState } from 'react'
import { ChevronDownIcon } from '@heroicons/react/20/solid'
import { useTranslation } from 'react-i18next'
import classNames from '@/utils/classnames'
import {
PortalToFollowElem,
PortalToFollowElemContent,
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
import InputVarTypeIcon from '@/app/components/workflow/nodes/_base/components/input-var-type-icon'
import type { InputVarType } from '@/app/components/workflow/types'
import cn from '@/utils/classnames'
import Badge from '@/app/components/base/badge'
import { inputVarTypeToVarType } from '@/app/components/workflow/nodes/_base/components/variable/utils'
export type Item = {
value: InputVarType
name: string
}
type Props = {
value: string | number
onSelect: (value: Item) => void
items: Item[]
popupClassName?: string
popupInnerClassName?: string
readonly?: boolean
hideChecked?: boolean
}
const TypeSelector: FC<Props> = ({
value,
onSelect,
items,
popupInnerClassName,
readonly,
}) => {
const { t } = useTranslation()
const [open, setOpen] = useState(false)
const selectedItem = value ? items.find(item => item.value === value) : undefined
return (
<PortalToFollowElem
open={open}
onOpenChange={setOpen}
placement='bottom-start'
offset={4}
>
<PortalToFollowElemTrigger onClick={() => !readonly && setOpen(v => !v)} className='w-full'>
<div
className={classNames(`group flex h-9 items-center justify-between rounded-lg border-0 bg-components-input-bg-normal px-2 text-sm hover:bg-state-base-hover-alt ${readonly ? 'cursor-not-allowed' : 'cursor-pointer'}`)}
title={selectedItem?.name}
>
<div className='flex items-center'>
<InputVarTypeIcon type={selectedItem?.value as InputVarType} className='size-4 shrink-0 text-text-secondary' />
<span
className={`
ml-1.5 ${!selectedItem?.name && 'text-components-input-text-placeholder'}
`}
>
{selectedItem?.name}
</span>
</div>
<div className='flex items-center space-x-1'>
<Badge uppercase={false}>{inputVarTypeToVarType(selectedItem?.value as InputVarType)}</Badge>
<ChevronDownIcon className={cn('h-4 w-4 shrink-0 text-text-quaternary group-hover:text-text-secondary', open && 'text-text-secondary')} />
</div>
</div>
</PortalToFollowElemTrigger>
<PortalToFollowElemContent className='z-[61]'>
<div
className={classNames('w-[432px] rounded-md border-[0.5px] border-components-panel-border bg-components-panel-bg px-1 py-1 text-base shadow-lg focus:outline-none sm:text-sm', popupInnerClassName)}
>
{items.map((item: Item) => (
<div
key={item.value}
className={'flex h-9 cursor-pointer items-center justify-between rounded-lg px-2 text-text-secondary hover:bg-state-base-hover'}
title={item.name}
onClick={() => {
onSelect(item)
setOpen(false)
}}
>
<div className='flex items-center space-x-2'>
<InputVarTypeIcon type={item.value} className='size-4 shrink-0 text-text-secondary' />
<span title={item.name}>{item.name}</span>
</div>
<Badge uppercase={false}>{inputVarTypeToVarType(item.value)}</Badge>
</div>
))}
</div>
</PortalToFollowElemContent>
</PortalToFollowElem>
)
}
export default TypeSelector

@ -190,7 +190,7 @@ const ConfigVar: FC<IConfigVarProps> = ({ promptVariables, readonly, onPromptVar
const handleConfig = ({ key, type, index, name, config, icon, icon_background }: ExternalDataToolParams) => {
// setCurrKey(key)
setCurrIndex(index)
if (type !== 'string' && type !== 'paragraph' && type !== 'select' && type !== 'number' && type !== 'boolean') {
if (type !== 'string' && type !== 'paragraph' && type !== 'select' && type !== 'number') {
handleOpenExternalDataToolModal({ key, type, index, name, config, icon, icon_background }, promptVariables)
return
}

@ -65,7 +65,6 @@ const SelectVarType: FC<Props> = ({
<SelectItem type={InputVarType.paragraph} value='paragraph' text={t('appDebug.variableConfig.paragraph')} onClick={handleChange}></SelectItem>
<SelectItem type={InputVarType.select} value='select' text={t('appDebug.variableConfig.select')} onClick={handleChange}></SelectItem>
<SelectItem type={InputVarType.number} value='number' text={t('appDebug.variableConfig.number')} onClick={handleChange}></SelectItem>
<SelectItem type={InputVarType.boolean} value='boolean' text={t('appDebug.variableConfig.boolean')} onClick={handleChange}></SelectItem>
</div>
<div className='h-[1px] border-t border-components-panel-border'></div>
<div className='p-1'>

@ -8,7 +8,6 @@ import Textarea from '@/app/components/base/textarea'
import { DEFAULT_VALUE_MAX_LEN } from '@/config'
import type { Inputs } from '@/models/debug'
import cn from '@/utils/classnames'
import BoolInput from '@/app/components/workflow/nodes/_base/components/before-run-form/bool-input'
type Props = {
inputs: Inputs
@ -32,7 +31,7 @@ const ChatUserInput = ({
return obj
})()
const handleInputValueChange = (key: string, value: string | boolean) => {
const handleInputValueChange = (key: string, value: string) => {
if (!(key in promptVariableObj))
return
@ -56,12 +55,10 @@ const ChatUserInput = ({
className='mb-4 last-of-type:mb-0'
>
<div>
{type !== 'boolean' && (
<div className='system-sm-semibold mb-1 flex h-6 items-center gap-1 text-text-secondary'>
<div className='truncate'>{name || key}</div>
{!required && <span className='system-xs-regular text-text-tertiary'>{t('workflow.panel.optional')}</span>}
</div>
)}
<div className='grow'>
{type === 'string' && (
<Input
@ -99,14 +96,6 @@ const ChatUserInput = ({
maxLength={max_length || DEFAULT_VALUE_MAX_LEN}
/>
)}
{type === 'boolean' && (
<BoolInput
name={name || key}
value={!!inputs[key]}
required={required}
onChange={(value) => { handleInputValueChange(key, value) }}
/>
)}
</div>
</div>
</div>

@ -34,7 +34,7 @@ import { RefreshCcw01 } from '@/app/components/base/icons/src/vender/line/arrows
import TooltipPlus from '@/app/components/base/tooltip'
import ActionButton, { ActionButtonState } from '@/app/components/base/action-button'
import type { ModelConfig as BackendModelConfig, VisionFile, VisionSettings } from '@/types/app'
import { formatBooleanInputs, promptVariablesToUserInputsForm } from '@/utils/model-config'
import { promptVariablesToUserInputsForm } from '@/utils/model-config'
import TextGeneration from '@/app/components/app/text-generate/item'
import { IS_CE_EDITION } from '@/config'
import type { Inputs } from '@/models/debug'
@ -259,7 +259,7 @@ const Debug: FC<IDebug> = ({
}
const data: Record<string, any> = {
inputs: formatBooleanInputs(modelConfig.configs.prompt_variables, inputs),
inputs,
model_config: postModelConfig,
}

@ -60,6 +60,7 @@ import {
useModelListAndDefaultModelAndCurrentProviderAndModel,
useTextGenerationCurrentProviderAndModelAndModelList,
} from '@/app/components/header/account-setting/model-provider-page/hooks'
import { fetchCollectionList } from '@/service/tools'
import type { Collection } from '@/app/components/tools/types'
import { useStore as useAppStore } from '@/app/components/app/store'
import {
@ -81,7 +82,6 @@ import { supportFunctionCall } from '@/utils/tool-call'
import { MittProvider } from '@/context/mitt-context'
import { fetchAndMergeValidCompletionParams } from '@/utils/completion-params'
import Toast from '@/app/components/base/toast'
import { fetchCollectionList } from '@/service/tools'
type PublishConfig = {
modelConfig: ModelConfig
@ -693,6 +693,7 @@ const Configuration: FC = () => {
setHasFetchedDetail(true)
})
})()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [appId])
const promptEmpty = (() => {

@ -22,7 +22,6 @@ import type { VisionFile, VisionSettings } from '@/types/app'
import { DEFAULT_VALUE_MAX_LEN } from '@/config'
import { useStore as useAppStore } from '@/app/components/app/store'
import cn from '@/utils/classnames'
import BoolInput from '@/app/components/workflow/nodes/_base/components/before-run-form/bool-input'
export type IPromptValuePanelProps = {
appType: AppType
@ -67,7 +66,7 @@ const PromptValuePanel: FC<IPromptValuePanelProps> = ({
else { return !modelConfig.configs.prompt_template }
}, [chatPromptConfig.prompt, completionPromptConfig.prompt?.text, isAdvancedMode, mode, modelConfig.configs.prompt_template, modelModeType])
const handleInputValueChange = (key: string, value: string | boolean) => {
const handleInputValueChange = (key: string, value: string) => {
if (!(key in promptVariableObj))
return
@ -110,12 +109,10 @@ const PromptValuePanel: FC<IPromptValuePanelProps> = ({
className='mb-4 last-of-type:mb-0'
>
<div>
{type !== 'boolean' && (
<div className='system-sm-semibold mb-1 flex h-6 items-center gap-1 text-text-secondary'>
<div className='truncate'>{name || key}</div>
{!required && <span className='system-xs-regular text-text-tertiary'>{t('workflow.panel.optional')}</span>}
</div>
)}
<div className='system-sm-semibold mb-1 flex h-6 items-center gap-1 text-text-secondary'>
<div className='truncate'>{name || key}</div>
{!required && <span className='system-xs-regular text-text-tertiary'>{t('workflow.panel.optional')}</span>}
</div>
<div className='grow'>
{type === 'string' && (
<Input
@ -154,14 +151,6 @@ const PromptValuePanel: FC<IPromptValuePanelProps> = ({
maxLength={max_length || DEFAULT_VALUE_MAX_LEN}
/>
)}
{type === 'boolean' && (
<BoolInput
name={name || key}
value={!!inputs[key]}
required={required}
onChange={(value) => { handleInputValueChange(key, value) }}
/>
)}
</div>
</div>
</div>

@ -23,7 +23,6 @@ import SuggestedQuestions from '@/app/components/base/chat/chat/answer/suggested
import { Markdown } from '@/app/components/base/markdown'
import cn from '@/utils/classnames'
import type { FileEntity } from '../../file-uploader/types'
import { formatBooleanInputs } from '@/utils/model-config'
import Avatar from '../../avatar'
const ChatWrapper = () => {
@ -90,7 +89,7 @@ const ChatWrapper = () => {
let hasEmptyInput = ''
let fileIsUploading = false
const requiredVars = inputsForms.filter(({ required, type }) => required && type !== InputVarType.boolean)
const requiredVars = inputsForms.filter(({ required }) => required)
if (requiredVars.length) {
requiredVars.forEach(({ variable, label, type }) => {
if (hasEmptyInput)
@ -132,7 +131,7 @@ const ChatWrapper = () => {
const data: any = {
query: message,
files,
inputs: formatBooleanInputs(inputsForms, currentConversationId ? currentConversationInputs : newConversationInputs),
inputs: currentConversationId ? currentConversationInputs : newConversationInputs,
conversation_id: currentConversationId,
parent_message_id: (isRegenerate ? parentAnswer?.id : getLastAnswer(chatList)?.id) || null,
}

@ -325,7 +325,7 @@ export const useChatWithHistory = (installedAppInfo?: InstalledApp) => {
let hasEmptyInput = ''
let fileIsUploading = false
const requiredVars = inputsForms.filter(({ required, type }) => required && type !== InputVarType.boolean)
const requiredVars = inputsForms.filter(({ required }) => required)
if (requiredVars.length) {
requiredVars.forEach(({ variable, label, type }) => {
if (hasEmptyInput)

@ -6,7 +6,6 @@ import Textarea from '@/app/components/base/textarea'
import { PortalSelect } from '@/app/components/base/select'
import { FileUploaderInAttachmentWrapper } from '@/app/components/base/file-uploader'
import { InputVarType } from '@/app/components/workflow/types'
import BoolInput from '@/app/components/workflow/nodes/_base/components/before-run-form/bool-input'
type Props = {
showTip?: boolean
@ -43,14 +42,12 @@ const InputsFormContent = ({ showTip }: Props) => {
<div className='space-y-4'>
{visibleInputsForms.map(form => (
<div key={form.variable} className='space-y-1'>
{form.type !== InputVarType.boolean && (
<div className='flex h-6 items-center gap-1'>
<div className='system-md-semibold text-text-secondary'>{form.label}</div>
{!form.required && (
<div className='system-xs-regular text-text-tertiary'>{t('appDebug.variableTable.optional')}</div>
)}
</div>
)}
<div className='flex h-6 items-center gap-1'>
<div className='system-md-semibold text-text-secondary'>{form.label}</div>
{!form.required && (
<div className='system-xs-regular text-text-tertiary'>{t('appDebug.variableTable.optional')}</div>
)}
</div>
{form.type === InputVarType.textInput && (
<Input
value={inputsFormValue?.[form.variable] || ''}
@ -73,14 +70,6 @@ const InputsFormContent = ({ showTip }: Props) => {
placeholder={form.label}
/>
)}
{form.type === InputVarType.boolean && (
<BoolInput
name={form.label}
value={!!inputsFormValue?.[form.variable]}
required={form.required}
onChange={value => handleFormChange(form.variable, value)}
/>
)}
{form.type === InputVarType.select && (
<PortalSelect
popupClassName='w-[200px]'

@ -12,7 +12,7 @@ export const useCheckInputsForms = () => {
const checkInputsForm = useCallback((inputs: Record<string, any>, inputsForm: InputForm[]) => {
let hasEmptyInput = ''
let fileIsUploading = false
const requiredVars = inputsForm.filter(({ required, type }) => required && type !== InputVarType.boolean) // boolean can be not checked
const requiredVars = inputsForm.filter(({ required }) => required)
if (requiredVars?.length) {
requiredVars.forEach(({ variable, label, type }) => {

@ -31,12 +31,6 @@ export const getProcessedInputs = (inputs: Record<string, any>, inputsForm: Inpu
inputsForm.forEach((item) => {
const inputValue = inputs[item.variable]
// set boolean type default value
if(item.type === InputVarType.boolean) {
processedInputs[item.variable] = !!inputValue
return
}
if (!inputValue)
return

@ -90,7 +90,7 @@ const ChatWrapper = () => {
let hasEmptyInput = ''
let fileIsUploading = false
const requiredVars = inputsForms.filter(({ required, type }) => required && type !== InputVarType.boolean)
const requiredVars = inputsForms.filter(({ required }) => required)
if (requiredVars.length) {
requiredVars.forEach(({ variable, label, type }) => {
if (hasEmptyInput)

@ -312,7 +312,7 @@ export const useEmbeddedChatbot = () => {
let hasEmptyInput = ''
let fileIsUploading = false
const requiredVars = inputsForms.filter(({ required, type }) => required && type !== InputVarType.boolean)
const requiredVars = inputsForms.filter(({ required }) => required)
if (requiredVars.length) {
requiredVars.forEach(({ variable, label, type }) => {
if (hasEmptyInput)

@ -6,7 +6,6 @@ import Textarea from '@/app/components/base/textarea'
import { PortalSelect } from '@/app/components/base/select'
import { FileUploaderInAttachmentWrapper } from '@/app/components/base/file-uploader'
import { InputVarType } from '@/app/components/workflow/types'
import BoolInput from '@/app/components/workflow/nodes/_base/components/before-run-form/bool-input'
type Props = {
showTip?: boolean
@ -43,14 +42,12 @@ const InputsFormContent = ({ showTip }: Props) => {
<div className='space-y-4'>
{visibleInputsForms.map(form => (
<div key={form.variable} className='space-y-1'>
{form.type !== InputVarType.boolean && (
<div className='flex h-6 items-center gap-1'>
<div className='system-md-semibold text-text-secondary'>{form.label}</div>
{!form.required && (
<div className='system-xs-regular text-text-tertiary'>{t('appDebug.variableTable.optional')}</div>
)}
</div>
)}
{form.type === InputVarType.textInput && (
<Input
value={inputsFormValue?.[form.variable] || ''}
@ -73,14 +70,6 @@ const InputsFormContent = ({ showTip }: Props) => {
placeholder={form.label}
/>
)}
{form.type === InputVarType.boolean && (
<BoolInput
name={form.label}
value={inputsFormValue?.[form.variable]}
required={form.required}
onChange={value => handleFormChange(form.variable, value)}
/>
)}
{form.type === InputVarType.select && (
<PortalSelect
popupClassName='w-[200px]'

@ -91,7 +91,7 @@ const PureSelect = ({
triggerPopupSameWidth={triggerPopupSameWidth}
>
<PortalToFollowElemTrigger
onClick={() => handleOpenChange(!mergedOpen)}
onClick={() => !disabled && handleOpenChange(!mergedOpen)}
asChild
>
<div
@ -116,7 +116,7 @@ const PureSelect = ({
</div>
</PortalToFollowElemTrigger>
<PortalToFollowElemContent className={cn(
'z-10',
'z-[9999]',
popupWrapperClassName,
)}>
<div

@ -70,7 +70,7 @@ const HitTestingPage: FC<Props> = ({ datasetId }: Props) => {
const [isShowModifyRetrievalModal, setIsShowModifyRetrievalModal] = useState(false)
const [isShowRightPanel, { setTrue: showRightPanel, setFalse: hideRightPanel, set: setShowRightPanel }] = useBoolean(!isMobile)
const renderHitResults = (results: HitTesting[] | ExternalKnowledgeBaseHitTesting[]) => (
<div className='flex h-full flex-col rounded-t-2xl bg-background-body px-4 py-3'>
<div className='flex h-full flex-col rounded-tl-2xl bg-background-body px-4 py-3'>
<div className='mb-2 shrink-0 pl-2 font-semibold leading-6 text-text-primary'>
{t('datasetHitTesting.hit.title', { num: results.length })}
</div>
@ -93,7 +93,7 @@ const HitTestingPage: FC<Props> = ({ datasetId }: Props) => {
)
const renderEmptyState = () => (
<div className='flex h-full flex-col items-center justify-center rounded-t-2xl bg-background-body px-4 py-3'>
<div className='flex h-full flex-col items-center justify-center rounded-tl-2xl bg-background-body px-4 py-3'>
<div className={cn(docStyle.commonIcon, docStyle.targetIcon, '!h-14 !w-14 !bg-text-quaternary')} />
<div className='mt-3 text-[13px] text-text-quaternary'>
{t('datasetHitTesting.hit.emptyTip')}
@ -180,7 +180,7 @@ const HitTestingPage: FC<Props> = ({ datasetId }: Props) => {
<div className='flex flex-col pt-3'>
{/* {renderHitResults(generalResultData)} */}
{submitLoading
? <div className='flex h-full flex-col rounded-t-2xl bg-background-body px-4 py-3'>
? <div className='flex h-full flex-col rounded-tl-2xl bg-background-body px-4 py-3'>
<CardSkelton />
</div>
: (

@ -1,5 +1,5 @@
'use client'
import React, { useState } from 'react'
import React, { useState, useEffect } from "react";
import { usePathname } from 'next/navigation'
import s from './index.module.css'
import { useEventEmitterContextContext } from '@/context/event-emitter'
@ -9,6 +9,33 @@ type HeaderWrapperProps = {
children: React.ReactNode
}
const useIframeHeader = () => {
const [show, setShow] = useState(true);
useEffect(() => {
// 监听父级指定操作
const handler = (event: MessageEvent) => {
// if (event.origin !== "https://gcgj.ngsk.tech:7001") return;
if (event.data.type === "HIDDEN") setShow(() => false);
};
// 初始化完成后提示iframe操作
window.parent.postMessage(
{
type: "HEADER",
},
"*"
);
window.addEventListener("message", handler);
return () => window.removeEventListener("message", handler);
}, []);
return {
show,
};
};
const HeaderWrapper = ({
children,
}: HeaderWrapperProps) => {
@ -19,6 +46,7 @@ const HeaderWrapper = ({
const workflowCanvasMaximize = localStorage.getItem('workflow-canvas-maximize') === 'true'
const [hideHeader, setHideHeader] = useState(workflowCanvasMaximize)
const { eventEmitter } = useEventEmitterContextContext()
const { show } = useIframeHeader();
eventEmitter?.useSubscription((v: any) => {
if (v?.type === 'workflow-canvas-maximize')
@ -26,15 +54,22 @@ const HeaderWrapper = ({
})
return (
<div className={classNames(
'sticky left-0 right-0 top-0 z-[15] flex min-h-[56px] shrink-0 grow-0 basis-auto flex-col',
s.header,
isBordered ? 'border-b border-divider-regular' : '',
hideHeader && inWorkflowCanvas && 'hidden',
)}
>
{children}
</div>
)
<>
{show ? (
<div
className={classNames(
"sticky left-0 right-0 top-0 z-[15] flex min-h-[56px] shrink-0 grow-0 basis-auto flex-col",
s.header,
isBordered ? "border-b border-divider-regular" : "",
hideHeader && inWorkflowCanvas && "hidden"
)}
>
{children}
</div>
) : (
""
)}
</>
);
}
export default HeaderWrapper

@ -21,7 +21,6 @@ import { TEXT_GENERATION_TIMEOUT_MS } from '@/config'
import {
getFilesInLogs,
} from '@/app/components/base/file-uploader/utils'
import { formatBooleanInputs } from '@/utils/model-config'
export type IResultProps = {
isWorkflow: boolean
@ -125,9 +124,7 @@ const Result: FC<IResultProps> = ({
}
let hasEmptyInput = ''
const requiredVars = prompt_variables?.filter(({ key, name, required, type }) => {
if(type === 'boolean')
return false // boolean input is not required
const requiredVars = prompt_variables?.filter(({ key, name, required }) => {
const res = (!key || !key.trim()) || (!name || !name.trim()) || (required || required === undefined || required === null)
return res
}) || [] // compatible with old version
@ -161,7 +158,7 @@ const Result: FC<IResultProps> = ({
return
const data: Record<string, any> = {
inputs: formatBooleanInputs(promptConfig?.prompt_variables, inputs),
inputs,
}
if (visionConfig.enabled && completionFiles && completionFiles?.length > 0) {
data.files = completionFiles.map((item) => {

@ -18,7 +18,6 @@ import { FileUploaderInAttachmentWrapper } from '@/app/components/base/file-uplo
import { getProcessedFiles } from '@/app/components/base/file-uploader/utils'
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
import cn from '@/utils/classnames'
import BoolInput from '@/app/components/workflow/nodes/_base/components/before-run-form/bool-input'
export type IRunOnceProps = {
siteInfo: SiteInfo
@ -83,9 +82,7 @@ const RunOnce: FC<IRunOnceProps> = ({
{(inputs === null || inputs === undefined || Object.keys(inputs).length === 0) ? null
: promptConfig.prompt_variables.map(item => (
<div className='mt-4 w-full' key={item.key}>
{item.type !== 'boolean' && (
<label className='system-md-semibold flex h-6 items-center text-text-secondary'>{item.name}</label>
)}
<label className='system-md-semibold flex h-6 items-center text-text-secondary'>{item.name}</label>
<div className='mt-1'>
{item.type === 'select' && (
<Select
@ -110,7 +107,7 @@ const RunOnce: FC<IRunOnceProps> = ({
className='h-[104px] sm:text-xs'
placeholder={`${item.name}${!item.required ? `(${t('appDebug.variableTable.optional')})` : ''}`}
value={inputs[item.key]}
onChange={(e: ChangeEvent<HTMLTextAreaElement>) => { handleInputsChange({ ...inputsRef.current, [item.key]: e.target.value }) }}
onChange={(e: ChangeEvent<HTMLInputElement>) => { handleInputsChange({ ...inputsRef.current, [item.key]: e.target.value }) }}
/>
)}
{item.type === 'number' && (
@ -121,14 +118,6 @@ const RunOnce: FC<IRunOnceProps> = ({
onChange={(e: ChangeEvent<HTMLInputElement>) => { handleInputsChange({ ...inputsRef.current, [item.key]: e.target.value }) }}
/>
)}
{item.type === 'boolean' && (
<BoolInput
name={item.name || item.key}
value={!!inputs[item.key]}
required={item.required}
onChange={(value) => { handleInputsChange({ ...inputsRef.current, [item.key]: value }) }}
/>
)}
{item.type === 'file' && (
<FileUploaderInAttachmentWrapper
onChange={(files) => { handleInputsChange({ ...inputsRef.current, [item.key]: getProcessedFiles(files)[0] }) }}

@ -1,38 +0,0 @@
'use client'
import Checkbox from '@/app/components/base/checkbox'
import type { FC } from 'react'
import React, { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
type Props = {
name: string
value: boolean
required?: boolean
onChange: (value: boolean) => void
}
const BoolInput: FC<Props> = ({
value,
onChange,
name,
required,
}) => {
const { t } = useTranslation()
const handleChange = useCallback(() => {
onChange(!value)
}, [value, onChange])
return (
<div className='flex h-6 items-center gap-2'>
<Checkbox
className='!h-4 !w-4'
checked={!!value}
onCheck={handleChange}
/>
<div className='system-sm-medium flex items-center gap-1 text-text-secondary'>
{name}
{!required && <span className='system-xs-regular text-text-tertiary'>{t('workflow.panel.optional')}</span>}
</div>
</div>
)
}
export default React.memo(BoolInput)

@ -25,7 +25,6 @@ import { BubbleX } from '@/app/components/base/icons/src/vender/line/others'
import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants'
import cn from '@/utils/classnames'
import type { FileEntity } from '@/app/components/base/file-uploader/types'
import BoolInput from './bool-input'
type Props = {
payload: InputVar
@ -93,7 +92,6 @@ const FormItem: FC<Props> = ({
return ''
})()
const isBooleanType = type === InputVarType.boolean
const isArrayLikeType = [InputVarType.contexts, InputVarType.iterator].includes(type)
const isContext = type === InputVarType.contexts
const isIterator = type === InputVarType.iterator
@ -115,7 +113,7 @@ const FormItem: FC<Props> = ({
return (
<div className={cn(className)}>
{!isArrayLikeType && !isBooleanType && (
{!isArrayLikeType && (
<div className='system-sm-semibold mb-1 flex h-6 items-center gap-1 text-text-secondary'>
<div className='truncate'>{typeof payload.label === 'object' ? nodeKey : payload.label}</div>
{!payload.required && <span className='system-xs-regular text-text-tertiary'>{t('workflow.panel.optional')}</span>}
@ -168,15 +166,6 @@ const FormItem: FC<Props> = ({
)
}
{isBooleanType && (
<BoolInput
name={payload.label as string}
value={!!value}
required={payload.required}
onChange={onChange}
/>
)}
{
type === InputVarType.json && (
<CodeEditor

@ -32,8 +32,6 @@ export type BeforeRunFormProps = {
} & Partial<SpecialResultPanelProps>
function formatValue(value: string | any, type: InputVarType) {
if(type === InputVarType.boolean)
return !!value
if(value === undefined || value === null)
return value
if (type === InputVarType.number)
@ -89,7 +87,7 @@ const BeforeRunForm: FC<BeforeRunFormProps> = ({
form.inputs.forEach((input) => {
const value = form.values[input.variable] as any
if (!errMsg && input.required && (input.type !== InputVarType.boolean) && !(input.variable in existVarValuesInForm) && (value === '' || value === undefined || value === null || (input.type === InputVarType.files && value.length === 0)))
if (!errMsg && input.required && !(input.variable in existVarValuesInForm) && (value === '' || value === undefined || value === null || (input.type === InputVarType.files && value.length === 0)))
errMsg = t('workflow.errorMsg.fieldRequired', { field: typeof input.label === 'object' ? input.label.variable : input.label })
if (!errMsg && (input.type === InputVarType.singleFile || input.type === InputVarType.multiFiles) && value) {

@ -164,7 +164,7 @@ const FormInputItem: FC<Props> = ({
...value,
[variable]: {
...varInput,
...newValue,
value: newValue,
},
})
}
@ -242,7 +242,7 @@ const FormInputItem: FC<Props> = ({
<AppSelector
disabled={readOnly}
scope={scope || 'all'}
value={varInput as any}
value={varInput?.value}
onSelect={handleAppOrModelSelect}
/>
)}
@ -251,7 +251,7 @@ const FormInputItem: FC<Props> = ({
popupClassName='!w-[387px]'
isAdvancedMode
isInWorkflow
value={varInput}
value={varInput?.value}
setModel={handleAppOrModelSelect}
readonly={readOnly}
scope={scope}

@ -1,7 +1,7 @@
'use client'
import type { FC } from 'react'
import React from 'react'
import { RiAlignLeft, RiCheckboxLine, RiCheckboxMultipleLine, RiFileCopy2Line, RiFileList2Line, RiHashtag, RiTextSnippet } from '@remixicon/react'
import { RiAlignLeft, RiCheckboxMultipleLine, RiFileCopy2Line, RiFileList2Line, RiHashtag, RiTextSnippet } from '@remixicon/react'
import { InputVarType } from '../../../types'
type Props = {
@ -15,7 +15,6 @@ const getIcon = (type: InputVarType) => {
[InputVarType.paragraph]: RiAlignLeft,
[InputVarType.select]: RiCheckboxMultipleLine,
[InputVarType.number]: RiHashtag,
[InputVarType.boolean]: RiCheckboxLine,
[InputVarType.singleFile]: RiFileList2Line,
[InputVarType.multiFiles]: RiFileCopy2Line,
} as any)[type] || RiTextSnippet

@ -49,10 +49,9 @@ export const isConversationVar = (valueSelector: ValueSelector) => {
return valueSelector[0] === 'conversation'
}
export const inputVarTypeToVarType = (type: InputVarType): VarType => {
const inputVarTypeToVarType = (type: InputVarType): VarType => {
return ({
[InputVarType.number]: VarType.number,
[InputVarType.boolean]: VarType.boolean,
[InputVarType.singleFile]: VarType.file,
[InputVarType.multiFiles]: VarType.arrayFile,
} as any)[type] || VarType.string

@ -18,7 +18,7 @@ type Props = {
onChange: (value: string) => void
}
const TYPES = [VarType.string, VarType.number, VarType.boolean, VarType.arrayNumber, VarType.arrayString, VarType.arrayBoolean, VarType.arrayObject, VarType.object]
const TYPES = [VarType.string, VarType.number, VarType.arrayNumber, VarType.arrayString, VarType.arrayObject, VarType.object]
const VarReferencePicker: FC<Props> = ({
readonly,
className,

@ -16,7 +16,6 @@ import Input from '@/app/components/base/input'
import Textarea from '@/app/components/base/textarea'
import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor'
import { noop } from 'lodash-es'
import BoolValue from '@/app/components/workflow/panel/chat-variable-panel/components/bool-value'
type Props = {
readonly: boolean
@ -76,7 +75,7 @@ const VarList: FC<Props> = ({
}, [list, onChange])
const handleToAssignedVarChange = useCallback((index: number) => {
return (value: ValueSelector | string | number | boolean) => {
return (value: ValueSelector | string | number) => {
const newList = produce(list, (draft) => {
draft[index].value = value as ValueSelector
})
@ -189,12 +188,6 @@ const VarList: FC<Props> = ({
className='w-full'
/>
)}
{assignedVarType === 'boolean' && (
<BoolValue
value={item.value as boolean}
onChange={value => handleToAssignedVarChange(index)(value)}
/>
)}
{assignedVarType === 'object' && (
<CodeEditor
value={item.value as string}

@ -43,7 +43,7 @@ export const getOperationItems = (
]
}
if (writeModeTypes && ['string', 'boolean', 'object'].includes(assignedVarType || '')) {
if (writeModeTypes && ['string', 'object'].includes(assignedVarType || '')) {
return writeModeTypes.map(type => ({
value: type,
name: type,

@ -60,6 +60,7 @@ const useConfig = (id: string, payload: CodeNodeType) => {
})
syncOutputKeyOrders(defaultConfig.outputs)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [defaultConfig])
const handleCodeChange = useCallback((code: string) => {
@ -84,7 +85,7 @@ const useConfig = (id: string, payload: CodeNodeType) => {
}, [allLanguageDefault, inputs, setInputs])
const handleSyncFunctionSignature = useCallback(() => {
const generateSyncSignatureCode = (code: string) => {
const generateSyncSignatureCode = (code: string) => {
let mainDefRe
let newMainDef
if (inputs.code_language === CodeLanguage.javascript) {
@ -158,7 +159,7 @@ const useConfig = (id: string, payload: CodeNodeType) => {
})
const filterVar = useCallback((varPayload: Var) => {
return [VarType.string, VarType.number, VarType.boolean, VarType.secret, VarType.object, VarType.array, VarType.arrayNumber, VarType.arrayString, VarType.arrayObject, VarType.arrayBoolean, VarType.file, VarType.arrayFile].includes(varPayload.type)
return [VarType.string, VarType.number, VarType.secret, VarType.object, VarType.array, VarType.arrayNumber, VarType.arrayString, VarType.arrayObject, VarType.file, VarType.arrayFile].includes(varPayload.type)
}, [])
const handleCodeAndVarsChange = useCallback((code: string, inputVariables: Variable[], outputVariables: OutputVar) => {

@ -38,7 +38,6 @@ import { VarType } from '@/app/components/workflow/types'
import cn from '@/utils/classnames'
import { SimpleSelect as Select } from '@/app/components/base/select'
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
import BoolValue from '@/app/components/workflow/panel/chat-variable-panel/components/bool-value'
import { getVarType } from '@/app/components/workflow/nodes/_base/components/variable/utils'
import { useIsChatMode } from '@/app/components/workflow/hooks/use-workflow'
const optionNameI18NPrefix = 'workflow.nodes.ifElse.optionName'
@ -143,12 +142,12 @@ const ConditionItem = ({
const isArrayValue = fileAttr?.key === 'transfer_method' || fileAttr?.key === 'type'
const handleUpdateConditionValue = useCallback((value: string | boolean) => {
if (value === condition.value || (isArrayValue && value === (condition.value as string[])?.[0]))
const handleUpdateConditionValue = useCallback((value: string) => {
if (value === condition.value || (isArrayValue && value === condition.value?.[0]))
return
const newCondition = {
...condition,
value: isArrayValue ? [value as string] : value,
value: isArrayValue ? [value] : value,
}
doUpdateCondition(newCondition)
}, [condition, doUpdateCondition, isArrayValue])
@ -204,12 +203,8 @@ const ConditionItem = ({
}, [caseId, condition, conditionId, isSubVariableKey, onRemoveCondition, onRemoveSubVariableCondition])
const handleVarChange = useCallback((valueSelector: ValueSelector, varItem: Var) => {
const {
conversationVariables,
} = workflowStore.getState()
const resolvedVarType = getVarType({
valueSelector,
conversationVariables,
availableNodes,
isChatMode,
})
@ -278,7 +273,7 @@ const ConditionItem = ({
/>
</div>
{
!comparisonOperatorNotRequireValue(condition.comparison_operator) && !isNotInput && condition.varType !== VarType.number && condition.varType !== VarType.boolean && (
!comparisonOperatorNotRequireValue(condition.comparison_operator) && !isNotInput && condition.varType !== VarType.number && (
<div className='max-h-[100px] overflow-y-auto border-t border-t-divider-subtle px-2 py-1'>
<ConditionInput
disabled={disabled}
@ -290,16 +285,6 @@ const ConditionItem = ({
</div>
)
}
{
!comparisonOperatorNotRequireValue(condition.comparison_operator) && !isNotInput && condition.varType === VarType.boolean && (
<div className='p-1'>
<BoolValue
value={condition.value as boolean}
onChange={handleUpdateConditionValue}
/>
</div>
)
}
{
!comparisonOperatorNotRequireValue(condition.comparison_operator) && !isNotInput && condition.varType === VarType.number && (
<div className='border-t border-t-divider-subtle px-2 py-1 pt-[3px]'>

@ -24,7 +24,7 @@ type ConditionValueProps = {
variableSelector: string[]
labelName?: string
operator: ComparisonOperator
value: string | string[] | boolean
value: string | string[]
}
const ConditionValue = ({
variableSelector,
@ -48,9 +48,6 @@ const ConditionValue = ({
if (Array.isArray(value)) // transfer method
return value[0]
if(value === true || value === false)
return value ? 'True' : 'False'
return value.replace(/{{#([^#]*)#}}/g, (a, b) => {
const arr: string[] = b.split('.')
if (isSystemVar(arr))

@ -1,4 +1,4 @@
import { BlockEnum, type NodeDefault, VarType } from '../../types'
import { BlockEnum, type NodeDefault } from '../../types'
import { type IfElseNodeType, LogicalOperator } from './types'
import { isEmptyRelatedOperator } from './utils'
import { ALL_CHAT_AVAILABLE_BLOCKS, ALL_COMPLETION_AVAILABLE_BLOCKS } from '@/app/components/workflow/blocks'
@ -64,7 +64,7 @@ const nodeDefault: NodeDefault<IfElseNodeType> = {
errorMessages = t(`${i18nPrefix}.fieldRequired`, { field: t(`${i18nPrefix}.fields.variableValue`) })
}
else {
if (!isEmptyRelatedOperator(condition.comparison_operator!) && (condition.varType === VarType.boolean ? condition.value === undefined : !condition.value))
if (!isEmptyRelatedOperator(condition.comparison_operator!) && !condition.value)
errorMessages = t(`${i18nPrefix}.fieldRequired`, { field: t(`${i18nPrefix}.fields.variableValue`) })
}
}

@ -26,7 +26,7 @@ const IfElseNode: FC<NodeProps<IfElseNodeType>> = (props) => {
if (isEmptyRelatedOperator(c.comparison_operator!))
return true
return typeof c.value === 'boolean' ? true : !!c.value
return !!c.value
})
return isSet
}
@ -34,7 +34,7 @@ const IfElseNode: FC<NodeProps<IfElseNodeType>> = (props) => {
if (isEmptyRelatedOperator(condition.comparison_operator!))
return true
return typeof condition.value === 'boolean' ? true : !!condition.value
return !!condition.value
}
}, [])
const conditionNotSet = (<div className='flex h-6 items-center space-x-1 rounded-md bg-workflow-block-parma-bg px-1 text-xs font-normal text-text-secondary'>

@ -41,7 +41,7 @@ export type Condition = {
variable_selector?: ValueSelector
key?: string // sub variable key
comparison_operator?: ComparisonOperator
value: string | string[] | boolean
value: string | string[]
numberVarType?: NumberVarType
sub_variable_condition?: CaseItem
}

@ -107,13 +107,6 @@ export const getOperators = (type?: VarType, file?: { key: string }) => {
ComparisonOperator.empty,
ComparisonOperator.notEmpty,
]
case VarType.boolean:
return [
ComparisonOperator.is,
ComparisonOperator.isNot,
ComparisonOperator.empty,
ComparisonOperator.notEmpty,
]
case VarType.file:
return [
ComparisonOperator.exists,
@ -121,7 +114,6 @@ export const getOperators = (type?: VarType, file?: { key: string }) => {
]
case VarType.arrayString:
case VarType.arrayNumber:
case VarType.arrayBoolean:
return [
ComparisonOperator.contains,
ComparisonOperator.notContains,

@ -58,9 +58,6 @@ const useConfig = (id: string, payload: ListFilterNodeType) => {
case VarType.arrayObject:
itemVarType = VarType.object
break
case VarType.arrayBoolean:
itemVarType = VarType.boolean
break
default:
itemVarType = varType
}
@ -98,7 +95,7 @@ const useConfig = (id: string, payload: ListFilterNodeType) => {
const filterVar = useCallback((varPayload: Var) => {
// Don't know the item struct of VarType.arrayObject, so not support it
return [VarType.arrayNumber, VarType.arrayString, VarType.arrayBoolean, VarType.arrayFile].includes(varPayload.type)
return [VarType.arrayNumber, VarType.arrayString, VarType.arrayFile].includes(varPayload.type)
}, [])
const handleFilterEnabledChange = useCallback((enabled: boolean) => {

@ -39,21 +39,21 @@ type EditCardProps = {
const TYPE_OPTIONS = [
{ value: Type.string, text: 'string' },
{ value: Type.number, text: 'number' },
{ value: Type.boolean, text: 'boolean' },
// { value: Type.boolean, text: 'boolean' },
{ value: Type.object, text: 'object' },
{ value: ArrayType.string, text: 'array[string]' },
{ value: ArrayType.number, text: 'array[number]' },
{ value: ArrayType.boolean, text: 'array[boolean]' },
// { value: ArrayType.boolean, text: 'array[boolean]' },
{ value: ArrayType.object, text: 'array[object]' },
]
const MAXIMUM_DEPTH_TYPE_OPTIONS = [
{ value: Type.string, text: 'string' },
{ value: Type.number, text: 'number' },
{ value: Type.boolean, text: 'boolean' },
// { value: Type.boolean, text: 'boolean' },
{ value: ArrayType.string, text: 'array[string]' },
{ value: ArrayType.number, text: 'array[number]' },
{ value: ArrayType.boolean, text: 'array[boolean]' },
// { value: ArrayType.boolean, text: 'array[boolean]' },
]
const EditCard: FC<EditCardProps> = ({

@ -36,7 +36,6 @@ import cn from '@/utils/classnames'
import { SimpleSelect as Select } from '@/app/components/base/select'
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
import ConditionVarSelector from './condition-var-selector'
import BoolValue from '@/app/components/workflow/panel/chat-variable-panel/components/bool-value'
const optionNameI18NPrefix = 'workflow.nodes.ifElse.optionName'
@ -130,12 +129,12 @@ const ConditionItem = ({
const isArrayValue = fileAttr?.key === 'transfer_method' || fileAttr?.key === 'type'
const handleUpdateConditionValue = useCallback((value: string | boolean) => {
if (value === condition.value || (isArrayValue && value === (condition.value as string[])?.[0]))
const handleUpdateConditionValue = useCallback((value: string) => {
if (value === condition.value || (isArrayValue && value === condition.value?.[0]))
return
const newCondition = {
...condition,
value: isArrayValue ? [value as string] : value,
value: isArrayValue ? [value] : value,
}
doUpdateCondition(newCondition)
}, [condition, doUpdateCondition, isArrayValue])
@ -254,7 +253,7 @@ const ConditionItem = ({
/>
</div>
{
!comparisonOperatorNotRequireValue(condition.comparison_operator) && !isNotInput && condition.varType !== VarType.number && condition.varType !== VarType.boolean && (
!comparisonOperatorNotRequireValue(condition.comparison_operator) && !isNotInput && condition.varType !== VarType.number && (
<div className='max-h-[100px] overflow-y-auto border-t border-t-divider-subtle px-2 py-1'>
<ConditionInput
disabled={disabled}
@ -265,14 +264,6 @@ const ConditionItem = ({
</div>
)
}
{!comparisonOperatorNotRequireValue(condition.comparison_operator) && condition.varType === VarType.boolean
&& <div className='p-1'>
<BoolValue
value={condition.value as boolean}
onChange={handleUpdateConditionValue}
/>
</div>
}
{
!comparisonOperatorNotRequireValue(condition.comparison_operator) && !isNotInput && condition.varType === VarType.number && (
<div className='border-t border-t-divider-subtle px-2 py-1 pt-[3px]'>

@ -18,15 +18,33 @@ import {
ValueType,
VarType,
} from '@/app/components/workflow/types'
import BoolValue from '@/app/components/workflow/panel/chat-variable-panel/components/bool-value'
import {
arrayBoolPlaceholder,
arrayNumberPlaceholder,
arrayObjectPlaceholder,
arrayStringPlaceholder,
objectPlaceholder,
} from '@/app/components/workflow/panel/chat-variable-panel/utils'
const objectPlaceholder = `# example
# {
# "name": "ray",
# "age": 20
# }`
const arrayStringPlaceholder = `# example
# [
# "value1",
# "value2"
# ]`
const arrayNumberPlaceholder = `# example
# [
# 100,
# 200
# ]`
const arrayObjectPlaceholder = `# example
# [
# {
# "name": "ray",
# "age": 20
# },
# {
# "name": "lily",
# "age": 18
# }
# ]`
type FormItemProps = {
nodeId: string
@ -65,8 +83,6 @@ const FormItem = ({
return arrayNumberPlaceholder
if (var_type === VarType.arrayObject)
return arrayObjectPlaceholder
if (var_type === VarType.arrayBoolean)
return arrayBoolPlaceholder
return objectPlaceholder
}, [var_type])
@ -104,17 +120,9 @@ const FormItem = ({
/>
)
}
{
value_type === ValueType.constant && var_type === VarType.boolean && (
<BoolValue
value={value}
onChange={handleChange}
/>
)
}
{
value_type === ValueType.constant
&& (var_type === VarType.object || var_type === VarType.arrayString || var_type === VarType.arrayNumber || var_type === VarType.arrayObject || var_type === VarType.arrayBoolean)
&& (var_type === VarType.object || var_type === VarType.arrayString || var_type === VarType.arrayNumber || var_type === VarType.arrayObject)
&& (
<div className='w-full rounded-[10px] bg-components-input-bg-normal py-2 pl-3 pr-1' style={{ height: editorMinHeight }}>
<CodeEditor

@ -22,10 +22,6 @@ const VariableTypeSelect = ({
label: 'Object',
value: VarType.object,
},
{
label: 'Boolean',
value: VarType.boolean,
},
{
label: 'Array[string]',
value: VarType.arrayString,
@ -38,10 +34,6 @@ const VariableTypeSelect = ({
label: 'Array[object]',
value: VarType.arrayObject,
},
{
label: 'Array[boolean]',
value: VarType.arrayBoolean,
},
]
return (

@ -1,4 +1,4 @@
import { BlockEnum, VarType } from '../../types'
import { BlockEnum } from '../../types'
import type { NodeDefault } from '../../types'
import { ComparisonOperator, LogicalOperator, type LoopNodeType } from './types'
import { isEmptyRelatedOperator } from './utils'
@ -55,7 +55,7 @@ const nodeDefault: NodeDefault<LoopNodeType> = {
errorMessages = t(`${i18nPrefix}.fieldRequired`, { field: t(`${i18nPrefix}.fields.variableValue`) })
}
else {
if (!isEmptyRelatedOperator(condition.comparison_operator!) && (condition.varType === VarType.boolean ? condition.value === undefined : !condition.value))
if (!isEmptyRelatedOperator(condition.comparison_operator!) && !condition.value)
errorMessages = t(`${i18nPrefix}.fieldRequired`, { field: t(`${i18nPrefix}.fields.variableValue`) })
}
}

@ -44,7 +44,7 @@ export type Condition = {
variable_selector?: ValueSelector
key?: string // sub variable key
comparison_operator?: ComparisonOperator
value: string | string[] | boolean
value: string | string[]
numberVarType?: NumberVarType
sub_variable_condition?: CaseItem
}

@ -107,13 +107,6 @@ export const getOperators = (type?: VarType, file?: { key: string }) => {
ComparisonOperator.empty,
ComparisonOperator.notEmpty,
]
case VarType.boolean:
return [
ComparisonOperator.is,
ComparisonOperator.isNot,
ComparisonOperator.empty,
ComparisonOperator.notEmpty,
]
case VarType.object:
return [
ComparisonOperator.empty,

@ -35,7 +35,7 @@ type Props = {
onCancel?: () => void
}
const TYPES = [ParamType.string, ParamType.number, ParamType.bool, ParamType.arrayString, ParamType.arrayNumber, ParamType.arrayObject, ParamType.arrayBool]
const TYPES = [ParamType.string, ParamType.number, ParamType.arrayString, ParamType.arrayNumber, ParamType.arrayObject]
const AddExtractParameter: FC<Props> = ({
type,

@ -3,12 +3,11 @@ import type { CommonNodeType, Memory, ModelConfig, ValueSelector, VisionSetting
export enum ParamType {
string = 'string',
number = 'number',
bool = 'boolean',
bool = 'bool',
select = 'select',
arrayString = 'array[string]',
arrayNumber = 'array[number]',
arrayObject = 'array[object]',
arrayBool = 'array[boolean]',
}
export type Param = {

@ -1,69 +0,0 @@
'use client'
import type { FC } from 'react'
import React, { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import { RiAddLine } from '@remixicon/react'
import produce from 'immer'
import RemoveButton from '@/app/components/workflow/nodes/_base/components/remove-button'
import Button from '@/app/components/base/button'
import BoolValue from './bool-value'
type Props = {
list: any[]
onChange: (list: any[]) => void
}
const ArrayValueList: FC<Props> = ({
list,
onChange,
}) => {
const { t } = useTranslation()
const handleChange = useCallback((index: number) => {
return (value: boolean) => {
const newList = produce(list, (draft: any[]) => {
draft[index] = value
})
onChange(newList)
}
}, [list, onChange])
const handleItemRemove = useCallback((index: number) => {
return () => {
const newList = produce(list, (draft) => {
draft.splice(index, 1)
})
onChange(newList)
}
}, [list, onChange])
const handleItemAdd = useCallback(() => {
const newList = produce(list, (draft: any[]) => {
draft.push(true)
})
onChange(newList)
}, [list, onChange])
return (
<div className='w-full space-y-2'>
{list.map((item, index) => (
<div className='flex items-center space-x-1' key={index}>
<BoolValue
value={item}
onChange={handleChange(index)}
/>
<RemoveButton
className='!bg-gray-100 !p-2 hover:!bg-gray-200'
onClick={handleItemRemove(index)}
/>
</div>
))}
<Button variant='tertiary' className='w-full' onClick={handleItemAdd}>
<RiAddLine className='mr-1 h-4 w-4' />
<span>{t('workflow.chatVariable.modal.addArrayValue')}</span>
</Button>
</div>
)
}
export default React.memo(ArrayValueList)

@ -1,35 +0,0 @@
'use client'
import type { FC } from 'react'
import React, { useCallback } from 'react'
import OptionCard from '../../../nodes/_base/components/option-card'
type Props = {
value: boolean
onChange: (value: boolean) => void
}
const BoolValue: FC<Props> = ({
value,
onChange,
}) => {
const handleChange = useCallback((newValue: boolean) => {
return () => {
onChange(newValue)
}
}, [onChange])
return (
<div className='flex w-full space-x-1'>
<OptionCard className='grow'
selected={value}
title='True'
onSelect={handleChange(true)}
/>
<OptionCard className='grow'
selected={!value}
title='False'
onSelect={handleChange(false)}
/>
</div>
)
}
export default React.memo(BoolValue)

@ -17,15 +17,6 @@ import { CodeLanguage } from '@/app/components/workflow/nodes/code/types'
import { ChatVarType } from '@/app/components/workflow/panel/chat-variable-panel/type'
import cn from '@/utils/classnames'
import { checkKeys, replaceSpaceWithUnderscreInVarNameInput } from '@/utils/var'
import BoolValue from './bool-value'
import ArrayBoolList from './array-bool-list'
import {
arrayBoolPlaceholder,
arrayNumberPlaceholder,
arrayObjectPlaceholder,
arrayStringPlaceholder,
objectPlaceholder,
} from '@/app/components/workflow/panel/chat-variable-panel/utils'
export type ModalPropsType = {
chatVar?: ConversationVariable
@ -42,14 +33,39 @@ type ObjectValueItem = {
const typeList = [
ChatVarType.String,
ChatVarType.Number,
ChatVarType.Boolean,
ChatVarType.Object,
ChatVarType.ArrayString,
ChatVarType.ArrayNumber,
ChatVarType.ArrayBoolean,
ChatVarType.ArrayObject,
]
const objectPlaceholder = `# example
# {
# "name": "ray",
# "age": 20
# }`
const arrayStringPlaceholder = `# example
# [
# "value1",
# "value2"
# ]`
const arrayNumberPlaceholder = `# example
# [
# 100,
# 200
# ]`
const arrayObjectPlaceholder = `# example
# [
# {
# "name": "ray",
# "age": 20
# },
# {
# "name": "lily",
# "age": 18
# }
# ]`
const ChatVariableModal = ({
chatVar,
onClose,
@ -78,8 +94,6 @@ const ChatVariableModal = ({
return arrayNumberPlaceholder
if (type === ChatVarType.ArrayObject)
return arrayObjectPlaceholder
if (type === ChatVarType.ArrayBoolean)
return arrayBoolPlaceholder
return objectPlaceholder
}, [type])
const getObjectValue = useCallback(() => {
@ -108,16 +122,12 @@ const ChatVariableModal = ({
return value || ''
case ChatVarType.Number:
return value || 0
case ChatVarType.Boolean:
return value === undefined ? true : value
case ChatVarType.Object:
return editInJSON ? value : formatValueFromObject(objectValue)
case ChatVarType.ArrayString:
case ChatVarType.ArrayNumber:
case ChatVarType.ArrayObject:
return value?.filter(Boolean) || []
case ChatVarType.ArrayBoolean:
return value || []
}
}
@ -147,10 +157,6 @@ const ChatVariableModal = ({
setEditInJSON(true)
if (v === ChatVarType.String || v === ChatVarType.Number || v === ChatVarType.Object)
setEditInJSON(false)
if(v === ChatVarType.Boolean)
setValue(true)
if (v === ChatVarType.ArrayBoolean)
setValue([true])
setType(v)
}
@ -196,11 +202,6 @@ const ChatVariableModal = ({
setValue(value?.length ? value : [undefined])
}
}
if(type === ChatVarType.ArrayBoolean) {
if(editInJSON)
setEditorContent(JSON.stringify(value.map((item: boolean) => item ? 'True' : 'False')))
}
setEditInJSON(editInJSON)
}
@ -212,16 +213,7 @@ const ChatVariableModal = ({
else {
setEditorContent(content)
try {
let newValue = JSON.parse(content)
if(type === ChatVarType.ArrayBoolean) {
newValue = newValue.map((item: string) => {
if (item === 'True' || item === 'true')
return true
if (item === 'False' || item === 'false')
return false
return undefined
}).filter((item?: boolean) => item !== undefined)
}
const newValue = JSON.parse(content)
setValue(newValue)
}
catch {
@ -312,7 +304,7 @@ const ChatVariableModal = ({
<div className='mb-4'>
<div className='system-sm-semibold mb-1 flex h-6 items-center justify-between text-text-secondary'>
<div>{t('workflow.chatVariable.modal.value')}</div>
{(type === ChatVarType.ArrayString || type === ChatVarType.ArrayNumber || type === ChatVarType.ArrayBoolean) && (
{(type === ChatVarType.ArrayString || type === ChatVarType.ArrayNumber) && (
<Button
variant='ghost'
size='small'
@ -353,12 +345,6 @@ const ChatVariableModal = ({
type='number'
/>
)}
{type === ChatVarType.Boolean && (
<BoolValue
value={value}
onChange={setValue}
/>
)}
{type === ChatVarType.Object && !editInJSON && (
<ObjectValueList
list={objectValue}
@ -379,13 +365,6 @@ const ChatVariableModal = ({
onChange={setValue}
/>
)}
{type === ChatVarType.ArrayBoolean && !editInJSON && (
<ArrayBoolList
list={value || [true]}
onChange={setValue}
/>
)}
{editInJSON && (
<div className='w-full rounded-[10px] bg-components-input-bg-normal py-2 pl-3 pr-1' style={{ height: editorMinHeight }}>
<CodeEditor

@ -1,10 +1,8 @@
export enum ChatVarType {
Number = 'number',
String = 'string',
Boolean = 'boolean',
Object = 'object',
ArrayString = 'array[string]',
ArrayNumber = 'array[number]',
ArrayBoolean = 'array[boolean]',
ArrayObject = 'array[object]',
}

@ -1,35 +0,0 @@
export const objectPlaceholder = `# example
# {
# "name": "ray",
# "age": 20
# }`
export const arrayStringPlaceholder = `# example
# [
# "value1",
# "value2"
# ]`
export const arrayNumberPlaceholder = `# example
# [
# 100,
# 200
# ]`
export const arrayObjectPlaceholder = `# example
# [
# {
# "name": "ray",
# "age": 20
# },
# {
# "name": "lily",
# "age": 18
# }
# ]`
export const arrayBoolPlaceholder = `# example
# [
# "True",
# "False"
# ]`

@ -178,7 +178,6 @@ export enum InputVarType {
paragraph = 'paragraph',
select = 'select',
number = 'number',
boolean = 'boolean',
url = 'url',
files = 'files',
json = 'json', // obj, array
@ -265,7 +264,6 @@ export enum VarType {
arrayString = 'array[string]',
arrayNumber = 'array[number]',
arrayObject = 'array[object]',
arrayBoolean = 'array[boolean]',
arrayFile = 'array[file]',
any = 'any',
arrayAny = 'array[any]',

@ -15,6 +15,37 @@ import Toast from '@/app/components/base/toast'
import { IS_CE_EDITION } from '@/config'
import { useGlobalPublicStore } from '@/context/global-public-context'
const useIframeToken = () => {
const router = useRouter();
useEffect(() => {
// 监听父级指定操作
const handler = (event: MessageEvent) => {
// if (event.origin !== "https://gcgj.ngsk.tech:7001") return;
if (
event.data.type === "LOGIN_AND_WORKFLOW_INTO" &&
event.data.tokens &&
event.data.workflowUrl
) {
localStorage.setItem("console_token", event.data.tokens.console_token);
localStorage.setItem("refresh_token", event.data.tokens.refresh_token);
router.replace(event.data.workflowUrl);
}
};
// 首页初始化完成后提示iframe操作
window.parent.postMessage(
{
type: "LOGIN_AND_WORKFLOW",
},
"*"
);
window.addEventListener("message", handler);
return () => window.removeEventListener("message", handler);
}, []);
};
const NormalForm = () => {
const { t } = useTranslation()
const router = useRouter()
@ -29,6 +60,7 @@ const NormalForm = () => {
const [showORLine, setShowORLine] = useState(false)
const [allMethodsAreDisabled, setAllMethodsAreDisabled] = useState(false)
const [workspaceName, setWorkSpaceName] = useState('')
useIframeToken();
const isInviteLink = Boolean(invite_token && invite_token !== 'null')

@ -2,6 +2,7 @@
import type { ChatConfig } from '@/app/components/base/chat/types'
import Loading from '@/app/components/base/loading'
import { checkOrSetAccessToken } from '@/app/components/share/utils'
import { AccessMode } from '@/models/access-control'
import type { AppData, AppMeta } from '@/models/share'
import { useGetWebAppAccessModeByCode } from '@/service/use-share'
@ -60,6 +61,8 @@ const WebAppStoreProvider: FC<PropsWithChildren> = ({ children }) => {
const pathname = usePathname()
const searchParams = useSearchParams()
const redirectUrlParam = searchParams.get('redirect_url')
const session = searchParams.get('session')
const sysUserId = searchParams.get('sys.user_id')
const [shareCode, setShareCode] = useState<string | null>(null)
useEffect(() => {
const shareCodeFromRedirect = getShareCodeFromRedirectUrl(redirectUrlParam)
@ -69,11 +72,22 @@ const WebAppStoreProvider: FC<PropsWithChildren> = ({ children }) => {
updateShareCode(newShareCode)
}, [pathname, redirectUrlParam, updateShareCode])
const { isFetching, data: accessModeResult } = useGetWebAppAccessModeByCode(shareCode)
const [isFetchingAccessToken, setIsFetchingAccessToken] = useState(true)
useEffect(() => {
if (accessModeResult?.accessMode)
if (accessModeResult?.accessMode) {
updateWebAppAccessMode(accessModeResult.accessMode)
}, [accessModeResult, updateWebAppAccessMode])
if (isFetching) {
if (accessModeResult?.accessMode === AccessMode.PUBLIC && session && sysUserId) {
setIsFetchingAccessToken(true)
checkOrSetAccessToken(shareCode).finally(() => {
setIsFetchingAccessToken(false)
})
}
else {
setIsFetchingAccessToken(false)
}
}
}, [accessModeResult, updateWebAppAccessMode, setIsFetchingAccessToken, shareCode, session, sysUserId])
if (isFetching || isFetchingAccessToken) {
return <div className='flex h-full w-full items-center justify-center'>
<Loading />
</div>

@ -359,7 +359,6 @@ const translation = {
'paragraph': 'Paragraph',
'select': 'Select',
'number': 'Number',
'boolean': 'Checkbox',
'single-file': 'Single File',
'multi-files': 'File List',
'notSet': 'Not set, try typing {{input}} in the prefix prompt',

@ -50,24 +50,35 @@ export const loadLangResources = async (lang: string) => {
acc[camelCase(NAMESPACES[index])] = mod
return acc
}, {} as Record<string, any>)
return resources
}
const getFallbackTranslation = () => {
const resources = NAMESPACES.reduce((acc, ns, index) => {
acc[camelCase(NAMESPACES[index])] = require(`./en-US/${ns}`).default
return acc
}, {} as Record<string, any>)
return {
translation: resources,
}
}
i18n.use(initReactI18next)
.init({
lng: undefined,
fallbackLng: 'en-US',
})
if (!i18n.isInitialized) {
i18n.use(initReactI18next)
.init({
lng: undefined,
fallbackLng: 'en-US',
resources: {
'en-US': getFallbackTranslation(),
},
})
}
export const changeLanguage = async (lng?: string) => {
const resolvedLng = lng ?? 'en-US'
const resources = {
[resolvedLng]: await loadLangResources(resolvedLng),
}
const resource = await loadLangResources(resolvedLng)
if (!i18n.hasResourceBundle(resolvedLng, 'translation'))
i18n.addResourceBundle(resolvedLng, 'translation', resources[resolvedLng].translation, true, true)
i18n.addResourceBundle(resolvedLng, 'translation', resource, true, true)
await i18n.changeLanguage(resolvedLng)
}

@ -349,7 +349,6 @@ const translation = {
'paragraph': '段落',
'select': '下拉选项',
'number': '数字',
'boolean': '复选框',
'single-file': '单文件',
'multi-files': '文件列表',
'notSet': '未设置,在 Prompt 中输入 {{input}} 试试',

@ -9,7 +9,7 @@ import type {
MetadataFilteringModeEnum,
} from '@/app/components/workflow/nodes/knowledge-retrieval/types'
import type { ModelConfig as NodeModelConfig } from '@/app/components/workflow/types'
export type Inputs = Record<string, string | number | object | boolean>
export type Inputs = Record<string, string | number | object>
export enum PromptMode {
simple = 'simple',

@ -43,14 +43,25 @@ const nextConfig = {
search: '',
})),
},
experimental: {
},
experimental: {},
// fix all before production. Now it slow the develop speed.
eslint: {
// Warning: This allows production builds to successfully complete even if
// your project has ESLint errors.
ignoreDuringBuilds: true,
dirs: ['app', 'bin', 'config', 'context', 'hooks', 'i18n', 'models', 'service', 'test', 'types', 'utils'],
dirs: [
'app',
'bin',
'config',
'context',
'hooks',
'i18n',
'models',
'service',
'test',
'types',
'utils',
],
},
typescript: {
// https://nextjs.org/docs/api-reference/next.config.js/ignoring-typescript-errors
@ -67,6 +78,23 @@ const nextConfig = {
]
},
output: 'standalone',
async headers() {
return [
{
source: '/(.*)', // 匹配所有路由
headers: [
{
key: 'X-Frame-Options',
value: 'SAMEORIGIN', // 或 ALLOWALL更宽松
},
{
key: 'Content-Security-Policy',
value: 'frame-ancestors "self" *', // 允许所有
},
],
},
]
},
}
module.exports = withBundleAnalyzer(withMDX(nextConfig))

@ -130,9 +130,9 @@ export const promptVariablesToUserInputsForm = (promptVariables: PromptVariable[
} as any)
return
}
if (item.type === 'number' || item.type === 'boolean') {
if (item.type === 'number') {
userInputs.push({
[item.type]: {
number: {
label: item.name,
variable: item.key,
required: item.required !== false, // default true
@ -172,17 +172,3 @@ export const promptVariablesToUserInputsForm = (promptVariables: PromptVariable[
return userInputs
}
export const formatBooleanInputs = (useInputs?: PromptVariable[] | null, inputs?: Record<string, string | number | object | boolean> | null) => {
if(!useInputs)
return inputs
const res = { ...(inputs || {}) }
useInputs.forEach((item) => {
const isBooleanInput = item.type === 'boolean'
if (isBooleanInput) {
// Convert boolean inputs to boolean type
res[item.key] = !!res[item.key]
}
})
return res
}

Loading…
Cancel
Save