mirror of
https://github.com/langgenius/dify.git
synced 2026-05-06 02:18:08 +08:00
Merge remote-tracking branch 'origin/main' into feat/collaboration
This commit is contained in:
@ -100,7 +100,10 @@ export default function MailAndPasswordAuth({ isEmailSetup }: MailAndPasswordAut
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
catch (e: any) {
|
||||
if (e.code === 'authentication_failed')
|
||||
Toast.notify({ type: 'error', message: e.message })
|
||||
}
|
||||
finally {
|
||||
setIsLoading(false)
|
||||
}
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { RiCloseLine } from '@remixicon/react'
|
||||
import { RiCloseLine, RiPlayLargeLine } from '@remixicon/react'
|
||||
import Run from '@/app/components/workflow/run'
|
||||
import { useStore } from '@/app/components/app/store'
|
||||
import TooltipPlus from '@/app/components/base/tooltip'
|
||||
import { useRouter } from 'next/navigation'
|
||||
|
||||
type ILogDetail = {
|
||||
runID: string
|
||||
@ -13,13 +15,34 @@ type ILogDetail = {
|
||||
const DetailPanel: FC<ILogDetail> = ({ runID, onClose }) => {
|
||||
const { t } = useTranslation()
|
||||
const appDetail = useStore(state => state.appDetail)
|
||||
const router = useRouter()
|
||||
|
||||
const handleReplay = () => {
|
||||
if (!appDetail?.id) return
|
||||
router.push(`/app/${appDetail.id}/workflow?replayRunId=${runID}`)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='relative flex grow flex-col pt-3'>
|
||||
<span className='absolute right-3 top-4 z-20 cursor-pointer p-1' onClick={onClose}>
|
||||
<RiCloseLine className='h-4 w-4 text-text-tertiary' />
|
||||
</span>
|
||||
<h1 className='system-xl-semibold shrink-0 px-4 py-1 text-text-primary'>{t('appLog.runDetail.workflowTitle')}</h1>
|
||||
<div className='flex items-center bg-components-panel-bg'>
|
||||
<h1 className='system-xl-semibold shrink-0 px-4 py-1 text-text-primary'>{t('appLog.runDetail.workflowTitle')}</h1>
|
||||
<TooltipPlus
|
||||
popupContent={t('appLog.runDetail.testWithParams')}
|
||||
popupClassName='rounded-xl'
|
||||
>
|
||||
<button
|
||||
type='button'
|
||||
className='mr-1 flex h-6 w-6 items-center justify-center rounded-md hover:bg-state-base-hover'
|
||||
aria-label={t('appLog.runDetail.testWithParams')}
|
||||
onClick={handleReplay}
|
||||
>
|
||||
<RiPlayLargeLine className='h-4 w-4 text-text-tertiary' />
|
||||
</button>
|
||||
</TooltipPlus>
|
||||
</div>
|
||||
<Run
|
||||
runDetailUrl={runID ? `/apps/${appDetail?.id}/workflow-runs/${runID}` : ''}
|
||||
tracingListUrl={runID ? `/apps/${appDetail?.id}/workflow-runs/${runID}/node-executions` : ''}
|
||||
|
||||
@ -14,16 +14,6 @@ import Divider from '@/app/components/base/divider'
|
||||
import { searchEmoji } from '@/utils/emoji'
|
||||
import cn from '@/utils/classnames'
|
||||
|
||||
declare global {
|
||||
// eslint-disable-next-line ts/no-namespace
|
||||
namespace JSX {
|
||||
// eslint-disable-next-line ts/consistent-type-definitions
|
||||
interface IntrinsicElements {
|
||||
'em-emoji': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init({ data })
|
||||
|
||||
const backgroundColors = [
|
||||
|
||||
@ -32,7 +32,7 @@ const TopKItem: FC<Props> = ({
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const handleParamChange = (key: string, value: number) => {
|
||||
let notOutRangeValue = Number.parseFloat(value.toFixed(2))
|
||||
let notOutRangeValue = Number.parseInt(value.toFixed(0))
|
||||
notOutRangeValue = Math.max(VALUE_LIMIT.min, notOutRangeValue)
|
||||
notOutRangeValue = Math.min(VALUE_LIMIT.max, notOutRangeValue)
|
||||
onChange(key, notOutRangeValue)
|
||||
|
||||
@ -25,8 +25,8 @@ export type TextareaProps = {
|
||||
destructive?: boolean
|
||||
styleCss?: CSSProperties
|
||||
ref?: React.Ref<HTMLTextAreaElement>
|
||||
onFocus?: () => void
|
||||
onBlur?: () => void
|
||||
onFocus?: React.FocusEventHandler<HTMLTextAreaElement>
|
||||
onBlur?: React.FocusEventHandler<HTMLTextAreaElement>
|
||||
} & React.TextareaHTMLAttributes<HTMLTextAreaElement> & VariantProps<typeof textareaVariants>
|
||||
|
||||
const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
'use client'
|
||||
|
||||
import {
|
||||
useEffect,
|
||||
useMemo,
|
||||
} from 'react'
|
||||
import {
|
||||
@ -23,9 +24,14 @@ import {
|
||||
WorkflowContextProvider,
|
||||
} from '@/app/components/workflow/context'
|
||||
import type { InjectWorkflowStoreSliceFn } from '@/app/components/workflow/store'
|
||||
import { useWorkflowStore } from '@/app/components/workflow/store'
|
||||
import { createWorkflowSlice } from './store/workflow/workflow-slice'
|
||||
import WorkflowAppMain from './components/workflow-main'
|
||||
import { collaborationManager } from '@/app/components/workflow/collaboration/core/collaboration-manager'
|
||||
import { useSearchParams } from 'next/navigation'
|
||||
|
||||
import { fetchRunDetail } from '@/service/log'
|
||||
import { useGetRunAndTraceUrl } from './hooks/use-get-run-and-trace-url'
|
||||
|
||||
const WorkflowAppWithAdditionalContext = () => {
|
||||
const {
|
||||
@ -53,6 +59,71 @@ const WorkflowAppWithAdditionalContext = () => {
|
||||
return []
|
||||
}, [data])
|
||||
|
||||
const searchParams = useSearchParams()
|
||||
const workflowStore = useWorkflowStore()
|
||||
const { getWorkflowRunAndTraceUrl } = useGetRunAndTraceUrl()
|
||||
const replayRunId = searchParams.get('replayRunId')
|
||||
|
||||
useEffect(() => {
|
||||
if (!replayRunId)
|
||||
return
|
||||
const { runUrl } = getWorkflowRunAndTraceUrl(replayRunId)
|
||||
if (!runUrl)
|
||||
return
|
||||
fetchRunDetail(runUrl).then((res) => {
|
||||
const { setInputs, setShowInputsPanel, setShowDebugAndPreviewPanel } = workflowStore.getState()
|
||||
const rawInputs = res.inputs
|
||||
let parsedInputs: Record<string, unknown> | null = null
|
||||
|
||||
if (typeof rawInputs === 'string') {
|
||||
try {
|
||||
const maybeParsed = JSON.parse(rawInputs) as unknown
|
||||
if (maybeParsed && typeof maybeParsed === 'object' && !Array.isArray(maybeParsed))
|
||||
parsedInputs = maybeParsed as Record<string, unknown>
|
||||
}
|
||||
catch (error) {
|
||||
console.error('Failed to parse workflow run inputs', error)
|
||||
}
|
||||
}
|
||||
else if (rawInputs && typeof rawInputs === 'object' && !Array.isArray(rawInputs)) {
|
||||
parsedInputs = rawInputs as Record<string, unknown>
|
||||
}
|
||||
|
||||
if (!parsedInputs)
|
||||
return
|
||||
|
||||
const userInputs: Record<string, string> = {}
|
||||
Object.entries(parsedInputs).forEach(([key, value]) => {
|
||||
if (key.startsWith('sys.'))
|
||||
return
|
||||
|
||||
if (value == null) {
|
||||
userInputs[key] = ''
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
||||
userInputs[key] = value
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
userInputs[key] = JSON.stringify(value)
|
||||
}
|
||||
catch {
|
||||
userInputs[key] = String(value)
|
||||
}
|
||||
})
|
||||
|
||||
if (!Object.keys(userInputs).length)
|
||||
return
|
||||
|
||||
setInputs(userInputs)
|
||||
setShowInputsPanel(true)
|
||||
setShowDebugAndPreviewPanel(true)
|
||||
})
|
||||
}, [replayRunId, workflowStore, getWorkflowRunAndTraceUrl])
|
||||
|
||||
if (!data || isLoading || isLoadingCurrentWorkspace || !currentWorkspace.id) {
|
||||
return (
|
||||
<div className='relative flex h-full w-full items-center justify-center'>
|
||||
|
||||
@ -234,6 +234,9 @@ const ConditionItem = ({
|
||||
draft.varType = resolvedVarType
|
||||
draft.value = resolvedVarType === VarType.boolean ? false : ''
|
||||
draft.comparison_operator = getOperators(resolvedVarType)[0]
|
||||
delete draft.key
|
||||
delete draft.sub_variable_condition
|
||||
delete draft.numberVarType
|
||||
setTimeout(() => setControlPromptEditorRerenderKey(Date.now()))
|
||||
})
|
||||
doUpdateCondition(newCondition)
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { memo } from 'react'
|
||||
import { memo, useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import Input from '@/app/components/base/input'
|
||||
import Switch from '@/app/components/base/switch'
|
||||
import { InputNumber } from '@/app/components/base/input-number'
|
||||
|
||||
export type TopKAndScoreThresholdProps = {
|
||||
topK: number
|
||||
@ -14,6 +14,24 @@ export type TopKAndScoreThresholdProps = {
|
||||
readonly?: boolean
|
||||
hiddenScoreThreshold?: boolean
|
||||
}
|
||||
|
||||
const maxTopK = (() => {
|
||||
const configValue = Number.parseInt(globalThis.document?.body?.getAttribute('data-public-top-k-max-value') || '', 10)
|
||||
if (configValue && !isNaN(configValue))
|
||||
return configValue
|
||||
return 10
|
||||
})()
|
||||
const TOP_K_VALUE_LIMIT = {
|
||||
amount: 1,
|
||||
min: 1,
|
||||
max: maxTopK,
|
||||
}
|
||||
const SCORE_THRESHOLD_VALUE_LIMIT = {
|
||||
step: 0.01,
|
||||
min: 0,
|
||||
max: 1,
|
||||
}
|
||||
|
||||
const TopKAndScoreThreshold = ({
|
||||
topK,
|
||||
onTopKChange,
|
||||
@ -25,18 +43,18 @@ const TopKAndScoreThreshold = ({
|
||||
hiddenScoreThreshold,
|
||||
}: TopKAndScoreThresholdProps) => {
|
||||
const { t } = useTranslation()
|
||||
const handleTopKChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = Number(e.target.value)
|
||||
if (Number.isNaN(value))
|
||||
return
|
||||
onTopKChange?.(value)
|
||||
}
|
||||
const handleTopKChange = useCallback((value: number) => {
|
||||
let notOutRangeValue = Number.parseInt(value.toFixed(0))
|
||||
notOutRangeValue = Math.max(TOP_K_VALUE_LIMIT.min, notOutRangeValue)
|
||||
notOutRangeValue = Math.min(TOP_K_VALUE_LIMIT.max, notOutRangeValue)
|
||||
onTopKChange?.(notOutRangeValue)
|
||||
}, [onTopKChange])
|
||||
|
||||
const handleScoreThresholdChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const value = Number(e.target.value)
|
||||
if (Number.isNaN(value))
|
||||
return
|
||||
onScoreThresholdChange?.(value)
|
||||
const handleScoreThresholdChange = (value: number) => {
|
||||
let notOutRangeValue = Number.parseFloat(value.toFixed(2))
|
||||
notOutRangeValue = Math.max(SCORE_THRESHOLD_VALUE_LIMIT.min, notOutRangeValue)
|
||||
notOutRangeValue = Math.min(SCORE_THRESHOLD_VALUE_LIMIT.max, notOutRangeValue)
|
||||
onScoreThresholdChange?.(notOutRangeValue)
|
||||
}
|
||||
|
||||
return (
|
||||
@ -49,11 +67,13 @@ const TopKAndScoreThreshold = ({
|
||||
popupContent={t('appDebug.datasetConfig.top_kTip')}
|
||||
/>
|
||||
</div>
|
||||
<Input
|
||||
<InputNumber
|
||||
disabled={readonly}
|
||||
type='number'
|
||||
{...TOP_K_VALUE_LIMIT}
|
||||
size='regular'
|
||||
value={topK}
|
||||
onChange={handleTopKChange}
|
||||
disabled={readonly}
|
||||
/>
|
||||
</div>
|
||||
{
|
||||
@ -74,11 +94,13 @@ const TopKAndScoreThreshold = ({
|
||||
popupContent={t('appDebug.datasetConfig.score_thresholdTip')}
|
||||
/>
|
||||
</div>
|
||||
<Input
|
||||
<InputNumber
|
||||
disabled={readonly || !isScoreThresholdEnabled}
|
||||
type='number'
|
||||
{...SCORE_THRESHOLD_VALUE_LIMIT}
|
||||
size='regular'
|
||||
value={scoreThreshold}
|
||||
onChange={handleScoreThresholdChange}
|
||||
disabled={readonly || !isScoreThresholdEnabled}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -18,7 +18,7 @@ type ConditionNumberProps = {
|
||||
nodesOutputVars: NodeOutPutVar[]
|
||||
availableNodes: Node[]
|
||||
isCommonVariable?: boolean
|
||||
commonVariables: { name: string, type: string }[]
|
||||
commonVariables: { name: string; type: string; value: string }[]
|
||||
} & ConditionValueMethodProps
|
||||
const ConditionNumber = ({
|
||||
value,
|
||||
|
||||
@ -18,7 +18,7 @@ type ConditionStringProps = {
|
||||
nodesOutputVars: NodeOutPutVar[]
|
||||
availableNodes: Node[]
|
||||
isCommonVariable?: boolean
|
||||
commonVariables: { name: string, type: string }[]
|
||||
commonVariables: { name: string; type: string; value: string }[]
|
||||
} & ConditionValueMethodProps
|
||||
const ConditionString = ({
|
||||
value,
|
||||
|
||||
@ -128,6 +128,6 @@ export type MetadataShape = {
|
||||
availableNumberVars?: NodeOutPutVar[]
|
||||
availableNumberNodesWithParent?: Node[]
|
||||
isCommonVariable?: boolean
|
||||
availableCommonStringVars?: { name: string; type: string; }[]
|
||||
availableCommonNumberVars?: { name: string; type: string; }[]
|
||||
availableCommonStringVars?: { name: string; type: string; value: string }[]
|
||||
availableCommonNumberVars?: { name: string; type: string; value: string }[]
|
||||
}
|
||||
|
||||
@ -24,7 +24,7 @@ const JsonImporter: FC<JsonImporterProps> = ({
|
||||
const [open, setOpen] = useState(false)
|
||||
const [json, setJson] = useState('')
|
||||
const [parseError, setParseError] = useState<any>(null)
|
||||
const importBtnRef = useRef<HTMLButtonElement>(null)
|
||||
const importBtnRef = useRef<HTMLElement>(null)
|
||||
const advancedEditing = useVisualEditorStore(state => state.advancedEditing)
|
||||
const isAddingNewField = useVisualEditorStore(state => state.isAddingNewField)
|
||||
const { emit } = useMittContext()
|
||||
|
||||
@ -18,7 +18,7 @@ type VisualEditorProviderProps = {
|
||||
export const VisualEditorContext = createContext<VisualEditorContextType>(null)
|
||||
|
||||
export const VisualEditorContextProvider = ({ children }: VisualEditorProviderProps) => {
|
||||
const storeRef = useRef<VisualEditorStore>()
|
||||
const storeRef = useRef<VisualEditorStore | null>(null)
|
||||
|
||||
if (!storeRef.current)
|
||||
storeRef.current = createVisualEditorStore()
|
||||
|
||||
@ -23,7 +23,7 @@ const useConfig = (id: string, payload: LLMNodeType) => {
|
||||
const { nodesReadOnly: readOnly } = useNodesReadOnly()
|
||||
const isChatMode = useIsChatMode()
|
||||
|
||||
const defaultConfig = useStore(s => s.nodesDefaultConfigs)[payload.type]
|
||||
const defaultConfig = useStore(s => s.nodesDefaultConfigs)?.[payload.type]
|
||||
const [defaultRolePrefix, setDefaultRolePrefix] = useState<{ user: string; assistant: string }>({ user: '', assistant: '' })
|
||||
const { inputs, setInputs: doSetInputs } = useNodeCrud<LLMNodeType>(id, payload)
|
||||
const inputRef = useRef(inputs)
|
||||
|
||||
@ -10,7 +10,7 @@ export const checkNodeValid = (_payload: LLMNodeType) => {
|
||||
|
||||
export const getFieldType = (field: Field) => {
|
||||
const { type, items } = field
|
||||
if(field.schemaType === 'file') return 'file'
|
||||
if(field.schemaType === 'file') return Type.file
|
||||
if (type !== Type.array || !items)
|
||||
return type
|
||||
|
||||
|
||||
@ -196,6 +196,9 @@ const ConditionItem = ({
|
||||
draft.varType = varItem.type
|
||||
draft.value = ''
|
||||
draft.comparison_operator = getOperators(varItem.type)[0]
|
||||
delete draft.key
|
||||
delete draft.sub_variable_condition
|
||||
delete draft.numberVarType
|
||||
})
|
||||
doUpdateCondition(newCondition)
|
||||
setOpen(false)
|
||||
|
||||
@ -9,7 +9,10 @@ import BlockSelector from '../../../../block-selector'
|
||||
import type { Param, ParamType } from '../../types'
|
||||
import cn from '@/utils/classnames'
|
||||
import { useStore } from '@/app/components/workflow/store'
|
||||
import type { ToolDefaultValue } from '@/app/components/workflow/block-selector/types'
|
||||
import type {
|
||||
DataSourceDefaultValue,
|
||||
ToolDefaultValue,
|
||||
} from '@/app/components/workflow/block-selector/types'
|
||||
import type { ToolParameter } from '@/app/components/tools/types'
|
||||
import { CollectionType } from '@/app/components/tools/types'
|
||||
import type { BlockEnum } from '@/app/components/workflow/types'
|
||||
@ -43,8 +46,11 @@ const ImportFromTool: FC<Props> = ({
|
||||
const customTools = useStore(s => s.customTools)
|
||||
const workflowTools = useStore(s => s.workflowTools)
|
||||
|
||||
const handleSelectTool = useCallback((_type: BlockEnum, toolInfo?: ToolDefaultValue) => {
|
||||
const { provider_id, provider_type, tool_name } = toolInfo!
|
||||
const handleSelectTool = useCallback((_type: BlockEnum, toolInfo?: ToolDefaultValue | DataSourceDefaultValue) => {
|
||||
if (!toolInfo || 'datasource_name' in toolInfo)
|
||||
return
|
||||
|
||||
const { provider_id, provider_type, tool_name } = toolInfo
|
||||
const currentTools = (() => {
|
||||
switch (provider_type) {
|
||||
case CollectionType.builtIn:
|
||||
|
||||
@ -27,7 +27,7 @@ const useConfig = (id: string, payload: ParameterExtractorNodeType) => {
|
||||
const { handleOutVarRenameChange } = useWorkflow()
|
||||
const isChatMode = useIsChatMode()
|
||||
|
||||
const defaultConfig = useStore(s => s.nodesDefaultConfigs)[payload.type]
|
||||
const defaultConfig = useStore(s => s.nodesDefaultConfigs)?.[payload.type]
|
||||
|
||||
const [defaultRolePrefix, setDefaultRolePrefix] = useState<{ user: string; assistant: string }>({ user: '', assistant: '' })
|
||||
const { inputs, setInputs: doSetInputs } = useNodeCrud<ParameterExtractorNodeType>(id, payload)
|
||||
|
||||
@ -20,7 +20,7 @@ const useConfig = (id: string, payload: QuestionClassifierNodeType) => {
|
||||
const updateNodeInternals = useUpdateNodeInternals()
|
||||
const { nodesReadOnly: readOnly } = useNodesReadOnly()
|
||||
const isChatMode = useIsChatMode()
|
||||
const defaultConfig = useStore(s => s.nodesDefaultConfigs)[payload.type]
|
||||
const defaultConfig = useStore(s => s.nodesDefaultConfigs)?.[payload.type]
|
||||
const { getBeforeNodesInSameBranch } = useWorkflow()
|
||||
const startNode = getBeforeNodesInSameBranch(id).find(node => node.data.type === BlockEnum.Start)
|
||||
const startNodeId = startNode?.id
|
||||
|
||||
@ -13,7 +13,7 @@ import useAvailableVarList from '@/app/components/workflow/nodes/_base/hooks/use
|
||||
|
||||
const useConfig = (id: string, payload: TemplateTransformNodeType) => {
|
||||
const { nodesReadOnly: readOnly } = useNodesReadOnly()
|
||||
const defaultConfig = useStore(s => s.nodesDefaultConfigs)[payload.type]
|
||||
const defaultConfig = useStore(s => s.nodesDefaultConfigs)?.[payload.type]
|
||||
|
||||
const { inputs, setInputs: doSetInputs } = useNodeCrud<TemplateTransformNodeType>(id, payload)
|
||||
const inputsRef = useRef(inputs)
|
||||
|
||||
@ -9,7 +9,7 @@ import Button from '@/app/components/base/button'
|
||||
import type { AgentLogItemWithChildren } from '@/types/workflow'
|
||||
|
||||
type AgentLogNavMoreProps = {
|
||||
options: { id: string; label: string }[]
|
||||
options: AgentLogItemWithChildren[]
|
||||
onShowAgentOrToolLog: (detail?: AgentLogItemWithChildren) => void
|
||||
}
|
||||
const AgentLogNavMore = ({
|
||||
@ -41,10 +41,10 @@ const AgentLogNavMore = ({
|
||||
{
|
||||
options.map(option => (
|
||||
<div
|
||||
key={option.id}
|
||||
key={option.message_id}
|
||||
className='system-md-regular flex h-8 cursor-pointer items-center rounded-lg px-2 text-text-secondary hover:bg-state-base-hover'
|
||||
onClick={() => {
|
||||
onShowAgentOrToolLog(option as AgentLogItemWithChildren)
|
||||
onShowAgentOrToolLog(option)
|
||||
setOpen(false)
|
||||
}}
|
||||
>
|
||||
|
||||
@ -4,8 +4,8 @@ import type {
|
||||
} from '@/app/components/workflow/types'
|
||||
|
||||
export type FormSliceShape = {
|
||||
inputs: Record<string, string>
|
||||
setInputs: (inputs: Record<string, string>) => void
|
||||
inputs: Record<string, string | number | boolean>
|
||||
setInputs: (inputs: Record<string, string | number | boolean>) => void
|
||||
files: RunFile[]
|
||||
setFiles: (files: RunFile[]) => void
|
||||
}
|
||||
|
||||
@ -23,8 +23,10 @@ import {
|
||||
} from '../node-handle'
|
||||
import ErrorHandleOnNode from '../error-handle-on-node'
|
||||
|
||||
type NodeChildElement = ReactElement<Partial<NodeProps>>
|
||||
|
||||
type NodeCardProps = NodeProps & {
|
||||
children?: ReactElement
|
||||
children?: NodeChildElement
|
||||
}
|
||||
|
||||
const BaseCard = ({
|
||||
|
||||
@ -242,7 +242,7 @@ const DebugConfigurationContext = createContext<IDebugConfiguration>({
|
||||
},
|
||||
datasetConfigsRef: {
|
||||
current: null,
|
||||
},
|
||||
} as unknown as RefObject<DatasetConfigs>,
|
||||
setDatasetConfigs: noop,
|
||||
hasSetContextVar: false,
|
||||
isShowVisionConfig: false,
|
||||
|
||||
5
web/global.d.ts
vendored
5
web/global.d.ts
vendored
@ -1,3 +1,6 @@
|
||||
import './types/i18n'
|
||||
import './types/jsx'
|
||||
|
||||
declare module 'lamejs';
|
||||
declare module 'lamejs/src/js/MPEGMode';
|
||||
declare module 'lamejs/src/js/Lame';
|
||||
@ -9,4 +12,4 @@ declare module '*.mdx' {
|
||||
export default MDXComponent
|
||||
}
|
||||
|
||||
import './types/i18n'
|
||||
export {}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
'use client'
|
||||
import { useEffect } from 'react'
|
||||
import { validateRedirectUrl } from '@/utils/urlValidation'
|
||||
|
||||
export const useOAuthCallback = () => {
|
||||
useEffect(() => {
|
||||
@ -18,6 +19,7 @@ export const openOAuthPopup = (url: string, callback: () => void) => {
|
||||
const left = window.screenX + (window.outerWidth - width) / 2
|
||||
const top = window.screenY + (window.outerHeight - height) / 2
|
||||
|
||||
validateRedirectUrl(url)
|
||||
const popup = window.open(
|
||||
url,
|
||||
'OAuth',
|
||||
|
||||
@ -83,6 +83,7 @@ const translation = {
|
||||
workflowTitle: 'Protokolldetail',
|
||||
fileListLabel: 'Details zur Datei',
|
||||
fileListDetail: 'Detail',
|
||||
testWithParams: 'Test mit Parametern',
|
||||
},
|
||||
promptLog: 'Prompt-Protokoll',
|
||||
agentLog: 'Agentenprotokoll',
|
||||
|
||||
@ -83,6 +83,7 @@ const translation = {
|
||||
workflowTitle: 'Log Detail',
|
||||
fileListLabel: 'File Details',
|
||||
fileListDetail: 'Detail',
|
||||
testWithParams: 'Test With Params',
|
||||
},
|
||||
promptLog: 'Prompt Log',
|
||||
agentLog: 'Agent Log',
|
||||
|
||||
@ -82,6 +82,7 @@ const translation = {
|
||||
workflowTitle: 'Detalle del Registro',
|
||||
fileListLabel: 'Detalles del archivo',
|
||||
fileListDetail: 'Detalle',
|
||||
testWithParams: 'Prueba con parámetros',
|
||||
},
|
||||
promptLog: 'Registro de Indicación',
|
||||
agentLog: 'Registro de Agente',
|
||||
|
||||
@ -82,6 +82,7 @@ const translation = {
|
||||
workflowTitle: 'جزئیات لاگ',
|
||||
fileListLabel: 'جزئیات فایل',
|
||||
fileListDetail: 'جزئیات',
|
||||
testWithParams: 'تست با پارامترها',
|
||||
},
|
||||
promptLog: 'لاگ درخواست',
|
||||
agentLog: 'لاگ عامل',
|
||||
|
||||
@ -82,6 +82,7 @@ const translation = {
|
||||
workflowTitle: 'Détail du journal',
|
||||
fileListDetail: 'Détail',
|
||||
fileListLabel: 'Détails du fichier',
|
||||
testWithParams: 'Test avec paramètres',
|
||||
},
|
||||
promptLog: 'Journal de consigne',
|
||||
agentLog: 'Journal des agents',
|
||||
|
||||
@ -84,6 +84,7 @@ const translation = {
|
||||
workflowTitle: 'लॉग विवरण',
|
||||
fileListDetail: 'विस्तार',
|
||||
fileListLabel: 'फ़ाइल विवरण',
|
||||
testWithParams: 'पैरामीटर्स के साथ परीक्षण',
|
||||
},
|
||||
promptLog: 'प्रॉम्प्ट लॉग',
|
||||
agentLog: 'एजेंट लॉग',
|
||||
|
||||
@ -74,6 +74,7 @@ const translation = {
|
||||
workflowTitle: 'Log Detail',
|
||||
title: 'Log Percakapan',
|
||||
fileListLabel: 'Rincian File',
|
||||
testWithParams: 'Uji Dengan Param',
|
||||
},
|
||||
agentLogDetail: {
|
||||
iterations: 'Iterasi',
|
||||
|
||||
@ -86,6 +86,7 @@ const translation = {
|
||||
workflowTitle: 'Dettagli Registro',
|
||||
fileListDetail: 'Dettaglio',
|
||||
fileListLabel: 'Dettagli del file',
|
||||
testWithParams: 'Test con parametri',
|
||||
},
|
||||
promptLog: 'Registro Prompt',
|
||||
agentLog: 'Registro Agente',
|
||||
|
||||
@ -83,6 +83,7 @@ const translation = {
|
||||
workflowTitle: 'ログの詳細',
|
||||
fileListLabel: 'ファイルの詳細',
|
||||
fileListDetail: '詳細',
|
||||
testWithParams: 'パラメータ付きテスト',
|
||||
},
|
||||
promptLog: 'プロンプトログ',
|
||||
agentLog: 'エージェントログ',
|
||||
|
||||
@ -83,6 +83,7 @@ const translation = {
|
||||
workflowTitle: '로그 세부 정보',
|
||||
fileListDetail: '세부',
|
||||
fileListLabel: '파일 세부 정보',
|
||||
testWithParams: '매개변수로 테스트',
|
||||
},
|
||||
promptLog: '프롬프트 로그',
|
||||
agentLog: '에이전트 로그',
|
||||
|
||||
@ -86,6 +86,7 @@ const translation = {
|
||||
workflowTitle: 'Szczegół dziennika',
|
||||
fileListDetail: 'Detal',
|
||||
fileListLabel: 'Szczegóły pliku',
|
||||
testWithParams: 'Test z parametrami',
|
||||
},
|
||||
promptLog: 'Dziennik monitów',
|
||||
agentLog: 'Dziennik agenta',
|
||||
|
||||
@ -82,6 +82,7 @@ const translation = {
|
||||
workflowTitle: 'Detalhes do Registro',
|
||||
fileListLabel: 'Detalhes do arquivo',
|
||||
fileListDetail: 'Detalhe',
|
||||
testWithParams: 'Teste com parâmetros',
|
||||
},
|
||||
promptLog: 'Registro de Prompt',
|
||||
agentLog: 'Registro do agente',
|
||||
|
||||
@ -82,6 +82,7 @@ const translation = {
|
||||
workflowTitle: 'Detalii jurnal',
|
||||
fileListDetail: 'Amănunt',
|
||||
fileListLabel: 'Detalii fișier',
|
||||
testWithParams: 'Test cu parametri',
|
||||
},
|
||||
promptLog: 'Jurnal prompt',
|
||||
agentLog: 'Jurnal agent',
|
||||
|
||||
@ -82,6 +82,7 @@ const translation = {
|
||||
workflowTitle: 'Подробная информация о журнале',
|
||||
fileListLabel: 'Сведения о файле',
|
||||
fileListDetail: 'Подробность',
|
||||
testWithParams: 'Тест с параметрами',
|
||||
},
|
||||
promptLog: 'Журнал подсказок',
|
||||
agentLog: 'Журнал агента',
|
||||
|
||||
@ -82,6 +82,7 @@ const translation = {
|
||||
workflowTitle: 'Podrobnosti dnevnika',
|
||||
fileListDetail: 'Podrobnosti',
|
||||
fileListLabel: 'Podrobnosti o datoteki',
|
||||
testWithParams: 'Preizkus s parametri',
|
||||
},
|
||||
promptLog: 'Dnevnik PROMPT-ov',
|
||||
agentLog: 'Dnevnik pomočnika',
|
||||
|
||||
@ -82,6 +82,7 @@ const translation = {
|
||||
workflowTitle: 'รายละเอียดบันทึก',
|
||||
fileListDetail: 'รายละเอียด',
|
||||
fileListLabel: 'รายละเอียดไฟล์',
|
||||
testWithParams: 'ทดสอบด้วยพารามิเตอร์',
|
||||
},
|
||||
promptLog: 'บันทึกพร้อมท์',
|
||||
agentLog: 'บันทึกตัวแทน',
|
||||
|
||||
@ -82,6 +82,7 @@ const translation = {
|
||||
workflowTitle: 'Günlük Detayı',
|
||||
fileListDetail: 'Ayrıntı',
|
||||
fileListLabel: 'Dosya Detayları',
|
||||
testWithParams: 'Parametrelerle Test',
|
||||
},
|
||||
promptLog: 'Prompt Günlüğü',
|
||||
agentLog: 'Agent Günlüğü',
|
||||
|
||||
@ -82,6 +82,7 @@ const translation = {
|
||||
workflowTitle: 'Деталі Журналу',
|
||||
fileListDetail: 'Деталь',
|
||||
fileListLabel: 'Подробиці файлу',
|
||||
testWithParams: 'Тест з параметрами',
|
||||
},
|
||||
promptLog: 'Журнал Запитань',
|
||||
agentLog: 'Журнал агента',
|
||||
|
||||
@ -82,6 +82,7 @@ const translation = {
|
||||
workflowTitle: 'Chi tiết nhật ký',
|
||||
fileListDetail: 'Chi tiết',
|
||||
fileListLabel: 'Chi tiết tệp',
|
||||
testWithParams: 'Kiểm tra với các tham số',
|
||||
},
|
||||
promptLog: 'Nhật ký lời nhắc',
|
||||
viewLog: 'Xem nhật ký',
|
||||
|
||||
@ -83,6 +83,7 @@ const translation = {
|
||||
workflowTitle: '日志详情',
|
||||
fileListLabel: '文件详情',
|
||||
fileListDetail: '详情',
|
||||
testWithParams: '按此参数测试',
|
||||
},
|
||||
promptLog: 'Prompt 日志',
|
||||
agentLog: 'Agent 日志',
|
||||
|
||||
@ -82,6 +82,7 @@ const translation = {
|
||||
workflowTitle: '日誌詳情',
|
||||
fileListDetail: '細節',
|
||||
fileListLabel: '檔詳細資訊',
|
||||
testWithParams: '使用參數測試',
|
||||
},
|
||||
promptLog: 'Prompt 日誌',
|
||||
agentLog: 'Agent 日誌',
|
||||
|
||||
@ -146,7 +146,7 @@
|
||||
"@babel/core": "^7.28.3",
|
||||
"@chromatic-com/storybook": "^3.1.0",
|
||||
"@eslint-react/eslint-plugin": "^1.15.0",
|
||||
"@happy-dom/jest-environment": "^20.0.0",
|
||||
"@happy-dom/jest-environment": "^20.0.2",
|
||||
"@mdx-js/loader": "^3.1.0",
|
||||
"@mdx-js/react": "^3.1.0",
|
||||
"@next/bundle-analyzer": "15.5.4",
|
||||
|
||||
18
web/pnpm-lock.yaml
generated
18
web/pnpm-lock.yaml
generated
@ -351,8 +351,8 @@ importers:
|
||||
specifier: ^1.15.0
|
||||
version: 1.52.3(eslint@9.36.0(jiti@2.6.1))(ts-api-utils@2.1.0(typescript@5.8.3))(typescript@5.8.3)
|
||||
'@happy-dom/jest-environment':
|
||||
specifier: ^20.0.0
|
||||
version: 20.0.0(@jest/environment@29.7.0)(@jest/fake-timers@29.7.0)(@jest/types@29.6.3)(jest-mock@29.7.0)(jest-util@29.7.0)
|
||||
specifier: ^20.0.2
|
||||
version: 20.0.4(@jest/environment@29.7.0)(@jest/fake-timers@29.7.0)(@jest/types@29.6.3)(jest-mock@29.7.0)(jest-util@29.7.0)
|
||||
'@mdx-js/loader':
|
||||
specifier: ^3.1.0
|
||||
version: 3.1.0(acorn@8.15.0)(webpack@5.100.2(esbuild@0.25.0)(uglify-js@3.19.3))
|
||||
@ -1649,8 +1649,8 @@ packages:
|
||||
'@formatjs/intl-localematcher@0.5.10':
|
||||
resolution: {integrity: sha512-af3qATX+m4Rnd9+wHcjJ4w2ijq+rAVP3CCinJQvFv1kgSu1W6jypUmvleJxcewdxmutM8dmIRZFxO/IQBZmP2Q==}
|
||||
|
||||
'@happy-dom/jest-environment@20.0.0':
|
||||
resolution: {integrity: sha512-dUyMDNJzPDFopSDyzKdbeYs8z9B4jLj9kXnru8TjYdGeLsQKf+6r0lq/9T2XVcu04QFxXMykt64A+KjsaJTaNA==}
|
||||
'@happy-dom/jest-environment@20.0.4':
|
||||
resolution: {integrity: sha512-75OcYtjO+jqxWiYiXvbwR8JZITX1/8iAjRSRpZ/rNjO6UnYebwX6HdI91Ix09xYZEO1X/xOof6HX1EiZnrgnXA==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
peerDependencies:
|
||||
'@jest/environment': '>=25.0.0'
|
||||
@ -5544,8 +5544,8 @@ packages:
|
||||
hachure-fill@0.5.2:
|
||||
resolution: {integrity: sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg==}
|
||||
|
||||
happy-dom@20.0.0:
|
||||
resolution: {integrity: sha512-GkWnwIFxVGCf2raNrxImLo397RdGhLapj5cT3R2PT7FwL62Ze1DROhzmYW7+J3p9105DYMVenEejEbnq5wA37w==}
|
||||
happy-dom@20.0.4:
|
||||
resolution: {integrity: sha512-WxFtvnij6G64/MtMimnZhF0nKx3LUQKc20zjATD6tKiqOykUwQkd+2FW/DZBAFNjk4oWh0xdv/HBleGJmSY/Iw==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
has-flag@4.0.0:
|
||||
@ -10122,12 +10122,12 @@ snapshots:
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
'@happy-dom/jest-environment@20.0.0(@jest/environment@29.7.0)(@jest/fake-timers@29.7.0)(@jest/types@29.6.3)(jest-mock@29.7.0)(jest-util@29.7.0)':
|
||||
'@happy-dom/jest-environment@20.0.4(@jest/environment@29.7.0)(@jest/fake-timers@29.7.0)(@jest/types@29.6.3)(jest-mock@29.7.0)(jest-util@29.7.0)':
|
||||
dependencies:
|
||||
'@jest/environment': 29.7.0
|
||||
'@jest/fake-timers': 29.7.0
|
||||
'@jest/types': 29.6.3
|
||||
happy-dom: 20.0.0
|
||||
happy-dom: 20.0.4
|
||||
jest-mock: 29.7.0
|
||||
jest-util: 29.7.0
|
||||
|
||||
@ -14730,7 +14730,7 @@ snapshots:
|
||||
|
||||
hachure-fill@0.5.2: {}
|
||||
|
||||
happy-dom@20.0.0:
|
||||
happy-dom@20.0.4:
|
||||
dependencies:
|
||||
'@types/node': 20.19.20
|
||||
'@types/whatwg-mimetype': 3.0.2
|
||||
|
||||
@ -324,7 +324,7 @@ const baseFetch = base
|
||||
|
||||
type UploadOptions = {
|
||||
xhr: XMLHttpRequest
|
||||
method: string
|
||||
method?: string
|
||||
url?: string
|
||||
headers?: Record<string, string>
|
||||
data: FormData
|
||||
|
||||
13
web/types/jsx.d.ts
vendored
Normal file
13
web/types/jsx.d.ts
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
// TypeScript type definitions for custom JSX elements
|
||||
// Custom JSX elements for emoji-mart web components
|
||||
|
||||
import 'react'
|
||||
|
||||
declare module 'react' {
|
||||
namespace JSX {
|
||||
// eslint-disable-next-line ts/consistent-type-definitions
|
||||
interface IntrinsicElements {
|
||||
'em-emoji': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement>
|
||||
}
|
||||
}
|
||||
}
|
||||
23
web/utils/urlValidation.ts
Normal file
23
web/utils/urlValidation.ts
Normal file
@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Validates that a URL is safe for redirection.
|
||||
* Only allows HTTP and HTTPS protocols to prevent XSS attacks.
|
||||
*
|
||||
* @param url - The URL string to validate
|
||||
* @throws Error if the URL has an unsafe protocol
|
||||
*/
|
||||
export function validateRedirectUrl(url: string): void {
|
||||
try {
|
||||
const parsedUrl = new URL(url)
|
||||
if (parsedUrl.protocol !== 'http:' && parsedUrl.protocol !== 'https:')
|
||||
throw new Error('Authorization URL must be HTTP or HTTPS')
|
||||
}
|
||||
catch (error) {
|
||||
if (
|
||||
error instanceof Error
|
||||
&& error.message === 'Authorization URL must be HTTP or HTTPS'
|
||||
)
|
||||
throw error
|
||||
// If URL parsing fails, it's also invalid
|
||||
throw new Error(`Invalid URL: ${url}`)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user