Merge remote-tracking branch 'origin/main' into feat/trigger

This commit is contained in:
lyzno1
2025-10-15 20:39:17 +08:00
73 changed files with 635 additions and 670 deletions

View File

@ -1,54 +0,0 @@
import { useEffect, useRef } from 'react'
import cn from '@/utils/classnames'
type AutoHeightTextareaProps
= & React.DetailedHTMLProps<React.TextareaHTMLAttributes<HTMLTextAreaElement>, HTMLTextAreaElement>
& { outerClassName?: string }
const AutoHeightTextarea = (
{
ref: outRef,
outerClassName,
value,
className,
placeholder,
autoFocus,
disabled,
...rest
}: AutoHeightTextareaProps & {
ref: React.RefObject<HTMLTextAreaElement>;
},
) => {
const innerRef = useRef<HTMLTextAreaElement>(null)
const ref = outRef || innerRef
useEffect(() => {
if (autoFocus && !disabled && value) {
if (typeof ref !== 'function') {
ref.current?.setSelectionRange(`${value}`.length, `${value}`.length)
ref.current?.focus()
}
}
}, [autoFocus, disabled, ref])
return (
(<div className={outerClassName}>
<div className='relative'>
<div className={cn(className, 'invisible whitespace-pre-wrap break-all')}>
{!value ? placeholder : `${value}`.replace(/\n$/, '\n ')}
</div>
<textarea
ref={ref}
placeholder={placeholder}
className={cn(className, 'absolute inset-0 h-full w-full resize-none appearance-none border-none outline-none disabled:bg-transparent')}
value={value}
disabled={disabled}
{...rest}
/>
</div>
</div>)
)
}
AutoHeightTextarea.displayName = 'AutoHeightTextarea'
export default AutoHeightTextarea

View File

@ -1,28 +0,0 @@
'use client'
import type { FC } from 'react'
import React from 'react'
import { useTranslation } from 'react-i18next'
type Props = {
isRequest: boolean
toolName: string
content: string
}
const Panel: FC<Props> = ({
isRequest,
toolName,
content,
}) => {
const { t } = useTranslation()
return (
<div className='overflow-hidden rounded-md border border-black/5 bg-gray-100'>
<div className='flex items-center bg-gray-50 px-2 py-1 text-xs font-medium uppercase leading-[18px] text-gray-500'>
{t(`tools.thought.${isRequest ? 'requestTitle' : 'responseTitle'}`)} {toolName}
</div>
<div className='border-t border-black/5 p-2 text-xs leading-4 text-gray-700'>{content}</div>
</div>
)
}
export default React.memo(Panel)

View File

@ -1,106 +0,0 @@
'use client'
import type { FC } from 'react'
import React, { useState } from 'react'
import { useTranslation } from 'react-i18next'
import {
RiArrowDownSLine,
RiLoader2Line,
} from '@remixicon/react'
import type { ToolInfoInThought } from '../type'
import Panel from './panel'
import cn from '@/utils/classnames'
import { CheckCircle } from '@/app/components/base/icons/src/vender/solid/general'
import { DataSet as DataSetIcon } from '@/app/components/base/icons/src/public/thought'
import type { Emoji } from '@/app/components/tools/types'
import AppIcon from '@/app/components/base/app-icon'
type Props = {
payload: ToolInfoInThought
allToolIcons?: Record<string, string | Emoji>
}
const getIcon = (toolName: string, allToolIcons: Record<string, string | Emoji>) => {
if (toolName.startsWith('dataset_'))
return <DataSetIcon className='shrink-0'></DataSetIcon>
const icon = allToolIcons[toolName]
if (!icon)
return null
return (
typeof icon === 'string'
? (
<div
className='h-3 w-3 shrink-0 rounded-[3px] bg-cover bg-center'
style={{
backgroundImage: `url(${icon})`,
}}
></div>
)
: (
<AppIcon
className='shrink-0 rounded-[3px]'
size='xs'
icon={icon?.content}
background={icon?.background}
/>
))
}
const Tool: FC<Props> = ({
payload,
allToolIcons = {},
}) => {
const { t } = useTranslation()
const { name, label, input, isFinished, output } = payload
const toolName = name.startsWith('dataset_') ? t('dataset.knowledge') : name
const toolLabel = name.startsWith('dataset_') ? t('dataset.knowledge') : label
const [isShowDetail, setIsShowDetail] = useState(false)
const icon = getIcon(name, allToolIcons) as any
return (
<div>
<div className={cn(!isShowDetail && 'shadow-sm', !isShowDetail && 'inline-block', 'max-w-full overflow-x-auto rounded-md bg-white')}>
<div
className={cn('flex h-7 cursor-pointer items-center px-2')}
onClick={() => setIsShowDetail(!isShowDetail)}
>
{!isFinished && (
<RiLoader2Line className='h-3 w-3 shrink-0 animate-spin text-gray-500' />
)}
{isFinished && !isShowDetail && (
<CheckCircle className='h-3 w-3 shrink-0 text-[#12B76A]' />
)}
{isFinished && isShowDetail && (
icon
)}
<span className='mx-1 shrink-0 text-xs font-medium text-gray-500'>
{t(`tools.thought.${isFinished ? 'used' : 'using'}`)}
</span>
<span
className='truncate text-xs font-medium text-gray-700'
title={toolLabel}
>
{toolLabel}
</span>
<RiArrowDownSLine
className={cn(isShowDetail && 'rotate-180', 'ml-1 h-3 w-3 shrink-0 cursor-pointer select-none text-gray-500')}
/>
</div>
{isShowDetail && (
<div className='space-y-2 border-t border-black/5 p-2 '>
<Panel
isRequest={true}
toolName={toolName}
content={input} />
{output && (
<Panel
isRequest={false}
toolName={toolName}
content={output as string} />
)}
</div>
)}
</div>
</div>
)
}
export default React.memo(Tool)

View File

@ -1,54 +0,0 @@
'use client'
import { useState } from 'react'
import { t } from 'i18next'
import { debounce } from 'lodash-es'
import copy from 'copy-to-clipboard'
import s from './style.module.css'
import Tooltip from '@/app/components/base/tooltip'
type ICopyBtnProps = {
value: string
className?: string
isPlain?: boolean
}
const CopyBtn = ({
value,
className,
isPlain,
}: ICopyBtnProps) => {
const [isCopied, setIsCopied] = useState(false)
const onClickCopy = debounce(() => {
copy(value)
setIsCopied(true)
}, 100)
const onMouseLeave = debounce(() => {
setIsCopied(false)
}, 100)
return (
<div className={`${className}`}>
<Tooltip
popupContent={(isCopied ? t('appApi.copied') : t('appApi.copy'))}
asChild={false}
>
<div
onMouseLeave={onMouseLeave}
className={'box-border flex cursor-pointer items-center justify-center rounded-md bg-components-button-secondary-bg p-0.5'}
style={!isPlain
? {
boxShadow: '0px 4px 8px -2px rgba(16, 24, 40, 0.1), 0px 2px 4px -2px rgba(16, 24, 40, 0.06)',
}
: {}}
onClick={onClickCopy}
>
<div className={`h-6 w-6 rounded-md hover:bg-components-button-secondary-bg-hover ${s.copyIcon} ${isCopied ? s.copied : ''}`}></div>
</div>
</Tooltip>
</div>
)
}
export default CopyBtn

View File

@ -1,15 +0,0 @@
.copyIcon {
background-image: url(~@/app/components/develop/secret-key/assets/copy.svg);
background-position: center;
background-repeat: no-repeat;
}
.copyIcon:hover {
background-image: url(~@/app/components/develop/secret-key/assets/copy-hover.svg);
background-position: center;
background-repeat: no-repeat;
}
.copyIcon.copied {
background-image: url(~@/app/components/develop/secret-key/assets/copied.svg);
}

View File

@ -1,16 +0,0 @@
import type { FC } from 'react'
import React from 'react'
type IconProps = {
icon: any
className?: string
[key: string]: any
}
const Icon: FC<IconProps> = ({ icon, className, ...other }) => {
return (
<img src={icon} className={`h-3 w-3 ${className}`} {...other} alt="icon" />
)
}
export default Icon

View File

@ -1,23 +0,0 @@
import type { FC } from 'react'
import type { DividerProps } from '.'
import Divider from '.'
import classNames from '@/utils/classnames'
export type DividerWithLabelProps = DividerProps & {
label: string
}
export const DividerWithLabel: FC<DividerWithLabelProps> = (props) => {
const { label, className, ...rest } = props
return <div
className="my-2 flex items-center gap-2"
>
<Divider {...rest} className={classNames('flex-1', className)} />
<span className="text-xs text-text-tertiary">
{label}
</span>
<Divider {...rest} className={classNames('flex-1', className)} />
</div>
}
export default DividerWithLabel

View File

@ -1,37 +0,0 @@
'use client'
import {
PortalToFollowElem,
PortalToFollowElemContent,
PortalToFollowElemTrigger,
} from '@/app/components/base/portal-to-follow-elem'
import type { PortalToFollowElemOptions } from '@/app/components/base/portal-to-follow-elem'
type IFloatRightContainerProps = {
isMobile: boolean
open: boolean
toggle: () => void
triggerElement?: React.ReactNode
children?: React.ReactNode
} & PortalToFollowElemOptions
const FloatRightContainer = ({ open, toggle, triggerElement, isMobile, children, ...portalProps }: IFloatRightContainerProps) => {
return (
<>
{isMobile && (
<PortalToFollowElem open={open} {...portalProps}>
<PortalToFollowElemTrigger onClick={toggle}>
{triggerElement}
</PortalToFollowElemTrigger>
<PortalToFollowElemContent>
{children}
</PortalToFollowElemContent>
</PortalToFollowElem>
)}
{!isMobile && open && (
<>{children}</>
)}
</>
)
}
export default FloatRightContainer

View File

@ -1,27 +0,0 @@
import Button from '../button'
import { RiInstallLine, RiLoader2Line } from '@remixicon/react'
type InstallButtonProps = {
loading: boolean
onInstall: (e: React.MouseEvent) => void
t: any
}
const InstallButton = ({ loading, onInstall, t }: InstallButtonProps) => {
return (
<Button size='small' className='z-[100]' onClick={onInstall}>
<div className={`flex items-center justify-center gap-1 px-[3px]
${loading ? 'text-components-button-secondary-text-disabled' : 'text-components-button-secondary-text'}
system-xs-medium`}
>
{loading ? t('workflow.nodes.agent.pluginInstaller.installing') : t('workflow.nodes.agent.pluginInstaller.install')}
</div>
{loading
? <RiLoader2Line className='h-3.5 w-3.5 animate-spin text-text-quaternary' />
: <RiInstallLine className='h-3.5 w-3.5 text-text-secondary' />
}
</Button>
)
}
export default InstallButton