mirror of
https://github.com/langgenius/dify.git
synced 2026-03-30 02:20:16 +08:00
Merge branch 'main' into feat/tool-oauth
This commit is contained in:
@ -9,8 +9,7 @@ import Button from '@/app/components/base/button'
|
||||
import { changeWebAppPasswordWithToken } from '@/service/common'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import Input from '@/app/components/base/input'
|
||||
|
||||
const validPassword = /^(?=.*[a-zA-Z])(?=.*\d).{8,}$/
|
||||
import { validPassword } from '@/config'
|
||||
|
||||
const ChangePasswordForm = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
@ -21,6 +21,7 @@ import { IS_CE_EDITION } from '@/config'
|
||||
import Input from '@/app/components/base/input'
|
||||
import PremiumBadge from '@/app/components/base/premium-badge'
|
||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { validPassword } from '@/config'
|
||||
|
||||
const titleClassName = `
|
||||
system-sm-semibold text-text-secondary
|
||||
@ -29,8 +30,6 @@ const descriptionClassName = `
|
||||
mt-1 body-xs-regular text-text-tertiary
|
||||
`
|
||||
|
||||
const validPassword = /^(?=.*[a-zA-Z])(?=.*\d).{8,}$/
|
||||
|
||||
export default function AccountPage() {
|
||||
const { t } = useTranslation()
|
||||
const { systemFeatures } = useGlobalPublicStore()
|
||||
|
||||
@ -308,13 +308,11 @@ const AppInfo = ({ expand, onlyShowDetail = false, openState = false, onDetailEx
|
||||
operations={operations}
|
||||
/>
|
||||
</div>
|
||||
<div className='flex flex-1'>
|
||||
<CardView
|
||||
appId={appDetail.id}
|
||||
isInPanel={true}
|
||||
className='flex grow flex-col gap-2 overflow-auto px-2 py-1'
|
||||
/>
|
||||
</div>
|
||||
<CardView
|
||||
appId={appDetail.id}
|
||||
isInPanel={true}
|
||||
className='flex flex-1 flex-col gap-2 overflow-auto px-2 py-1'
|
||||
/>
|
||||
<Divider />
|
||||
<div className='flex min-h-fit shrink-0 flex-col items-start justify-center gap-3 self-stretch border-t-[0.5px] border-divider-subtle p-2'>
|
||||
<Button
|
||||
|
||||
@ -470,8 +470,6 @@ function DetailPanel({ detail, onFeedback }: IDetailPanel) {
|
||||
className="py-4"
|
||||
id="scrollableDiv"
|
||||
style={{
|
||||
height: 1000, // Specify a value
|
||||
overflow: 'auto',
|
||||
display: 'flex',
|
||||
flexDirection: 'column-reverse',
|
||||
}}>
|
||||
|
||||
@ -83,12 +83,11 @@ export const useChat = (
|
||||
const ret = [...threadMessages]
|
||||
if (config?.opening_statement) {
|
||||
const index = threadMessages.findIndex(item => item.isOpeningStatement)
|
||||
|
||||
if (index > -1) {
|
||||
ret[index] = {
|
||||
...ret[index],
|
||||
content: getIntroduction(config.opening_statement),
|
||||
suggestedQuestions: config.suggested_questions,
|
||||
suggestedQuestions: config.suggested_questions?.map(item => getIntroduction(item)),
|
||||
}
|
||||
}
|
||||
else {
|
||||
@ -97,7 +96,7 @@ export const useChat = (
|
||||
content: getIntroduction(config.opening_statement),
|
||||
isAnswer: true,
|
||||
isOpeningStatement: true,
|
||||
suggestedQuestions: config.suggested_questions,
|
||||
suggestedQuestions: config.suggested_questions?.map(item => getIntroduction(item)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -101,7 +101,7 @@ const EmojiPickerInner: FC<IEmojiPickerInnerProps> = ({
|
||||
</div>
|
||||
<Divider className='my-3' />
|
||||
|
||||
<div className="w-full flex-1 overflow-y-auto overflow-x-hidden px-3">
|
||||
<div className="max-h-[200px] w-full overflow-y-auto overflow-x-hidden px-3">
|
||||
{isSearching && <>
|
||||
<div key={'category-search'} className='flex flex-col'>
|
||||
<p className='system-xs-medium-uppercase mb-1 text-text-primary'>Search</p>
|
||||
@ -170,7 +170,7 @@ const EmojiPickerInner: FC<IEmojiPickerInnerProps> = ({
|
||||
'flex h-8 w-8 items-center justify-center rounded-lg p-1',
|
||||
)
|
||||
} style={{ background: color }}>
|
||||
{selectedEmoji !== '' && <em-emoji id={selectedEmoji} />}
|
||||
{selectedEmoji !== '' && <em-emoji id={selectedEmoji} />}
|
||||
</div>
|
||||
</div>
|
||||
})}
|
||||
|
||||
@ -130,6 +130,7 @@ const OpeningSettingModal = ({
|
||||
<input
|
||||
type="input"
|
||||
value={question || ''}
|
||||
placeholder={t('appDebug.openingStatement.openingQuestionPlaceholder') as string}
|
||||
onChange={(e) => {
|
||||
const value = e.target.value
|
||||
setTempSuggestedQuestions(tempSuggestedQuestions.map((item, i) => {
|
||||
|
||||
@ -49,7 +49,7 @@ type Props = {
|
||||
value?: ToolValue
|
||||
selectedTools?: ToolValue[]
|
||||
onSelect: (tool: ToolValue) => void
|
||||
onSelectMultiple: (tool: ToolValue[]) => void
|
||||
onSelectMultiple?: (tool: ToolValue[]) => void
|
||||
isEdit?: boolean
|
||||
onDelete?: () => void
|
||||
supportEnableSwitch?: boolean
|
||||
@ -137,7 +137,7 @@ const ToolSelector: FC<Props> = ({
|
||||
}
|
||||
const handleSelectMultipleTool = (tool: ToolDefaultValue[]) => {
|
||||
const toolValues = tool.map(item => getToolValue(item))
|
||||
onSelectMultiple(toolValues)
|
||||
onSelectMultiple?.(toolValues)
|
||||
}
|
||||
|
||||
const handleDescriptionChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
|
||||
|
||||
@ -15,7 +15,7 @@ import {
|
||||
useWorkflowRun,
|
||||
useWorkflowStartRun,
|
||||
} from '../hooks'
|
||||
import { useWorkflowStore } from '@/app/components/workflow/store'
|
||||
import { useStore, useWorkflowStore } from '@/app/components/workflow/store'
|
||||
|
||||
type WorkflowMainProps = Pick<WorkflowProps, 'nodes' | 'edges' | 'viewport'>
|
||||
const WorkflowMain = ({
|
||||
@ -64,7 +64,11 @@ const WorkflowMain = ({
|
||||
handleWorkflowStartRunInChatflow,
|
||||
handleWorkflowStartRunInWorkflow,
|
||||
} = useWorkflowStartRun()
|
||||
const { fetchInspectVars } = useSetWorkflowVarsWithValue()
|
||||
const appId = useStore(s => s.appId)
|
||||
const { fetchInspectVars } = useSetWorkflowVarsWithValue({
|
||||
flowId: appId,
|
||||
...useConfigsMap(),
|
||||
})
|
||||
const {
|
||||
hasNodeInspectVars,
|
||||
hasSetInspectVar,
|
||||
|
||||
@ -5,6 +5,6 @@ export * from './use-workflow-run'
|
||||
export * from './use-workflow-start-run'
|
||||
export * from './use-is-chat-mode'
|
||||
export * from './use-workflow-refresh-draft'
|
||||
export * from './use-fetch-workflow-inspect-vars'
|
||||
export * from '../../workflow/hooks/use-fetch-workflow-inspect-vars'
|
||||
export * from './use-inspect-vars-crud'
|
||||
export * from './use-configs-map'
|
||||
|
||||
@ -1,234 +1,16 @@
|
||||
import { fetchNodeInspectVars } from '@/service/workflow'
|
||||
import { useStore, useWorkflowStore } from '@/app/components/workflow/store'
|
||||
import type { ValueSelector } from '@/app/components/workflow/types'
|
||||
import type { VarInInspect } from '@/types/workflow'
|
||||
import { VarInInspectType } from '@/types/workflow'
|
||||
import {
|
||||
useDeleteAllInspectorVars,
|
||||
useDeleteInspectVar,
|
||||
useDeleteNodeInspectorVars,
|
||||
useEditInspectorVar,
|
||||
useInvalidateConversationVarValues,
|
||||
useInvalidateSysVarValues,
|
||||
useResetConversationVar,
|
||||
useResetToLastRunValue,
|
||||
} from '@/service/use-workflow'
|
||||
import { useCallback } from 'react'
|
||||
import { isConversationVar, isENV, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||
import produce from 'immer'
|
||||
import type { Node } from '@/app/components/workflow/types'
|
||||
import { useNodesInteractionsWithoutSync } from '@/app/components/workflow/hooks/use-nodes-interactions-without-sync'
|
||||
import { useEdgesInteractionsWithoutSync } from '@/app/components/workflow/hooks/use-edges-interactions-without-sync'
|
||||
import { useStore } from '@/app/components/workflow/store'
|
||||
import { useInspectVarsCrudCommon } from '../../workflow/hooks/use-inspect-vars-crud-common'
|
||||
import { useConfigsMap } from './use-configs-map'
|
||||
|
||||
export const useInspectVarsCrud = () => {
|
||||
const workflowStore = useWorkflowStore()
|
||||
const appId = useStore(s => s.appId)
|
||||
const { conversationVarsUrl, systemVarsUrl } = useConfigsMap()
|
||||
const invalidateConversationVarValues = useInvalidateConversationVarValues(conversationVarsUrl)
|
||||
const { mutateAsync: doResetConversationVar } = useResetConversationVar(appId)
|
||||
const { mutateAsync: doResetToLastRunValue } = useResetToLastRunValue(appId)
|
||||
const invalidateSysVarValues = useInvalidateSysVarValues(systemVarsUrl)
|
||||
|
||||
const { mutateAsync: doDeleteAllInspectorVars } = useDeleteAllInspectorVars(appId)
|
||||
const { mutate: doDeleteNodeInspectorVars } = useDeleteNodeInspectorVars(appId)
|
||||
const { mutate: doDeleteInspectVar } = useDeleteInspectVar(appId)
|
||||
|
||||
const { mutateAsync: doEditInspectorVar } = useEditInspectorVar(appId)
|
||||
const { handleCancelNodeSuccessStatus } = useNodesInteractionsWithoutSync()
|
||||
const { handleEdgeCancelRunningStatus } = useEdgesInteractionsWithoutSync()
|
||||
const getNodeInspectVars = useCallback((nodeId: string) => {
|
||||
const { nodesWithInspectVars } = workflowStore.getState()
|
||||
const node = nodesWithInspectVars.find(node => node.nodeId === nodeId)
|
||||
return node
|
||||
}, [workflowStore])
|
||||
|
||||
const getVarId = useCallback((nodeId: string, varName: string) => {
|
||||
const node = getNodeInspectVars(nodeId)
|
||||
if (!node)
|
||||
return undefined
|
||||
const varId = node.vars.find((varItem) => {
|
||||
return varItem.selector[1] === varName
|
||||
})?.id
|
||||
return varId
|
||||
}, [getNodeInspectVars])
|
||||
|
||||
const getInspectVar = useCallback((nodeId: string, name: string): VarInInspect | undefined => {
|
||||
const node = getNodeInspectVars(nodeId)
|
||||
if (!node)
|
||||
return undefined
|
||||
|
||||
const variable = node.vars.find((varItem) => {
|
||||
return varItem.name === name
|
||||
})
|
||||
return variable
|
||||
}, [getNodeInspectVars])
|
||||
|
||||
const hasSetInspectVar = useCallback((nodeId: string, name: string, sysVars: VarInInspect[], conversationVars: VarInInspect[]) => {
|
||||
const isEnv = isENV([nodeId])
|
||||
if (isEnv) // always have value
|
||||
return true
|
||||
const isSys = isSystemVar([nodeId])
|
||||
if (isSys)
|
||||
return sysVars.some(varItem => varItem.selector?.[1]?.replace('sys.', '') === name)
|
||||
const isChatVar = isConversationVar([nodeId])
|
||||
if (isChatVar)
|
||||
return conversationVars.some(varItem => varItem.selector?.[1] === name)
|
||||
return getInspectVar(nodeId, name) !== undefined
|
||||
}, [getInspectVar])
|
||||
|
||||
const hasNodeInspectVars = useCallback((nodeId: string) => {
|
||||
return !!getNodeInspectVars(nodeId)
|
||||
}, [getNodeInspectVars])
|
||||
|
||||
const fetchInspectVarValue = useCallback(async (selector: ValueSelector) => {
|
||||
const {
|
||||
appId,
|
||||
setNodeInspectVars,
|
||||
} = workflowStore.getState()
|
||||
const nodeId = selector[0]
|
||||
const isSystemVar = nodeId === 'sys'
|
||||
const isConversationVar = nodeId === 'conversation'
|
||||
if (isSystemVar) {
|
||||
invalidateSysVarValues()
|
||||
return
|
||||
}
|
||||
if (isConversationVar) {
|
||||
invalidateConversationVarValues()
|
||||
return
|
||||
}
|
||||
const vars = await fetchNodeInspectVars(appId, nodeId)
|
||||
setNodeInspectVars(nodeId, vars)
|
||||
}, [workflowStore, invalidateSysVarValues, invalidateConversationVarValues])
|
||||
|
||||
// after last run would call this
|
||||
const appendNodeInspectVars = useCallback((nodeId: string, payload: VarInInspect[], allNodes: Node[]) => {
|
||||
const {
|
||||
nodesWithInspectVars,
|
||||
setNodesWithInspectVars,
|
||||
} = workflowStore.getState()
|
||||
const nodes = produce(nodesWithInspectVars, (draft) => {
|
||||
const nodeInfo = allNodes.find(node => node.id === nodeId)
|
||||
if (nodeInfo) {
|
||||
const index = draft.findIndex(node => node.nodeId === nodeId)
|
||||
if (index === -1) {
|
||||
draft.unshift({
|
||||
nodeId,
|
||||
nodeType: nodeInfo.data.type,
|
||||
title: nodeInfo.data.title,
|
||||
vars: payload,
|
||||
nodePayload: nodeInfo.data,
|
||||
})
|
||||
}
|
||||
else {
|
||||
draft[index].vars = payload
|
||||
// put the node to the topAdd commentMore actions
|
||||
draft.unshift(draft.splice(index, 1)[0])
|
||||
}
|
||||
}
|
||||
})
|
||||
setNodesWithInspectVars(nodes)
|
||||
handleCancelNodeSuccessStatus(nodeId)
|
||||
}, [workflowStore, handleCancelNodeSuccessStatus])
|
||||
|
||||
const hasNodeInspectVar = useCallback((nodeId: string, varId: string) => {
|
||||
const { nodesWithInspectVars } = workflowStore.getState()
|
||||
const targetNode = nodesWithInspectVars.find(item => item.nodeId === nodeId)
|
||||
if(!targetNode || !targetNode.vars)
|
||||
return false
|
||||
return targetNode.vars.some(item => item.id === varId)
|
||||
}, [workflowStore])
|
||||
|
||||
const deleteInspectVar = useCallback(async (nodeId: string, varId: string) => {
|
||||
const { deleteInspectVar } = workflowStore.getState()
|
||||
if(hasNodeInspectVar(nodeId, varId)) {
|
||||
await doDeleteInspectVar(varId)
|
||||
deleteInspectVar(nodeId, varId)
|
||||
}
|
||||
}, [doDeleteInspectVar, workflowStore, hasNodeInspectVar])
|
||||
|
||||
const resetConversationVar = useCallback(async (varId: string) => {
|
||||
await doResetConversationVar(varId)
|
||||
invalidateConversationVarValues()
|
||||
}, [doResetConversationVar, invalidateConversationVarValues])
|
||||
|
||||
const deleteNodeInspectorVars = useCallback(async (nodeId: string) => {
|
||||
const { deleteNodeInspectVars } = workflowStore.getState()
|
||||
if (hasNodeInspectVars(nodeId)) {
|
||||
await doDeleteNodeInspectorVars(nodeId)
|
||||
deleteNodeInspectVars(nodeId)
|
||||
}
|
||||
}, [doDeleteNodeInspectorVars, workflowStore, hasNodeInspectVars])
|
||||
|
||||
const deleteAllInspectorVars = useCallback(async () => {
|
||||
const { deleteAllInspectVars } = workflowStore.getState()
|
||||
await doDeleteAllInspectorVars()
|
||||
await invalidateConversationVarValues()
|
||||
await invalidateSysVarValues()
|
||||
deleteAllInspectVars()
|
||||
handleEdgeCancelRunningStatus()
|
||||
}, [doDeleteAllInspectorVars, invalidateConversationVarValues, invalidateSysVarValues, workflowStore, handleEdgeCancelRunningStatus])
|
||||
|
||||
const editInspectVarValue = useCallback(async (nodeId: string, varId: string, value: any) => {
|
||||
const { setInspectVarValue } = workflowStore.getState()
|
||||
await doEditInspectorVar({
|
||||
varId,
|
||||
value,
|
||||
})
|
||||
setInspectVarValue(nodeId, varId, value)
|
||||
if (nodeId === VarInInspectType.conversation)
|
||||
invalidateConversationVarValues()
|
||||
if (nodeId === VarInInspectType.system)
|
||||
invalidateSysVarValues()
|
||||
}, [doEditInspectorVar, invalidateConversationVarValues, invalidateSysVarValues, workflowStore])
|
||||
|
||||
const renameInspectVarName = useCallback(async (nodeId: string, oldName: string, newName: string) => {
|
||||
const { renameInspectVarName } = workflowStore.getState()
|
||||
const varId = getVarId(nodeId, oldName)
|
||||
if (!varId)
|
||||
return
|
||||
|
||||
const newSelector = [nodeId, newName]
|
||||
await doEditInspectorVar({
|
||||
varId,
|
||||
name: newName,
|
||||
})
|
||||
renameInspectVarName(nodeId, varId, newSelector)
|
||||
}, [doEditInspectorVar, getVarId, workflowStore])
|
||||
|
||||
const isInspectVarEdited = useCallback((nodeId: string, name: string) => {
|
||||
const inspectVar = getInspectVar(nodeId, name)
|
||||
if (!inspectVar)
|
||||
return false
|
||||
|
||||
return inspectVar.edited
|
||||
}, [getInspectVar])
|
||||
|
||||
const resetToLastRunVar = useCallback(async (nodeId: string, varId: string) => {
|
||||
const { resetToLastRunVar } = workflowStore.getState()
|
||||
const isSysVar = nodeId === 'sys'
|
||||
const data = await doResetToLastRunValue(varId)
|
||||
|
||||
if(isSysVar)
|
||||
invalidateSysVarValues()
|
||||
else
|
||||
resetToLastRunVar(nodeId, varId, data.value)
|
||||
}, [doResetToLastRunValue, invalidateSysVarValues, workflowStore])
|
||||
const configsMap = useConfigsMap()
|
||||
const apis = useInspectVarsCrudCommon({
|
||||
flowId: appId,
|
||||
...configsMap,
|
||||
})
|
||||
|
||||
return {
|
||||
hasNodeInspectVars,
|
||||
hasSetInspectVar,
|
||||
fetchInspectVarValue,
|
||||
editInspectVarValue,
|
||||
renameInspectVarName,
|
||||
appendNodeInspectVars,
|
||||
deleteInspectVar,
|
||||
deleteNodeInspectorVars,
|
||||
deleteAllInspectorVars,
|
||||
isInspectVarEdited,
|
||||
resetToLastRunVar,
|
||||
invalidateSysVarValues,
|
||||
resetConversationVar,
|
||||
invalidateConversationVarValues,
|
||||
...apis,
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,7 +20,8 @@ import type { VersionHistory } from '@/types/workflow'
|
||||
import { noop } from 'lodash-es'
|
||||
import { useNodesSyncDraft } from './use-nodes-sync-draft'
|
||||
import { useInvalidAllLastRun } from '@/service/use-workflow'
|
||||
import { useSetWorkflowVarsWithValue } from './use-fetch-workflow-inspect-vars'
|
||||
import { useSetWorkflowVarsWithValue } from '../../workflow/hooks/use-fetch-workflow-inspect-vars'
|
||||
import { useConfigsMap } from './use-configs-map'
|
||||
|
||||
export const useWorkflowRun = () => {
|
||||
const store = useStoreApi()
|
||||
@ -32,7 +33,11 @@ export const useWorkflowRun = () => {
|
||||
const pathname = usePathname()
|
||||
const appId = useAppStore.getState().appDetail?.id
|
||||
const invalidAllLastRun = useInvalidAllLastRun(appId as string)
|
||||
const { fetchInspectVars } = useSetWorkflowVarsWithValue()
|
||||
const configsMap = useConfigsMap()
|
||||
const { fetchInspectVars } = useSetWorkflowVarsWithValue({
|
||||
flowId: appId as string,
|
||||
...configsMap,
|
||||
})
|
||||
|
||||
const {
|
||||
handleWorkflowStarted,
|
||||
|
||||
@ -6,12 +6,20 @@ import type { Node } from '@/app/components/workflow/types'
|
||||
import { fetchAllInspectVars } from '@/service/workflow'
|
||||
import { useInvalidateConversationVarValues, useInvalidateSysVarValues } from '@/service/use-workflow'
|
||||
import { useNodesInteractionsWithoutSync } from '@/app/components/workflow/hooks/use-nodes-interactions-without-sync'
|
||||
import { useConfigsMap } from './use-configs-map'
|
||||
|
||||
export const useSetWorkflowVarsWithValue = () => {
|
||||
type Params = {
|
||||
flowId: string
|
||||
conversationVarsUrl: string
|
||||
systemVarsUrl: string
|
||||
}
|
||||
|
||||
export const useSetWorkflowVarsWithValue = ({
|
||||
flowId,
|
||||
conversationVarsUrl,
|
||||
systemVarsUrl,
|
||||
}: Params) => {
|
||||
const workflowStore = useWorkflowStore()
|
||||
const store = useStoreApi()
|
||||
const { conversationVarsUrl, systemVarsUrl } = useConfigsMap()
|
||||
const invalidateConversationVarValues = useInvalidateConversationVarValues(conversationVarsUrl)
|
||||
const invalidateSysVarValues = useInvalidateSysVarValues(systemVarsUrl)
|
||||
const { handleCancelAllNodeSuccessStatus } = useNodesInteractionsWithoutSync()
|
||||
@ -58,13 +66,12 @@ export const useSetWorkflowVarsWithValue = () => {
|
||||
}, [workflowStore, store])
|
||||
|
||||
const fetchInspectVars = useCallback(async () => {
|
||||
const { appId } = workflowStore.getState()
|
||||
invalidateConversationVarValues()
|
||||
invalidateSysVarValues()
|
||||
const data = await fetchAllInspectVars(appId)
|
||||
const data = await fetchAllInspectVars(flowId)
|
||||
setInspectVarsToStore(data)
|
||||
handleCancelAllNodeSuccessStatus() // to make sure clear node output show the unset status
|
||||
}, [workflowStore, invalidateConversationVarValues, invalidateSysVarValues, setInspectVarsToStore, handleCancelAllNodeSuccessStatus])
|
||||
}, [invalidateConversationVarValues, invalidateSysVarValues, flowId, setInspectVarsToStore, handleCancelAllNodeSuccessStatus])
|
||||
return {
|
||||
fetchInspectVars,
|
||||
}
|
||||
@ -0,0 +1,240 @@
|
||||
import { fetchNodeInspectVars } from '@/service/workflow'
|
||||
import { useWorkflowStore } from '@/app/components/workflow/store'
|
||||
import type { ValueSelector } from '@/app/components/workflow/types'
|
||||
import type { VarInInspect } from '@/types/workflow'
|
||||
import { VarInInspectType } from '@/types/workflow'
|
||||
import {
|
||||
useDeleteAllInspectorVars,
|
||||
useDeleteInspectVar,
|
||||
useDeleteNodeInspectorVars,
|
||||
useEditInspectorVar,
|
||||
useInvalidateConversationVarValues,
|
||||
useInvalidateSysVarValues,
|
||||
useResetConversationVar,
|
||||
useResetToLastRunValue,
|
||||
} from '@/service/use-workflow'
|
||||
import { useCallback } from 'react'
|
||||
import { isConversationVar, isENV, isSystemVar } from '@/app/components/workflow/nodes/_base/components/variable/utils'
|
||||
import produce from 'immer'
|
||||
import type { Node } from '@/app/components/workflow/types'
|
||||
import { useNodesInteractionsWithoutSync } from '@/app/components/workflow/hooks/use-nodes-interactions-without-sync'
|
||||
import { useEdgesInteractionsWithoutSync } from '@/app/components/workflow/hooks/use-edges-interactions-without-sync'
|
||||
|
||||
type Params = {
|
||||
flowId: string
|
||||
conversationVarsUrl: string
|
||||
systemVarsUrl: string
|
||||
}
|
||||
export const useInspectVarsCrudCommon = ({
|
||||
flowId,
|
||||
conversationVarsUrl,
|
||||
systemVarsUrl,
|
||||
}: Params) => {
|
||||
const workflowStore = useWorkflowStore()
|
||||
const invalidateConversationVarValues = useInvalidateConversationVarValues(conversationVarsUrl!)
|
||||
const { mutateAsync: doResetConversationVar } = useResetConversationVar(flowId)
|
||||
const { mutateAsync: doResetToLastRunValue } = useResetToLastRunValue(flowId)
|
||||
const invalidateSysVarValues = useInvalidateSysVarValues(systemVarsUrl!)
|
||||
|
||||
const { mutateAsync: doDeleteAllInspectorVars } = useDeleteAllInspectorVars(flowId)
|
||||
const { mutate: doDeleteNodeInspectorVars } = useDeleteNodeInspectorVars(flowId)
|
||||
const { mutate: doDeleteInspectVar } = useDeleteInspectVar(flowId)
|
||||
|
||||
const { mutateAsync: doEditInspectorVar } = useEditInspectorVar(flowId)
|
||||
const { handleCancelNodeSuccessStatus } = useNodesInteractionsWithoutSync()
|
||||
const { handleEdgeCancelRunningStatus } = useEdgesInteractionsWithoutSync()
|
||||
const getNodeInspectVars = useCallback((nodeId: string) => {
|
||||
const { nodesWithInspectVars } = workflowStore.getState()
|
||||
const node = nodesWithInspectVars.find(node => node.nodeId === nodeId)
|
||||
return node
|
||||
}, [workflowStore])
|
||||
|
||||
const getVarId = useCallback((nodeId: string, varName: string) => {
|
||||
const node = getNodeInspectVars(nodeId)
|
||||
if (!node)
|
||||
return undefined
|
||||
const varId = node.vars.find((varItem) => {
|
||||
return varItem.selector[1] === varName
|
||||
})?.id
|
||||
return varId
|
||||
}, [getNodeInspectVars])
|
||||
|
||||
const getInspectVar = useCallback((nodeId: string, name: string): VarInInspect | undefined => {
|
||||
const node = getNodeInspectVars(nodeId)
|
||||
if (!node)
|
||||
return undefined
|
||||
|
||||
const variable = node.vars.find((varItem) => {
|
||||
return varItem.name === name
|
||||
})
|
||||
return variable
|
||||
}, [getNodeInspectVars])
|
||||
|
||||
const hasSetInspectVar = useCallback((nodeId: string, name: string, sysVars: VarInInspect[], conversationVars: VarInInspect[]) => {
|
||||
const isEnv = isENV([nodeId])
|
||||
if (isEnv) // always have value
|
||||
return true
|
||||
const isSys = isSystemVar([nodeId])
|
||||
if (isSys)
|
||||
return sysVars.some(varItem => varItem.selector?.[1]?.replace('sys.', '') === name)
|
||||
const isChatVar = isConversationVar([nodeId])
|
||||
if (isChatVar)
|
||||
return conversationVars.some(varItem => varItem.selector?.[1] === name)
|
||||
return getInspectVar(nodeId, name) !== undefined
|
||||
}, [getInspectVar])
|
||||
|
||||
const hasNodeInspectVars = useCallback((nodeId: string) => {
|
||||
return !!getNodeInspectVars(nodeId)
|
||||
}, [getNodeInspectVars])
|
||||
|
||||
const fetchInspectVarValue = useCallback(async (selector: ValueSelector) => {
|
||||
const {
|
||||
appId,
|
||||
setNodeInspectVars,
|
||||
} = workflowStore.getState()
|
||||
const nodeId = selector[0]
|
||||
const isSystemVar = nodeId === 'sys'
|
||||
const isConversationVar = nodeId === 'conversation'
|
||||
if (isSystemVar) {
|
||||
invalidateSysVarValues()
|
||||
return
|
||||
}
|
||||
if (isConversationVar) {
|
||||
invalidateConversationVarValues()
|
||||
return
|
||||
}
|
||||
const vars = await fetchNodeInspectVars(appId, nodeId)
|
||||
setNodeInspectVars(nodeId, vars)
|
||||
}, [workflowStore, invalidateSysVarValues, invalidateConversationVarValues])
|
||||
|
||||
// after last run would call this
|
||||
const appendNodeInspectVars = useCallback((nodeId: string, payload: VarInInspect[], allNodes: Node[]) => {
|
||||
const {
|
||||
nodesWithInspectVars,
|
||||
setNodesWithInspectVars,
|
||||
} = workflowStore.getState()
|
||||
const nodes = produce(nodesWithInspectVars, (draft) => {
|
||||
const nodeInfo = allNodes.find(node => node.id === nodeId)
|
||||
if (nodeInfo) {
|
||||
const index = draft.findIndex(node => node.nodeId === nodeId)
|
||||
if (index === -1) {
|
||||
draft.unshift({
|
||||
nodeId,
|
||||
nodeType: nodeInfo.data.type,
|
||||
title: nodeInfo.data.title,
|
||||
vars: payload,
|
||||
nodePayload: nodeInfo.data,
|
||||
})
|
||||
}
|
||||
else {
|
||||
draft[index].vars = payload
|
||||
// put the node to the topAdd commentMore actions
|
||||
draft.unshift(draft.splice(index, 1)[0])
|
||||
}
|
||||
}
|
||||
})
|
||||
setNodesWithInspectVars(nodes)
|
||||
handleCancelNodeSuccessStatus(nodeId)
|
||||
}, [workflowStore, handleCancelNodeSuccessStatus])
|
||||
|
||||
const hasNodeInspectVar = useCallback((nodeId: string, varId: string) => {
|
||||
const { nodesWithInspectVars } = workflowStore.getState()
|
||||
const targetNode = nodesWithInspectVars.find(item => item.nodeId === nodeId)
|
||||
if(!targetNode || !targetNode.vars)
|
||||
return false
|
||||
return targetNode.vars.some(item => item.id === varId)
|
||||
}, [workflowStore])
|
||||
|
||||
const deleteInspectVar = useCallback(async (nodeId: string, varId: string) => {
|
||||
const { deleteInspectVar } = workflowStore.getState()
|
||||
if(hasNodeInspectVar(nodeId, varId)) {
|
||||
await doDeleteInspectVar(varId)
|
||||
deleteInspectVar(nodeId, varId)
|
||||
}
|
||||
}, [doDeleteInspectVar, workflowStore, hasNodeInspectVar])
|
||||
|
||||
const resetConversationVar = useCallback(async (varId: string) => {
|
||||
await doResetConversationVar(varId)
|
||||
invalidateConversationVarValues()
|
||||
}, [doResetConversationVar, invalidateConversationVarValues])
|
||||
|
||||
const deleteNodeInspectorVars = useCallback(async (nodeId: string) => {
|
||||
const { deleteNodeInspectVars } = workflowStore.getState()
|
||||
if (hasNodeInspectVars(nodeId)) {
|
||||
await doDeleteNodeInspectorVars(nodeId)
|
||||
deleteNodeInspectVars(nodeId)
|
||||
}
|
||||
}, [doDeleteNodeInspectorVars, workflowStore, hasNodeInspectVars])
|
||||
|
||||
const deleteAllInspectorVars = useCallback(async () => {
|
||||
const { deleteAllInspectVars } = workflowStore.getState()
|
||||
await doDeleteAllInspectorVars()
|
||||
await invalidateConversationVarValues()
|
||||
await invalidateSysVarValues()
|
||||
deleteAllInspectVars()
|
||||
handleEdgeCancelRunningStatus()
|
||||
}, [doDeleteAllInspectorVars, invalidateConversationVarValues, invalidateSysVarValues, workflowStore, handleEdgeCancelRunningStatus])
|
||||
|
||||
const editInspectVarValue = useCallback(async (nodeId: string, varId: string, value: any) => {
|
||||
const { setInspectVarValue } = workflowStore.getState()
|
||||
await doEditInspectorVar({
|
||||
varId,
|
||||
value,
|
||||
})
|
||||
setInspectVarValue(nodeId, varId, value)
|
||||
if (nodeId === VarInInspectType.conversation)
|
||||
invalidateConversationVarValues()
|
||||
if (nodeId === VarInInspectType.system)
|
||||
invalidateSysVarValues()
|
||||
}, [doEditInspectorVar, invalidateConversationVarValues, invalidateSysVarValues, workflowStore])
|
||||
|
||||
const renameInspectVarName = useCallback(async (nodeId: string, oldName: string, newName: string) => {
|
||||
const { renameInspectVarName } = workflowStore.getState()
|
||||
const varId = getVarId(nodeId, oldName)
|
||||
if (!varId)
|
||||
return
|
||||
|
||||
const newSelector = [nodeId, newName]
|
||||
await doEditInspectorVar({
|
||||
varId,
|
||||
name: newName,
|
||||
})
|
||||
renameInspectVarName(nodeId, varId, newSelector)
|
||||
}, [doEditInspectorVar, getVarId, workflowStore])
|
||||
|
||||
const isInspectVarEdited = useCallback((nodeId: string, name: string) => {
|
||||
const inspectVar = getInspectVar(nodeId, name)
|
||||
if (!inspectVar)
|
||||
return false
|
||||
|
||||
return inspectVar.edited
|
||||
}, [getInspectVar])
|
||||
|
||||
const resetToLastRunVar = useCallback(async (nodeId: string, varId: string) => {
|
||||
const { resetToLastRunVar } = workflowStore.getState()
|
||||
const isSysVar = nodeId === 'sys'
|
||||
const data = await doResetToLastRunValue(varId)
|
||||
|
||||
if(isSysVar)
|
||||
invalidateSysVarValues()
|
||||
else
|
||||
resetToLastRunVar(nodeId, varId, data.value)
|
||||
}, [doResetToLastRunValue, invalidateSysVarValues, workflowStore])
|
||||
|
||||
return {
|
||||
hasNodeInspectVars,
|
||||
hasSetInspectVar,
|
||||
fetchInspectVarValue,
|
||||
editInspectVarValue,
|
||||
renameInspectVarName,
|
||||
appendNodeInspectVars,
|
||||
deleteInspectVar,
|
||||
deleteNodeInspectorVars,
|
||||
deleteAllInspectorVars,
|
||||
isInspectVarEdited,
|
||||
resetToLastRunVar,
|
||||
invalidateSysVarValues,
|
||||
resetConversationVar,
|
||||
invalidateConversationVarValues,
|
||||
}
|
||||
}
|
||||
@ -20,7 +20,7 @@ import { useRenderI18nObject } from '@/hooks/use-i18n'
|
||||
import type { NodeOutPutVar } from '../../../types'
|
||||
import type { Node } from 'reactflow'
|
||||
import type { PluginMeta } from '@/app/components/plugins/types'
|
||||
import { noop } from 'lodash'
|
||||
import { noop } from 'lodash-es'
|
||||
import { useDocLink } from '@/context/i18n'
|
||||
|
||||
export type Strategy = {
|
||||
|
||||
@ -242,7 +242,7 @@ const FormInputItem: FC<Props> = ({
|
||||
<AppSelector
|
||||
disabled={readOnly}
|
||||
scope={scope || 'all'}
|
||||
value={varInput?.value as any}
|
||||
value={varInput as any}
|
||||
onSelect={handleAppOrModelSelect}
|
||||
/>
|
||||
)}
|
||||
@ -251,7 +251,7 @@ const FormInputItem: FC<Props> = ({
|
||||
popupClassName='!w-[387px]'
|
||||
isAdvancedMode
|
||||
isInWorkflow
|
||||
value={varInput?.value as any}
|
||||
value={varInput}
|
||||
setModel={handleAppOrModelSelect}
|
||||
readonly={readOnly}
|
||||
scope={scope}
|
||||
|
||||
@ -612,6 +612,7 @@ const getIterationItemType = ({
|
||||
}): VarType => {
|
||||
const outputVarNodeId = valueSelector[0]
|
||||
const isSystem = isSystemVar(valueSelector)
|
||||
const isChatVar = isConversationVar(valueSelector)
|
||||
|
||||
const targetVar = isSystem ? beforeNodesOutputVars.find(v => v.isStartNode) : beforeNodesOutputVars.find(v => v.nodeId === outputVarNodeId)
|
||||
|
||||
@ -621,7 +622,7 @@ const getIterationItemType = ({
|
||||
let arrayType: VarType = VarType.string
|
||||
|
||||
let curr: any = targetVar.vars
|
||||
if (isSystem) {
|
||||
if (isSystem || isChatVar) {
|
||||
arrayType = curr.find((v: any) => v.variable === (valueSelector).join('.'))?.type
|
||||
}
|
||||
else {
|
||||
|
||||
@ -89,18 +89,19 @@ const BasePanel: FC<BasePanelProps> = ({
|
||||
const otherPanelWidth = useStore(s => s.otherPanelWidth)
|
||||
const setNodePanelWidth = useStore(s => s.setNodePanelWidth)
|
||||
|
||||
const reservedCanvasWidth = 400 // Reserve the minimum visible width for the canvas
|
||||
|
||||
const maxNodePanelWidth = useMemo(() => {
|
||||
if (!workflowCanvasWidth)
|
||||
return 720
|
||||
if (!otherPanelWidth)
|
||||
return workflowCanvasWidth - 400
|
||||
|
||||
return workflowCanvasWidth - otherPanelWidth - 400
|
||||
const available = workflowCanvasWidth - (otherPanelWidth || 0) - reservedCanvasWidth
|
||||
return Math.max(available, 400)
|
||||
}, [workflowCanvasWidth, otherPanelWidth])
|
||||
|
||||
const updateNodePanelWidth = useCallback((width: number) => {
|
||||
// Ensure the width is within the min and max range
|
||||
const newValue = Math.min(Math.max(width, 400), maxNodePanelWidth)
|
||||
const newValue = Math.max(400, Math.min(width, maxNodePanelWidth))
|
||||
localStorage.setItem('workflow-node-panel-width', `${newValue}`)
|
||||
setNodePanelWidth(newValue)
|
||||
}, [maxNodePanelWidth, setNodePanelWidth])
|
||||
@ -124,8 +125,13 @@ const BasePanel: FC<BasePanelProps> = ({
|
||||
useEffect(() => {
|
||||
if (!workflowCanvasWidth)
|
||||
return
|
||||
if (workflowCanvasWidth - 400 <= nodePanelWidth + otherPanelWidth)
|
||||
debounceUpdate(workflowCanvasWidth - 400 - otherPanelWidth)
|
||||
|
||||
// If the total width of the three exceeds the canvas, shrink the node panel to the available range (at least 400px)
|
||||
const total = nodePanelWidth + otherPanelWidth + reservedCanvasWidth
|
||||
if (total > workflowCanvasWidth) {
|
||||
const target = Math.max(workflowCanvasWidth - otherPanelWidth - reservedCanvasWidth, 400)
|
||||
debounceUpdate(target)
|
||||
}
|
||||
}, [nodePanelWidth, otherPanelWidth, workflowCanvasWidth, updateNodePanelWidth])
|
||||
|
||||
const { handleNodeSelect } = useNodesInteractions()
|
||||
|
||||
@ -6,7 +6,7 @@ import type { EditData } from './edit-card'
|
||||
import { ArrayType, type Field, Type } from '../../../types'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import { findPropertyWithPath } from '../../../utils'
|
||||
import _ from 'lodash'
|
||||
import { noop } from 'lodash-es'
|
||||
|
||||
type ChangeEventParams = {
|
||||
path: string[],
|
||||
@ -21,7 +21,7 @@ type AddEventParams = {
|
||||
|
||||
export const useSchemaNodeOperations = (props: VisualEditorProps) => {
|
||||
const { schema: jsonSchema, onChange: doOnChange } = props
|
||||
const onChange = doOnChange || _.noop
|
||||
const onChange = doOnChange || noop
|
||||
const backupSchema = useVisualEditorStore(state => state.backupSchema)
|
||||
const setBackupSchema = useVisualEditorStore(state => state.setBackupSchema)
|
||||
const isAddingNewField = useVisualEditorStore(state => state.isAddingNewField)
|
||||
|
||||
@ -13,18 +13,22 @@ import { Edit03 } from '@/app/components/base/icons/src/vender/solid/general'
|
||||
import Badge from '@/app/components/base/badge'
|
||||
import ConfigVarModal from '@/app/components/app/configuration/config-var/config-modal'
|
||||
import { noop } from 'lodash-es'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type Props = {
|
||||
className?: string
|
||||
readonly: boolean
|
||||
payload: InputVar
|
||||
onChange?: (item: InputVar, moreInfo?: MoreInfo) => void
|
||||
onRemove?: () => void
|
||||
rightContent?: React.JSX.Element
|
||||
varKeys?: string[]
|
||||
showLegacyBadge?: boolean
|
||||
showLegacyBadge?: boolean,
|
||||
canDrag?: boolean,
|
||||
}
|
||||
|
||||
const VarItem: FC<Props> = ({
|
||||
className,
|
||||
readonly,
|
||||
payload,
|
||||
onChange = noop,
|
||||
@ -32,6 +36,7 @@ const VarItem: FC<Props> = ({
|
||||
rightContent,
|
||||
varKeys = [],
|
||||
showLegacyBadge = false,
|
||||
canDrag,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
@ -47,9 +52,9 @@ const VarItem: FC<Props> = ({
|
||||
hideEditVarModal()
|
||||
}, [onChange, hideEditVarModal])
|
||||
return (
|
||||
<div ref={ref} className='flex h-8 cursor-pointer items-center justify-between rounded-lg border border-components-panel-border-subtle bg-components-panel-on-panel-item-bg px-2.5 shadow-xs hover:shadow-md'>
|
||||
<div ref={ref} className={cn('flex h-8 cursor-pointer items-center justify-between rounded-lg border border-components-panel-border-subtle bg-components-panel-on-panel-item-bg px-2.5 shadow-xs hover:shadow-md', className)}>
|
||||
<div className='flex w-0 grow items-center space-x-1'>
|
||||
<Variable02 className='h-3.5 w-3.5 text-text-accent' />
|
||||
<Variable02 className={cn('h-3.5 w-3.5 text-text-accent', canDrag && 'group-hover:opacity-0')} />
|
||||
<div title={payload.variable} className='max-w-[130px] shrink-0 truncate text-[13px] font-medium text-text-secondary'>{payload.variable}</div>
|
||||
{payload.label && (<><div className='shrink-0 text-xs font-medium text-text-quaternary'>·</div>
|
||||
<div title={payload.label as string} className='max-w-[130px] truncate text-[13px] font-medium text-text-tertiary'>{payload.label as string}</div>
|
||||
|
||||
@ -1,10 +1,15 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import React, { useCallback } from 'react'
|
||||
import React, { useCallback, useMemo } from 'react'
|
||||
import produce from 'immer'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import VarItem from './var-item'
|
||||
import { ChangeType, type InputVar, type MoreInfo } from '@/app/components/workflow/types'
|
||||
import { v4 as uuid4 } from 'uuid'
|
||||
import { ReactSortable } from 'react-sortablejs'
|
||||
import { RiDraggable } from '@remixicon/react'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
type Props = {
|
||||
readonly: boolean
|
||||
list: InputVar[]
|
||||
@ -44,6 +49,16 @@ const VarList: FC<Props> = ({
|
||||
}
|
||||
}, [list, onChange])
|
||||
|
||||
const listWithIds = useMemo(() => list.map((item) => {
|
||||
const id = uuid4()
|
||||
return {
|
||||
id,
|
||||
variable: { ...item },
|
||||
}
|
||||
}), [list])
|
||||
|
||||
const varCount = list.length
|
||||
|
||||
if (list.length === 0) {
|
||||
return (
|
||||
<div className='flex h-[42px] items-center justify-center rounded-md bg-components-panel-bg text-xs font-normal leading-[18px] text-text-tertiary'>
|
||||
@ -53,18 +68,39 @@ const VarList: FC<Props> = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='space-y-1'>
|
||||
{list.map((item, index) => (
|
||||
<VarItem
|
||||
key={index}
|
||||
readonly={readonly}
|
||||
payload={item}
|
||||
onChange={handleVarChange(index)}
|
||||
onRemove={handleVarRemove(index)}
|
||||
varKeys={list.map(item => item.variable)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<ReactSortable
|
||||
className='space-y-1'
|
||||
list={listWithIds}
|
||||
setList={(list) => { onChange(list.map(item => item.variable)) }}
|
||||
handle='.handle'
|
||||
ghostClass='opacity-50'
|
||||
animation={150}
|
||||
>
|
||||
{list.map((item, index) => {
|
||||
const canDrag = (() => {
|
||||
if (readonly)
|
||||
return false
|
||||
return varCount > 1
|
||||
})()
|
||||
return (
|
||||
<div key={index} className='group relative'>
|
||||
<VarItem
|
||||
className={cn(canDrag && 'handle')}
|
||||
readonly={readonly}
|
||||
payload={item}
|
||||
onChange={handleVarChange(index)}
|
||||
onRemove={handleVarRemove(index)}
|
||||
varKeys={list.map(item => item.variable)}
|
||||
canDrag={canDrag}
|
||||
/>
|
||||
{canDrag && <RiDraggable className={cn(
|
||||
'handle absolute left-3 top-2.5 hidden h-3 w-3 cursor-pointer text-text-tertiary',
|
||||
'group-hover:block',
|
||||
)} />}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</ReactSortable>
|
||||
)
|
||||
}
|
||||
export default React.memo(VarList)
|
||||
|
||||
@ -92,7 +92,7 @@ export const useChat = (
|
||||
ret[index] = {
|
||||
...ret[index],
|
||||
content: getIntroduction(config.opening_statement),
|
||||
suggestedQuestions: config.suggested_questions,
|
||||
suggestedQuestions: config.suggested_questions?.map((item: string) => getIntroduction(item)),
|
||||
}
|
||||
}
|
||||
else {
|
||||
@ -101,7 +101,7 @@ export const useChat = (
|
||||
content: getIntroduction(config.opening_statement),
|
||||
isAnswer: true,
|
||||
isOpeningStatement: true,
|
||||
suggestedQuestions: config.suggested_questions,
|
||||
suggestedQuestions: config.suggested_questions?.map((item: string) => getIntroduction(item)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -77,6 +77,30 @@ const Panel: FC<PanelProps> = ({
|
||||
const isRestoring = useStore(s => s.isRestoring)
|
||||
const showWorkflowVersionHistoryPanel = useStore(s => s.showWorkflowVersionHistoryPanel)
|
||||
|
||||
// widths used for adaptive layout
|
||||
const workflowCanvasWidth = useStore(s => s.workflowCanvasWidth)
|
||||
const previewPanelWidth = useStore(s => s.previewPanelWidth)
|
||||
const setPreviewPanelWidth = useStore(s => s.setPreviewPanelWidth)
|
||||
|
||||
// When a node is selected and the NodePanel appears, if the current width
|
||||
// of preview/otherPanel is too large, it may result in the total width of
|
||||
// the two panels exceeding the workflowCanvasWidth, causing the NodePanel
|
||||
// to be pushed out. Here we check and, if necessary, reduce the previewPanelWidth
|
||||
// to "workflowCanvasWidth - 400 (minimum NodePanel width) - 400 (minimum canvas space)",
|
||||
// while still ensuring that previewPanelWidth ≥ 400.
|
||||
|
||||
useEffect(() => {
|
||||
if (!selectedNode || !workflowCanvasWidth)
|
||||
return
|
||||
|
||||
const reservedCanvasWidth = 400 // Reserve the minimum visible width for the canvas
|
||||
const minNodePanelWidth = 400
|
||||
const maxAllowed = Math.max(workflowCanvasWidth - reservedCanvasWidth - minNodePanelWidth, 400)
|
||||
|
||||
if (previewPanelWidth > maxAllowed)
|
||||
setPreviewPanelWidth(maxAllowed)
|
||||
}, [selectedNode, workflowCanvasWidth, previewPanelWidth, setPreviewPanelWidth])
|
||||
|
||||
const setRightPanelWidth = useStore(s => s.setRightPanelWidth)
|
||||
const setOtherPanelWidth = useStore(s => s.setOtherPanelWidth)
|
||||
|
||||
|
||||
@ -32,6 +32,9 @@ const WorkflowPreview = () => {
|
||||
const { handleCancelDebugAndPreviewPanel } = useWorkflowInteractions()
|
||||
const workflowRunningData = useStore(s => s.workflowRunningData)
|
||||
const showInputsPanel = useStore(s => s.showInputsPanel)
|
||||
const workflowCanvasWidth = useStore(s => s.workflowCanvasWidth)
|
||||
const panelWidth = useStore(s => s.previewPanelWidth)
|
||||
const setPreviewPanelWidth = useStore(s => s.setPreviewPanelWidth)
|
||||
const showDebugAndPreviewPanel = useStore(s => s.showDebugAndPreviewPanel)
|
||||
const [currentTab, setCurrentTab] = useState<string>(showInputsPanel ? 'INPUT' : 'TRACING')
|
||||
|
||||
@ -49,7 +52,6 @@ const WorkflowPreview = () => {
|
||||
switchTab('DETAIL')
|
||||
}, [workflowRunningData])
|
||||
|
||||
const [panelWidth, setPanelWidth] = useState(420)
|
||||
const [isResizing, setIsResizing] = useState(false)
|
||||
|
||||
const startResizing = useCallback((e: React.MouseEvent) => {
|
||||
@ -64,10 +66,14 @@ const WorkflowPreview = () => {
|
||||
const resize = useCallback((e: MouseEvent) => {
|
||||
if (isResizing) {
|
||||
const newWidth = window.innerWidth - e.clientX
|
||||
if (newWidth > 420 && newWidth < 1024)
|
||||
setPanelWidth(newWidth)
|
||||
// width constraints: 400 <= width <= maxAllowed (canvas - reserved 400)
|
||||
const reservedCanvasWidth = 400
|
||||
const maxAllowed = workflowCanvasWidth ? (workflowCanvasWidth - reservedCanvasWidth) : 1024
|
||||
|
||||
if (newWidth >= 400 && newWidth <= maxAllowed)
|
||||
setPreviewPanelWidth(newWidth)
|
||||
}
|
||||
}, [isResizing])
|
||||
}, [isResizing, workflowCanvasWidth, setPreviewPanelWidth])
|
||||
|
||||
useEffect(() => {
|
||||
window.addEventListener('mousemove', resize)
|
||||
@ -79,9 +85,9 @@ const WorkflowPreview = () => {
|
||||
}, [resize, stopResizing])
|
||||
|
||||
return (
|
||||
<div className={`
|
||||
relative flex h-full flex-col rounded-l-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-xl
|
||||
`}
|
||||
<div className={
|
||||
'relative flex h-full flex-col rounded-l-2xl border-[0.5px] border-components-panel-border bg-components-panel-bg shadow-xl'
|
||||
}
|
||||
style={{ width: `${panelWidth}px` }}
|
||||
>
|
||||
<div
|
||||
|
||||
@ -11,8 +11,7 @@ import Button from '@/app/components/base/button'
|
||||
import { changePasswordWithToken, verifyForgotPasswordToken } from '@/service/common'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
|
||||
const validPassword = /^(?=.*[a-zA-Z])(?=.*\d).{8,}$/
|
||||
import { validPassword } from '@/config'
|
||||
|
||||
const ChangePasswordForm = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
@ -18,8 +18,7 @@ import { fetchInitValidateStatus, fetchSetupStatus, setup } from '@/service/comm
|
||||
import type { InitValidateStatusResponse, SetupStatusResponse } from '@/models/common'
|
||||
import useDocumentTitle from '@/hooks/use-document-title'
|
||||
import { useDocLink } from '@/context/i18n'
|
||||
|
||||
const validPassword = /^(?=.*[a-zA-Z])(?=.*\d).{8,}$/
|
||||
import { validPassword } from '@/config'
|
||||
|
||||
const accountFormSchema = z.object({
|
||||
email: z
|
||||
|
||||
@ -9,8 +9,7 @@ import Button from '@/app/components/base/button'
|
||||
import { changePasswordWithToken } from '@/service/common'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import Input from '@/app/components/base/input'
|
||||
|
||||
const validPassword = /^(?=.*[a-zA-Z])(?=.*\d).{8,}$/
|
||||
import { validPassword } from '@/config'
|
||||
|
||||
const ChangePasswordForm = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
@ -16,6 +16,7 @@ import I18n from '@/context/i18n'
|
||||
import { activateMember, invitationCheck } from '@/service/common'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import { noop } from 'lodash-es'
|
||||
|
||||
export default function InviteSettingsPage() {
|
||||
const { t } = useTranslation()
|
||||
@ -88,8 +89,7 @@ export default function InviteSettingsPage() {
|
||||
<div className='pb-4 pt-2'>
|
||||
<h2 className='title-4xl-semi-bold'>{t('login.setYourAccount')}</h2>
|
||||
</div>
|
||||
<form action=''>
|
||||
|
||||
<form onSubmit={noop}>
|
||||
<div className='mb-5'>
|
||||
<label htmlFor="name" className="system-md-semibold my-2">
|
||||
{t('login.name')}
|
||||
@ -101,6 +101,13 @@ export default function InviteSettingsPage() {
|
||||
value={name}
|
||||
onChange={e => setName(e.target.value)}
|
||||
placeholder={t('login.namePlaceholder') || ''}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
handleActivate()
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
56
web/config/index.spec.ts
Normal file
56
web/config/index.spec.ts
Normal file
@ -0,0 +1,56 @@
|
||||
import { validPassword } from './index'
|
||||
|
||||
describe('validPassword Tests', () => {
|
||||
const passwordRegex = validPassword
|
||||
|
||||
// Valid passwords
|
||||
test('Valid passwords: contains letter+digit, length ≥8', () => {
|
||||
expect(passwordRegex.test('password1')).toBe(true)
|
||||
expect(passwordRegex.test('PASSWORD1')).toBe(true)
|
||||
expect(passwordRegex.test('12345678a')).toBe(true)
|
||||
expect(passwordRegex.test('a1b2c3d4')).toBe(true)
|
||||
expect(passwordRegex.test('VeryLongPassword123')).toBe(true)
|
||||
expect(passwordRegex.test('short1')).toBe(false)
|
||||
})
|
||||
|
||||
// Missing letter
|
||||
test('Invalid passwords: missing letter', () => {
|
||||
expect(passwordRegex.test('12345678')).toBe(false)
|
||||
expect(passwordRegex.test('!@#$%^&*123')).toBe(false)
|
||||
})
|
||||
|
||||
// Missing digit
|
||||
test('Invalid passwords: missing digit', () => {
|
||||
expect(passwordRegex.test('password')).toBe(false)
|
||||
expect(passwordRegex.test('PASSWORD')).toBe(false)
|
||||
expect(passwordRegex.test('AbCdEfGh')).toBe(false)
|
||||
})
|
||||
|
||||
// Too short
|
||||
test('Invalid passwords: less than 8 characters', () => {
|
||||
expect(passwordRegex.test('pass1')).toBe(false)
|
||||
expect(passwordRegex.test('abc123')).toBe(false)
|
||||
expect(passwordRegex.test('1a')).toBe(false)
|
||||
})
|
||||
|
||||
// Boundary test
|
||||
test('Boundary test: exactly 8 characters', () => {
|
||||
expect(passwordRegex.test('abc12345')).toBe(true)
|
||||
expect(passwordRegex.test('1abcdefg')).toBe(true)
|
||||
})
|
||||
|
||||
// Special characters
|
||||
test('Special characters: non-whitespace special chars allowed', () => {
|
||||
expect(passwordRegex.test('pass@123')).toBe(true)
|
||||
expect(passwordRegex.test('p@$$w0rd')).toBe(true)
|
||||
expect(passwordRegex.test('!1aBcDeF')).toBe(true)
|
||||
})
|
||||
|
||||
// Contains whitespace
|
||||
test('Invalid passwords: contains whitespace', () => {
|
||||
expect(passwordRegex.test('pass word1')).toBe(false)
|
||||
expect(passwordRegex.test('password1 ')).toBe(false)
|
||||
expect(passwordRegex.test(' password1')).toBe(false)
|
||||
expect(passwordRegex.test('pass\tword1')).toBe(false)
|
||||
})
|
||||
})
|
||||
@ -276,3 +276,5 @@ export const ENABLE_WEBSITE_FIRECRAWL = getBooleanConfig(process.env.NEXT_PUBLIC
|
||||
export const ENABLE_WEBSITE_WATERCRAWL = getBooleanConfig(process.env.NEXT_PUBLIC_ENABLE_WEBSITE_WATERCRAWL, DatasetAttr.DATA_PUBLIC_ENABLE_WEBSITE_WATERCRAWL, false)
|
||||
|
||||
export const VALUE_SELECTOR_DELIMITER = '@@@'
|
||||
|
||||
export const validPassword = /^(?=.*[a-zA-Z])(?=.*\d)\S{8,}$/
|
||||
|
||||
@ -298,6 +298,7 @@ const translation = {
|
||||
add: 'Hinzufügen',
|
||||
writeOpener: 'Eröffnung schreiben',
|
||||
placeholder: 'Schreiben Sie hier Ihre Eröffnungsnachricht, Sie können Variablen verwenden, versuchen Sie {{Variable}} zu tippen.',
|
||||
openingQuestionPlaceholder: 'Sie können Variablen verwenden, versuchen Sie {{variable}} einzugeben.',
|
||||
openingQuestion: 'Eröffnungsfragen',
|
||||
noDataPlaceHolder:
|
||||
'Den Dialog mit dem Benutzer zu beginnen, kann helfen, in konversationellen Anwendungen eine engere Verbindung mit ihnen herzustellen.',
|
||||
|
||||
@ -446,6 +446,7 @@ const translation = {
|
||||
writeOpener: 'Edit opener',
|
||||
placeholder: 'Write your opener message here, you can use variables, try type {{variable}}.',
|
||||
openingQuestion: 'Opening Questions',
|
||||
openingQuestionPlaceholder: 'You can use variables, try typing {{variable}}.',
|
||||
noDataPlaceHolder:
|
||||
'Starting the conversation with the user can help AI establish a closer connection with them in conversational applications.',
|
||||
varTip: 'You can use variables, try type {{variable}}',
|
||||
|
||||
@ -329,6 +329,7 @@ const translation = {
|
||||
writeOpener: 'Escribir apertura',
|
||||
placeholder: 'Escribe tu mensaje de apertura aquí, puedes usar variables, intenta escribir {{variable}}.',
|
||||
openingQuestion: 'Preguntas de Apertura',
|
||||
openingQuestionPlaceholder: 'Puede usar variables, intente escribir {{variable}}.',
|
||||
noDataPlaceHolder: 'Iniciar la conversación con el usuario puede ayudar a la IA a establecer una conexión más cercana con ellos en aplicaciones de conversación.',
|
||||
varTip: 'Puedes usar variables, intenta escribir {{variable}}',
|
||||
tooShort: 'Se requieren al menos 20 palabras en la indicación inicial para generar una apertura de conversación.',
|
||||
|
||||
@ -364,6 +364,7 @@ const translation = {
|
||||
writeOpener: 'نوشتن آغازگر',
|
||||
placeholder: 'پیام آغازگر خود را اینجا بنویسید، میتوانید از متغیرها استفاده کنید، سعی کنید {{variable}} را تایپ کنید.',
|
||||
openingQuestion: 'سوالات آغازین',
|
||||
openingQuestionPlaceholder: 'میتوانید از متغیرها استفاده کنید، سعی کنید {{variable}} را تایپ کنید.',
|
||||
noDataPlaceHolder: 'شروع مکالمه با کاربر میتواند به AI کمک کند تا ارتباط نزدیکتری با آنها برقرار کند.',
|
||||
varTip: 'میتوانید از متغیرها استفاده کنید، سعی کنید {{variable}} را تایپ کنید',
|
||||
tooShort: 'حداقل 20 کلمه از پرسش اولیه برای تولید نظرات آغازین مکالمه مورد نیاز است.',
|
||||
|
||||
@ -317,6 +317,7 @@ const translation = {
|
||||
writeOpener: 'Écrire l\'introduction',
|
||||
placeholder: 'Rédigez votre message d\'ouverture ici, vous pouvez utiliser des variables, essayez de taper {{variable}}.',
|
||||
openingQuestion: 'Questions d\'ouverture',
|
||||
openingQuestionPlaceholder: 'Vous pouvez utiliser des variables, essayez de taper {{variable}}.',
|
||||
noDataPlaceHolder:
|
||||
'Commencer la conversation avec l\'utilisateur peut aider l\'IA à établir une connexion plus proche avec eux dans les applications conversationnelles.',
|
||||
varTip: 'Vous pouvez utiliser des variables, essayez de taper {{variable}}',
|
||||
|
||||
@ -362,6 +362,7 @@ const translation = {
|
||||
placeholder:
|
||||
'यहां अपना प्रारंभक संदेश लिखें, आप वेरिएबल्स का उपयोग कर सकते हैं, {{variable}} टाइप करने का प्रयास करें।',
|
||||
openingQuestion: 'प्रारंभिक प्रश्न',
|
||||
openingQuestionPlaceholder: 'आप वेरिएबल्स का उपयोग कर सकते हैं, {{variable}} टाइप करके देखें।',
|
||||
noDataPlaceHolder:
|
||||
'उपयोगकर्ता के साथ संवाद प्रारंभ करने से एआई को संवादात्मक अनुप्रयोगों में उनके साथ निकट संबंध स्थापित करने में मदद मिल सकती है।',
|
||||
varTip:
|
||||
|
||||
@ -365,6 +365,7 @@ const translation = {
|
||||
placeholder:
|
||||
'Scrivi qui il tuo messaggio introduttivo, puoi usare variabili, prova a scrivere {{variable}}.',
|
||||
openingQuestion: 'Domande iniziali',
|
||||
openingQuestionPlaceholder: 'Puoi usare variabili, prova a digitare {{variable}}.',
|
||||
noDataPlaceHolder:
|
||||
'Iniziare la conversazione con l\'utente può aiutare l\'IA a stabilire un legame più stretto con loro nelle applicazioni conversazionali.',
|
||||
varTip: 'Puoi usare variabili, prova a scrivere {{variable}}',
|
||||
|
||||
@ -434,6 +434,7 @@ const translation = {
|
||||
writeOpener: 'オープナーを書く',
|
||||
placeholder: 'ここにオープナーメッセージを書いてください。変数を使用できます。{{variable}} を入力してみてください。',
|
||||
openingQuestion: '開始質問',
|
||||
openingQuestionPlaceholder: '変数を使用できます。{{variable}} と入力してみてください。',
|
||||
noDataPlaceHolder:
|
||||
'ユーザーとの会話を開始すると、会話アプリケーションで彼らとのより密接な関係を築くのに役立ちます。',
|
||||
varTip: '変数を使用できます。{{variable}} を入力してみてください',
|
||||
|
||||
@ -328,6 +328,7 @@ const translation = {
|
||||
writeOpener: '오프너 작성',
|
||||
placeholder: '여기에 오프너 메시지를 작성하세요. 변수를 사용할 수 있습니다. {{variable}}를 입력해보세요.',
|
||||
openingQuestion: '시작 질문',
|
||||
openingQuestionPlaceholder: '변수를 사용할 수 있습니다. {{variable}}을(를) 입력해 보세요.',
|
||||
noDataPlaceHolder: '사용자와의 대화를 시작하면 대화 애플리케이션에서 그들과 더 밀접한 관계를 구축하는 데 도움이 됩니다.',
|
||||
varTip: '변수를 사용할 수 있습니다. {{variable}}를 입력해보세요.',
|
||||
tooShort: '대화 시작에는 최소 20 단어의 초기 프롬프트가 필요합니다.',
|
||||
|
||||
@ -360,6 +360,7 @@ const translation = {
|
||||
placeholder:
|
||||
'Tutaj napisz swoją wiadomość wprowadzającą, możesz użyć zmiennych, spróbuj wpisać {{variable}}.',
|
||||
openingQuestion: 'Pytania otwierające',
|
||||
openingQuestionPlaceholder: 'Możesz używać zmiennych, spróbuj wpisać {{variable}}.',
|
||||
noDataPlaceHolder:
|
||||
'Rozpoczynanie rozmowy z użytkownikiem może pomóc AI nawiązać bliższe połączenie z nim w aplikacjach konwersacyjnych.',
|
||||
varTip: 'Możesz używać zmiennych, spróbuj wpisać {{variable}}',
|
||||
|
||||
@ -334,6 +334,7 @@ const translation = {
|
||||
writeOpener: 'Escrever abertura',
|
||||
placeholder: 'Escreva sua mensagem de abertura aqui, você pode usar variáveis, tente digitar {{variável}}.',
|
||||
openingQuestion: 'Perguntas de Abertura',
|
||||
openingQuestionPlaceholder: 'Você pode usar variáveis, tente digitar {{variable}}.',
|
||||
noDataPlaceHolder:
|
||||
'Iniciar a conversa com o usuário pode ajudar a IA a estabelecer uma conexão mais próxima com eles em aplicativos de conversação.',
|
||||
varTip: 'Você pode usar variáveis, tente digitar {{variável}}',
|
||||
|
||||
@ -370,6 +370,7 @@ const translation = {
|
||||
writeOpener: 'Написать начальное сообщение',
|
||||
placeholder: 'Напишите здесь свое начальное сообщение, вы можете использовать переменные, попробуйте ввести {{variable}}.',
|
||||
openingQuestion: 'Начальные вопросы',
|
||||
openingQuestionPlaceholder: 'Вы можете использовать переменные, попробуйте ввести {{variable}}.',
|
||||
noDataPlaceHolder:
|
||||
'Начало разговора с пользователем может помочь ИИ установить более тесную связь с ним в диалоговых приложениях.',
|
||||
varTip: 'Вы можете использовать переменные, попробуйте ввести {{variable}}',
|
||||
|
||||
@ -368,6 +368,7 @@ const translation = {
|
||||
writeOpener: 'Başlangıç mesajı yaz',
|
||||
placeholder: 'Başlangıç mesajınızı buraya yazın, değişkenler kullanabilirsiniz, örneğin {{variable}} yazmayı deneyin.',
|
||||
openingQuestion: 'Açılış Soruları',
|
||||
openingQuestionPlaceholder: 'Değişkenler kullanabilirsiniz, {{variable}} yazmayı deneyin.',
|
||||
noDataPlaceHolder:
|
||||
'Kullanıcı ile konuşmayı başlatmak, AI\'ın konuşma uygulamalarında onlarla daha yakın bir bağlantı kurmasına yardımcı olabilir.',
|
||||
varTip: 'Değişkenler kullanabilirsiniz, örneğin {{variable}} yazmayı deneyin',
|
||||
|
||||
@ -328,6 +328,7 @@ const translation = {
|
||||
writeOpener: 'Напишіть вступне повідомлення', // Write opener
|
||||
placeholder: 'Напишіть тут своє вступне повідомлення, ви можете використовувати змінні, спробуйте ввести {{variable}}.', // Write your opener message here...
|
||||
openingQuestion: 'Відкриваючі питання', // Opening Questions
|
||||
openingQuestionPlaceholder: 'Ви можете використовувати змінні, спробуйте ввести {{variable}}.',
|
||||
noDataPlaceHolder: 'Початок розмови з користувачем може допомогти ШІ встановити більш тісний зв’язок з ним у розмовних застосунках.', // ... conversational applications.
|
||||
varTip: 'Ви можете використовувати змінні, спробуйте ввести {{variable}}', // You can use variables, try type {{variable}}
|
||||
tooShort: 'Для створення вступних зауважень для розмови потрібно принаймні 20 слів вступного запиту.', // ... are required to generate an opening remarks for the conversation.
|
||||
|
||||
@ -328,6 +328,7 @@ const translation = {
|
||||
writeOpener: 'Viết câu mở đầu',
|
||||
placeholder: 'Viết thông điệp mở đầu của bạn ở đây, bạn có thể sử dụng biến, hãy thử nhập {{biến}}.',
|
||||
openingQuestion: 'Câu hỏi mở đầu',
|
||||
openingQuestionPlaceholder: 'Bạn có thể sử dụng biến, hãy thử nhập {{variable}}.',
|
||||
noDataPlaceHolder: 'Bắt đầu cuộc trò chuyện với người dùng có thể giúp AI thiết lập mối quan hệ gần gũi hơn với họ trong các ứng dụng trò chuyện.',
|
||||
varTip: 'Bạn có thể sử dụng biến, hãy thử nhập {{biến}}',
|
||||
tooShort: 'Cần ít nhất 20 từ trong lời nhắc ban đầu để tạo ra các câu mở đầu cho cuộc trò chuyện.',
|
||||
|
||||
@ -436,6 +436,7 @@ const translation = {
|
||||
writeOpener: '编写开场白',
|
||||
placeholder: '在这里写下你的开场白,你可以使用变量,尝试输入 {{variable}}。',
|
||||
openingQuestion: '开场问题',
|
||||
openingQuestionPlaceholder: '可以使用变量,尝试输入 {{variable}}。',
|
||||
noDataPlaceHolder:
|
||||
'在对话型应用中,让 AI 主动说第一段话可以拉近与用户间的距离。',
|
||||
varTip: '你可以使用变量,试试输入 {{variable}}',
|
||||
|
||||
@ -313,6 +313,7 @@ const translation = {
|
||||
writeOpener: '編寫開場白',
|
||||
placeholder: '在這裡寫下你的開場白,你可以使用變數,嘗試輸入 {{variable}}。',
|
||||
openingQuestion: '開場問題',
|
||||
openingQuestionPlaceholder: '可以使用變量,嘗試輸入 {{variable}}。',
|
||||
noDataPlaceHolder:
|
||||
'在對話型應用中,讓 AI 主動說第一段話可以拉近與使用者間的距離。',
|
||||
varTip: '你可以使用變數,試試輸入 {{variable}}',
|
||||
|
||||
Reference in New Issue
Block a user