mirror of
https://github.com/langgenius/dify.git
synced 2026-07-24 21:18:35 +08:00
Co-authored-by: hjlarry <hjlarry@163.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: yyh <yuanyouhuilyz@gmail.com> Co-authored-by: yyh <92089059+lyzno1@users.noreply.github.com>
175 lines
5.9 KiB
TypeScript
175 lines
5.9 KiB
TypeScript
'use client'
|
|
import type { FC } from 'react'
|
|
import type { TryAppInfo } from '@/service/try-app'
|
|
import { Button } from '@langgenius/dify-ui/button'
|
|
import { cn } from '@langgenius/dify-ui/cn'
|
|
import * as React from 'react'
|
|
import { useTranslation } from 'react-i18next'
|
|
import { AppTypeIcon } from '@/app/components/app/type-selector'
|
|
import AppIcon from '@/app/components/base/app-icon'
|
|
import useGetRequirements from './use-get-requirements'
|
|
|
|
type Props = Readonly<{
|
|
appId: string
|
|
appDetail: TryAppInfo
|
|
canCreate?: boolean
|
|
categories?: string[]
|
|
className?: string
|
|
createButtonStepByStepTourTarget?: string
|
|
onCreate: () => void
|
|
}>
|
|
|
|
const headerClassName = 'system-sm-semibold-uppercase text-text-secondary mb-3'
|
|
const requirementIconSize = 20
|
|
|
|
type RequirementIconProps = {
|
|
iconUrl: string
|
|
}
|
|
|
|
const RequirementIcon: FC<RequirementIconProps> = ({ iconUrl }) => {
|
|
const [failedSource, setFailedSource] = React.useState<string | null>(null)
|
|
const hasLoadError = !iconUrl || failedSource === iconUrl
|
|
|
|
if (hasLoadError) {
|
|
return (
|
|
<div className="flex size-5 items-center justify-center overflow-hidden rounded-md border-[0.5px] border-components-panel-border-subtle bg-background-default-dodge">
|
|
<div className="i-custom-public-other-default-tool-icon size-3 text-text-tertiary" />
|
|
</div>
|
|
)
|
|
}
|
|
|
|
return (
|
|
<img
|
|
className="size-5 rounded-md object-cover shadow-xs"
|
|
src={iconUrl}
|
|
alt=""
|
|
aria-hidden="true"
|
|
width={requirementIconSize}
|
|
height={requirementIconSize}
|
|
onError={() => setFailedSource(iconUrl)}
|
|
/>
|
|
)
|
|
}
|
|
|
|
const AppInfo: FC<Props> = ({
|
|
appId,
|
|
canCreate = true,
|
|
className,
|
|
categories,
|
|
createButtonStepByStepTourTarget,
|
|
appDetail,
|
|
onCreate,
|
|
}) => {
|
|
const { t } = useTranslation()
|
|
const mode = appDetail?.mode
|
|
const visibleCategories = Array.from(new Set(categories?.filter(Boolean) ?? []))
|
|
const { requirements } = useGetRequirements({ appDetail, appId })
|
|
return (
|
|
<div className={cn('flex h-full flex-col px-4 pt-2', className)}>
|
|
{/* name and icon */}
|
|
<div className="flex shrink-0 grow-0 items-center gap-3">
|
|
<div className="relative shrink-0">
|
|
<AppIcon
|
|
size="large"
|
|
iconType={appDetail.site.icon_type}
|
|
icon={appDetail.site.icon ?? undefined}
|
|
background={appDetail.site.icon_background ?? undefined}
|
|
imageUrl={appDetail.site.icon_url ?? undefined}
|
|
/>
|
|
<AppTypeIcon
|
|
wrapperClassName="absolute -bottom-0.5 -right-0.5 w-4 h-4 shadow-sm"
|
|
className="size-3"
|
|
type={mode}
|
|
/>
|
|
</div>
|
|
<div className="w-0 grow py-px">
|
|
<div className="flex items-center text-sm/5 font-semibold text-text-secondary">
|
|
<div className="truncate" title={appDetail.name}>
|
|
{appDetail.name}
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center text-[10px] leading-[18px] font-medium text-text-tertiary">
|
|
{mode === 'advanced-chat' && (
|
|
<div className="truncate">
|
|
{t(($) => $['types.advanced'], { ns: 'app' }).toUpperCase()}
|
|
</div>
|
|
)}
|
|
{mode === 'chat' && (
|
|
<div className="truncate">
|
|
{t(($) => $['types.chatbot'], { ns: 'app' }).toUpperCase()}
|
|
</div>
|
|
)}
|
|
{mode === 'agent-chat' && (
|
|
<div className="truncate">
|
|
{t(($) => $['types.agent'], { ns: 'app' }).toUpperCase()}
|
|
</div>
|
|
)}
|
|
{mode === 'workflow' && (
|
|
<div className="truncate">
|
|
{t(($) => $['types.workflow'], { ns: 'app' }).toUpperCase()}
|
|
</div>
|
|
)}
|
|
{mode === 'completion' && (
|
|
<div className="truncate">
|
|
{t(($) => $['types.completion'], { ns: 'app' }).toUpperCase()}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{appDetail.description && (
|
|
<div className="mt-[14px] shrink-0 system-sm-regular text-text-secondary">
|
|
{appDetail.description}
|
|
</div>
|
|
)}
|
|
{canCreate && (
|
|
<Button
|
|
variant="primary"
|
|
className="mt-3 flex w-full max-w-full"
|
|
data-step-by-step-tour-target={createButtonStepByStepTourTarget}
|
|
onClick={onCreate}
|
|
>
|
|
<span className="mr-1 i-ri-add-line size-4 shrink-0" />
|
|
<span className="truncate">
|
|
{t(($) => $['tryApp.createFromSampleApp'], { ns: 'explore' })}
|
|
</span>
|
|
</Button>
|
|
)}
|
|
|
|
{visibleCategories.length > 0 && (
|
|
<div className="mt-6 shrink-0">
|
|
<div className={headerClassName}>{t(($) => $['tryApp.category'], { ns: 'explore' })}</div>
|
|
<div className="flex flex-wrap gap-1.5">
|
|
{visibleCategories.map((category) => (
|
|
<span
|
|
key={category}
|
|
className="rounded-md border-[0.5px] border-components-panel-border-subtle bg-components-badge-white-to-dark px-2 py-0.5 system-xs-medium text-text-secondary shadow-xs"
|
|
>
|
|
{category}
|
|
</span>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
{requirements.length > 0 && (
|
|
<div className="mt-5 grow overflow-y-auto">
|
|
<div className={headerClassName}>
|
|
{t(($) => $['tryApp.requirements'], { ns: 'explore' })}
|
|
</div>
|
|
<div className="space-y-0.5">
|
|
{requirements.map((item) => (
|
|
<div className="flex items-center space-x-2 py-1" key={item.name}>
|
|
<RequirementIcon iconUrl={item.iconUrl} />
|
|
<div className="w-0 grow truncate system-md-regular text-text-secondary">
|
|
{item.name}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|
|
export default React.memo(AppInfo)
|