Compare commits

..

10 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">

@ -511,8 +511,6 @@ class CheckEmailUnique(Resource):
parser = reqparse.RequestParser()
parser.add_argument("email", type=email, required=True, location="json")
args = parser.parse_args()
if AccountService.is_account_in_freeze(args["new_email"]):
raise AccountInFreezeError()
if not AccountService.check_email_unique(args["email"]):
raise EmailAlreadyInUseError()
return {"result": "success"}

@ -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

@ -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'

@ -15,8 +15,6 @@ import {
verifyEmail,
} from '@/service/common'
import { noop } from 'lodash-es'
import { asyncRunSafe } from '@/utils'
import type { ResponseError } from '@/service/fetch'
type Props = {
show: boolean
@ -41,7 +39,6 @@ const EmailChangeModal = ({ onClose, email, show }: Props) => {
const [time, setTime] = useState<number>(0)
const [stepToken, setStepToken] = useState<string>('')
const [newEmailExited, setNewEmailExited] = useState<boolean>(false)
const [unAvailableEmail, setUnAvailableEmail] = useState<boolean>(false)
const [isCheckingEmail, setIsCheckingEmail] = useState<boolean>(false)
const startCount = () => {
@ -127,17 +124,9 @@ const EmailChangeModal = ({ onClose, email, show }: Props) => {
email,
})
setNewEmailExited(false)
setUnAvailableEmail(false)
}
catch (e: any) {
if (e.status === 400) {
const [, errRespData] = await asyncRunSafe<ResponseError>(e.json())
const { code } = errRespData || {}
if (code === 'email_already_in_use')
setNewEmailExited(true)
if (code === 'account_in_freeze')
setUnAvailableEmail(true)
}
catch {
setNewEmailExited(true)
}
finally {
setIsCheckingEmail(false)
@ -302,18 +291,15 @@ const EmailChangeModal = ({ onClose, email, show }: Props) => {
placeholder={t('common.account.changeEmail.emailPlaceholder')}
value={mail}
onChange={e => handleNewEmailValueChange(e.target.value)}
destructive={newEmailExited || unAvailableEmail}
destructive={newEmailExited}
/>
{newEmailExited && (
<div className='body-xs-regular mt-1 py-0.5 text-text-destructive'>{t('common.account.changeEmail.existingEmail')}</div>
)}
{unAvailableEmail && (
<div className='body-xs-regular mt-1 py-0.5 text-text-destructive'>{t('common.account.changeEmail.unAvailableEmail')}</div>
)}
</div>
<div className='mt-3 space-y-2'>
<Button
disabled={!mail || newEmailExited || unAvailableEmail || isCheckingEmail || !isValidEmail(mail)}
disabled={!mail || newEmailExited || isCheckingEmail || !isValidEmail(mail)}
className='!w-full'
variant='primary'
onClick={sendCodeToNewEmail}

@ -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

@ -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

@ -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}

@ -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')

@ -248,7 +248,6 @@ const translation = {
emailLabel: 'New email',
emailPlaceholder: 'Enter a new email',
existingEmail: 'A user with this email already exists.',
unAvailableEmail: 'This email is temporarily unavailable.',
sendVerifyCode: 'Send verification code',
continue: 'Continue',
changeTo: 'Change to {{email}}',

@ -249,7 +249,6 @@ const translation = {
emailLabel: '新しいメール',
emailPlaceholder: '新しいメールを入力してください',
existingEmail: 'このメールアドレスのユーザーは既に存在します',
unAvailableEmail: 'このメールアドレスは現在使用できません。',
sendVerifyCode: '確認コードを送信',
continue: '続行',
changeTo: '{{email}} に変更',

@ -248,7 +248,6 @@ const translation = {
emailLabel: '新邮箱',
emailPlaceholder: '输入新邮箱',
existingEmail: '该邮箱已存在',
unAvailableEmail: '该邮箱暂时无法使用。',
sendVerifyCode: '发送验证码',
continue: '继续',
changeTo: '更改为 {{email}}',

@ -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))

Loading…
Cancel
Save