mirror of
https://github.com/langgenius/dify.git
synced 2026-05-05 09:58:04 +08:00
Merge branch 'main' into feat/llm-node-support-tools
This commit is contained in:
41
web/app/components/apps/app-card-skeleton.tsx
Normal file
41
web/app/components/apps/app-card-skeleton.tsx
Normal file
@ -0,0 +1,41 @@
|
||||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
import { SkeletonContainer, SkeletonRectangle, SkeletonRow } from '@/app/components/base/skeleton'
|
||||
|
||||
type AppCardSkeletonProps = {
|
||||
count?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Skeleton placeholder for App cards during loading states.
|
||||
* Matches the visual layout of AppCard component.
|
||||
*/
|
||||
export const AppCardSkeleton = React.memo(({ count = 6 }: AppCardSkeletonProps) => {
|
||||
return (
|
||||
<>
|
||||
{Array.from({ length: count }).map((_, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="h-[160px] rounded-xl border-[0.5px] border-components-card-border bg-components-card-bg p-4"
|
||||
>
|
||||
<SkeletonContainer className="h-full">
|
||||
<SkeletonRow>
|
||||
<SkeletonRectangle className="h-10 w-10 rounded-lg" />
|
||||
<div className="flex flex-1 flex-col gap-1">
|
||||
<SkeletonRectangle className="h-4 w-2/3" />
|
||||
<SkeletonRectangle className="h-3 w-1/3" />
|
||||
</div>
|
||||
</SkeletonRow>
|
||||
<div className="mt-4 flex flex-col gap-2">
|
||||
<SkeletonRectangle className="h-3 w-full" />
|
||||
<SkeletonRectangle className="h-3 w-4/5" />
|
||||
</div>
|
||||
</SkeletonContainer>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
})
|
||||
|
||||
AppCardSkeleton.displayName = 'AppCardSkeleton'
|
||||
@ -27,7 +27,9 @@ import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { CheckModal } from '@/hooks/use-pay'
|
||||
import { useInfiniteAppList } from '@/service/use-apps'
|
||||
import { AppModeEnum } from '@/types/app'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import AppCard from './app-card'
|
||||
import { AppCardSkeleton } from './app-card-skeleton'
|
||||
import Empty from './empty'
|
||||
import Footer from './footer'
|
||||
import useAppsQueryState from './hooks/use-apps-query-state'
|
||||
@ -45,7 +47,7 @@ const List = () => {
|
||||
const { t } = useTranslation()
|
||||
const { systemFeatures } = useGlobalPublicStore()
|
||||
const router = useRouter()
|
||||
const { isCurrentWorkspaceEditor, isCurrentWorkspaceDatasetOperator } = useAppContext()
|
||||
const { isCurrentWorkspaceEditor, isCurrentWorkspaceDatasetOperator, isLoadingCurrentWorkspace } = useAppContext()
|
||||
const showTagManagementModal = useTagStore(s => s.showTagManagementModal)
|
||||
const [activeTab, setActiveTab] = useQueryState(
|
||||
'category',
|
||||
@ -89,6 +91,7 @@ const List = () => {
|
||||
const {
|
||||
data,
|
||||
isLoading,
|
||||
isFetching,
|
||||
isFetchingNextPage,
|
||||
fetchNextPage,
|
||||
hasNextPage,
|
||||
@ -172,6 +175,8 @@ const List = () => {
|
||||
|
||||
const pages = data?.pages ?? []
|
||||
const hasAnyApp = (pages[0]?.total ?? 0) > 0
|
||||
// Show skeleton during initial load or when refetching with no previous data
|
||||
const showSkeleton = isLoading || (isFetching && pages.length === 0)
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -205,23 +210,34 @@ const List = () => {
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{hasAnyApp
|
||||
? (
|
||||
<div className="relative grid grow grid-cols-1 content-start gap-4 px-12 pt-2 sm:grid-cols-1 md:grid-cols-2 xl:grid-cols-4 2xl:grid-cols-5 2k:grid-cols-6">
|
||||
{isCurrentWorkspaceEditor
|
||||
&& <NewAppCard ref={newAppCardRef} onSuccess={refetch} selectedAppType={activeTab} />}
|
||||
{pages.map(({ data: apps }) => apps.map(app => (
|
||||
<AppCard key={app.id} app={app} onRefresh={refetch} />
|
||||
)))}
|
||||
</div>
|
||||
)
|
||||
: (
|
||||
<div className="relative grid grow grid-cols-1 content-start gap-4 overflow-hidden px-12 pt-2 sm:grid-cols-1 md:grid-cols-2 xl:grid-cols-4 2xl:grid-cols-5 2k:grid-cols-6">
|
||||
{isCurrentWorkspaceEditor
|
||||
&& <NewAppCard ref={newAppCardRef} className="z-10" onSuccess={refetch} selectedAppType={activeTab} />}
|
||||
<Empty />
|
||||
</div>
|
||||
)}
|
||||
<div className={cn(
|
||||
'relative grid grow grid-cols-1 content-start gap-4 px-12 pt-2 sm:grid-cols-1 md:grid-cols-2 xl:grid-cols-4 2xl:grid-cols-5 2k:grid-cols-6',
|
||||
!hasAnyApp && 'overflow-hidden',
|
||||
)}
|
||||
>
|
||||
{(isCurrentWorkspaceEditor || isLoadingCurrentWorkspace) && (
|
||||
<NewAppCard
|
||||
ref={newAppCardRef}
|
||||
isLoading={isLoadingCurrentWorkspace}
|
||||
onSuccess={refetch}
|
||||
selectedAppType={activeTab}
|
||||
className={cn(!hasAnyApp && 'z-10')}
|
||||
/>
|
||||
)}
|
||||
{(() => {
|
||||
if (showSkeleton)
|
||||
return <AppCardSkeleton count={6} />
|
||||
|
||||
if (hasAnyApp) {
|
||||
return pages.flatMap(({ data: apps }) => apps).map(app => (
|
||||
<AppCard key={app.id} app={app} onRefresh={refetch} />
|
||||
))
|
||||
}
|
||||
|
||||
// No apps - show empty state
|
||||
return <Empty />
|
||||
})()}
|
||||
</div>
|
||||
|
||||
{isCurrentWorkspaceEditor && (
|
||||
<div
|
||||
|
||||
@ -25,6 +25,7 @@ const CreateFromDSLModal = dynamic(() => import('@/app/components/app/create-fro
|
||||
|
||||
export type CreateAppCardProps = {
|
||||
className?: string
|
||||
isLoading?: boolean
|
||||
onSuccess?: () => void
|
||||
ref: React.RefObject<HTMLDivElement | null>
|
||||
selectedAppType?: string
|
||||
@ -33,6 +34,7 @@ export type CreateAppCardProps = {
|
||||
const CreateAppCard = ({
|
||||
ref,
|
||||
className,
|
||||
isLoading = false,
|
||||
onSuccess,
|
||||
selectedAppType,
|
||||
}: CreateAppCardProps) => {
|
||||
@ -56,7 +58,11 @@ const CreateAppCard = ({
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn('relative col-span-1 inline-flex h-[160px] flex-col justify-between rounded-xl border-[0.5px] border-components-card-border bg-components-card-bg', className)}
|
||||
className={cn(
|
||||
'relative col-span-1 inline-flex h-[160px] flex-col justify-between rounded-xl border-[0.5px] border-components-card-border bg-components-card-bg transition-opacity',
|
||||
isLoading && 'pointer-events-none opacity-50',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<div className="grow rounded-t-xl p-2">
|
||||
<div className="px-6 pb-1 pt-2 text-xs font-medium leading-[18px] text-text-tertiary">{t('createApp', { ns: 'app' })}</div>
|
||||
|
||||
@ -17,7 +17,7 @@ vi.mock('@/hooks/use-app-favicon', () => ({
|
||||
useAppFavicon: vi.fn(),
|
||||
}))
|
||||
|
||||
vi.mock('@/i18n-config/i18next-config', () => ({
|
||||
vi.mock('@/i18n-config/client', () => ({
|
||||
changeLanguage: vi.fn().mockResolvedValue(undefined),
|
||||
}))
|
||||
|
||||
|
||||
@ -25,7 +25,7 @@ import { useToastContext } from '@/app/components/base/toast'
|
||||
import { InputVarType } from '@/app/components/workflow/types'
|
||||
import { useWebAppStore } from '@/context/web-app-context'
|
||||
import { useAppFavicon } from '@/hooks/use-app-favicon'
|
||||
import { changeLanguage } from '@/i18n-config/i18next-config'
|
||||
import { changeLanguage } from '@/i18n-config/client'
|
||||
import {
|
||||
delConversation,
|
||||
pinConversation,
|
||||
|
||||
@ -13,7 +13,7 @@ import { shareQueryKeys } from '@/service/use-share'
|
||||
import { CONVERSATION_ID_INFO } from '../constants'
|
||||
import { useEmbeddedChatbot } from './hooks'
|
||||
|
||||
vi.mock('@/i18n-config/i18next-config', () => ({
|
||||
vi.mock('@/i18n-config/client', () => ({
|
||||
changeLanguage: vi.fn().mockResolvedValue(undefined),
|
||||
}))
|
||||
|
||||
|
||||
@ -23,7 +23,7 @@ import { useToastContext } from '@/app/components/base/toast'
|
||||
import { addFileInfos, sortAgentSorts } from '@/app/components/tools/utils'
|
||||
import { InputVarType } from '@/app/components/workflow/types'
|
||||
import { useWebAppStore } from '@/context/web-app-context'
|
||||
import { changeLanguage } from '@/i18n-config/i18next-config'
|
||||
import { changeLanguage } from '@/i18n-config/client'
|
||||
import { updateFeedback } from '@/service/share'
|
||||
import {
|
||||
useInvalidateShareConversations,
|
||||
|
||||
@ -4,7 +4,7 @@ import type {
|
||||
GetValuesOptions,
|
||||
} from '../types'
|
||||
import { useCallback } from 'react'
|
||||
import { getTransformedValuesWhenSecretInputPristine } from '../utils'
|
||||
import { getTransformedValuesWhenSecretInputPristine } from '../utils/secret-input'
|
||||
import { useCheckValidated } from './use-check-validated'
|
||||
|
||||
export const useGetFormValues = (form: AnyFormApi, formSchemas: FormSchema[]) => {
|
||||
|
||||
@ -1 +0,0 @@
|
||||
export * from './secret-input'
|
||||
22
web/app/components/base/form/utils/zod-submit-validator.ts
Normal file
22
web/app/components/base/form/utils/zod-submit-validator.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import type { ZodSchema } from 'zod'
|
||||
|
||||
type SubmitValidator<T> = ({ value }: { value: T }) => { fields: Record<string, string> } | undefined
|
||||
|
||||
export const zodSubmitValidator = <T>(schema: ZodSchema<T>): SubmitValidator<T> => {
|
||||
return ({ value }) => {
|
||||
const result = schema.safeParse(value)
|
||||
if (!result.success) {
|
||||
const fieldErrors: Record<string, string> = {}
|
||||
for (const issue of result.error.issues) {
|
||||
const path = issue.path[0]
|
||||
if (path === undefined)
|
||||
continue
|
||||
const key = String(path)
|
||||
if (!fieldErrors[key])
|
||||
fieldErrors[key] = issue.message
|
||||
}
|
||||
return { fields: fieldErrors }
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
<svg width="25" height="25" viewBox="0 0 25 25" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g clip-path="url(#clip0_6305_73327)">
|
||||
<path d="M0.5 12.5C0.5 8.77247 0.5 6.9087 1.10896 5.43853C1.92092 3.47831 3.47831 1.92092 5.43853 1.10896C6.9087 0.5 8.77247 0.5 12.5 0.5C16.2275 0.5 18.0913 0.5 19.5615 1.10896C21.5217 1.92092 23.0791 3.47831 23.891 5.43853C24.5 6.9087 24.5 8.77247 24.5 12.5C24.5 16.2275 24.5 18.0913 23.891 19.5615C23.0791 21.5217 21.5217 23.0791 19.5615 23.891C18.0913 24.5 16.2275 24.5 12.5 24.5C8.77247 24.5 6.9087 24.5 5.43853 23.891C3.47831 23.0791 1.92092 21.5217 1.10896 19.5615C0.5 18.0913 0.5 16.2275 0.5 12.5Z" fill="white"/>
|
||||
<rect width="24" height="24" transform="translate(0.5 0.5)" fill="url(#pattern0_6305_73327)"/>
|
||||
<rect width="24" height="24" transform="translate(0.5 0.5)" fill="white" fill-opacity="0.01"/>
|
||||
</g>
|
||||
<path d="M12.5 0.25C14.3603 0.25 15.7684 0.250313 16.8945 0.327148C18.0228 0.404144 18.8867 0.558755 19.6572 0.87793C21.6787 1.71525 23.2847 3.32133 24.1221 5.34277C24.4412 6.11333 24.5959 6.97723 24.6729 8.10547C24.7497 9.23161 24.75 10.6397 24.75 12.5C24.75 14.3603 24.7497 15.7684 24.6729 16.8945C24.5959 18.0228 24.4412 18.8867 24.1221 19.6572C23.2847 21.6787 21.6787 23.2847 19.6572 24.1221C18.8867 24.4412 18.0228 24.5959 16.8945 24.6729C15.7684 24.7497 14.3603 24.75 12.5 24.75C10.6397 24.75 9.23161 24.7497 8.10547 24.6729C6.97723 24.5959 6.11333 24.4412 5.34277 24.1221C3.32133 23.2847 1.71525 21.6787 0.87793 19.6572C0.558755 18.8867 0.404144 18.0228 0.327148 16.8945C0.250313 15.7684 0.25 14.3603 0.25 12.5C0.25 10.6397 0.250313 9.23161 0.327148 8.10547C0.404144 6.97723 0.558755 6.11333 0.87793 5.34277C1.71525 3.32133 3.32133 1.71525 5.34277 0.87793C6.11333 0.558755 6.97723 0.404144 8.10547 0.327148C9.23161 0.250313 10.6397 0.25 12.5 0.25Z" stroke="#101828" stroke-opacity="0.08" stroke-width="0.5"/>
|
||||
<defs>
|
||||
<pattern id="pattern0_6305_73327" patternContentUnits="objectBoundingBox" width="1" height="1">
|
||||
<use xlink:href="#image0_6305_73327" transform="scale(0.00625)"/>
|
||||
</pattern>
|
||||
<clipPath id="clip0_6305_73327">
|
||||
<path d="M0.5 12.5C0.5 8.77247 0.5 6.9087 1.10896 5.43853C1.92092 3.47831 3.47831 1.92092 5.43853 1.10896C6.9087 0.5 8.77247 0.5 12.5 0.5C16.2275 0.5 18.0913 0.5 19.5615 1.10896C21.5217 1.92092 23.0791 3.47831 23.891 5.43853C24.5 6.9087 24.5 8.77247 24.5 12.5C24.5 16.2275 24.5 18.0913 23.891 19.5615C23.0791 21.5217 21.5217 23.0791 19.5615 23.891C18.0913 24.5 16.2275 24.5 12.5 24.5C8.77247 24.5 6.9087 24.5 5.43853 23.891C3.47831 23.0791 1.92092 21.5217 1.10896 19.5615C0.5 18.0913 0.5 16.2275 0.5 12.5Z" fill="white"/>
|
||||
</clipPath>
|
||||
<image id="image0_6305_73327" width="160" height="160" preserveAspectRatio="none" xlink:href="data:image/png;base64,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"/>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 6.1 KiB |
@ -1,4 +0,0 @@
|
||||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="40" height="40" fill="white"/>
|
||||
<path d="M25.7926 10.1311H21.5089L29.3208 29.869H33.6045L25.7926 10.1311ZM13.4164 10.1311L5.60449 29.869H9.97273L11.5703 25.724H19.743L21.3405 29.869H25.7087L17.8969 10.1311H13.4164ZM12.9834 22.0583L15.6566 15.1217L18.3299 22.0583H12.9834Z" fill="black"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 403 B |
@ -1,4 +0,0 @@
|
||||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="40" height="40" fill="white"/>
|
||||
<path d="M36.6676 11.2917C36.3316 11.1277 36.1871 11.4402 35.9906 11.599C35.9242 11.6511 35.8668 11.7188 35.8108 11.7787C35.3199 12.3048 34.747 12.6485 33.9996 12.6068C32.9046 12.5469 31.971 12.8907 31.1455 13.7293C30.9696 12.6954 30.3863 12.0782 29.4996 11.6824C29.0348 11.4766 28.5647 11.2709 28.2406 10.823C28.0127 10.5053 27.9515 10.1511 27.8368 9.80214C27.7652 9.59121 27.6923 9.37506 27.4502 9.33861C27.1871 9.29694 27.0843 9.51829 26.9814 9.70318C26.5674 10.4584 26.4084 11.2917 26.4228 12.1355C26.4592 14.0313 27.26 15.5417 28.8486 16.6173C29.0296 16.7397 29.0764 16.8646 29.0191 17.0443C28.9111 17.4141 28.7822 17.7735 28.6676 18.1433C28.596 18.3803 28.4879 18.4323 28.2354 18.3282C27.363 17.9637 26.609 17.4246 25.9436 16.7709C24.8135 15.6771 23.7914 14.4689 22.5166 13.5235C22.2171 13.3021 21.919 13.0964 21.609 12.9011C20.3082 11.6355 21.7796 10.5964 22.1194 10.474C22.4762 10.3464 22.2431 9.9037 21.092 9.90891C19.9423 9.91413 18.889 10.2995 17.5478 10.8126C17.3512 10.8907 17.1455 10.948 16.9332 10.9922C15.7158 10.7631 14.4515 10.711 13.1298 10.8594C10.6428 11.1381 8.65587 12.3152 7.19493 14.3255C5.44102 16.7397 5.02826 19.4845 5.53347 22.349C6.06473 25.3646 7.60249 27.8646 9.96707 29.8178C12.4176 31.8413 15.2406 32.8334 18.4606 32.6433C20.4163 32.5313 22.5947 32.2683 25.0504 30.1875C25.6702 30.4949 26.3199 30.6173 27.3994 30.711C28.2302 30.7891 29.0296 30.6694 29.6494 30.5417C30.6194 30.3361 30.5518 29.4375 30.2015 29.2709C27.3578 27.9454 27.9814 28.4845 27.4136 28.0495C28.859 26.3361 31.0374 24.5574 31.889 18.797C31.9554 18.3386 31.898 18.0522 31.889 17.6798C31.8838 17.4558 31.9346 17.3673 32.1923 17.3413C32.9046 17.2605 33.596 17.0651 34.2314 16.7137C36.0739 15.7058 36.816 14.0522 36.9918 12.0678C37.0179 11.7657 36.9866 11.4506 36.6676 11.2917ZM20.613 29.1485C17.8564 26.9793 16.5204 26.2657 15.9684 26.297C15.4527 26.3255 15.5452 26.9167 15.6584 27.3022C15.777 27.6823 15.9319 27.9454 16.1494 28.2787C16.2991 28.5001 16.402 28.8307 15.9996 29.0755C15.1116 29.6277 13.5687 28.8907 13.4958 28.8542C11.7001 27.797 10.1988 26.3985 9.14025 24.487C8.11941 22.6459 7.52566 20.6719 7.42801 18.5651C7.40197 18.0547 7.5517 17.875 8.05691 17.7839C8.72227 17.6615 9.40978 17.6355 10.0751 17.7318C12.8876 18.1433 15.2822 19.4037 17.2887 21.3959C18.4346 22.5339 19.3018 23.8907 20.195 25.2162C21.1442 26.6251 22.1663 27.9662 23.4671 29.0651C23.9254 29.4506 24.2926 29.7449 24.6428 29.961C23.5856 30.0782 21.8199 30.1042 20.613 29.1485ZM21.9332 20.6407C21.9332 20.4141 22.1142 20.2345 22.342 20.2345C22.3928 20.2345 22.4398 20.2449 22.4814 20.2605C22.5374 20.2813 22.5895 20.3126 22.6299 20.3594C22.7027 20.4298 22.7444 20.5339 22.7444 20.6407C22.7444 20.8673 22.5635 21.047 22.3368 21.047C22.109 21.047 21.9332 20.8673 21.9332 20.6407ZM26.036 22.7501C25.7731 22.8569 25.51 22.9506 25.2575 22.961C24.8655 22.9793 24.4371 22.8203 24.204 22.6251C23.8434 22.323 23.5856 22.1537 23.4762 21.6225C23.4306 21.3959 23.4567 21.047 23.497 20.8465C23.5908 20.4141 23.4866 20.1381 23.1832 19.8855C22.9346 19.6798 22.6207 19.6251 22.2744 19.6251C22.1455 19.6251 22.027 19.5678 21.9384 19.5209C21.7939 19.4479 21.6754 19.2683 21.7887 19.047C21.8251 18.9766 22.001 18.8022 22.0426 18.7709C22.5114 18.5027 23.053 18.5913 23.5543 18.7918C24.0191 18.9818 24.3694 19.3307 24.8746 19.823C25.3915 20.4194 25.484 20.5861 25.7783 21.0313C26.01 21.3829 26.2223 21.7422 26.3668 22.1537C26.454 22.4089 26.3408 22.6198 26.036 22.7501Z" fill="#4D6BFE"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 3.5 KiB |
@ -1,105 +0,0 @@
|
||||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="40" height="40" fill="white"/>
|
||||
<mask id="mask0_3892_95663" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="6" y="6" width="28" height="29">
|
||||
<path d="M20 6C20.2936 6 20.5488 6.2005 20.6205 6.48556C20.8393 7.3566 21.1277 8.20866 21.4828 9.03356C22.4116 11.191 23.6854 13.0791 25.3032 14.6968C26.9218 16.3146 28.8095 17.5888 30.9664 18.5172C31.7941 18.8735 32.6436 19.16 33.5149 19.3795C33.6533 19.4143 33.7762 19.4942 33.8641 19.6067C33.9519 19.7192 33.9998 19.8578 34 20.0005C34 20.2941 33.7995 20.5492 33.5149 20.621C32.6437 20.8399 31.7915 21.1282 30.9664 21.4833C28.8095 22.4121 26.9209 23.6859 25.3032 25.3036C23.6854 26.9223 22.4116 28.8099 21.4828 30.9669C21.1278 31.7919 20.8394 32.6439 20.6205 33.5149C20.586 33.6534 20.5062 33.7764 20.3937 33.8644C20.2813 33.9524 20.1427 34.0003 20 34.0005C19.8572 34.0003 19.7186 33.9525 19.6062 33.8645C19.4937 33.7765 19.414 33.6535 19.3795 33.5149C19.1605 32.6439 18.872 31.7918 18.5167 30.9669C17.5884 28.8099 16.3151 26.9214 14.6964 25.3036C13.0782 23.6859 11.1906 22.4121 9.03309 21.4833C8.20814 21.1283 7.35608 20.8399 6.48509 20.621C6.34667 20.5864 6.22377 20.5065 6.13589 20.3941C6.04801 20.2817 6.00018 20.1432 6 20.0005C6.00024 19.8578 6.04808 19.7192 6.13594 19.6067C6.2238 19.4942 6.34667 19.4143 6.48509 19.3795C7.35612 19.1607 8.20819 18.8723 9.03309 18.5172C11.1906 17.5888 13.0786 16.3146 14.6964 14.6968C16.3141 13.0791 17.5884 11.191 18.5167 9.03356C18.8719 8.20862 19.1604 7.35656 19.3795 6.48556C19.4508 6.2005 19.7064 6 20 6Z" fill="black"/>
|
||||
<path d="M20 6C20.2936 6 20.5488 6.2005 20.6205 6.48556C20.8393 7.3566 21.1277 8.20866 21.4828 9.03356C22.4116 11.191 23.6854 13.0791 25.3032 14.6968C26.9218 16.3146 28.8095 17.5888 30.9664 18.5172C31.7941 18.8735 32.6436 19.16 33.5149 19.3795C33.6533 19.4143 33.7762 19.4942 33.8641 19.6067C33.9519 19.7192 33.9998 19.8578 34 20.0005C34 20.2941 33.7995 20.5492 33.5149 20.621C32.6437 20.8399 31.7915 21.1282 30.9664 21.4833C28.8095 22.4121 26.9209 23.6859 25.3032 25.3036C23.6854 26.9223 22.4116 28.8099 21.4828 30.9669C21.1278 31.7919 20.8394 32.6439 20.6205 33.5149C20.586 33.6534 20.5062 33.7764 20.3937 33.8644C20.2813 33.9524 20.1427 34.0003 20 34.0005C19.8572 34.0003 19.7186 33.9525 19.6062 33.8645C19.4937 33.7765 19.414 33.6535 19.3795 33.5149C19.1605 32.6439 18.872 31.7918 18.5167 30.9669C17.5884 28.8099 16.3151 26.9214 14.6964 25.3036C13.0782 23.6859 11.1906 22.4121 9.03309 21.4833C8.20814 21.1283 7.35608 20.8399 6.48509 20.621C6.34667 20.5864 6.22377 20.5065 6.13589 20.3941C6.04801 20.2817 6.00018 20.1432 6 20.0005C6.00024 19.8578 6.04808 19.7192 6.13594 19.6067C6.2238 19.4942 6.34667 19.4143 6.48509 19.3795C7.35612 19.1607 8.20819 18.8723 9.03309 18.5172C11.1906 17.5888 13.0786 16.3146 14.6964 14.6968C16.3141 13.0791 17.5884 11.191 18.5167 9.03356C18.8719 8.20862 19.1604 7.35656 19.3795 6.48556C19.4508 6.2005 19.7064 6 20 6Z" fill="url(#paint0_linear_3892_95663)"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_3892_95663)">
|
||||
<g filter="url(#filter0_f_3892_95663)">
|
||||
<path d="M3.47232 27.8921C6.70753 29.0411 10.426 26.8868 11.7778 23.0804C13.1296 19.274 11.6028 15.2569 8.36763 14.108C5.13242 12.959 1.41391 15.1133 0.06211 18.9197C-1.28969 22.7261 0.23711 26.7432 3.47232 27.8921Z" fill="#FFE432"/>
|
||||
</g>
|
||||
<g filter="url(#filter1_f_3892_95663)">
|
||||
<path d="M17.8359 15.341C22.2806 15.341 25.8838 11.6588 25.8838 7.11644C25.8838 2.57412 22.2806 -1.10815 17.8359 -1.10815C13.3912 -1.10815 9.78809 2.57412 9.78809 7.11644C9.78809 11.6588 13.3912 15.341 17.8359 15.341Z" fill="#FC413D"/>
|
||||
</g>
|
||||
<g filter="url(#filter2_f_3892_95663)">
|
||||
<path d="M14.7081 41.6431C19.3478 41.4163 22.8707 36.3599 22.5768 30.3493C22.283 24.3387 18.2836 19.65 13.644 19.8769C9.00433 20.1037 5.48139 25.1601 5.77525 31.1707C6.06911 37.1813 10.0685 41.87 14.7081 41.6431Z" fill="#00B95C"/>
|
||||
</g>
|
||||
<g filter="url(#filter3_f_3892_95663)">
|
||||
<path d="M14.7081 41.6431C19.3478 41.4163 22.8707 36.3599 22.5768 30.3493C22.283 24.3387 18.2836 19.65 13.644 19.8769C9.00433 20.1037 5.48139 25.1601 5.77525 31.1707C6.06911 37.1813 10.0685 41.87 14.7081 41.6431Z" fill="#00B95C"/>
|
||||
</g>
|
||||
<g filter="url(#filter4_f_3892_95663)">
|
||||
<path d="M19.355 38.0071C23.2447 35.6405 24.2857 30.2506 21.6803 25.9684C19.0748 21.6862 13.8095 20.1334 9.91983 22.5C6.03016 24.8666 4.98909 30.2565 7.59454 34.5387C10.2 38.8209 15.4653 40.3738 19.355 38.0071Z" fill="#00B95C"/>
|
||||
</g>
|
||||
<g filter="url(#filter5_f_3892_95663)">
|
||||
<path d="M35.0759 24.5504C39.4477 24.5504 42.9917 21.1377 42.9917 16.9278C42.9917 12.7179 39.4477 9.30518 35.0759 9.30518C30.7042 9.30518 27.1602 12.7179 27.1602 16.9278C27.1602 21.1377 30.7042 24.5504 35.0759 24.5504Z" fill="#3186FF"/>
|
||||
</g>
|
||||
<g filter="url(#filter6_f_3892_95663)">
|
||||
<path d="M0.362818 23.6667C4.3882 26.7279 10.2688 25.7676 13.4976 21.5219C16.7264 17.2762 16.0806 11.3528 12.0552 8.29156C8.02982 5.23037 2.14917 6.19062 -1.07959 10.4364C-4.30835 14.6821 -3.66256 20.6055 0.362818 23.6667Z" fill="#FBBC04"/>
|
||||
</g>
|
||||
<g filter="url(#filter7_f_3892_95663)">
|
||||
<path d="M20.9877 28.1903C25.7924 31.4936 32.1612 30.5732 35.2128 26.1346C38.2644 21.696 36.8432 15.4199 32.0385 12.1166C27.2338 8.81334 20.865 9.73372 17.8134 14.1723C14.7618 18.611 16.183 24.887 20.9877 28.1903Z" fill="#3186FF"/>
|
||||
</g>
|
||||
<g filter="url(#filter8_f_3892_95663)">
|
||||
<path d="M29.7231 4.99175C30.9455 6.65415 29.3748 9.88535 26.2149 12.2096C23.0549 14.5338 19.5026 15.0707 18.2801 13.4088C17.0576 11.7468 18.6284 8.51514 21.7883 6.19092C24.9482 3.86717 28.5006 3.32982 29.7231 4.99175Z" fill="#749BFF"/>
|
||||
</g>
|
||||
<g filter="url(#filter9_f_3892_95663)">
|
||||
<path d="M19.6891 12.9486C24.5759 8.41581 26.2531 2.27858 23.4354 -0.759249C20.6176 -3.79708 14.3718 -2.58516 9.485 1.94765C4.59823 6.48046 2.92099 12.6177 5.73879 15.6555C8.55658 18.6933 14.8024 17.4814 19.6891 12.9486Z" fill="#FC413D"/>
|
||||
</g>
|
||||
<g filter="url(#filter10_f_3892_95663)">
|
||||
<path d="M9.6712 29.23C12.5757 31.3088 15.9102 31.6247 17.1191 29.9356C18.328 28.2465 16.9535 25.1921 14.049 23.1133C11.1446 21.0345 7.81003 20.7186 6.60113 22.4077C5.39223 24.0968 6.76675 27.1512 9.6712 29.23Z" fill="#FFEE48"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_f_3892_95663" x="-3.44095" y="10.7885" width="18.7217" height="20.4229" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="1.50514" result="effect1_foregroundBlur_3892_95663"/>
|
||||
</filter>
|
||||
<filter id="filter1_f_3892_95663" x="-4.76352" y="-15.6598" width="45.1989" height="45.5524" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="7.2758" result="effect1_foregroundBlur_3892_95663"/>
|
||||
</filter>
|
||||
<filter id="filter2_f_3892_95663" x="-6.61209" y="7.49899" width="41.5757" height="46.522" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="6.18495" result="effect1_foregroundBlur_3892_95663"/>
|
||||
</filter>
|
||||
<filter id="filter3_f_3892_95663" x="-6.61209" y="7.49899" width="41.5757" height="46.522" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="6.18495" result="effect1_foregroundBlur_3892_95663"/>
|
||||
</filter>
|
||||
<filter id="filter4_f_3892_95663" x="-6.21073" y="9.02316" width="41.6959" height="42.4608" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="6.18495" result="effect1_foregroundBlur_3892_95663"/>
|
||||
</filter>
|
||||
<filter id="filter5_f_3892_95663" x="15.405" y="-2.44994" width="39.3423" height="38.7556" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="5.87756" result="effect1_foregroundBlur_3892_95663"/>
|
||||
</filter>
|
||||
<filter id="filter6_f_3892_95663" x="-13.7886" y="-4.15284" width="39.9951" height="40.2639" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="5.32691" result="effect1_foregroundBlur_3892_95663"/>
|
||||
</filter>
|
||||
<filter id="filter7_f_3892_95663" x="6.6925" y="0.620963" width="39.6414" height="39.065" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="4.75678" result="effect1_foregroundBlur_3892_95663"/>
|
||||
</filter>
|
||||
<filter id="filter8_f_3892_95663" x="9.35225" y="-4.48661" width="29.2984" height="27.3739" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="4.25649" result="effect1_foregroundBlur_3892_95663"/>
|
||||
</filter>
|
||||
<filter id="filter9_f_3892_95663" x="-2.81919" y="-9.62339" width="34.8122" height="34.143" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="3.59514" result="effect1_foregroundBlur_3892_95663"/>
|
||||
</filter>
|
||||
<filter id="filter10_f_3892_95663" x="-2.73761" y="12.4221" width="29.1949" height="27.4994" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="4.44986" result="effect1_foregroundBlur_3892_95663"/>
|
||||
</filter>
|
||||
<linearGradient id="paint0_linear_3892_95663" x1="13.9595" y1="24.7349" x2="28.5025" y2="12.4738" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#4893FC"/>
|
||||
<stop offset="0.27" stop-color="#4893FC"/>
|
||||
<stop offset="0.777" stop-color="#969DFF"/>
|
||||
<stop offset="1" stop-color="#BD99FE"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 10 KiB |
@ -1,11 +0,0 @@
|
||||
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="40" height="40" fill="white"/>
|
||||
<g clip-path="url(#clip0_3892_95659)">
|
||||
<path d="M15.745 24.54L26.715 16.35C27.254 15.95 28.022 16.106 28.279 16.73C29.628 20.018 29.025 23.971 26.341 26.685C23.658 29.399 19.924 29.995 16.511 28.639L12.783 30.384C18.13 34.081 24.623 33.166 28.681 29.06C31.9 25.805 32.897 21.368 31.965 17.367L31.973 17.376C30.622 11.498 32.305 9.149 35.755 4.345L36 4L31.46 8.59V8.576L15.743 24.544M13.48 26.531C9.643 22.824 10.305 17.085 13.58 13.776C16 11.327 19.968 10.328 23.432 11.797L27.152 10.06C26.482 9.57 25.622 9.043 24.637 8.673C20.182 6.819 14.848 7.742 11.227 11.401C7.744 14.924 6.648 20.341 8.53 24.962C9.935 28.416 7.631 30.86 5.31 33.326C4.49 34.2 3.666 35.074 3 36L13.478 26.534" fill="black"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_3892_95659">
|
||||
<rect width="33" height="32" fill="white" transform="translate(3 4)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 981 B |
@ -1,17 +0,0 @@
|
||||
<svg width="26" height="26" viewBox="0 0 26 26" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g clip-path="url(#clip0_3892_83671)">
|
||||
<path d="M1 13C1 9.27247 1 7.4087 1.60896 5.93853C2.42092 3.97831 3.97831 2.42092 5.93853 1.60896C7.4087 1 9.27247 1 13 1C16.7275 1 18.5913 1 20.0615 1.60896C22.0217 2.42092 23.5791 3.97831 24.391 5.93853C25 7.4087 25 9.27247 25 13C25 16.7275 25 18.5913 24.391 20.0615C23.5791 22.0217 22.0217 23.5791 20.0615 24.391C18.5913 25 16.7275 25 13 25C9.27247 25 7.4087 25 5.93853 24.391C3.97831 23.5791 2.42092 22.0217 1.60896 20.0615C1 18.5913 1 16.7275 1 13Z" fill="white"/>
|
||||
<rect width="24" height="24" transform="translate(1 1)" fill="url(#pattern0_3892_83671)"/>
|
||||
<rect width="24" height="24" transform="translate(1 1)" fill="white" fill-opacity="0.01"/>
|
||||
</g>
|
||||
<path d="M13 0.75C14.8603 0.75 16.2684 0.750313 17.3945 0.827148C18.5228 0.904144 19.3867 1.05876 20.1572 1.37793C22.1787 2.21525 23.7847 3.82133 24.6221 5.84277C24.9412 6.61333 25.0959 7.47723 25.1729 8.60547C25.2497 9.73161 25.25 11.1397 25.25 13C25.25 14.8603 25.2497 16.2684 25.1729 17.3945C25.0959 18.5228 24.9412 19.3867 24.6221 20.1572C23.7847 22.1787 22.1787 23.7847 20.1572 24.6221C19.3867 24.9412 18.5228 25.0959 17.3945 25.1729C16.2684 25.2497 14.8603 25.25 13 25.25C11.1397 25.25 9.73161 25.2497 8.60547 25.1729C7.47723 25.0959 6.61333 24.9412 5.84277 24.6221C3.82133 23.7847 2.21525 22.1787 1.37793 20.1572C1.05876 19.3867 0.904144 18.5228 0.827148 17.3945C0.750313 16.2684 0.75 14.8603 0.75 13C0.75 11.1397 0.750313 9.73161 0.827148 8.60547C0.904144 7.47723 1.05876 6.61333 1.37793 5.84277C2.21525 3.82133 3.82133 2.21525 5.84277 1.37793C6.61333 1.05876 7.47723 0.904144 8.60547 0.827148C9.73161 0.750313 11.1397 0.75 13 0.75Z" stroke="#101828" stroke-opacity="0.08" stroke-width="0.5"/>
|
||||
<defs>
|
||||
<pattern id="pattern0_3892_83671" patternContentUnits="objectBoundingBox" width="1" height="1">
|
||||
<use xlink:href="#image0_3892_83671" transform="scale(0.00625)"/>
|
||||
</pattern>
|
||||
<clipPath id="clip0_3892_83671">
|
||||
<path d="M1 13C1 9.27247 1 7.4087 1.60896 5.93853C2.42092 3.97831 3.97831 2.42092 5.93853 1.60896C7.4087 1 9.27247 1 13 1C16.7275 1 18.5913 1 20.0615 1.60896C22.0217 2.42092 23.5791 3.97831 24.391 5.93853C25 7.4087 25 9.27247 25 13C25 16.7275 25 18.5913 24.391 20.0615C23.5791 22.0217 22.0217 23.5791 20.0615 24.391C18.5913 25 16.7275 25 13 25C9.27247 25 7.4087 25 5.93853 24.391C3.97831 23.5791 2.42092 22.0217 1.60896 20.0615C1 18.5913 1 16.7275 1 13Z" fill="white"/>
|
||||
</clipPath>
|
||||
<image id="image0_3892_83671" width="160" height="160" preserveAspectRatio="none" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAKAAAACgCAYAAACLz2ctAAAACXBIWXMAACxLAAAsSwGlPZapAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAA0ySURBVHgB7Z3vsdM6E8Z93nm/QwdABUAFgQqACgIVABUAFcCpAKgAOoBUwKECOBUAFfj6yYzumNx4pV2vtKtkfzO+3CHESezHWu0frS7GiSEIjPjfEASGhAADU0KAgSkhwMCUEGBgSggwMCUEGJgSAgxMCQEGpoQAA1NCgIEpIcDAlBBgYEoIMDAlBBiYEgIMTAkBBqaEAANTQoCBKSHAwJQQYGBKCDAwJQQYmPL/4cz5/fv38PXr12G32w0/f/4crq6u9n+P/0/cvHlzuH379v7Pe/fuDZvNZv8n/i5Yx8U5LkyH6D5+/Dh8/vx5Lz4pEOGDBw+G58+fhxiljGfEly9fxkkweODUD5wX5w94nIUAf/z4UU14x4SIzwvKOHkBvn79epzmbk3ENz9evHgx/vr1awxoTnYOCCfi2bNnq+Z4a8G8cDLLMT8kOEkBwpN9+PDh3tngMvd4EzgPBC05H3j37t3eUTkknffw3PjsdMDROWnGE+PDhw8sk4s526tXr/YORM5k4vVPnz6NT58+HSeRskwypgJ4/yRG9vsnEe5NOj771DgpAUJ8JTcUAoXovn37Nq7h/fv3zZyb+XeHgE/F4z4ZAUJMJTdwMoXqzgFGJu6IqHHgM/HQ9cxJCBBhj5wA8PraES8HRtXWIsTRc+jnJHLBcDjmqbNDttvtMImv+oT+4uLiL+elFfD079y5M7x582bojrFzMLkfiNEBo1JtMB+zMMHHDjgsPY2GXYdhMOrhyV9iEt8wCXSo+fnSWCNG41TQcOvWrb9eQ0jm+vp6H07CwQ3/dBV/HDsG3uCwMBI8fvx4rAWcmNzIOyzM1eA5c50gzF3fvn3LGmXhLdee82rQrQBh9gbC4ahlhiAgbmoPD4PW9+EUVPQgwm4FSI1+EIk2kkqamhUy+I0lI2LNh1GDLgWIC9rK9MJcUmJfGnlgMmuD61Dy3eCYeC2M6FKA1PxLc8SRVNLUCHTnKIk/IpXnkS4FiCd6yeRpIAmrWAeDS0ToMX3XnQAp87t27icpXIVQvdzYnAjx4HqjOwFCZEsXWDoCpbAKx9ymggZvcyuYWup7e8sddyNA3GiIb8n8Sp9uSVgFE3+vk3p8L2r6gNc84V6AMG/wbHMi4U6yvYRVMGpri5mKkXqbC7oVIFcgKPQshZvFgPi1Y4v4vvOHStuJoa6dlrOmgTsBSlewlVYLU05Mi3le7sGCedcQYm4U9DKFcCVAbjm9xKyUjn7aIxInoK1VaEoJnWMxauJGgDnvTUuAORHUCKtIl4auTaNREYOaxRoczAWIkQEXY434tASILIYmWnWCUrOMa7t0TjwQHjAVIC7QUlhl6aLVFKDWvKhGJwYIWWI2qe/hoUjBtCQfxZypGxUFGgChwHJK8A81WVtOj8JRlMXfv39ffUE8il+nacq+ABdNlUqhliGUXPvamAkQNyp3ISEIiA7igwg9MzkNe+GhAru0ghm/aZqnsbprQYhPnjzZP7zUOpjE3bt3F1/78+fPYM5oADU5TsextQ3U+zRMsARJQPtYuVZpadXhAQeHglqumntvC5oLEBc65xFut9uj8zFPAsT3k3juubgirg9nXjwMdNiGinuepQBzTznEt4QXAR5mMUoOblyxtOJ5fhzzlikB4t9b01SAOdObKyiwFiA+QzI6SeOKGCkli91THxoQI+CMXJVGboSwEiC+FzdWmdJ4Gkjmh8ksexdgMy8YXiLltcEb9LaOdR5W4YQ+0nvRKUEDXBdcnynfzfKWJ9Huu0ZQ5zVnbEQuAV9CyxFQK4tRo4GQZH645prVpIkAcxUopZPzFgKs1U9au2WGNGwzPzysGW5igi8vLxdfg5nwYnphbpFpKM1ipC6mJSDrgHOXBpBzJLM8CVEUpHfTfXVsAOU5ckMTQ8URkHOksvw1DoImXLPsZYFSdQFS5pdbmetBgEtl+dIVddpmkBO2OYswzOQ9Ll4AbnWHpQBL43laAeQ1cEZlaxFWFyCVruJ6YRYClJblS7pZaYuh1JO3rI6uLkDKLHFpLcC1bTY8zA9LC36tPOLqAtRcx9tKgHhoNG+IRIjaZjk3N4TwLRYqVRfgUtJesjSwtgBrt9mQzA/ned215Kp3LBoYVRfg0o+VLIrxWA8ogSNAbbOc89RbZ0fMKqItusn3SsrrIpC9Noidyye37rRvJkDpvmvnTGrKviabggcfGZQlkAVqucFjdQEuPW0a6ahzBZVFqHLBru8SkLqj0nctR8EYAR0BUcDUljA3y9xSMZAbBVvdn+oCXEp4r9n+9Bi73W7onVRgwKmNnK+S41xPnJ8aBakCEk3MTDDQnGtgOWSXW1UdASMbqlyw0U6pswazDCFywPmXaDUPrC5A6inTHrUgQqlJ8gh+D/a4KzXLXAcC92ZJ4K3McHUBbjabxdfw1HIoqV/jLtz2zrzur8Qsf//+feBAibvFKFhdgHjClkZBPGGcHwkBlhZfQtzd7iB5BIgPIoQYKbPMHbVaWqhjNPGCNV1+3IBSkwSSWZaGLLyB345gshaUhWpiQcYGUG3CcEiS7pK8KtJ/mtU5UgaiAKEE7aWWS9exRUPzJiMgZYYB5mtcJJ4inJOWUf5esEyLNgtE51x+qTC4nmLwXyyzVc0EmEv/cAOpc5KnCCF6W9zeA2cxAgJqFEzhkzXAS06eYgixHMu0aFMBYgREl88lYIZfvnw5rAXmGE0tqc86Rm1vGb8Pn+GNJQE2GRnHxuS2khoG3ZVaksZC2uXwKO8vWbJp5QVrb3/GwaRDKtW1c5iFTDTXKFgsl+Q2sbQS4NK5WuyoZNYlH8sWczekxhoNbr89SXd6ye6bVgKkdlRqsUbEdJuGUjFYLtyeC7FkXcaarloWAsTDtXSu0u3P1mC+UY1lKwnNndElzco9CNB6HxEXW3Vx2t566beXRmXOHnDeBEidp1XzIhcClOyjpr2ZoMQs43tzvzs14rcWIPV7W60RNhdgiUdcMhJpIdkmgfvAeBCgVtPQtZhu1QXWRuHXrhA7BBkUpPS0sik4B4LiODxlZ6gyOCQMWn1XcwFSCe/SSPzaFWLHwPkgRCp9SIHvjvdKO5jWBNeLqkbfbrdDK8wFeH19vfgaCi+lK8Q0KjkgInS656akUqkYKnW8geuS65zftLJoNIbawTzNQ/CnZFusNY19pCGaksD5YDgHzKUlERFoibkAKU/s2LZT3LwuN2wjabULT5hz46wEmHuILbbu6mIEPEQSt8ttk5DSZ5xz4pB0T7UQYC7EhIeolec7x7UAc00icfMl+dbDC91i88E5LQVYOqK3Nr0JcwFSPf9KcpHSvC7MET5b0tl+bYFEKwHi95U8WFp72kkwF6BWF32MlhrbV1EmSutG1RYgJy8taRSqibkAqYsp7aKqLcS0KY0G1BJVDQFyphKYF1v0hZ7jOheMv5cgMcvHjtKwCgfKE9UQYOmhXfArxYUAqdDKGgFIwjZJ+NqT8pK4YisBYvsJL7gQIFUUqTFH4ZhlSViFghNX1CokaPlgrcWFAHOtO7QEQZVCaZd3ScrySz+fKqNv9fu0cCFAQI0SmrVph/PDGvO80vDH4chbChU5OCa81lsvcHAjwNxT7WFz5RySsnyJQHIZG4gfUxfPwku4ESCg5mle9rc9hqQsf818bEnkuEY4pwfvthRXAqSyItqmWAPJPE+6++acpc/z5mCU4EqAIFcOb7m16BzNFXUcqPmfRycjhzsBlnh42qviOEjKtTQX2C/FNT1PUSjcCRCUFJ+23umb22Zjbm610FiM5A2XAsTNLlmZppmjpZCUa9X4bpzi3V5wKUCAC1oyx6qxUD3RKqxSArV81aKSWQu3AgS46KUjj+aNl+SQa6a5ci3teoj3LeFagIAjwjQiYq7GDVzjJksKVHFo548P0aig8coF/jM4Jy0l5C61xHJKtO3FgeWGN27c+Ot1LAnFOa+urvYHF6z3rd0OGAvIqeWdPwp3UHLL2Am1WmZIjhp9C4+RS7lZltJr0Y0AE9wGk5qHdlhliZKQT4tNZFrQnQCBJBisddSOt2HumhvpIb5ewy6HdCnARI31H6UmWLt7KGddcg+VQaV0LcAE5mPwBiV9BtccGIXXxiAhJk5BQ48FBxQnIcA5GJkwf+J2XYWA0TgdI5GkkTnej8/OhWPwOh4YiI4zjfBYTq9BF2GYNSC8gh6E6UggRIPwBY5j3a+m9Jt405wU/pmDz0bIR9IPEedDf8GSDbt74+QFuAZp/FGTFrFGS8z7A3omdUvlbvmlQWpw6a2zqjpjUETL0I/XFWw1CAEy0dgPhBJez4UFEmIOKATzwsvLy/0OmJI8cgJzvEePHu3b4lru22tFCFCBVNCw2+3+9boPPd40j0uFEZvNZi++cxTdnBBgYEp4wYEpIcDAlBBgYEoIMDAlBBiYEgIMTAkBBqaEAANTQoCBKSHAwJQQYGBKCDAwJQQYmBICDEwJAQamhAADU0KAgSkhwMCUEGBgSggwMCUEGJgSAgxMCQEGpvwDojzI2oXtJzYAAAAASUVORK5CYII="/>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 7.1 KiB |
@ -1,36 +0,0 @@
|
||||
{
|
||||
"icon": {
|
||||
"type": "element",
|
||||
"isRootNode": true,
|
||||
"name": "svg",
|
||||
"attributes": {
|
||||
"width": "40",
|
||||
"height": "40",
|
||||
"viewBox": "0 0 40 40",
|
||||
"fill": "none",
|
||||
"xmlns": "http://www.w3.org/2000/svg"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "rect",
|
||||
"attributes": {
|
||||
"width": "40",
|
||||
"height": "40",
|
||||
"fill": "white"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M25.7926 10.1311H21.5089L29.3208 29.869H33.6045L25.7926 10.1311ZM13.4164 10.1311L5.60449 29.869H9.97273L11.5703 25.724H19.743L21.3405 29.869H25.7087L17.8969 10.1311H13.4164ZM12.9834 22.0583L15.6566 15.1217L18.3299 22.0583H12.9834Z",
|
||||
"fill": "black"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "AnthropicShortLight"
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
// GENERATE BY script
|
||||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import type { IconData } from '@/app/components/base/icons/IconBase'
|
||||
import * as React from 'react'
|
||||
import IconBase from '@/app/components/base/icons/IconBase'
|
||||
import data from './AnthropicShortLight.json'
|
||||
|
||||
const Icon = (
|
||||
{
|
||||
ref,
|
||||
...props
|
||||
}: React.SVGProps<SVGSVGElement> & {
|
||||
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>
|
||||
},
|
||||
) => <IconBase {...props} ref={ref} data={data as IconData} />
|
||||
|
||||
Icon.displayName = 'AnthropicShortLight'
|
||||
|
||||
export default Icon
|
||||
@ -1,36 +0,0 @@
|
||||
{
|
||||
"icon": {
|
||||
"type": "element",
|
||||
"isRootNode": true,
|
||||
"name": "svg",
|
||||
"attributes": {
|
||||
"width": "40",
|
||||
"height": "40",
|
||||
"viewBox": "0 0 40 40",
|
||||
"fill": "none",
|
||||
"xmlns": "http://www.w3.org/2000/svg"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "rect",
|
||||
"attributes": {
|
||||
"width": "40",
|
||||
"height": "40",
|
||||
"fill": "white"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M36.6676 11.2917C36.3316 11.1277 36.1871 11.4402 35.9906 11.599C35.9242 11.6511 35.8668 11.7188 35.8108 11.7787C35.3199 12.3048 34.747 12.6485 33.9996 12.6068C32.9046 12.5469 31.971 12.8907 31.1455 13.7293C30.9696 12.6954 30.3863 12.0782 29.4996 11.6824C29.0348 11.4766 28.5647 11.2709 28.2406 10.823C28.0127 10.5053 27.9515 10.1511 27.8368 9.80214C27.7652 9.59121 27.6923 9.37506 27.4502 9.33861C27.1871 9.29694 27.0843 9.51829 26.9814 9.70318C26.5674 10.4584 26.4084 11.2917 26.4228 12.1355C26.4592 14.0313 27.26 15.5417 28.8486 16.6173C29.0296 16.7397 29.0764 16.8646 29.0191 17.0443C28.9111 17.4141 28.7822 17.7735 28.6676 18.1433C28.596 18.3803 28.4879 18.4323 28.2354 18.3282C27.363 17.9637 26.609 17.4246 25.9436 16.7709C24.8135 15.6771 23.7914 14.4689 22.5166 13.5235C22.2171 13.3021 21.919 13.0964 21.609 12.9011C20.3082 11.6355 21.7796 10.5964 22.1194 10.474C22.4762 10.3464 22.2431 9.9037 21.092 9.90891C19.9423 9.91413 18.889 10.2995 17.5478 10.8126C17.3512 10.8907 17.1455 10.948 16.9332 10.9922C15.7158 10.7631 14.4515 10.711 13.1298 10.8594C10.6428 11.1381 8.65587 12.3152 7.19493 14.3255C5.44102 16.7397 5.02826 19.4845 5.53347 22.349C6.06473 25.3646 7.60249 27.8646 9.96707 29.8178C12.4176 31.8413 15.2406 32.8334 18.4606 32.6433C20.4163 32.5313 22.5947 32.2683 25.0504 30.1875C25.6702 30.4949 26.3199 30.6173 27.3994 30.711C28.2302 30.7891 29.0296 30.6694 29.6494 30.5417C30.6194 30.3361 30.5518 29.4375 30.2015 29.2709C27.3578 27.9454 27.9814 28.4845 27.4136 28.0495C28.859 26.3361 31.0374 24.5574 31.889 18.797C31.9554 18.3386 31.898 18.0522 31.889 17.6798C31.8838 17.4558 31.9346 17.3673 32.1923 17.3413C32.9046 17.2605 33.596 17.0651 34.2314 16.7137C36.0739 15.7058 36.816 14.0522 36.9918 12.0678C37.0179 11.7657 36.9866 11.4506 36.6676 11.2917ZM20.613 29.1485C17.8564 26.9793 16.5204 26.2657 15.9684 26.297C15.4527 26.3255 15.5452 26.9167 15.6584 27.3022C15.777 27.6823 15.9319 27.9454 16.1494 28.2787C16.2991 28.5001 16.402 28.8307 15.9996 29.0755C15.1116 29.6277 13.5687 28.8907 13.4958 28.8542C11.7001 27.797 10.1988 26.3985 9.14025 24.487C8.11941 22.6459 7.52566 20.6719 7.42801 18.5651C7.40197 18.0547 7.5517 17.875 8.05691 17.7839C8.72227 17.6615 9.40978 17.6355 10.0751 17.7318C12.8876 18.1433 15.2822 19.4037 17.2887 21.3959C18.4346 22.5339 19.3018 23.8907 20.195 25.2162C21.1442 26.6251 22.1663 27.9662 23.4671 29.0651C23.9254 29.4506 24.2926 29.7449 24.6428 29.961C23.5856 30.0782 21.8199 30.1042 20.613 29.1485ZM21.9332 20.6407C21.9332 20.4141 22.1142 20.2345 22.342 20.2345C22.3928 20.2345 22.4398 20.2449 22.4814 20.2605C22.5374 20.2813 22.5895 20.3126 22.6299 20.3594C22.7027 20.4298 22.7444 20.5339 22.7444 20.6407C22.7444 20.8673 22.5635 21.047 22.3368 21.047C22.109 21.047 21.9332 20.8673 21.9332 20.6407ZM26.036 22.7501C25.7731 22.8569 25.51 22.9506 25.2575 22.961C24.8655 22.9793 24.4371 22.8203 24.204 22.6251C23.8434 22.323 23.5856 22.1537 23.4762 21.6225C23.4306 21.3959 23.4567 21.047 23.497 20.8465C23.5908 20.4141 23.4866 20.1381 23.1832 19.8855C22.9346 19.6798 22.6207 19.6251 22.2744 19.6251C22.1455 19.6251 22.027 19.5678 21.9384 19.5209C21.7939 19.4479 21.6754 19.2683 21.7887 19.047C21.8251 18.9766 22.001 18.8022 22.0426 18.7709C22.5114 18.5027 23.053 18.5913 23.5543 18.7918C24.0191 18.9818 24.3694 19.3307 24.8746 19.823C25.3915 20.4194 25.484 20.5861 25.7783 21.0313C26.01 21.3829 26.2223 21.7422 26.3668 22.1537C26.454 22.4089 26.3408 22.6198 26.036 22.7501Z",
|
||||
"fill": "#4D6BFE"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "Deepseek"
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
// GENERATE BY script
|
||||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import type { IconData } from '@/app/components/base/icons/IconBase'
|
||||
import * as React from 'react'
|
||||
import IconBase from '@/app/components/base/icons/IconBase'
|
||||
import data from './Deepseek.json'
|
||||
|
||||
const Icon = (
|
||||
{
|
||||
ref,
|
||||
...props
|
||||
}: React.SVGProps<SVGSVGElement> & {
|
||||
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>
|
||||
},
|
||||
) => <IconBase {...props} ref={ref} data={data as IconData} />
|
||||
|
||||
Icon.displayName = 'Deepseek'
|
||||
|
||||
export default Icon
|
||||
@ -1,807 +0,0 @@
|
||||
{
|
||||
"icon": {
|
||||
"type": "element",
|
||||
"isRootNode": true,
|
||||
"name": "svg",
|
||||
"attributes": {
|
||||
"width": "40",
|
||||
"height": "40",
|
||||
"viewBox": "0 0 40 40",
|
||||
"fill": "none",
|
||||
"xmlns": "http://www.w3.org/2000/svg"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "rect",
|
||||
"attributes": {
|
||||
"width": "40",
|
||||
"height": "40",
|
||||
"fill": "white"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "mask",
|
||||
"attributes": {
|
||||
"id": "mask0_3892_95663",
|
||||
"style": "mask-type:alpha",
|
||||
"maskUnits": "userSpaceOnUse",
|
||||
"x": "6",
|
||||
"y": "6",
|
||||
"width": "28",
|
||||
"height": "29"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M20 6C20.2936 6 20.5488 6.2005 20.6205 6.48556C20.8393 7.3566 21.1277 8.20866 21.4828 9.03356C22.4116 11.191 23.6854 13.0791 25.3032 14.6968C26.9218 16.3146 28.8095 17.5888 30.9664 18.5172C31.7941 18.8735 32.6436 19.16 33.5149 19.3795C33.6533 19.4143 33.7762 19.4942 33.8641 19.6067C33.9519 19.7192 33.9998 19.8578 34 20.0005C34 20.2941 33.7995 20.5492 33.5149 20.621C32.6437 20.8399 31.7915 21.1282 30.9664 21.4833C28.8095 22.4121 26.9209 23.6859 25.3032 25.3036C23.6854 26.9223 22.4116 28.8099 21.4828 30.9669C21.1278 31.7919 20.8394 32.6439 20.6205 33.5149C20.586 33.6534 20.5062 33.7764 20.3937 33.8644C20.2813 33.9524 20.1427 34.0003 20 34.0005C19.8572 34.0003 19.7186 33.9525 19.6062 33.8645C19.4937 33.7765 19.414 33.6535 19.3795 33.5149C19.1605 32.6439 18.872 31.7918 18.5167 30.9669C17.5884 28.8099 16.3151 26.9214 14.6964 25.3036C13.0782 23.6859 11.1906 22.4121 9.03309 21.4833C8.20814 21.1283 7.35608 20.8399 6.48509 20.621C6.34667 20.5864 6.22377 20.5065 6.13589 20.3941C6.04801 20.2817 6.00018 20.1432 6 20.0005C6.00024 19.8578 6.04808 19.7192 6.13594 19.6067C6.2238 19.4942 6.34667 19.4143 6.48509 19.3795C7.35612 19.1607 8.20819 18.8723 9.03309 18.5172C11.1906 17.5888 13.0786 16.3146 14.6964 14.6968C16.3141 13.0791 17.5884 11.191 18.5167 9.03356C18.8719 8.20862 19.1604 7.35656 19.3795 6.48556C19.4508 6.2005 19.7064 6 20 6Z",
|
||||
"fill": "black"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M20 6C20.2936 6 20.5488 6.2005 20.6205 6.48556C20.8393 7.3566 21.1277 8.20866 21.4828 9.03356C22.4116 11.191 23.6854 13.0791 25.3032 14.6968C26.9218 16.3146 28.8095 17.5888 30.9664 18.5172C31.7941 18.8735 32.6436 19.16 33.5149 19.3795C33.6533 19.4143 33.7762 19.4942 33.8641 19.6067C33.9519 19.7192 33.9998 19.8578 34 20.0005C34 20.2941 33.7995 20.5492 33.5149 20.621C32.6437 20.8399 31.7915 21.1282 30.9664 21.4833C28.8095 22.4121 26.9209 23.6859 25.3032 25.3036C23.6854 26.9223 22.4116 28.8099 21.4828 30.9669C21.1278 31.7919 20.8394 32.6439 20.6205 33.5149C20.586 33.6534 20.5062 33.7764 20.3937 33.8644C20.2813 33.9524 20.1427 34.0003 20 34.0005C19.8572 34.0003 19.7186 33.9525 19.6062 33.8645C19.4937 33.7765 19.414 33.6535 19.3795 33.5149C19.1605 32.6439 18.872 31.7918 18.5167 30.9669C17.5884 28.8099 16.3151 26.9214 14.6964 25.3036C13.0782 23.6859 11.1906 22.4121 9.03309 21.4833C8.20814 21.1283 7.35608 20.8399 6.48509 20.621C6.34667 20.5864 6.22377 20.5065 6.13589 20.3941C6.04801 20.2817 6.00018 20.1432 6 20.0005C6.00024 19.8578 6.04808 19.7192 6.13594 19.6067C6.2238 19.4942 6.34667 19.4143 6.48509 19.3795C7.35612 19.1607 8.20819 18.8723 9.03309 18.5172C11.1906 17.5888 13.0786 16.3146 14.6964 14.6968C16.3141 13.0791 17.5884 11.191 18.5167 9.03356C18.8719 8.20862 19.1604 7.35656 19.3795 6.48556C19.4508 6.2005 19.7064 6 20 6Z",
|
||||
"fill": "url(#paint0_linear_3892_95663)"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "g",
|
||||
"attributes": {
|
||||
"mask": "url(#mask0_3892_95663)"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "g",
|
||||
"attributes": {
|
||||
"filter": "url(#filter0_f_3892_95663)"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M3.47232 27.8921C6.70753 29.0411 10.426 26.8868 11.7778 23.0804C13.1296 19.274 11.6028 15.2569 8.36763 14.108C5.13242 12.959 1.41391 15.1133 0.06211 18.9197C-1.28969 22.7261 0.23711 26.7432 3.47232 27.8921Z",
|
||||
"fill": "#FFE432"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "g",
|
||||
"attributes": {
|
||||
"filter": "url(#filter1_f_3892_95663)"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M17.8359 15.341C22.2806 15.341 25.8838 11.6588 25.8838 7.11644C25.8838 2.57412 22.2806 -1.10815 17.8359 -1.10815C13.3912 -1.10815 9.78809 2.57412 9.78809 7.11644C9.78809 11.6588 13.3912 15.341 17.8359 15.341Z",
|
||||
"fill": "#FC413D"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "g",
|
||||
"attributes": {
|
||||
"filter": "url(#filter2_f_3892_95663)"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M14.7081 41.6431C19.3478 41.4163 22.8707 36.3599 22.5768 30.3493C22.283 24.3387 18.2836 19.65 13.644 19.8769C9.00433 20.1037 5.48139 25.1601 5.77525 31.1707C6.06911 37.1813 10.0685 41.87 14.7081 41.6431Z",
|
||||
"fill": "#00B95C"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "g",
|
||||
"attributes": {
|
||||
"filter": "url(#filter3_f_3892_95663)"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M14.7081 41.6431C19.3478 41.4163 22.8707 36.3599 22.5768 30.3493C22.283 24.3387 18.2836 19.65 13.644 19.8769C9.00433 20.1037 5.48139 25.1601 5.77525 31.1707C6.06911 37.1813 10.0685 41.87 14.7081 41.6431Z",
|
||||
"fill": "#00B95C"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "g",
|
||||
"attributes": {
|
||||
"filter": "url(#filter4_f_3892_95663)"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M19.355 38.0071C23.2447 35.6405 24.2857 30.2506 21.6803 25.9684C19.0748 21.6862 13.8095 20.1334 9.91983 22.5C6.03016 24.8666 4.98909 30.2565 7.59454 34.5387C10.2 38.8209 15.4653 40.3738 19.355 38.0071Z",
|
||||
"fill": "#00B95C"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "g",
|
||||
"attributes": {
|
||||
"filter": "url(#filter5_f_3892_95663)"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M35.0759 24.5504C39.4477 24.5504 42.9917 21.1377 42.9917 16.9278C42.9917 12.7179 39.4477 9.30518 35.0759 9.30518C30.7042 9.30518 27.1602 12.7179 27.1602 16.9278C27.1602 21.1377 30.7042 24.5504 35.0759 24.5504Z",
|
||||
"fill": "#3186FF"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "g",
|
||||
"attributes": {
|
||||
"filter": "url(#filter6_f_3892_95663)"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M0.362818 23.6667C4.3882 26.7279 10.2688 25.7676 13.4976 21.5219C16.7264 17.2762 16.0806 11.3528 12.0552 8.29156C8.02982 5.23037 2.14917 6.19062 -1.07959 10.4364C-4.30835 14.6821 -3.66256 20.6055 0.362818 23.6667Z",
|
||||
"fill": "#FBBC04"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "g",
|
||||
"attributes": {
|
||||
"filter": "url(#filter7_f_3892_95663)"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M20.9877 28.1903C25.7924 31.4936 32.1612 30.5732 35.2128 26.1346C38.2644 21.696 36.8432 15.4199 32.0385 12.1166C27.2338 8.81334 20.865 9.73372 17.8134 14.1723C14.7618 18.611 16.183 24.887 20.9877 28.1903Z",
|
||||
"fill": "#3186FF"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "g",
|
||||
"attributes": {
|
||||
"filter": "url(#filter8_f_3892_95663)"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M29.7231 4.99175C30.9455 6.65415 29.3748 9.88535 26.2149 12.2096C23.0549 14.5338 19.5026 15.0707 18.2801 13.4088C17.0576 11.7468 18.6284 8.51514 21.7883 6.19092C24.9482 3.86717 28.5006 3.32982 29.7231 4.99175Z",
|
||||
"fill": "#749BFF"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "g",
|
||||
"attributes": {
|
||||
"filter": "url(#filter9_f_3892_95663)"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M19.6891 12.9486C24.5759 8.41581 26.2531 2.27858 23.4354 -0.759249C20.6176 -3.79708 14.3718 -2.58516 9.485 1.94765C4.59823 6.48046 2.92099 12.6177 5.73879 15.6555C8.55658 18.6933 14.8024 17.4814 19.6891 12.9486Z",
|
||||
"fill": "#FC413D"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "g",
|
||||
"attributes": {
|
||||
"filter": "url(#filter10_f_3892_95663)"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M9.6712 29.23C12.5757 31.3088 15.9102 31.6247 17.1191 29.9356C18.328 28.2465 16.9535 25.1921 14.049 23.1133C11.1446 21.0345 7.81003 20.7186 6.60113 22.4077C5.39223 24.0968 6.76675 27.1512 9.6712 29.23Z",
|
||||
"fill": "#FFEE48"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "defs",
|
||||
"attributes": {},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "filter",
|
||||
"attributes": {
|
||||
"id": "filter0_f_3892_95663",
|
||||
"x": "-3.44095",
|
||||
"y": "10.7885",
|
||||
"width": "18.7217",
|
||||
"height": "20.4229",
|
||||
"filterUnits": "userSpaceOnUse",
|
||||
"color-interpolation-filters": "sRGB"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "feFlood",
|
||||
"attributes": {
|
||||
"flood-opacity": "0",
|
||||
"result": "BackgroundImageFix"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "feBlend",
|
||||
"attributes": {
|
||||
"mode": "normal",
|
||||
"in": "SourceGraphic",
|
||||
"in2": "BackgroundImageFix",
|
||||
"result": "shape"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "feGaussianBlur",
|
||||
"attributes": {
|
||||
"stdDeviation": "1.50514",
|
||||
"result": "effect1_foregroundBlur_3892_95663"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "filter",
|
||||
"attributes": {
|
||||
"id": "filter1_f_3892_95663",
|
||||
"x": "-4.76352",
|
||||
"y": "-15.6598",
|
||||
"width": "45.1989",
|
||||
"height": "45.5524",
|
||||
"filterUnits": "userSpaceOnUse",
|
||||
"color-interpolation-filters": "sRGB"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "feFlood",
|
||||
"attributes": {
|
||||
"flood-opacity": "0",
|
||||
"result": "BackgroundImageFix"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "feBlend",
|
||||
"attributes": {
|
||||
"mode": "normal",
|
||||
"in": "SourceGraphic",
|
||||
"in2": "BackgroundImageFix",
|
||||
"result": "shape"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "feGaussianBlur",
|
||||
"attributes": {
|
||||
"stdDeviation": "7.2758",
|
||||
"result": "effect1_foregroundBlur_3892_95663"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "filter",
|
||||
"attributes": {
|
||||
"id": "filter2_f_3892_95663",
|
||||
"x": "-6.61209",
|
||||
"y": "7.49899",
|
||||
"width": "41.5757",
|
||||
"height": "46.522",
|
||||
"filterUnits": "userSpaceOnUse",
|
||||
"color-interpolation-filters": "sRGB"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "feFlood",
|
||||
"attributes": {
|
||||
"flood-opacity": "0",
|
||||
"result": "BackgroundImageFix"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "feBlend",
|
||||
"attributes": {
|
||||
"mode": "normal",
|
||||
"in": "SourceGraphic",
|
||||
"in2": "BackgroundImageFix",
|
||||
"result": "shape"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "feGaussianBlur",
|
||||
"attributes": {
|
||||
"stdDeviation": "6.18495",
|
||||
"result": "effect1_foregroundBlur_3892_95663"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "filter",
|
||||
"attributes": {
|
||||
"id": "filter3_f_3892_95663",
|
||||
"x": "-6.61209",
|
||||
"y": "7.49899",
|
||||
"width": "41.5757",
|
||||
"height": "46.522",
|
||||
"filterUnits": "userSpaceOnUse",
|
||||
"color-interpolation-filters": "sRGB"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "feFlood",
|
||||
"attributes": {
|
||||
"flood-opacity": "0",
|
||||
"result": "BackgroundImageFix"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "feBlend",
|
||||
"attributes": {
|
||||
"mode": "normal",
|
||||
"in": "SourceGraphic",
|
||||
"in2": "BackgroundImageFix",
|
||||
"result": "shape"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "feGaussianBlur",
|
||||
"attributes": {
|
||||
"stdDeviation": "6.18495",
|
||||
"result": "effect1_foregroundBlur_3892_95663"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "filter",
|
||||
"attributes": {
|
||||
"id": "filter4_f_3892_95663",
|
||||
"x": "-6.21073",
|
||||
"y": "9.02316",
|
||||
"width": "41.6959",
|
||||
"height": "42.4608",
|
||||
"filterUnits": "userSpaceOnUse",
|
||||
"color-interpolation-filters": "sRGB"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "feFlood",
|
||||
"attributes": {
|
||||
"flood-opacity": "0",
|
||||
"result": "BackgroundImageFix"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "feBlend",
|
||||
"attributes": {
|
||||
"mode": "normal",
|
||||
"in": "SourceGraphic",
|
||||
"in2": "BackgroundImageFix",
|
||||
"result": "shape"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "feGaussianBlur",
|
||||
"attributes": {
|
||||
"stdDeviation": "6.18495",
|
||||
"result": "effect1_foregroundBlur_3892_95663"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "filter",
|
||||
"attributes": {
|
||||
"id": "filter5_f_3892_95663",
|
||||
"x": "15.405",
|
||||
"y": "-2.44994",
|
||||
"width": "39.3423",
|
||||
"height": "38.7556",
|
||||
"filterUnits": "userSpaceOnUse",
|
||||
"color-interpolation-filters": "sRGB"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "feFlood",
|
||||
"attributes": {
|
||||
"flood-opacity": "0",
|
||||
"result": "BackgroundImageFix"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "feBlend",
|
||||
"attributes": {
|
||||
"mode": "normal",
|
||||
"in": "SourceGraphic",
|
||||
"in2": "BackgroundImageFix",
|
||||
"result": "shape"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "feGaussianBlur",
|
||||
"attributes": {
|
||||
"stdDeviation": "5.87756",
|
||||
"result": "effect1_foregroundBlur_3892_95663"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "filter",
|
||||
"attributes": {
|
||||
"id": "filter6_f_3892_95663",
|
||||
"x": "-13.7886",
|
||||
"y": "-4.15284",
|
||||
"width": "39.9951",
|
||||
"height": "40.2639",
|
||||
"filterUnits": "userSpaceOnUse",
|
||||
"color-interpolation-filters": "sRGB"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "feFlood",
|
||||
"attributes": {
|
||||
"flood-opacity": "0",
|
||||
"result": "BackgroundImageFix"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "feBlend",
|
||||
"attributes": {
|
||||
"mode": "normal",
|
||||
"in": "SourceGraphic",
|
||||
"in2": "BackgroundImageFix",
|
||||
"result": "shape"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "feGaussianBlur",
|
||||
"attributes": {
|
||||
"stdDeviation": "5.32691",
|
||||
"result": "effect1_foregroundBlur_3892_95663"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "filter",
|
||||
"attributes": {
|
||||
"id": "filter7_f_3892_95663",
|
||||
"x": "6.6925",
|
||||
"y": "0.620963",
|
||||
"width": "39.6414",
|
||||
"height": "39.065",
|
||||
"filterUnits": "userSpaceOnUse",
|
||||
"color-interpolation-filters": "sRGB"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "feFlood",
|
||||
"attributes": {
|
||||
"flood-opacity": "0",
|
||||
"result": "BackgroundImageFix"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "feBlend",
|
||||
"attributes": {
|
||||
"mode": "normal",
|
||||
"in": "SourceGraphic",
|
||||
"in2": "BackgroundImageFix",
|
||||
"result": "shape"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "feGaussianBlur",
|
||||
"attributes": {
|
||||
"stdDeviation": "4.75678",
|
||||
"result": "effect1_foregroundBlur_3892_95663"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "filter",
|
||||
"attributes": {
|
||||
"id": "filter8_f_3892_95663",
|
||||
"x": "9.35225",
|
||||
"y": "-4.48661",
|
||||
"width": "29.2984",
|
||||
"height": "27.3739",
|
||||
"filterUnits": "userSpaceOnUse",
|
||||
"color-interpolation-filters": "sRGB"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "feFlood",
|
||||
"attributes": {
|
||||
"flood-opacity": "0",
|
||||
"result": "BackgroundImageFix"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "feBlend",
|
||||
"attributes": {
|
||||
"mode": "normal",
|
||||
"in": "SourceGraphic",
|
||||
"in2": "BackgroundImageFix",
|
||||
"result": "shape"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "feGaussianBlur",
|
||||
"attributes": {
|
||||
"stdDeviation": "4.25649",
|
||||
"result": "effect1_foregroundBlur_3892_95663"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "filter",
|
||||
"attributes": {
|
||||
"id": "filter9_f_3892_95663",
|
||||
"x": "-2.81919",
|
||||
"y": "-9.62339",
|
||||
"width": "34.8122",
|
||||
"height": "34.143",
|
||||
"filterUnits": "userSpaceOnUse",
|
||||
"color-interpolation-filters": "sRGB"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "feFlood",
|
||||
"attributes": {
|
||||
"flood-opacity": "0",
|
||||
"result": "BackgroundImageFix"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "feBlend",
|
||||
"attributes": {
|
||||
"mode": "normal",
|
||||
"in": "SourceGraphic",
|
||||
"in2": "BackgroundImageFix",
|
||||
"result": "shape"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "feGaussianBlur",
|
||||
"attributes": {
|
||||
"stdDeviation": "3.59514",
|
||||
"result": "effect1_foregroundBlur_3892_95663"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "filter",
|
||||
"attributes": {
|
||||
"id": "filter10_f_3892_95663",
|
||||
"x": "-2.73761",
|
||||
"y": "12.4221",
|
||||
"width": "29.1949",
|
||||
"height": "27.4994",
|
||||
"filterUnits": "userSpaceOnUse",
|
||||
"color-interpolation-filters": "sRGB"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "feFlood",
|
||||
"attributes": {
|
||||
"flood-opacity": "0",
|
||||
"result": "BackgroundImageFix"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "feBlend",
|
||||
"attributes": {
|
||||
"mode": "normal",
|
||||
"in": "SourceGraphic",
|
||||
"in2": "BackgroundImageFix",
|
||||
"result": "shape"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "feGaussianBlur",
|
||||
"attributes": {
|
||||
"stdDeviation": "4.44986",
|
||||
"result": "effect1_foregroundBlur_3892_95663"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "linearGradient",
|
||||
"attributes": {
|
||||
"id": "paint0_linear_3892_95663",
|
||||
"x1": "13.9595",
|
||||
"y1": "24.7349",
|
||||
"x2": "28.5025",
|
||||
"y2": "12.4738",
|
||||
"gradientUnits": "userSpaceOnUse"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "stop",
|
||||
"attributes": {
|
||||
"stop-color": "#4893FC"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "stop",
|
||||
"attributes": {
|
||||
"offset": "0.27",
|
||||
"stop-color": "#4893FC"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "stop",
|
||||
"attributes": {
|
||||
"offset": "0.777",
|
||||
"stop-color": "#969DFF"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "stop",
|
||||
"attributes": {
|
||||
"offset": "1",
|
||||
"stop-color": "#BD99FE"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "Gemini"
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
// GENERATE BY script
|
||||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import type { IconData } from '@/app/components/base/icons/IconBase'
|
||||
import * as React from 'react'
|
||||
import IconBase from '@/app/components/base/icons/IconBase'
|
||||
import data from './Gemini.json'
|
||||
|
||||
const Icon = (
|
||||
{
|
||||
ref,
|
||||
...props
|
||||
}: React.SVGProps<SVGSVGElement> & {
|
||||
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>
|
||||
},
|
||||
) => <IconBase {...props} ref={ref} data={data as IconData} />
|
||||
|
||||
Icon.displayName = 'Gemini'
|
||||
|
||||
export default Icon
|
||||
@ -1,72 +0,0 @@
|
||||
{
|
||||
"icon": {
|
||||
"type": "element",
|
||||
"isRootNode": true,
|
||||
"name": "svg",
|
||||
"attributes": {
|
||||
"width": "40",
|
||||
"height": "40",
|
||||
"viewBox": "0 0 40 40",
|
||||
"fill": "none",
|
||||
"xmlns": "http://www.w3.org/2000/svg"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "rect",
|
||||
"attributes": {
|
||||
"width": "40",
|
||||
"height": "40",
|
||||
"fill": "white"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "g",
|
||||
"attributes": {
|
||||
"clip-path": "url(#clip0_3892_95659)"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M15.745 24.54L26.715 16.35C27.254 15.95 28.022 16.106 28.279 16.73C29.628 20.018 29.025 23.971 26.341 26.685C23.658 29.399 19.924 29.995 16.511 28.639L12.783 30.384C18.13 34.081 24.623 33.166 28.681 29.06C31.9 25.805 32.897 21.368 31.965 17.367L31.973 17.376C30.622 11.498 32.305 9.149 35.755 4.345L36 4L31.46 8.59V8.576L15.743 24.544M13.48 26.531C9.643 22.824 10.305 17.085 13.58 13.776C16 11.327 19.968 10.328 23.432 11.797L27.152 10.06C26.482 9.57 25.622 9.043 24.637 8.673C20.182 6.819 14.848 7.742 11.227 11.401C7.744 14.924 6.648 20.341 8.53 24.962C9.935 28.416 7.631 30.86 5.31 33.326C4.49 34.2 3.666 35.074 3 36L13.478 26.534",
|
||||
"fill": "black"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "defs",
|
||||
"attributes": {},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "clipPath",
|
||||
"attributes": {
|
||||
"id": "clip0_3892_95659"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "rect",
|
||||
"attributes": {
|
||||
"width": "33",
|
||||
"height": "32",
|
||||
"fill": "white",
|
||||
"transform": "translate(3 4)"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "Grok"
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
// GENERATE BY script
|
||||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import type { IconData } from '@/app/components/base/icons/IconBase'
|
||||
import * as React from 'react'
|
||||
import IconBase from '@/app/components/base/icons/IconBase'
|
||||
import data from './Grok.json'
|
||||
|
||||
const Icon = (
|
||||
{
|
||||
ref,
|
||||
...props
|
||||
}: React.SVGProps<SVGSVGElement> & {
|
||||
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>
|
||||
},
|
||||
) => <IconBase {...props} ref={ref} data={data as IconData} />
|
||||
|
||||
Icon.displayName = 'Grok'
|
||||
|
||||
export default Icon
|
||||
File diff suppressed because one or more lines are too long
@ -1,20 +0,0 @@
|
||||
// GENERATE BY script
|
||||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import type { IconData } from '@/app/components/base/icons/IconBase'
|
||||
import * as React from 'react'
|
||||
import IconBase from '@/app/components/base/icons/IconBase'
|
||||
import data from './OpenaiBlue.json'
|
||||
|
||||
const Icon = (
|
||||
{
|
||||
ref,
|
||||
...props
|
||||
}: React.SVGProps<SVGSVGElement> & {
|
||||
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>
|
||||
},
|
||||
) => <IconBase {...props} ref={ref} data={data as IconData} />
|
||||
|
||||
Icon.displayName = 'OpenaiBlue'
|
||||
|
||||
export default Icon
|
||||
@ -1,128 +0,0 @@
|
||||
{
|
||||
"icon": {
|
||||
"type": "element",
|
||||
"isRootNode": true,
|
||||
"name": "svg",
|
||||
"attributes": {
|
||||
"width": "26",
|
||||
"height": "26",
|
||||
"viewBox": "0 0 26 26",
|
||||
"fill": "none",
|
||||
"xmlns": "http://www.w3.org/2000/svg",
|
||||
"xmlns:xlink": "http://www.w3.org/1999/xlink"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "g",
|
||||
"attributes": {
|
||||
"clip-path": "url(#clip0_3892_83671)"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M1 13C1 9.27247 1 7.4087 1.60896 5.93853C2.42092 3.97831 3.97831 2.42092 5.93853 1.60896C7.4087 1 9.27247 1 13 1C16.7275 1 18.5913 1 20.0615 1.60896C22.0217 2.42092 23.5791 3.97831 24.391 5.93853C25 7.4087 25 9.27247 25 13C25 16.7275 25 18.5913 24.391 20.0615C23.5791 22.0217 22.0217 23.5791 20.0615 24.391C18.5913 25 16.7275 25 13 25C9.27247 25 7.4087 25 5.93853 24.391C3.97831 23.5791 2.42092 22.0217 1.60896 20.0615C1 18.5913 1 16.7275 1 13Z",
|
||||
"fill": "white"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "rect",
|
||||
"attributes": {
|
||||
"width": "24",
|
||||
"height": "24",
|
||||
"transform": "translate(1 1)",
|
||||
"fill": "url(#pattern0_3892_83671)"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "rect",
|
||||
"attributes": {
|
||||
"width": "24",
|
||||
"height": "24",
|
||||
"transform": "translate(1 1)",
|
||||
"fill": "white",
|
||||
"fill-opacity": "0.01"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M13 0.75C14.8603 0.75 16.2684 0.750313 17.3945 0.827148C18.5228 0.904144 19.3867 1.05876 20.1572 1.37793C22.1787 2.21525 23.7847 3.82133 24.6221 5.84277C24.9412 6.61333 25.0959 7.47723 25.1729 8.60547C25.2497 9.73161 25.25 11.1397 25.25 13C25.25 14.8603 25.2497 16.2684 25.1729 17.3945C25.0959 18.5228 24.9412 19.3867 24.6221 20.1572C23.7847 22.1787 22.1787 23.7847 20.1572 24.6221C19.3867 24.9412 18.5228 25.0959 17.3945 25.1729C16.2684 25.2497 14.8603 25.25 13 25.25C11.1397 25.25 9.73161 25.2497 8.60547 25.1729C7.47723 25.0959 6.61333 24.9412 5.84277 24.6221C3.82133 23.7847 2.21525 22.1787 1.37793 20.1572C1.05876 19.3867 0.904144 18.5228 0.827148 17.3945C0.750313 16.2684 0.75 14.8603 0.75 13C0.75 11.1397 0.750313 9.73161 0.827148 8.60547C0.904144 7.47723 1.05876 6.61333 1.37793 5.84277C2.21525 3.82133 3.82133 2.21525 5.84277 1.37793C6.61333 1.05876 7.47723 0.904144 8.60547 0.827148C9.73161 0.750313 11.1397 0.75 13 0.75Z",
|
||||
"stroke": "#101828",
|
||||
"stroke-opacity": "0.08",
|
||||
"stroke-width": "0.5"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "defs",
|
||||
"attributes": {},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "pattern",
|
||||
"attributes": {
|
||||
"id": "pattern0_3892_83671",
|
||||
"patternContentUnits": "objectBoundingBox",
|
||||
"width": "1",
|
||||
"height": "1"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "use",
|
||||
"attributes": {
|
||||
"xlink:href": "#image0_3892_83671",
|
||||
"transform": "scale(0.00625)"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "clipPath",
|
||||
"attributes": {
|
||||
"id": "clip0_3892_83671"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M1 13C1 9.27247 1 7.4087 1.60896 5.93853C2.42092 3.97831 3.97831 2.42092 5.93853 1.60896C7.4087 1 9.27247 1 13 1C16.7275 1 18.5913 1 20.0615 1.60896C22.0217 2.42092 23.5791 3.97831 24.391 5.93853C25 7.4087 25 9.27247 25 13C25 16.7275 25 18.5913 24.391 20.0615C23.5791 22.0217 22.0217 23.5791 20.0615 24.391C18.5913 25 16.7275 25 13 25C9.27247 25 7.4087 25 5.93853 24.391C3.97831 23.5791 2.42092 22.0217 1.60896 20.0615C1 18.5913 1 16.7275 1 13Z",
|
||||
"fill": "white"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "image",
|
||||
"attributes": {
|
||||
"id": "image0_3892_83671",
|
||||
"width": "160",
|
||||
"height": "160",
|
||||
"preserveAspectRatio": "none",
|
||||
"xlink:href": "data:image/png;base64,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"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "OpenaiSmall"
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
// GENERATE BY script
|
||||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import type { IconData } from '@/app/components/base/icons/IconBase'
|
||||
import * as React from 'react'
|
||||
import IconBase from '@/app/components/base/icons/IconBase'
|
||||
import data from './OpenaiSmall.json'
|
||||
|
||||
const Icon = (
|
||||
{
|
||||
ref,
|
||||
...props
|
||||
}: React.SVGProps<SVGSVGElement> & {
|
||||
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>
|
||||
},
|
||||
) => <IconBase {...props} ref={ref} data={data as IconData} />
|
||||
|
||||
Icon.displayName = 'OpenaiSmall'
|
||||
|
||||
export default Icon
|
||||
File diff suppressed because one or more lines are too long
@ -1,20 +0,0 @@
|
||||
// GENERATE BY script
|
||||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import type { IconData } from '@/app/components/base/icons/IconBase'
|
||||
import * as React from 'react'
|
||||
import IconBase from '@/app/components/base/icons/IconBase'
|
||||
import data from './OpenaiTeal.json'
|
||||
|
||||
const Icon = (
|
||||
{
|
||||
ref,
|
||||
...props
|
||||
}: React.SVGProps<SVGSVGElement> & {
|
||||
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>
|
||||
},
|
||||
) => <IconBase {...props} ref={ref} data={data as IconData} />
|
||||
|
||||
Icon.displayName = 'OpenaiTeal'
|
||||
|
||||
export default Icon
|
||||
File diff suppressed because one or more lines are too long
@ -1,20 +0,0 @@
|
||||
// GENERATE BY script
|
||||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import type { IconData } from '@/app/components/base/icons/IconBase'
|
||||
import * as React from 'react'
|
||||
import IconBase from '@/app/components/base/icons/IconBase'
|
||||
import data from './OpenaiViolet.json'
|
||||
|
||||
const Icon = (
|
||||
{
|
||||
ref,
|
||||
...props
|
||||
}: React.SVGProps<SVGSVGElement> & {
|
||||
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>
|
||||
},
|
||||
) => <IconBase {...props} ref={ref} data={data as IconData} />
|
||||
|
||||
Icon.displayName = 'OpenaiViolet'
|
||||
|
||||
export default Icon
|
||||
@ -1,128 +0,0 @@
|
||||
{
|
||||
"icon": {
|
||||
"type": "element",
|
||||
"isRootNode": true,
|
||||
"name": "svg",
|
||||
"attributes": {
|
||||
"width": "25",
|
||||
"height": "25",
|
||||
"viewBox": "0 0 25 25",
|
||||
"fill": "none",
|
||||
"xmlns": "http://www.w3.org/2000/svg",
|
||||
"xmlns:xlink": "http://www.w3.org/1999/xlink"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "g",
|
||||
"attributes": {
|
||||
"clip-path": "url(#clip0_6305_73327)"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M0.5 12.5C0.5 8.77247 0.5 6.9087 1.10896 5.43853C1.92092 3.47831 3.47831 1.92092 5.43853 1.10896C6.9087 0.5 8.77247 0.5 12.5 0.5C16.2275 0.5 18.0913 0.5 19.5615 1.10896C21.5217 1.92092 23.0791 3.47831 23.891 5.43853C24.5 6.9087 24.5 8.77247 24.5 12.5C24.5 16.2275 24.5 18.0913 23.891 19.5615C23.0791 21.5217 21.5217 23.0791 19.5615 23.891C18.0913 24.5 16.2275 24.5 12.5 24.5C8.77247 24.5 6.9087 24.5 5.43853 23.891C3.47831 23.0791 1.92092 21.5217 1.10896 19.5615C0.5 18.0913 0.5 16.2275 0.5 12.5Z",
|
||||
"fill": "white"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "rect",
|
||||
"attributes": {
|
||||
"width": "24",
|
||||
"height": "24",
|
||||
"transform": "translate(0.5 0.5)",
|
||||
"fill": "url(#pattern0_6305_73327)"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "rect",
|
||||
"attributes": {
|
||||
"width": "24",
|
||||
"height": "24",
|
||||
"transform": "translate(0.5 0.5)",
|
||||
"fill": "white",
|
||||
"fill-opacity": "0.01"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M12.5 0.25C14.3603 0.25 15.7684 0.250313 16.8945 0.327148C18.0228 0.404144 18.8867 0.558755 19.6572 0.87793C21.6787 1.71525 23.2847 3.32133 24.1221 5.34277C24.4412 6.11333 24.5959 6.97723 24.6729 8.10547C24.7497 9.23161 24.75 10.6397 24.75 12.5C24.75 14.3603 24.7497 15.7684 24.6729 16.8945C24.5959 18.0228 24.4412 18.8867 24.1221 19.6572C23.2847 21.6787 21.6787 23.2847 19.6572 24.1221C18.8867 24.4412 18.0228 24.5959 16.8945 24.6729C15.7684 24.7497 14.3603 24.75 12.5 24.75C10.6397 24.75 9.23161 24.7497 8.10547 24.6729C6.97723 24.5959 6.11333 24.4412 5.34277 24.1221C3.32133 23.2847 1.71525 21.6787 0.87793 19.6572C0.558755 18.8867 0.404144 18.0228 0.327148 16.8945C0.250313 15.7684 0.25 14.3603 0.25 12.5C0.25 10.6397 0.250313 9.23161 0.327148 8.10547C0.404144 6.97723 0.558755 6.11333 0.87793 5.34277C1.71525 3.32133 3.32133 1.71525 5.34277 0.87793C6.11333 0.558755 6.97723 0.404144 8.10547 0.327148C9.23161 0.250313 10.6397 0.25 12.5 0.25Z",
|
||||
"stroke": "#101828",
|
||||
"stroke-opacity": "0.08",
|
||||
"stroke-width": "0.5"
|
||||
},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "defs",
|
||||
"attributes": {},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "pattern",
|
||||
"attributes": {
|
||||
"id": "pattern0_6305_73327",
|
||||
"patternContentUnits": "objectBoundingBox",
|
||||
"width": "1",
|
||||
"height": "1"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "use",
|
||||
"attributes": {
|
||||
"xlink:href": "#image0_6305_73327",
|
||||
"transform": "scale(0.00625)"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "clipPath",
|
||||
"attributes": {
|
||||
"id": "clip0_6305_73327"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
"name": "path",
|
||||
"attributes": {
|
||||
"d": "M0.5 12.5C0.5 8.77247 0.5 6.9087 1.10896 5.43853C1.92092 3.47831 3.47831 1.92092 5.43853 1.10896C6.9087 0.5 8.77247 0.5 12.5 0.5C16.2275 0.5 18.0913 0.5 19.5615 1.10896C21.5217 1.92092 23.0791 3.47831 23.891 5.43853C24.5 6.9087 24.5 8.77247 24.5 12.5C24.5 16.2275 24.5 18.0913 23.891 19.5615C23.0791 21.5217 21.5217 23.0791 19.5615 23.891C18.0913 24.5 16.2275 24.5 12.5 24.5C8.77247 24.5 6.9087 24.5 5.43853 23.891C3.47831 23.0791 1.92092 21.5217 1.10896 19.5615C0.5 18.0913 0.5 16.2275 0.5 12.5Z",
|
||||
"fill": "white"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "element",
|
||||
"name": "image",
|
||||
"attributes": {
|
||||
"id": "image0_6305_73327",
|
||||
"width": "160",
|
||||
"height": "160",
|
||||
"preserveAspectRatio": "none",
|
||||
"xlink:href": "data:image/png;base64,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"
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"name": "Tongyi"
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
// GENERATE BY script
|
||||
// DON NOT EDIT IT MANUALLY
|
||||
|
||||
import type { IconData } from '@/app/components/base/icons/IconBase'
|
||||
import * as React from 'react'
|
||||
import IconBase from '@/app/components/base/icons/IconBase'
|
||||
import data from './Tongyi.json'
|
||||
|
||||
const Icon = (
|
||||
{
|
||||
ref,
|
||||
...props
|
||||
}: React.SVGProps<SVGSVGElement> & {
|
||||
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>
|
||||
},
|
||||
) => <IconBase {...props} ref={ref} data={data as IconData} />
|
||||
|
||||
Icon.displayName = 'Tongyi'
|
||||
|
||||
export default Icon
|
||||
@ -1,7 +1,6 @@
|
||||
export { default as Anthropic } from './Anthropic'
|
||||
export { default as AnthropicDark } from './AnthropicDark'
|
||||
export { default as AnthropicLight } from './AnthropicLight'
|
||||
export { default as AnthropicShortLight } from './AnthropicShortLight'
|
||||
export { default as AnthropicText } from './AnthropicText'
|
||||
export { default as Azureai } from './Azureai'
|
||||
export { default as AzureaiText } from './AzureaiText'
|
||||
@ -13,11 +12,8 @@ export { default as Chatglm } from './Chatglm'
|
||||
export { default as ChatglmText } from './ChatglmText'
|
||||
export { default as Cohere } from './Cohere'
|
||||
export { default as CohereText } from './CohereText'
|
||||
export { default as Deepseek } from './Deepseek'
|
||||
export { default as Gemini } from './Gemini'
|
||||
export { default as Gpt3 } from './Gpt3'
|
||||
export { default as Gpt4 } from './Gpt4'
|
||||
export { default as Grok } from './Grok'
|
||||
export { default as Huggingface } from './Huggingface'
|
||||
export { default as HuggingfaceText } from './HuggingfaceText'
|
||||
export { default as HuggingfaceTextHub } from './HuggingfaceTextHub'
|
||||
@ -30,19 +26,14 @@ export { default as Localai } from './Localai'
|
||||
export { default as LocalaiText } from './LocalaiText'
|
||||
export { default as Microsoft } from './Microsoft'
|
||||
export { default as OpenaiBlack } from './OpenaiBlack'
|
||||
export { default as OpenaiBlue } from './OpenaiBlue'
|
||||
export { default as OpenaiGreen } from './OpenaiGreen'
|
||||
export { default as OpenaiSmall } from './OpenaiSmall'
|
||||
export { default as OpenaiTeal } from './OpenaiTeal'
|
||||
export { default as OpenaiText } from './OpenaiText'
|
||||
export { default as OpenaiTransparent } from './OpenaiTransparent'
|
||||
export { default as OpenaiViolet } from './OpenaiViolet'
|
||||
export { default as OpenaiYellow } from './OpenaiYellow'
|
||||
export { default as Openllm } from './Openllm'
|
||||
export { default as OpenllmText } from './OpenllmText'
|
||||
export { default as Replicate } from './Replicate'
|
||||
export { default as ReplicateText } from './ReplicateText'
|
||||
export { default as Tongyi } from './Tongyi'
|
||||
export { default as XorbitsInference } from './XorbitsInference'
|
||||
export { default as XorbitsInferenceText } from './XorbitsInferenceText'
|
||||
export { default as Zhipuai } from './Zhipuai'
|
||||
|
||||
@ -11,7 +11,7 @@ const Icon = (
|
||||
ref,
|
||||
...props
|
||||
}: React.SVGProps<SVGSVGElement> & {
|
||||
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>
|
||||
ref?: React.RefObject<React.MutableRefObject<HTMLOrSVGElement>>
|
||||
},
|
||||
) => <IconBase {...props} ref={ref} data={data as IconData} />
|
||||
|
||||
|
||||
@ -11,7 +11,7 @@ const Icon = (
|
||||
ref,
|
||||
...props
|
||||
}: React.SVGProps<SVGSVGElement> & {
|
||||
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>
|
||||
ref?: React.RefObject<React.MutableRefObject<HTMLOrSVGElement>>
|
||||
},
|
||||
) => <IconBase {...props} ref={ref} data={data as IconData} />
|
||||
|
||||
|
||||
@ -11,7 +11,7 @@ const Icon = (
|
||||
ref,
|
||||
...props
|
||||
}: React.SVGProps<SVGSVGElement> & {
|
||||
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>
|
||||
ref?: React.RefObject<React.MutableRefObject<HTMLOrSVGElement>>
|
||||
},
|
||||
) => <IconBase {...props} ref={ref} data={data as IconData} />
|
||||
|
||||
|
||||
@ -11,7 +11,7 @@ const Icon = (
|
||||
ref,
|
||||
...props
|
||||
}: React.SVGProps<SVGSVGElement> & {
|
||||
ref?: React.RefObject<React.RefObject<HTMLOrSVGElement>>
|
||||
ref?: React.RefObject<React.MutableRefObject<HTMLOrSVGElement>>
|
||||
},
|
||||
) => <IconBase {...props} ref={ref} data={data as IconData} />
|
||||
|
||||
|
||||
@ -1,16 +1,14 @@
|
||||
{
|
||||
"icon": {
|
||||
"type": "element",
|
||||
"isRootNode": true,
|
||||
"name": "svg",
|
||||
"attributes": {
|
||||
"width": "80px",
|
||||
"height": "18px",
|
||||
"viewBox": "0 0 80 18",
|
||||
"version": "1.1",
|
||||
"xmlns": "http://www.w3.org/2000/svg",
|
||||
"xmlns:xlink": "http://www.w3.org/1999/xlink"
|
||||
"version": "1.1"
|
||||
},
|
||||
"isRootNode": true,
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
|
||||
@ -1,16 +1,14 @@
|
||||
{
|
||||
"icon": {
|
||||
"type": "element",
|
||||
"isRootNode": true,
|
||||
"name": "svg",
|
||||
"attributes": {
|
||||
"width": "120px",
|
||||
"height": "27px",
|
||||
"width": "80px",
|
||||
"height": "18px",
|
||||
"viewBox": "0 0 80 18",
|
||||
"version": "1.1",
|
||||
"xmlns": "http://www.w3.org/2000/svg",
|
||||
"xmlns:xlink": "http://www.w3.org/1999/xlink"
|
||||
"version": "1.1"
|
||||
},
|
||||
"isRootNode": true,
|
||||
"children": [
|
||||
{
|
||||
"type": "element",
|
||||
|
||||
@ -75,9 +75,6 @@ const buildAppContext = (overrides: Partial<AppContextValue> = {}): AppContextVa
|
||||
created_at: 0,
|
||||
role: 'normal',
|
||||
providers: [],
|
||||
trial_credits: 200,
|
||||
trial_credits_used: 0,
|
||||
next_credit_reset_date: 0,
|
||||
}
|
||||
const langGeniusVersionInfo: LangGeniusVersionResponse = {
|
||||
current_env: '',
|
||||
@ -99,7 +96,6 @@ const buildAppContext = (overrides: Partial<AppContextValue> = {}): AppContextVa
|
||||
mutateCurrentWorkspace: vi.fn(),
|
||||
langGeniusVersionInfo,
|
||||
isLoadingCurrentWorkspace: false,
|
||||
isValidatingCurrentWorkspace: false,
|
||||
}
|
||||
const useSelector: AppContextValue['useSelector'] = selector => selector({ ...base, useSelector })
|
||||
return {
|
||||
|
||||
@ -0,0 +1,97 @@
|
||||
'use client'
|
||||
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { ENABLE_WEBSITE_FIRECRAWL, ENABLE_WEBSITE_JINAREADER, ENABLE_WEBSITE_WATERCRAWL } from '@/config'
|
||||
import { DataSourceType } from '@/models/datasets'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import s from '../index.module.css'
|
||||
|
||||
type DataSourceTypeSelectorProps = {
|
||||
currentType: DataSourceType
|
||||
disabled: boolean
|
||||
onChange: (type: DataSourceType) => void
|
||||
onClearPreviews: (type: DataSourceType) => void
|
||||
}
|
||||
|
||||
type DataSourceLabelKey
|
||||
= | 'stepOne.dataSourceType.file'
|
||||
| 'stepOne.dataSourceType.notion'
|
||||
| 'stepOne.dataSourceType.web'
|
||||
|
||||
type DataSourceOption = {
|
||||
type: DataSourceType
|
||||
iconClass?: string
|
||||
labelKey: DataSourceLabelKey
|
||||
}
|
||||
|
||||
const DATA_SOURCE_OPTIONS: DataSourceOption[] = [
|
||||
{
|
||||
type: DataSourceType.FILE,
|
||||
labelKey: 'stepOne.dataSourceType.file',
|
||||
},
|
||||
{
|
||||
type: DataSourceType.NOTION,
|
||||
iconClass: s.notion,
|
||||
labelKey: 'stepOne.dataSourceType.notion',
|
||||
},
|
||||
{
|
||||
type: DataSourceType.WEB,
|
||||
iconClass: s.web,
|
||||
labelKey: 'stepOne.dataSourceType.web',
|
||||
},
|
||||
]
|
||||
|
||||
/**
|
||||
* Data source type selector component for choosing between file, notion, and web sources.
|
||||
*/
|
||||
function DataSourceTypeSelector({
|
||||
currentType,
|
||||
disabled,
|
||||
onChange,
|
||||
onClearPreviews,
|
||||
}: DataSourceTypeSelectorProps) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const isWebEnabled = ENABLE_WEBSITE_FIRECRAWL || ENABLE_WEBSITE_JINAREADER || ENABLE_WEBSITE_WATERCRAWL
|
||||
|
||||
const handleTypeChange = useCallback((type: DataSourceType) => {
|
||||
if (disabled)
|
||||
return
|
||||
onChange(type)
|
||||
onClearPreviews(type)
|
||||
}, [disabled, onChange, onClearPreviews])
|
||||
|
||||
const visibleOptions = useMemo(() => DATA_SOURCE_OPTIONS.filter((option) => {
|
||||
if (option.type === DataSourceType.WEB)
|
||||
return isWebEnabled
|
||||
return true
|
||||
}), [isWebEnabled])
|
||||
|
||||
return (
|
||||
<div className="mb-8 grid grid-cols-3 gap-4">
|
||||
{visibleOptions.map(option => (
|
||||
<div
|
||||
key={option.type}
|
||||
className={cn(
|
||||
s.dataSourceItem,
|
||||
'system-sm-medium',
|
||||
currentType === option.type && s.active,
|
||||
disabled && currentType !== option.type && s.disabled,
|
||||
)}
|
||||
onClick={() => handleTypeChange(option.type)}
|
||||
>
|
||||
<span className={cn(s.datasetIcon, option.iconClass)} />
|
||||
<span
|
||||
title={t(option.labelKey, { ns: 'datasetCreation' }) || undefined}
|
||||
className="truncate"
|
||||
>
|
||||
{t(option.labelKey, { ns: 'datasetCreation' })}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default DataSourceTypeSelector
|
||||
@ -0,0 +1,3 @@
|
||||
export { default as DataSourceTypeSelector } from './data-source-type-selector'
|
||||
export { default as NextStepButton } from './next-step-button'
|
||||
export { default as PreviewPanel } from './preview-panel'
|
||||
@ -0,0 +1,30 @@
|
||||
'use client'
|
||||
|
||||
import { RiArrowRightLine } from '@remixicon/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Button from '@/app/components/base/button'
|
||||
|
||||
type NextStepButtonProps = {
|
||||
disabled: boolean
|
||||
onClick: () => void
|
||||
}
|
||||
|
||||
/**
|
||||
* Reusable next step button component for dataset creation flow.
|
||||
*/
|
||||
function NextStepButton({ disabled, onClick }: NextStepButtonProps) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className="flex max-w-[640px] justify-end gap-2">
|
||||
<Button disabled={disabled} variant="primary" onClick={onClick}>
|
||||
<span className="flex gap-0.5 px-[10px]">
|
||||
<span className="px-0.5">{t('stepOne.button', { ns: 'datasetCreation' })}</span>
|
||||
<RiArrowRightLine className="size-4" />
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default NextStepButton
|
||||
@ -0,0 +1,62 @@
|
||||
'use client'
|
||||
|
||||
import type { NotionPage } from '@/models/common'
|
||||
import type { CrawlResultItem } from '@/models/datasets'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import PlanUpgradeModal from '@/app/components/billing/plan-upgrade-modal'
|
||||
import FilePreview from '../../file-preview'
|
||||
import NotionPagePreview from '../../notion-page-preview'
|
||||
import WebsitePreview from '../../website/preview'
|
||||
|
||||
type PreviewPanelProps = {
|
||||
currentFile: File | undefined
|
||||
currentNotionPage: NotionPage | undefined
|
||||
currentWebsite: CrawlResultItem | undefined
|
||||
notionCredentialId: string
|
||||
isShowPlanUpgradeModal: boolean
|
||||
hideFilePreview: () => void
|
||||
hideNotionPagePreview: () => void
|
||||
hideWebsitePreview: () => void
|
||||
hidePlanUpgradeModal: () => void
|
||||
}
|
||||
|
||||
/**
|
||||
* Right panel component for displaying file, notion page, or website previews.
|
||||
*/
|
||||
function PreviewPanel({
|
||||
currentFile,
|
||||
currentNotionPage,
|
||||
currentWebsite,
|
||||
notionCredentialId,
|
||||
isShowPlanUpgradeModal,
|
||||
hideFilePreview,
|
||||
hideNotionPagePreview,
|
||||
hideWebsitePreview,
|
||||
hidePlanUpgradeModal,
|
||||
}: PreviewPanelProps) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className="h-full w-1/2 overflow-y-auto">
|
||||
{currentFile && <FilePreview file={currentFile} hidePreview={hideFilePreview} />}
|
||||
{currentNotionPage && (
|
||||
<NotionPagePreview
|
||||
currentPage={currentNotionPage}
|
||||
hidePreview={hideNotionPagePreview}
|
||||
notionCredentialId={notionCredentialId}
|
||||
/>
|
||||
)}
|
||||
{currentWebsite && <WebsitePreview payload={currentWebsite} hidePreview={hideWebsitePreview} />}
|
||||
{isShowPlanUpgradeModal && (
|
||||
<PlanUpgradeModal
|
||||
show
|
||||
onClose={hidePlanUpgradeModal}
|
||||
title={t('upgrade.uploadMultiplePages.title', { ns: 'billing' })!}
|
||||
description={t('upgrade.uploadMultiplePages.description', { ns: 'billing' })!}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default PreviewPanel
|
||||
@ -0,0 +1,2 @@
|
||||
export { default as usePreviewState } from './use-preview-state'
|
||||
export type { PreviewActions, PreviewState, UsePreviewStateReturn } from './use-preview-state'
|
||||
@ -0,0 +1,70 @@
|
||||
'use client'
|
||||
|
||||
import type { NotionPage } from '@/models/common'
|
||||
import type { CrawlResultItem } from '@/models/datasets'
|
||||
import { useCallback, useState } from 'react'
|
||||
|
||||
export type PreviewState = {
|
||||
currentFile: File | undefined
|
||||
currentNotionPage: NotionPage | undefined
|
||||
currentWebsite: CrawlResultItem | undefined
|
||||
}
|
||||
|
||||
export type PreviewActions = {
|
||||
showFilePreview: (file: File) => void
|
||||
hideFilePreview: () => void
|
||||
showNotionPagePreview: (page: NotionPage) => void
|
||||
hideNotionPagePreview: () => void
|
||||
showWebsitePreview: (website: CrawlResultItem) => void
|
||||
hideWebsitePreview: () => void
|
||||
}
|
||||
|
||||
export type UsePreviewStateReturn = PreviewState & PreviewActions
|
||||
|
||||
/**
|
||||
* Custom hook for managing preview state across different data source types.
|
||||
* Handles file, notion page, and website preview visibility.
|
||||
*/
|
||||
function usePreviewState(): UsePreviewStateReturn {
|
||||
const [currentFile, setCurrentFile] = useState<File | undefined>()
|
||||
const [currentNotionPage, setCurrentNotionPage] = useState<NotionPage | undefined>()
|
||||
const [currentWebsite, setCurrentWebsite] = useState<CrawlResultItem | undefined>()
|
||||
|
||||
const showFilePreview = useCallback((file: File) => {
|
||||
setCurrentFile(file)
|
||||
}, [])
|
||||
|
||||
const hideFilePreview = useCallback(() => {
|
||||
setCurrentFile(undefined)
|
||||
}, [])
|
||||
|
||||
const showNotionPagePreview = useCallback((page: NotionPage) => {
|
||||
setCurrentNotionPage(page)
|
||||
}, [])
|
||||
|
||||
const hideNotionPagePreview = useCallback(() => {
|
||||
setCurrentNotionPage(undefined)
|
||||
}, [])
|
||||
|
||||
const showWebsitePreview = useCallback((website: CrawlResultItem) => {
|
||||
setCurrentWebsite(website)
|
||||
}, [])
|
||||
|
||||
const hideWebsitePreview = useCallback(() => {
|
||||
setCurrentWebsite(undefined)
|
||||
}, [])
|
||||
|
||||
return {
|
||||
currentFile,
|
||||
currentNotionPage,
|
||||
currentWebsite,
|
||||
showFilePreview,
|
||||
hideFilePreview,
|
||||
showNotionPagePreview,
|
||||
hideNotionPagePreview,
|
||||
showWebsitePreview,
|
||||
hideWebsitePreview,
|
||||
}
|
||||
}
|
||||
|
||||
export default usePreviewState
|
||||
1204
web/app/components/datasets/create/step-one/index.spec.tsx
Normal file
1204
web/app/components/datasets/create/step-one/index.spec.tsx
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,29 +1,25 @@
|
||||
'use client'
|
||||
|
||||
import type { DataSourceAuth } from '@/app/components/header/account-setting/data-source-page-new/types'
|
||||
import type { DataSourceProvider, NotionPage } from '@/models/common'
|
||||
import type { CrawlOptions, CrawlResultItem, FileItem } from '@/models/datasets'
|
||||
import { RiArrowRightLine, RiFolder6Line } from '@remixicon/react'
|
||||
import { RiFolder6Line } from '@remixicon/react'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import * as React from 'react'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import { useCallback, useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Button from '@/app/components/base/button'
|
||||
import NotionConnector from '@/app/components/base/notion-connector'
|
||||
import { NotionPageSelector } from '@/app/components/base/notion-page-selector'
|
||||
import PlanUpgradeModal from '@/app/components/billing/plan-upgrade-modal'
|
||||
import { Plan } from '@/app/components/billing/type'
|
||||
import VectorSpaceFull from '@/app/components/billing/vector-space-full'
|
||||
import { ENABLE_WEBSITE_FIRECRAWL, ENABLE_WEBSITE_JINAREADER, ENABLE_WEBSITE_WATERCRAWL } from '@/config'
|
||||
import { useDatasetDetailContextWithSelector } from '@/context/dataset-detail'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { DataSourceType } from '@/models/datasets'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import EmptyDatasetCreationModal from '../empty-dataset-creation-modal'
|
||||
import FilePreview from '../file-preview'
|
||||
import FileUploader from '../file-uploader'
|
||||
import NotionPagePreview from '../notion-page-preview'
|
||||
import Website from '../website'
|
||||
import WebsitePreview from '../website/preview'
|
||||
import { DataSourceTypeSelector, NextStepButton, PreviewPanel } from './components'
|
||||
import { usePreviewState } from './hooks'
|
||||
import s from './index.module.css'
|
||||
import UpgradeCard from './upgrade-card'
|
||||
|
||||
@ -50,6 +46,24 @@ type IStepOneProps = {
|
||||
authedDataSourceList: DataSourceAuth[]
|
||||
}
|
||||
|
||||
// Helper function to check if notion is authenticated
|
||||
function checkNotionAuth(authedDataSourceList: DataSourceAuth[]): boolean {
|
||||
const notionSource = authedDataSourceList.find(item => item.provider === 'notion_datasource')
|
||||
return Boolean(notionSource && notionSource.credentials_list.length > 0)
|
||||
}
|
||||
|
||||
// Helper function to get notion credential list
|
||||
function getNotionCredentialList(authedDataSourceList: DataSourceAuth[]) {
|
||||
return authedDataSourceList.find(item => item.provider === 'notion_datasource')?.credentials_list || []
|
||||
}
|
||||
|
||||
// Lookup table for checking multiple items by data source type
|
||||
const MULTIPLE_ITEMS_CHECK: Record<DataSourceType, (props: { files: FileItem[], notionPages: NotionPage[], websitePages: CrawlResultItem[] }) => boolean> = {
|
||||
[DataSourceType.FILE]: ({ files }) => files.length > 1,
|
||||
[DataSourceType.NOTION]: ({ notionPages }) => notionPages.length > 1,
|
||||
[DataSourceType.WEB]: ({ websitePages }) => websitePages.length > 1,
|
||||
}
|
||||
|
||||
const StepOne = ({
|
||||
datasetId,
|
||||
dataSourceType: inCreatePageDataSourceType,
|
||||
@ -72,76 +86,47 @@ const StepOne = ({
|
||||
onCrawlOptionsChange,
|
||||
authedDataSourceList,
|
||||
}: IStepOneProps) => {
|
||||
const dataset = useDatasetDetailContextWithSelector(state => state.dataset)
|
||||
const [showModal, setShowModal] = useState(false)
|
||||
const [currentFile, setCurrentFile] = useState<File | undefined>()
|
||||
const [currentNotionPage, setCurrentNotionPage] = useState<NotionPage | undefined>()
|
||||
const [currentWebsite, setCurrentWebsite] = useState<CrawlResultItem | undefined>()
|
||||
const { t } = useTranslation()
|
||||
const dataset = useDatasetDetailContextWithSelector(state => state.dataset)
|
||||
const { plan, enableBilling } = useProviderContext()
|
||||
|
||||
const modalShowHandle = () => setShowModal(true)
|
||||
const modalCloseHandle = () => setShowModal(false)
|
||||
// Preview state management
|
||||
const {
|
||||
currentFile,
|
||||
currentNotionPage,
|
||||
currentWebsite,
|
||||
showFilePreview,
|
||||
hideFilePreview,
|
||||
showNotionPagePreview,
|
||||
hideNotionPagePreview,
|
||||
showWebsitePreview,
|
||||
hideWebsitePreview,
|
||||
} = usePreviewState()
|
||||
|
||||
const updateCurrentFile = useCallback((file: File) => {
|
||||
setCurrentFile(file)
|
||||
}, [])
|
||||
// Empty dataset modal state
|
||||
const [showModal, { setTrue: openModal, setFalse: closeModal }] = useBoolean(false)
|
||||
|
||||
const hideFilePreview = useCallback(() => {
|
||||
setCurrentFile(undefined)
|
||||
}, [])
|
||||
|
||||
const updateCurrentPage = useCallback((page: NotionPage) => {
|
||||
setCurrentNotionPage(page)
|
||||
}, [])
|
||||
|
||||
const hideNotionPagePreview = useCallback(() => {
|
||||
setCurrentNotionPage(undefined)
|
||||
}, [])
|
||||
|
||||
const updateWebsite = useCallback((website: CrawlResultItem) => {
|
||||
setCurrentWebsite(website)
|
||||
}, [])
|
||||
|
||||
const hideWebsitePreview = useCallback(() => {
|
||||
setCurrentWebsite(undefined)
|
||||
}, [])
|
||||
// Plan upgrade modal state
|
||||
const [isShowPlanUpgradeModal, { setTrue: showPlanUpgradeModal, setFalse: hidePlanUpgradeModal }] = useBoolean(false)
|
||||
|
||||
// Computed values
|
||||
const shouldShowDataSourceTypeList = !datasetId || (datasetId && !dataset?.data_source_type)
|
||||
const isInCreatePage = shouldShowDataSourceTypeList
|
||||
const dataSourceType = isInCreatePage ? inCreatePageDataSourceType : dataset?.data_source_type
|
||||
const { plan, enableBilling } = useProviderContext()
|
||||
const allFileLoaded = (files.length > 0 && files.every(file => file.file.id))
|
||||
const hasNotin = notionPages.length > 0
|
||||
// Default to FILE type when no type is provided from either source
|
||||
const dataSourceType = isInCreatePage
|
||||
? (inCreatePageDataSourceType ?? DataSourceType.FILE)
|
||||
: (dataset?.data_source_type ?? DataSourceType.FILE)
|
||||
|
||||
const allFileLoaded = files.length > 0 && files.every(file => file.file.id)
|
||||
const hasNotion = notionPages.length > 0
|
||||
const isVectorSpaceFull = plan.usage.vectorSpace >= plan.total.vectorSpace
|
||||
const isShowVectorSpaceFull = (allFileLoaded || hasNotin) && isVectorSpaceFull && enableBilling
|
||||
const isShowVectorSpaceFull = (allFileLoaded || hasNotion) && isVectorSpaceFull && enableBilling
|
||||
const supportBatchUpload = !enableBilling || plan.type !== Plan.sandbox
|
||||
const notSupportBatchUpload = !supportBatchUpload
|
||||
|
||||
const [isShowPlanUpgradeModal, {
|
||||
setTrue: showPlanUpgradeModal,
|
||||
setFalse: hidePlanUpgradeModal,
|
||||
}] = useBoolean(false)
|
||||
const onStepChange = useCallback(() => {
|
||||
if (notSupportBatchUpload) {
|
||||
let isMultiple = false
|
||||
if (dataSourceType === DataSourceType.FILE && files.length > 1)
|
||||
isMultiple = true
|
||||
const isNotionAuthed = useMemo(() => checkNotionAuth(authedDataSourceList), [authedDataSourceList])
|
||||
const notionCredentialList = useMemo(() => getNotionCredentialList(authedDataSourceList), [authedDataSourceList])
|
||||
|
||||
if (dataSourceType === DataSourceType.NOTION && notionPages.length > 1)
|
||||
isMultiple = true
|
||||
|
||||
if (dataSourceType === DataSourceType.WEB && websitePages.length > 1)
|
||||
isMultiple = true
|
||||
|
||||
if (isMultiple) {
|
||||
showPlanUpgradeModal()
|
||||
return
|
||||
}
|
||||
}
|
||||
doOnStepChange()
|
||||
}, [dataSourceType, doOnStepChange, files.length, notSupportBatchUpload, notionPages.length, showPlanUpgradeModal, websitePages.length])
|
||||
|
||||
const nextDisabled = useMemo(() => {
|
||||
const fileNextDisabled = useMemo(() => {
|
||||
if (!files.length)
|
||||
return true
|
||||
if (files.some(file => !file.file.id))
|
||||
@ -149,109 +134,50 @@ const StepOne = ({
|
||||
return isShowVectorSpaceFull
|
||||
}, [files, isShowVectorSpaceFull])
|
||||
|
||||
const isNotionAuthed = useMemo(() => {
|
||||
if (!authedDataSourceList)
|
||||
return false
|
||||
const notionSource = authedDataSourceList.find(item => item.provider === 'notion_datasource')
|
||||
if (!notionSource)
|
||||
return false
|
||||
return notionSource.credentials_list.length > 0
|
||||
}, [authedDataSourceList])
|
||||
// Clear previews when switching data source type
|
||||
const handleClearPreviews = useCallback((newType: DataSourceType) => {
|
||||
if (newType !== DataSourceType.FILE)
|
||||
hideFilePreview()
|
||||
if (newType !== DataSourceType.NOTION)
|
||||
hideNotionPagePreview()
|
||||
if (newType !== DataSourceType.WEB)
|
||||
hideWebsitePreview()
|
||||
}, [hideFilePreview, hideNotionPagePreview, hideWebsitePreview])
|
||||
|
||||
const notionCredentialList = useMemo(() => {
|
||||
return authedDataSourceList.find(item => item.provider === 'notion_datasource')?.credentials_list || []
|
||||
}, [authedDataSourceList])
|
||||
// Handle step change with batch upload check
|
||||
const onStepChange = useCallback(() => {
|
||||
if (!supportBatchUpload && dataSourceType) {
|
||||
const checkFn = MULTIPLE_ITEMS_CHECK[dataSourceType]
|
||||
if (checkFn?.({ files, notionPages, websitePages })) {
|
||||
showPlanUpgradeModal()
|
||||
return
|
||||
}
|
||||
}
|
||||
doOnStepChange()
|
||||
}, [dataSourceType, doOnStepChange, files, supportBatchUpload, notionPages, showPlanUpgradeModal, websitePages])
|
||||
|
||||
return (
|
||||
<div className="h-full w-full overflow-x-auto">
|
||||
<div className="flex h-full w-full min-w-[1440px]">
|
||||
{/* Left Panel - Form */}
|
||||
<div className="relative h-full w-1/2 overflow-y-auto">
|
||||
<div className="flex justify-end">
|
||||
<div className={cn(s.form)}>
|
||||
{
|
||||
shouldShowDataSourceTypeList && (
|
||||
{shouldShowDataSourceTypeList && (
|
||||
<>
|
||||
<div className={cn(s.stepHeader, 'system-md-semibold text-text-secondary')}>
|
||||
{t('steps.one', { ns: 'datasetCreation' })}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
shouldShowDataSourceTypeList && (
|
||||
<div className="mb-8 grid grid-cols-3 gap-4">
|
||||
<div
|
||||
className={cn(
|
||||
s.dataSourceItem,
|
||||
'system-sm-medium',
|
||||
dataSourceType === DataSourceType.FILE && s.active,
|
||||
dataSourceTypeDisable && dataSourceType !== DataSourceType.FILE && s.disabled,
|
||||
)}
|
||||
onClick={() => {
|
||||
if (dataSourceTypeDisable)
|
||||
return
|
||||
changeType(DataSourceType.FILE)
|
||||
hideNotionPagePreview()
|
||||
hideWebsitePreview()
|
||||
}}
|
||||
>
|
||||
<span className={cn(s.datasetIcon)} />
|
||||
<span
|
||||
title={t('stepOne.dataSourceType.file', { ns: 'datasetCreation' })!}
|
||||
className="truncate"
|
||||
>
|
||||
{t('stepOne.dataSourceType.file', { ns: 'datasetCreation' })}
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
s.dataSourceItem,
|
||||
'system-sm-medium',
|
||||
dataSourceType === DataSourceType.NOTION && s.active,
|
||||
dataSourceTypeDisable && dataSourceType !== DataSourceType.NOTION && s.disabled,
|
||||
)}
|
||||
onClick={() => {
|
||||
if (dataSourceTypeDisable)
|
||||
return
|
||||
changeType(DataSourceType.NOTION)
|
||||
hideFilePreview()
|
||||
hideWebsitePreview()
|
||||
}}
|
||||
>
|
||||
<span className={cn(s.datasetIcon, s.notion)} />
|
||||
<span
|
||||
title={t('stepOne.dataSourceType.notion', { ns: 'datasetCreation' })!}
|
||||
className="truncate"
|
||||
>
|
||||
{t('stepOne.dataSourceType.notion', { ns: 'datasetCreation' })}
|
||||
</span>
|
||||
</div>
|
||||
{(ENABLE_WEBSITE_FIRECRAWL || ENABLE_WEBSITE_JINAREADER || ENABLE_WEBSITE_WATERCRAWL) && (
|
||||
<div
|
||||
className={cn(
|
||||
s.dataSourceItem,
|
||||
'system-sm-medium',
|
||||
dataSourceType === DataSourceType.WEB && s.active,
|
||||
dataSourceTypeDisable && dataSourceType !== DataSourceType.WEB && s.disabled,
|
||||
)}
|
||||
onClick={() => {
|
||||
if (dataSourceTypeDisable)
|
||||
return
|
||||
changeType(DataSourceType.WEB)
|
||||
hideFilePreview()
|
||||
hideNotionPagePreview()
|
||||
}}
|
||||
>
|
||||
<span className={cn(s.datasetIcon, s.web)} />
|
||||
<span
|
||||
title={t('stepOne.dataSourceType.web', { ns: 'datasetCreation' })!}
|
||||
className="truncate"
|
||||
>
|
||||
{t('stepOne.dataSourceType.web', { ns: 'datasetCreation' })}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<DataSourceTypeSelector
|
||||
currentType={dataSourceType}
|
||||
disabled={dataSourceTypeDisable}
|
||||
onChange={changeType}
|
||||
onClearPreviews={handleClearPreviews}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* File Data Source */}
|
||||
{dataSourceType === DataSourceType.FILE && (
|
||||
<>
|
||||
<FileUploader
|
||||
@ -260,7 +186,7 @@ const StepOne = ({
|
||||
prepareFileList={updateFileList}
|
||||
onFileListUpdate={updateFileList}
|
||||
onFileUpdate={updateFile}
|
||||
onPreview={updateCurrentFile}
|
||||
onPreview={showFilePreview}
|
||||
supportBatchUpload={supportBatchUpload}
|
||||
/>
|
||||
{isShowVectorSpaceFull && (
|
||||
@ -268,24 +194,17 @@ const StepOne = ({
|
||||
<VectorSpaceFull />
|
||||
</div>
|
||||
)}
|
||||
<div className="flex max-w-[640px] justify-end gap-2">
|
||||
<Button disabled={nextDisabled} variant="primary" onClick={onStepChange}>
|
||||
<span className="flex gap-0.5 px-[10px]">
|
||||
<span className="px-0.5">{t('stepOne.button', { ns: 'datasetCreation' })}</span>
|
||||
<RiArrowRightLine className="size-4" />
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
{
|
||||
enableBilling && plan.type === Plan.sandbox && files.length > 0 && (
|
||||
<div className="mt-5">
|
||||
<div className="mb-4 h-px bg-divider-subtle"></div>
|
||||
<UpgradeCard />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<NextStepButton disabled={fileNextDisabled} onClick={onStepChange} />
|
||||
{enableBilling && plan.type === Plan.sandbox && files.length > 0 && (
|
||||
<div className="mt-5">
|
||||
<div className="mb-4 h-px bg-divider-subtle" />
|
||||
<UpgradeCard />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Notion Data Source */}
|
||||
{dataSourceType === DataSourceType.NOTION && (
|
||||
<>
|
||||
{!isNotionAuthed && <NotionConnector onSetting={onSetting} />}
|
||||
@ -295,7 +214,7 @@ const StepOne = ({
|
||||
<NotionPageSelector
|
||||
value={notionPages.map(page => page.page_id)}
|
||||
onSelect={updateNotionPages}
|
||||
onPreview={updateCurrentPage}
|
||||
onPreview={showNotionPagePreview}
|
||||
credentialList={notionCredentialList}
|
||||
onSelectCredential={updateNotionCredentialId}
|
||||
datasetId={datasetId}
|
||||
@ -306,23 +225,21 @@ const StepOne = ({
|
||||
<VectorSpaceFull />
|
||||
</div>
|
||||
)}
|
||||
<div className="flex max-w-[640px] justify-end gap-2">
|
||||
<Button disabled={isShowVectorSpaceFull || !notionPages.length} variant="primary" onClick={onStepChange}>
|
||||
<span className="flex gap-0.5 px-[10px]">
|
||||
<span className="px-0.5">{t('stepOne.button', { ns: 'datasetCreation' })}</span>
|
||||
<RiArrowRightLine className="size-4" />
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
<NextStepButton
|
||||
disabled={isShowVectorSpaceFull || !notionPages.length}
|
||||
onClick={onStepChange}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Web Data Source */}
|
||||
{dataSourceType === DataSourceType.WEB && (
|
||||
<>
|
||||
<div className={cn('mb-8 w-[640px]', !shouldShowDataSourceTypeList && 'mt-12')}>
|
||||
<Website
|
||||
onPreview={updateWebsite}
|
||||
onPreview={showWebsitePreview}
|
||||
checkedCrawlResult={websitePages}
|
||||
onCheckedCrawlResultChange={updateWebsitePages}
|
||||
onCrawlProviderChange={onWebsiteCrawlProviderChange}
|
||||
@ -337,48 +254,43 @@ const StepOne = ({
|
||||
<VectorSpaceFull />
|
||||
</div>
|
||||
)}
|
||||
<div className="flex max-w-[640px] justify-end gap-2">
|
||||
<Button disabled={isShowVectorSpaceFull || !websitePages.length} variant="primary" onClick={onStepChange}>
|
||||
<span className="flex gap-0.5 px-[10px]">
|
||||
<span className="px-0.5">{t('stepOne.button', { ns: 'datasetCreation' })}</span>
|
||||
<RiArrowRightLine className="size-4" />
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
<NextStepButton
|
||||
disabled={isShowVectorSpaceFull || !websitePages.length}
|
||||
onClick={onStepChange}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Empty Dataset Creation Link */}
|
||||
{!datasetId && (
|
||||
<>
|
||||
<div className="my-8 h-px max-w-[640px] bg-divider-regular" />
|
||||
<span className="inline-flex cursor-pointer items-center text-[13px] leading-4 text-text-accent" onClick={modalShowHandle}>
|
||||
<span
|
||||
className="inline-flex cursor-pointer items-center text-[13px] leading-4 text-text-accent"
|
||||
onClick={openModal}
|
||||
>
|
||||
<RiFolder6Line className="mr-1 size-4" />
|
||||
{t('stepOne.emptyDatasetCreation', { ns: 'datasetCreation' })}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<EmptyDatasetCreationModal show={showModal} onHide={modalCloseHandle} />
|
||||
<EmptyDatasetCreationModal show={showModal} onHide={closeModal} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="h-full w-1/2 overflow-y-auto">
|
||||
{currentFile && <FilePreview file={currentFile} hidePreview={hideFilePreview} />}
|
||||
{currentNotionPage && (
|
||||
<NotionPagePreview
|
||||
currentPage={currentNotionPage}
|
||||
hidePreview={hideNotionPagePreview}
|
||||
notionCredentialId={notionCredentialId}
|
||||
/>
|
||||
)}
|
||||
{currentWebsite && <WebsitePreview payload={currentWebsite} hidePreview={hideWebsitePreview} />}
|
||||
{isShowPlanUpgradeModal && (
|
||||
<PlanUpgradeModal
|
||||
show
|
||||
onClose={hidePlanUpgradeModal}
|
||||
title={t('upgrade.uploadMultiplePages.title', { ns: 'billing' })!}
|
||||
description={t('upgrade.uploadMultiplePages.description', { ns: 'billing' })!}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Right Panel - Preview */}
|
||||
<PreviewPanel
|
||||
currentFile={currentFile}
|
||||
currentNotionPage={currentNotionPage}
|
||||
currentWebsite={currentWebsite}
|
||||
notionCredentialId={notionCredentialId}
|
||||
isShowPlanUpgradeModal={isShowPlanUpgradeModal}
|
||||
hideFilePreview={hideFilePreview}
|
||||
hideNotionPagePreview={hideNotionPagePreview}
|
||||
hideWebsitePreview={hideWebsitePreview}
|
||||
hidePlanUpgradeModal={hidePlanUpgradeModal}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -0,0 +1,36 @@
|
||||
import type { DataSet } from '@/models/datasets'
|
||||
import * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import CornerLabel from '@/app/components/base/corner-label'
|
||||
|
||||
type CornerLabelsProps = {
|
||||
dataset: DataSet
|
||||
}
|
||||
|
||||
const CornerLabels = ({ dataset }: CornerLabelsProps) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
if (!dataset.embedding_available) {
|
||||
return (
|
||||
<CornerLabel
|
||||
label={t('cornerLabel.unavailable', { ns: 'dataset' })}
|
||||
className="absolute right-0 top-0 z-10"
|
||||
labelClassName="rounded-tr-xl"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
if (dataset.runtime_mode === 'rag_pipeline') {
|
||||
return (
|
||||
<CornerLabel
|
||||
label={t('cornerLabel.pipeline', { ns: 'dataset' })}
|
||||
className="absolute right-0 top-0 z-10"
|
||||
labelClassName="rounded-tr-xl"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export default React.memo(CornerLabels)
|
||||
@ -0,0 +1,62 @@
|
||||
import type { DataSet } from '@/models/datasets'
|
||||
import { RiFileTextFill, RiRobot2Fill } from '@remixicon/react'
|
||||
import * as React from 'react'
|
||||
import { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now'
|
||||
import { cn } from '@/utils/classnames'
|
||||
|
||||
const EXTERNAL_PROVIDER = 'external'
|
||||
|
||||
type DatasetCardFooterProps = {
|
||||
dataset: DataSet
|
||||
}
|
||||
|
||||
const DatasetCardFooter = ({ dataset }: DatasetCardFooterProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { formatTimeFromNow } = useFormatTimeFromNow()
|
||||
const isExternalProvider = dataset.provider === EXTERNAL_PROVIDER
|
||||
|
||||
const documentCount = useMemo(() => {
|
||||
const availableDocCount = dataset.total_available_documents ?? 0
|
||||
if (availableDocCount < dataset.document_count)
|
||||
return `${availableDocCount} / ${dataset.document_count}`
|
||||
return `${dataset.document_count}`
|
||||
}, [dataset.document_count, dataset.total_available_documents])
|
||||
|
||||
const documentCountTooltip = useMemo(() => {
|
||||
const availableDocCount = dataset.total_available_documents ?? 0
|
||||
if (availableDocCount < dataset.document_count)
|
||||
return t('partialEnabled', { ns: 'dataset', count: dataset.document_count, num: availableDocCount })
|
||||
return t('docAllEnabled', { ns: 'dataset', count: availableDocCount })
|
||||
}, [t, dataset.document_count, dataset.total_available_documents])
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-center gap-x-3 px-4 pb-3 pt-2 text-text-tertiary',
|
||||
!dataset.embedding_available && 'opacity-30',
|
||||
)}
|
||||
>
|
||||
<Tooltip popupContent={documentCountTooltip}>
|
||||
<div className="flex items-center gap-x-1">
|
||||
<RiFileTextFill className="size-3 text-text-quaternary" />
|
||||
<span className="system-xs-medium">{documentCount}</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
{!isExternalProvider && (
|
||||
<Tooltip popupContent={`${dataset.app_count} ${t('appCount', { ns: 'dataset' })}`}>
|
||||
<div className="flex items-center gap-x-1">
|
||||
<RiRobot2Fill className="size-3 text-text-quaternary" />
|
||||
<span className="system-xs-medium">{dataset.app_count}</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
<span className="system-xs-regular text-divider-deep">/</span>
|
||||
<span className="system-xs-regular">{`${t('updated', { ns: 'dataset' })} ${formatTimeFromNow(dataset.updated_at * 1000)}`}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(DatasetCardFooter)
|
||||
@ -0,0 +1,148 @@
|
||||
import type { DataSet } from '@/models/datasets'
|
||||
import * as React from 'react'
|
||||
import { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now'
|
||||
import { useKnowledge } from '@/hooks/use-knowledge'
|
||||
import { DOC_FORM_ICON_WITH_BG, DOC_FORM_TEXT } from '@/models/datasets'
|
||||
import { cn } from '@/utils/classnames'
|
||||
|
||||
const EXTERNAL_PROVIDER = 'external'
|
||||
|
||||
type DatasetCardHeaderProps = {
|
||||
dataset: DataSet
|
||||
}
|
||||
|
||||
// DocModeInfo component - placed before usage
|
||||
type DocModeInfoProps = {
|
||||
dataset: DataSet
|
||||
isExternalProvider: boolean
|
||||
isShowDocModeInfo: boolean
|
||||
}
|
||||
|
||||
const DocModeInfo = ({
|
||||
dataset,
|
||||
isExternalProvider,
|
||||
isShowDocModeInfo,
|
||||
}: DocModeInfoProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { formatIndexingTechniqueAndMethod } = useKnowledge()
|
||||
|
||||
if (isExternalProvider) {
|
||||
return (
|
||||
<div className="system-2xs-medium-uppercase flex items-center gap-x-3 text-text-tertiary">
|
||||
<span>{t('externalKnowledgeBase', { ns: 'dataset' })}</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (!isShowDocModeInfo)
|
||||
return null
|
||||
|
||||
const indexingText = dataset.indexing_technique
|
||||
? formatIndexingTechniqueAndMethod(
|
||||
dataset.indexing_technique as 'economy' | 'high_quality',
|
||||
dataset.retrieval_model_dict?.search_method as Parameters<typeof formatIndexingTechniqueAndMethod>[1],
|
||||
)
|
||||
: ''
|
||||
|
||||
return (
|
||||
<div className="system-2xs-medium-uppercase flex items-center gap-x-3 text-text-tertiary">
|
||||
{dataset.doc_form && (
|
||||
<span
|
||||
className="min-w-0 max-w-full truncate"
|
||||
title={t(`chunkingMode.${DOC_FORM_TEXT[dataset.doc_form]}`, { ns: 'dataset' })}
|
||||
>
|
||||
{t(`chunkingMode.${DOC_FORM_TEXT[dataset.doc_form]}`, { ns: 'dataset' })}
|
||||
</span>
|
||||
)}
|
||||
{dataset.indexing_technique && indexingText && (
|
||||
<span
|
||||
className="min-w-0 max-w-full truncate"
|
||||
title={indexingText}
|
||||
>
|
||||
{indexingText}
|
||||
</span>
|
||||
)}
|
||||
{dataset.is_multimodal && (
|
||||
<span
|
||||
className="min-w-0 max-w-full truncate"
|
||||
title={t('multimodal', { ns: 'dataset' })}
|
||||
>
|
||||
{t('multimodal', { ns: 'dataset' })}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Main DatasetCardHeader component
|
||||
const DatasetCardHeader = ({ dataset }: DatasetCardHeaderProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { formatTimeFromNow } = useFormatTimeFromNow()
|
||||
|
||||
const isExternalProvider = dataset.provider === EXTERNAL_PROVIDER
|
||||
|
||||
const isShowChunkingModeIcon = dataset.doc_form && (dataset.runtime_mode !== 'rag_pipeline' || dataset.is_published)
|
||||
const isShowDocModeInfo = Boolean(
|
||||
dataset.doc_form
|
||||
&& dataset.indexing_technique
|
||||
&& dataset.retrieval_model_dict?.search_method
|
||||
&& (dataset.runtime_mode !== 'rag_pipeline' || dataset.is_published),
|
||||
)
|
||||
|
||||
const chunkingModeIcon = dataset.doc_form ? DOC_FORM_ICON_WITH_BG[dataset.doc_form] : React.Fragment
|
||||
const Icon = isExternalProvider ? DOC_FORM_ICON_WITH_BG.external : chunkingModeIcon
|
||||
|
||||
const iconInfo = useMemo(() => dataset.icon_info || {
|
||||
icon: '📙',
|
||||
icon_type: 'emoji' as const,
|
||||
icon_background: '#FFF4ED',
|
||||
icon_url: '',
|
||||
}, [dataset.icon_info])
|
||||
|
||||
const editTimeText = useMemo(
|
||||
() => `${t('segment.editedAt', { ns: 'datasetDocuments' })} ${formatTimeFromNow(dataset.updated_at * 1000)}`,
|
||||
[t, dataset.updated_at, formatTimeFromNow],
|
||||
)
|
||||
|
||||
return (
|
||||
<div className={cn('flex items-center gap-x-3 px-4 pb-2 pt-4', !dataset.embedding_available && 'opacity-30')}>
|
||||
<div className="relative shrink-0">
|
||||
<AppIcon
|
||||
size="large"
|
||||
iconType={iconInfo.icon_type}
|
||||
icon={iconInfo.icon}
|
||||
background={iconInfo.icon_type === 'image' ? undefined : iconInfo.icon_background}
|
||||
imageUrl={iconInfo.icon_type === 'image' ? iconInfo.icon_url : undefined}
|
||||
/>
|
||||
{(isShowChunkingModeIcon || isExternalProvider) && (
|
||||
<div className="absolute -bottom-1 -right-1 z-[5]">
|
||||
<Icon className="size-4" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex grow flex-col gap-y-1 overflow-hidden py-px">
|
||||
<div
|
||||
className="system-md-semibold truncate text-text-secondary"
|
||||
title={dataset.name}
|
||||
>
|
||||
{dataset.name}
|
||||
</div>
|
||||
<div className="flex items-center gap-1 text-[10px] font-medium leading-[18px] text-text-tertiary">
|
||||
<div className="truncate" title={dataset.author_name}>{dataset.author_name}</div>
|
||||
<div>·</div>
|
||||
<div className="truncate" title={editTimeText}>{editTimeText}</div>
|
||||
</div>
|
||||
<DocModeInfo
|
||||
dataset={dataset}
|
||||
isExternalProvider={isExternalProvider}
|
||||
isShowDocModeInfo={isShowDocModeInfo}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(DatasetCardHeader)
|
||||
@ -0,0 +1,55 @@
|
||||
import type { DataSet } from '@/models/datasets'
|
||||
import * as React from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import RenameDatasetModal from '../../../rename-modal'
|
||||
|
||||
type ModalState = {
|
||||
showRenameModal: boolean
|
||||
showConfirmDelete: boolean
|
||||
confirmMessage: string
|
||||
}
|
||||
|
||||
type DatasetCardModalsProps = {
|
||||
dataset: DataSet
|
||||
modalState: ModalState
|
||||
onCloseRename: () => void
|
||||
onCloseConfirm: () => void
|
||||
onConfirmDelete: () => void
|
||||
onSuccess?: () => void
|
||||
}
|
||||
|
||||
const DatasetCardModals = ({
|
||||
dataset,
|
||||
modalState,
|
||||
onCloseRename,
|
||||
onCloseConfirm,
|
||||
onConfirmDelete,
|
||||
onSuccess,
|
||||
}: DatasetCardModalsProps) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<>
|
||||
{modalState.showRenameModal && (
|
||||
<RenameDatasetModal
|
||||
show={modalState.showRenameModal}
|
||||
dataset={dataset}
|
||||
onClose={onCloseRename}
|
||||
onSuccess={onSuccess}
|
||||
/>
|
||||
)}
|
||||
{modalState.showConfirmDelete && (
|
||||
<Confirm
|
||||
title={t('deleteDatasetConfirmTitle', { ns: 'dataset' })}
|
||||
content={modalState.confirmMessage}
|
||||
isShow={modalState.showConfirmDelete}
|
||||
onConfirm={onConfirmDelete}
|
||||
onCancel={onCloseConfirm}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(DatasetCardModals)
|
||||
@ -0,0 +1,18 @@
|
||||
import type { DataSet } from '@/models/datasets'
|
||||
import * as React from 'react'
|
||||
import { cn } from '@/utils/classnames'
|
||||
|
||||
type DescriptionProps = {
|
||||
dataset: DataSet
|
||||
}
|
||||
|
||||
const Description = ({ dataset }: DescriptionProps) => (
|
||||
<div
|
||||
className={cn('system-xs-regular line-clamp-2 h-10 px-4 py-1 text-text-tertiary', !dataset.embedding_available && 'opacity-30')}
|
||||
title={dataset.description}
|
||||
>
|
||||
{dataset.description}
|
||||
</div>
|
||||
)
|
||||
|
||||
export default React.memo(Description)
|
||||
@ -0,0 +1,52 @@
|
||||
import type { DataSet } from '@/models/datasets'
|
||||
import { RiMoreFill } from '@remixicon/react'
|
||||
import * as React from 'react'
|
||||
import CustomPopover from '@/app/components/base/popover'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import Operations from '../operations'
|
||||
|
||||
type OperationsPopoverProps = {
|
||||
dataset: DataSet
|
||||
isCurrentWorkspaceDatasetOperator: boolean
|
||||
openRenameModal: () => void
|
||||
handleExportPipeline: (include?: boolean) => void
|
||||
detectIsUsedByApp: () => void
|
||||
}
|
||||
|
||||
const OperationsPopover = ({
|
||||
dataset,
|
||||
isCurrentWorkspaceDatasetOperator,
|
||||
openRenameModal,
|
||||
handleExportPipeline,
|
||||
detectIsUsedByApp,
|
||||
}: OperationsPopoverProps) => (
|
||||
<div className="absolute right-2 top-2 z-[15] hidden group-hover:block">
|
||||
<CustomPopover
|
||||
htmlContent={(
|
||||
<Operations
|
||||
showDelete={!isCurrentWorkspaceDatasetOperator}
|
||||
showExportPipeline={dataset.runtime_mode === 'rag_pipeline'}
|
||||
openRenameModal={openRenameModal}
|
||||
handleExportPipeline={handleExportPipeline}
|
||||
detectIsUsedByApp={detectIsUsedByApp}
|
||||
/>
|
||||
)}
|
||||
className="z-20 min-w-[186px]"
|
||||
popupClassName="rounded-xl bg-none shadow-none ring-0 min-w-[186px]"
|
||||
position="br"
|
||||
trigger="click"
|
||||
btnElement={(
|
||||
<div className="flex size-8 items-center justify-center rounded-[10px] hover:bg-state-base-hover">
|
||||
<RiMoreFill className="h-5 w-5 text-text-tertiary" />
|
||||
</div>
|
||||
)}
|
||||
btnClassName={open =>
|
||||
cn(
|
||||
'size-9 cursor-pointer justify-center rounded-[10px] border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0 shadow-lg shadow-shadow-shadow-5 ring-[2px] ring-inset ring-components-actionbar-bg hover:border-components-actionbar-border',
|
||||
open ? 'border-components-actionbar-border bg-state-base-hover' : '',
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
export default React.memo(OperationsPopover)
|
||||
@ -0,0 +1,55 @@
|
||||
import type { Tag } from '@/app/components/base/tag-management/constant'
|
||||
import type { DataSet } from '@/models/datasets'
|
||||
import * as React from 'react'
|
||||
import TagSelector from '@/app/components/base/tag-management/selector'
|
||||
import { cn } from '@/utils/classnames'
|
||||
|
||||
type TagAreaProps = {
|
||||
dataset: DataSet
|
||||
tags: Tag[]
|
||||
setTags: (tags: Tag[]) => void
|
||||
onSuccess?: () => void
|
||||
isHoveringTagSelector: boolean
|
||||
onClick: (e: React.MouseEvent) => void
|
||||
}
|
||||
|
||||
const TagArea = React.forwardRef<HTMLDivElement, TagAreaProps>(({
|
||||
dataset,
|
||||
tags,
|
||||
setTags,
|
||||
onSuccess,
|
||||
isHoveringTagSelector,
|
||||
onClick,
|
||||
}, ref) => (
|
||||
<div
|
||||
className={cn('relative w-full px-3', !dataset.embedding_available && 'opacity-30')}
|
||||
onClick={onClick}
|
||||
>
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'invisible w-full group-hover:visible',
|
||||
tags.length > 0 && 'visible',
|
||||
)}
|
||||
>
|
||||
<TagSelector
|
||||
position="bl"
|
||||
type="knowledge"
|
||||
targetID={dataset.id}
|
||||
value={tags.map(tag => tag.id)}
|
||||
selectedTags={tags}
|
||||
onCacheUpdate={setTags}
|
||||
onChange={onSuccess}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
'absolute right-0 top-0 z-[5] h-full w-20 bg-tag-selector-mask-bg group-hover:bg-tag-selector-mask-hover-bg',
|
||||
isHoveringTagSelector && 'hidden',
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
TagArea.displayName = 'TagArea'
|
||||
|
||||
export default TagArea
|
||||
@ -0,0 +1,138 @@
|
||||
import type { Tag } from '@/app/components/base/tag-management/constant'
|
||||
import type { DataSet } from '@/models/datasets'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import { useCheckDatasetUsage, useDeleteDataset } from '@/service/use-dataset-card'
|
||||
import { useExportPipelineDSL } from '@/service/use-pipeline'
|
||||
|
||||
type ModalState = {
|
||||
showRenameModal: boolean
|
||||
showConfirmDelete: boolean
|
||||
confirmMessage: string
|
||||
}
|
||||
|
||||
type UseDatasetCardStateOptions = {
|
||||
dataset: DataSet
|
||||
onSuccess?: () => void
|
||||
}
|
||||
|
||||
export const useDatasetCardState = ({ dataset, onSuccess }: UseDatasetCardStateOptions) => {
|
||||
const { t } = useTranslation()
|
||||
const [tags, setTags] = useState<Tag[]>(dataset.tags)
|
||||
|
||||
useEffect(() => {
|
||||
setTags(dataset.tags)
|
||||
}, [dataset.tags])
|
||||
|
||||
// Modal state
|
||||
const [modalState, setModalState] = useState<ModalState>({
|
||||
showRenameModal: false,
|
||||
showConfirmDelete: false,
|
||||
confirmMessage: '',
|
||||
})
|
||||
|
||||
// Export state
|
||||
const [exporting, setExporting] = useState(false)
|
||||
|
||||
// Modal handlers
|
||||
const openRenameModal = useCallback(() => {
|
||||
setModalState(prev => ({ ...prev, showRenameModal: true }))
|
||||
}, [])
|
||||
|
||||
const closeRenameModal = useCallback(() => {
|
||||
setModalState(prev => ({ ...prev, showRenameModal: false }))
|
||||
}, [])
|
||||
|
||||
const closeConfirmDelete = useCallback(() => {
|
||||
setModalState(prev => ({ ...prev, showConfirmDelete: false }))
|
||||
}, [])
|
||||
|
||||
// API mutations
|
||||
const { mutateAsync: checkUsage } = useCheckDatasetUsage()
|
||||
const { mutateAsync: deleteDatasetMutation } = useDeleteDataset()
|
||||
const { mutateAsync: exportPipelineConfig } = useExportPipelineDSL()
|
||||
|
||||
// Export pipeline handler
|
||||
const handleExportPipeline = useCallback(async (include: boolean = false) => {
|
||||
const { pipeline_id, name } = dataset
|
||||
if (!pipeline_id || exporting)
|
||||
return
|
||||
|
||||
try {
|
||||
setExporting(true)
|
||||
const { data } = await exportPipelineConfig({
|
||||
pipelineId: pipeline_id,
|
||||
include,
|
||||
})
|
||||
const a = document.createElement('a')
|
||||
const file = new Blob([data], { type: 'application/yaml' })
|
||||
const url = URL.createObjectURL(file)
|
||||
a.href = url
|
||||
a.download = `${name}.pipeline`
|
||||
a.click()
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
catch {
|
||||
Toast.notify({ type: 'error', message: t('exportFailed', { ns: 'app' }) })
|
||||
}
|
||||
finally {
|
||||
setExporting(false)
|
||||
}
|
||||
}, [dataset, exportPipelineConfig, exporting, t])
|
||||
|
||||
// Delete flow handlers
|
||||
const detectIsUsedByApp = useCallback(async () => {
|
||||
try {
|
||||
const { is_using: isUsedByApp } = await checkUsage(dataset.id)
|
||||
const message = isUsedByApp
|
||||
? t('datasetUsedByApp', { ns: 'dataset' })!
|
||||
: t('deleteDatasetConfirmContent', { ns: 'dataset' })!
|
||||
setModalState(prev => ({
|
||||
...prev,
|
||||
confirmMessage: message,
|
||||
showConfirmDelete: true,
|
||||
}))
|
||||
}
|
||||
catch (e: unknown) {
|
||||
if (e instanceof Response) {
|
||||
const res = await e.json()
|
||||
Toast.notify({ type: 'error', message: res?.message || 'Unknown error' })
|
||||
}
|
||||
else {
|
||||
Toast.notify({ type: 'error', message: (e as Error)?.message || 'Unknown error' })
|
||||
}
|
||||
}
|
||||
}, [dataset.id, checkUsage, t])
|
||||
|
||||
const onConfirmDelete = useCallback(async () => {
|
||||
try {
|
||||
await deleteDatasetMutation(dataset.id)
|
||||
Toast.notify({ type: 'success', message: t('datasetDeleted', { ns: 'dataset' }) })
|
||||
onSuccess?.()
|
||||
}
|
||||
finally {
|
||||
closeConfirmDelete()
|
||||
}
|
||||
}, [dataset.id, deleteDatasetMutation, onSuccess, t, closeConfirmDelete])
|
||||
|
||||
return {
|
||||
// Tag state
|
||||
tags,
|
||||
setTags,
|
||||
|
||||
// Modal state
|
||||
modalState,
|
||||
openRenameModal,
|
||||
closeRenameModal,
|
||||
closeConfirmDelete,
|
||||
|
||||
// Export state
|
||||
exporting,
|
||||
|
||||
// Handlers
|
||||
handleExportPipeline,
|
||||
detectIsUsedByApp,
|
||||
onConfirmDelete,
|
||||
}
|
||||
}
|
||||
@ -1,28 +1,17 @@
|
||||
'use client'
|
||||
import type { Tag } from '@/app/components/base/tag-management/constant'
|
||||
import type { DataSet } from '@/models/datasets'
|
||||
import { RiFileTextFill, RiMoreFill, RiRobot2Fill } from '@remixicon/react'
|
||||
import { useHover } from 'ahooks'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import * as React from 'react'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import AppIcon from '@/app/components/base/app-icon'
|
||||
import Confirm from '@/app/components/base/confirm'
|
||||
import CornerLabel from '@/app/components/base/corner-label'
|
||||
import CustomPopover from '@/app/components/base/popover'
|
||||
import TagSelector from '@/app/components/base/tag-management/selector'
|
||||
import Toast from '@/app/components/base/toast'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { useMemo, useRef } from 'react'
|
||||
import { useSelector as useAppContextWithSelector } from '@/context/app-context'
|
||||
import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now'
|
||||
import { useKnowledge } from '@/hooks/use-knowledge'
|
||||
import { DOC_FORM_ICON_WITH_BG, DOC_FORM_TEXT } from '@/models/datasets'
|
||||
import { checkIsUsedInApp, deleteDataset } from '@/service/datasets'
|
||||
import { useExportPipelineDSL } from '@/service/use-pipeline'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import RenameDatasetModal from '../../rename-modal'
|
||||
import Operations from './operations'
|
||||
import CornerLabels from './components/corner-labels'
|
||||
import DatasetCardFooter from './components/dataset-card-footer'
|
||||
import DatasetCardHeader from './components/dataset-card-header'
|
||||
import DatasetCardModals from './components/dataset-card-modals'
|
||||
import Description from './components/description'
|
||||
import OperationsPopover from './components/operations-popover'
|
||||
import TagArea from './components/tag-area'
|
||||
import { useDatasetCardState } from './hooks/use-dataset-card-state'
|
||||
|
||||
const EXTERNAL_PROVIDER = 'external'
|
||||
|
||||
@ -35,320 +24,80 @@ const DatasetCard = ({
|
||||
dataset,
|
||||
onSuccess,
|
||||
}: DatasetCardProps) => {
|
||||
const { t } = useTranslation()
|
||||
const { push } = useRouter()
|
||||
|
||||
const isCurrentWorkspaceDatasetOperator = useAppContextWithSelector(state => state.isCurrentWorkspaceDatasetOperator)
|
||||
const [tags, setTags] = useState<Tag[]>(dataset.tags)
|
||||
const tagSelectorRef = useRef<HTMLDivElement>(null)
|
||||
const isHoveringTagSelector = useHover(tagSelectorRef)
|
||||
|
||||
const [showRenameModal, setShowRenameModal] = useState(false)
|
||||
const [showConfirmDelete, setShowConfirmDelete] = useState(false)
|
||||
const [confirmMessage, setConfirmMessage] = useState<string>('')
|
||||
const [exporting, setExporting] = useState(false)
|
||||
const {
|
||||
tags,
|
||||
setTags,
|
||||
modalState,
|
||||
openRenameModal,
|
||||
closeRenameModal,
|
||||
closeConfirmDelete,
|
||||
handleExportPipeline,
|
||||
detectIsUsedByApp,
|
||||
onConfirmDelete,
|
||||
} = useDatasetCardState({ dataset, onSuccess })
|
||||
|
||||
const isExternalProvider = useMemo(() => {
|
||||
return dataset.provider === EXTERNAL_PROVIDER
|
||||
}, [dataset.provider])
|
||||
const isExternalProvider = dataset.provider === EXTERNAL_PROVIDER
|
||||
const isPipelineUnpublished = useMemo(() => {
|
||||
return dataset.runtime_mode === 'rag_pipeline' && !dataset.is_published
|
||||
}, [dataset.runtime_mode, dataset.is_published])
|
||||
const isShowChunkingModeIcon = useMemo(() => {
|
||||
return dataset.doc_form && (dataset.runtime_mode !== 'rag_pipeline' || dataset.is_published)
|
||||
}, [dataset.doc_form, dataset.runtime_mode, dataset.is_published])
|
||||
const isShowDocModeInfo = useMemo(() => {
|
||||
return dataset.doc_form && dataset.indexing_technique && dataset.retrieval_model_dict?.search_method && (dataset.runtime_mode !== 'rag_pipeline' || dataset.is_published)
|
||||
}, [dataset.doc_form, dataset.indexing_technique, dataset.retrieval_model_dict?.search_method, dataset.runtime_mode, dataset.is_published])
|
||||
|
||||
const chunkingModeIcon = dataset.doc_form ? DOC_FORM_ICON_WITH_BG[dataset.doc_form] : React.Fragment
|
||||
const Icon = isExternalProvider ? DOC_FORM_ICON_WITH_BG.external : chunkingModeIcon
|
||||
const iconInfo = dataset.icon_info || {
|
||||
icon: '📙',
|
||||
icon_type: 'emoji',
|
||||
icon_background: '#FFF4ED',
|
||||
icon_url: '',
|
||||
const handleCardClick = (e: React.MouseEvent) => {
|
||||
e.preventDefault()
|
||||
if (isExternalProvider)
|
||||
push(`/datasets/${dataset.id}/hitTesting`)
|
||||
else if (isPipelineUnpublished)
|
||||
push(`/datasets/${dataset.id}/pipeline`)
|
||||
else
|
||||
push(`/datasets/${dataset.id}/documents`)
|
||||
}
|
||||
const { formatIndexingTechniqueAndMethod } = useKnowledge()
|
||||
const documentCount = useMemo(() => {
|
||||
const availableDocCount = dataset.total_available_documents ?? 0
|
||||
if (availableDocCount === dataset.document_count)
|
||||
return `${dataset.document_count}`
|
||||
if (availableDocCount < dataset.document_count)
|
||||
return `${availableDocCount} / ${dataset.document_count}`
|
||||
}, [dataset.document_count, dataset.total_available_documents])
|
||||
const documentCountTooltip = useMemo(() => {
|
||||
const availableDocCount = dataset.total_available_documents ?? 0
|
||||
if (availableDocCount === dataset.document_count)
|
||||
return t('docAllEnabled', { ns: 'dataset', count: availableDocCount })
|
||||
if (availableDocCount < dataset.document_count)
|
||||
return t('partialEnabled', { ns: 'dataset', count: dataset.document_count, num: availableDocCount })
|
||||
}, [t, dataset.document_count, dataset.total_available_documents])
|
||||
|
||||
const { formatTimeFromNow } = useFormatTimeFromNow()
|
||||
const editTimeText = useMemo(() => {
|
||||
return `${t('segment.editedAt', { ns: 'datasetDocuments' })} ${formatTimeFromNow(dataset.updated_at * 1000)}`
|
||||
}, [t, dataset.updated_at, formatTimeFromNow])
|
||||
|
||||
const openRenameModal = useCallback(() => {
|
||||
setShowRenameModal(true)
|
||||
}, [])
|
||||
|
||||
const { mutateAsync: exportPipelineConfig } = useExportPipelineDSL()
|
||||
|
||||
const handleExportPipeline = useCallback(async (include = false) => {
|
||||
const { pipeline_id, name } = dataset
|
||||
if (!pipeline_id)
|
||||
return
|
||||
|
||||
if (exporting)
|
||||
return
|
||||
|
||||
try {
|
||||
setExporting(true)
|
||||
const { data } = await exportPipelineConfig({
|
||||
pipelineId: pipeline_id,
|
||||
include,
|
||||
})
|
||||
const a = document.createElement('a')
|
||||
const file = new Blob([data], { type: 'application/yaml' })
|
||||
const url = URL.createObjectURL(file)
|
||||
a.href = url
|
||||
a.download = `${name}.pipeline`
|
||||
a.click()
|
||||
URL.revokeObjectURL(url)
|
||||
}
|
||||
catch {
|
||||
Toast.notify({ type: 'error', message: t('exportFailed', { ns: 'app' }) })
|
||||
}
|
||||
finally {
|
||||
setExporting(false)
|
||||
}
|
||||
}, [dataset, exportPipelineConfig, exporting, t])
|
||||
|
||||
const detectIsUsedByApp = useCallback(async () => {
|
||||
try {
|
||||
const { is_using: isUsedByApp } = await checkIsUsedInApp(dataset.id)
|
||||
setConfirmMessage(isUsedByApp ? t('datasetUsedByApp', { ns: 'dataset' })! : t('deleteDatasetConfirmContent', { ns: 'dataset' })!)
|
||||
setShowConfirmDelete(true)
|
||||
}
|
||||
catch (e: any) {
|
||||
const res = await e.json()
|
||||
Toast.notify({ type: 'error', message: res?.message || 'Unknown error' })
|
||||
}
|
||||
}, [dataset.id, t])
|
||||
|
||||
const onConfirmDelete = useCallback(async () => {
|
||||
try {
|
||||
await deleteDataset(dataset.id)
|
||||
Toast.notify({ type: 'success', message: t('datasetDeleted', { ns: 'dataset' }) })
|
||||
if (onSuccess)
|
||||
onSuccess()
|
||||
}
|
||||
finally {
|
||||
setShowConfirmDelete(false)
|
||||
}
|
||||
}, [dataset.id, onSuccess, t])
|
||||
|
||||
useEffect(() => {
|
||||
setTags(dataset.tags)
|
||||
}, [dataset])
|
||||
const handleTagAreaClick = (e: React.MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className="group relative col-span-1 flex h-[190px] cursor-pointer flex-col rounded-xl border-[0.5px] border-solid border-components-card-border bg-components-card-bg shadow-xs shadow-shadow-shadow-3 transition-all duration-200 ease-in-out hover:bg-components-card-bg-alt hover:shadow-md hover:shadow-shadow-shadow-5"
|
||||
data-disable-nprogress={true}
|
||||
onClick={(e) => {
|
||||
e.preventDefault()
|
||||
if (isExternalProvider)
|
||||
push(`/datasets/${dataset.id}/hitTesting`)
|
||||
else if (isPipelineUnpublished)
|
||||
push(`/datasets/${dataset.id}/pipeline`)
|
||||
else
|
||||
push(`/datasets/${dataset.id}/documents`)
|
||||
}}
|
||||
onClick={handleCardClick}
|
||||
>
|
||||
{!dataset.embedding_available && (
|
||||
<CornerLabel
|
||||
label={t('cornerLabel.unavailable', { ns: 'dataset' })}
|
||||
className="absolute right-0 top-0 z-10"
|
||||
labelClassName="rounded-tr-xl"
|
||||
/>
|
||||
)}
|
||||
{dataset.embedding_available && dataset.runtime_mode === 'rag_pipeline' && (
|
||||
<CornerLabel
|
||||
label={t('cornerLabel.pipeline', { ns: 'dataset' })}
|
||||
className="absolute right-0 top-0 z-10"
|
||||
labelClassName="rounded-tr-xl"
|
||||
/>
|
||||
)}
|
||||
<div className={cn('flex items-center gap-x-3 px-4 pb-2 pt-4', !dataset.embedding_available && 'opacity-30')}>
|
||||
<div className="relative shrink-0">
|
||||
<AppIcon
|
||||
size="large"
|
||||
iconType={iconInfo.icon_type}
|
||||
icon={iconInfo.icon}
|
||||
background={iconInfo.icon_type === 'image' ? undefined : iconInfo.icon_background}
|
||||
imageUrl={iconInfo.icon_type === 'image' ? iconInfo.icon_url : undefined}
|
||||
/>
|
||||
{(isShowChunkingModeIcon || isExternalProvider) && (
|
||||
<div className="absolute -bottom-1 -right-1 z-[5]">
|
||||
<Icon className="size-4" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex grow flex-col gap-y-1 overflow-hidden py-px">
|
||||
<div
|
||||
className="system-md-semibold truncate text-text-secondary"
|
||||
title={dataset.name}
|
||||
>
|
||||
{dataset.name}
|
||||
</div>
|
||||
<div className="flex items-center gap-1 text-[10px] font-medium leading-[18px] text-text-tertiary">
|
||||
<div className="truncate" title={dataset.author_name}>{dataset.author_name}</div>
|
||||
<div>·</div>
|
||||
<div className="truncate" title={editTimeText}>{editTimeText}</div>
|
||||
</div>
|
||||
<div className="system-2xs-medium-uppercase flex items-center gap-x-3 text-text-tertiary">
|
||||
{isExternalProvider && <span>{t('externalKnowledgeBase', { ns: 'dataset' })}</span>}
|
||||
{!isExternalProvider && isShowDocModeInfo && (
|
||||
<>
|
||||
{dataset.doc_form && (
|
||||
<span
|
||||
className="min-w-0 max-w-full truncate"
|
||||
title={t(`chunkingMode.${DOC_FORM_TEXT[dataset.doc_form]}`, { ns: 'dataset' })}
|
||||
>
|
||||
{t(`chunkingMode.${DOC_FORM_TEXT[dataset.doc_form]}`, { ns: 'dataset' })}
|
||||
</span>
|
||||
)}
|
||||
{dataset.indexing_technique && (
|
||||
<span
|
||||
className="min-w-0 max-w-full truncate"
|
||||
title={formatIndexingTechniqueAndMethod(dataset.indexing_technique, dataset.retrieval_model_dict?.search_method) as any}
|
||||
>
|
||||
{formatIndexingTechniqueAndMethod(dataset.indexing_technique, dataset.retrieval_model_dict?.search_method) as any}
|
||||
</span>
|
||||
)}
|
||||
{dataset.is_multimodal && (
|
||||
<span
|
||||
className="min-w-0 max-w-full truncate"
|
||||
title={t('multimodal', { ns: 'dataset' })}
|
||||
>
|
||||
{t('multimodal', { ns: 'dataset' })}
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={cn('system-xs-regular line-clamp-2 h-10 px-4 py-1 text-text-tertiary', !dataset.embedding_available && 'opacity-30')}
|
||||
title={dataset.description}
|
||||
>
|
||||
{dataset.description}
|
||||
</div>
|
||||
<div
|
||||
className={cn('relative w-full px-3', !dataset.embedding_available && 'opacity-30')}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
}}
|
||||
>
|
||||
<div
|
||||
ref={tagSelectorRef}
|
||||
className={cn(
|
||||
'invisible w-full group-hover:visible',
|
||||
tags.length > 0 && 'visible',
|
||||
)}
|
||||
>
|
||||
<TagSelector
|
||||
position="bl"
|
||||
type="knowledge"
|
||||
targetID={dataset.id}
|
||||
value={tags.map(tag => tag.id)}
|
||||
selectedTags={tags}
|
||||
onCacheUpdate={setTags}
|
||||
onChange={onSuccess}
|
||||
/>
|
||||
</div>
|
||||
{/* Tag Mask */}
|
||||
<div
|
||||
className={cn(
|
||||
'absolute right-0 top-0 z-[5] h-full w-20 bg-tag-selector-mask-bg group-hover:bg-tag-selector-mask-hover-bg',
|
||||
isHoveringTagSelector && 'hidden',
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-center gap-x-3 px-4 pb-3 pt-2 text-text-tertiary',
|
||||
!dataset.embedding_available && 'opacity-30',
|
||||
)}
|
||||
>
|
||||
<Tooltip popupContent={documentCountTooltip}>
|
||||
<div className="flex items-center gap-x-1">
|
||||
<RiFileTextFill className="size-3 text-text-quaternary" />
|
||||
<span className="system-xs-medium">{documentCount}</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
{!isExternalProvider && (
|
||||
<Tooltip popupContent={`${dataset.app_count} ${t('appCount', { ns: 'dataset' })}`}>
|
||||
<div className="flex items-center gap-x-1">
|
||||
<RiRobot2Fill className="size-3 text-text-quaternary" />
|
||||
<span className="system-xs-medium">{dataset.app_count}</span>
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
<span className="system-xs-regular text-divider-deep">/</span>
|
||||
<span className="system-xs-regular">{`${t('updated', { ns: 'dataset' })} ${formatTimeFromNow(dataset.updated_at * 1000)}`}</span>
|
||||
</div>
|
||||
<div className="absolute right-2 top-2 z-[15] hidden group-hover:block">
|
||||
<CustomPopover
|
||||
htmlContent={(
|
||||
<Operations
|
||||
showDelete={!isCurrentWorkspaceDatasetOperator}
|
||||
showExportPipeline={dataset.runtime_mode === 'rag_pipeline'}
|
||||
openRenameModal={openRenameModal}
|
||||
handleExportPipeline={handleExportPipeline}
|
||||
detectIsUsedByApp={detectIsUsedByApp}
|
||||
/>
|
||||
)}
|
||||
className="z-20 min-w-[186px]"
|
||||
popupClassName="rounded-xl bg-none shadow-none ring-0 min-w-[186px]"
|
||||
position="br"
|
||||
trigger="click"
|
||||
btnElement={(
|
||||
<div className="flex size-8 items-center justify-center rounded-[10px] hover:bg-state-base-hover">
|
||||
<RiMoreFill className="h-5 w-5 text-text-tertiary" />
|
||||
</div>
|
||||
)}
|
||||
btnClassName={open =>
|
||||
cn(
|
||||
'size-9 cursor-pointer justify-center rounded-[10px] border-[0.5px] border-components-actionbar-border bg-components-actionbar-bg p-0 shadow-lg shadow-shadow-shadow-5 ring-[2px] ring-inset ring-components-actionbar-bg hover:border-components-actionbar-border',
|
||||
open ? 'border-components-actionbar-border bg-state-base-hover' : '',
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{showRenameModal && (
|
||||
<RenameDatasetModal
|
||||
show={showRenameModal}
|
||||
<CornerLabels dataset={dataset} />
|
||||
<DatasetCardHeader dataset={dataset} />
|
||||
<Description dataset={dataset} />
|
||||
<TagArea
|
||||
ref={tagSelectorRef}
|
||||
dataset={dataset}
|
||||
onClose={() => setShowRenameModal(false)}
|
||||
tags={tags}
|
||||
setTags={setTags}
|
||||
onSuccess={onSuccess}
|
||||
isHoveringTagSelector={isHoveringTagSelector}
|
||||
onClick={handleTagAreaClick}
|
||||
/>
|
||||
)}
|
||||
{showConfirmDelete && (
|
||||
<Confirm
|
||||
title={t('deleteDatasetConfirmTitle', { ns: 'dataset' })}
|
||||
content={confirmMessage}
|
||||
isShow={showConfirmDelete}
|
||||
onConfirm={onConfirmDelete}
|
||||
onCancel={() => setShowConfirmDelete(false)}
|
||||
<DatasetCardFooter dataset={dataset} />
|
||||
<OperationsPopover
|
||||
dataset={dataset}
|
||||
isCurrentWorkspaceDatasetOperator={isCurrentWorkspaceDatasetOperator}
|
||||
openRenameModal={openRenameModal}
|
||||
handleExportPipeline={handleExportPipeline}
|
||||
detectIsUsedByApp={detectIsUsedByApp}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<DatasetCardModals
|
||||
dataset={dataset}
|
||||
modalState={modalState}
|
||||
onCloseRename={closeRenameModal}
|
||||
onCloseConfirm={closeConfirmDelete}
|
||||
onConfirmDelete={onConfirmDelete}
|
||||
onSuccess={onSuccess}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import type { SlashCommandHandler } from './types'
|
||||
import { RiUser3Line } from '@remixicon/react'
|
||||
import * as React from 'react'
|
||||
import i18n from '@/i18n-config/i18next-config'
|
||||
import { getI18n } from 'react-i18next'
|
||||
import { registerCommands, unregisterCommands } from './command-bus'
|
||||
|
||||
// Account command dependency types - no external dependencies needed
|
||||
@ -21,6 +21,7 @@ export const accountCommand: SlashCommandHandler<AccountDeps> = {
|
||||
},
|
||||
|
||||
async search(args: string, locale: string = 'en') {
|
||||
const i18n = getI18n()
|
||||
return [{
|
||||
id: 'account',
|
||||
title: i18n.t('account.account', { ns: 'common', lng: locale }),
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import type { SlashCommandHandler } from './types'
|
||||
import { RiDiscordLine } from '@remixicon/react'
|
||||
import * as React from 'react'
|
||||
import i18n from '@/i18n-config/i18next-config'
|
||||
import { getI18n } from 'react-i18next'
|
||||
import { registerCommands, unregisterCommands } from './command-bus'
|
||||
|
||||
// Community command dependency types
|
||||
@ -22,6 +22,7 @@ export const communityCommand: SlashCommandHandler<CommunityDeps> = {
|
||||
},
|
||||
|
||||
async search(args: string, locale: string = 'en') {
|
||||
const i18n = getI18n()
|
||||
return [{
|
||||
id: 'community',
|
||||
title: i18n.t('userProfile.community', { ns: 'common', lng: locale }),
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import type { SlashCommandHandler } from './types'
|
||||
import { RiBookOpenLine } from '@remixicon/react'
|
||||
import * as React from 'react'
|
||||
import { getI18n } from 'react-i18next'
|
||||
import { defaultDocBaseUrl } from '@/context/i18n'
|
||||
import i18n from '@/i18n-config/i18next-config'
|
||||
import { getDocLanguage } from '@/i18n-config/language'
|
||||
import { registerCommands, unregisterCommands } from './command-bus'
|
||||
|
||||
@ -19,6 +19,7 @@ export const docsCommand: SlashCommandHandler<DocDeps> = {
|
||||
|
||||
// Direct execution function
|
||||
execute: () => {
|
||||
const i18n = getI18n()
|
||||
const currentLocale = i18n.language
|
||||
const docLanguage = getDocLanguage(currentLocale)
|
||||
const url = `${defaultDocBaseUrl}/${docLanguage}`
|
||||
@ -26,6 +27,7 @@ export const docsCommand: SlashCommandHandler<DocDeps> = {
|
||||
},
|
||||
|
||||
async search(args: string, locale: string = 'en') {
|
||||
const i18n = getI18n()
|
||||
return [{
|
||||
id: 'doc',
|
||||
title: i18n.t('userProfile.helpCenter', { ns: 'common', lng: locale }),
|
||||
@ -41,6 +43,7 @@ export const docsCommand: SlashCommandHandler<DocDeps> = {
|
||||
},
|
||||
|
||||
register(_deps: DocDeps) {
|
||||
const i18n = getI18n()
|
||||
registerCommands({
|
||||
'navigation.doc': async (_args) => {
|
||||
// Get the current language from i18n
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import type { SlashCommandHandler } from './types'
|
||||
import { RiFeedbackLine } from '@remixicon/react'
|
||||
import * as React from 'react'
|
||||
import i18n from '@/i18n-config/i18next-config'
|
||||
import { getI18n } from 'react-i18next'
|
||||
import { registerCommands, unregisterCommands } from './command-bus'
|
||||
|
||||
// Forum command dependency types
|
||||
@ -22,6 +22,7 @@ export const forumCommand: SlashCommandHandler<ForumDeps> = {
|
||||
},
|
||||
|
||||
async search(args: string, locale: string = 'en') {
|
||||
const i18n = getI18n()
|
||||
return [{
|
||||
id: 'forum',
|
||||
title: i18n.t('userProfile.forum', { ns: 'common', lng: locale }),
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { CommandSearchResult } from '../types'
|
||||
import type { SlashCommandHandler } from './types'
|
||||
import i18n from '@/i18n-config/i18next-config'
|
||||
import { getI18n } from 'react-i18next'
|
||||
import { languages } from '@/i18n-config/language'
|
||||
import { registerCommands, unregisterCommands } from './command-bus'
|
||||
|
||||
@ -14,6 +14,7 @@ const buildLanguageCommands = (query: string): CommandSearchResult[] => {
|
||||
const list = languages.filter(item => item.supported && (
|
||||
!q || item.name.toLowerCase().includes(q) || String(item.value).toLowerCase().includes(q)
|
||||
))
|
||||
const i18n = getI18n()
|
||||
return list.map(item => ({
|
||||
id: `lang-${item.value}`,
|
||||
title: item.name,
|
||||
|
||||
@ -2,8 +2,8 @@
|
||||
import type { ActionItem } from '../types'
|
||||
import { useTheme } from 'next-themes'
|
||||
import { useEffect } from 'react'
|
||||
import { getI18n } from 'react-i18next'
|
||||
import { setLocaleOnClient } from '@/i18n-config'
|
||||
import i18n from '@/i18n-config/i18next-config'
|
||||
import { accountCommand } from './account'
|
||||
import { executeCommand } from './command-bus'
|
||||
import { communityCommand } from './community'
|
||||
@ -14,6 +14,8 @@ import { slashCommandRegistry } from './registry'
|
||||
import { themeCommand } from './theme'
|
||||
import { zenCommand } from './zen'
|
||||
|
||||
const i18n = getI18n()
|
||||
|
||||
export const slashAction: ActionItem = {
|
||||
key: '/',
|
||||
shortcut: '/',
|
||||
|
||||
@ -2,7 +2,7 @@ import type { CommandSearchResult } from '../types'
|
||||
import type { SlashCommandHandler } from './types'
|
||||
import { RiComputerLine, RiMoonLine, RiSunLine } from '@remixicon/react'
|
||||
import * as React from 'react'
|
||||
import i18n from '@/i18n-config/i18next-config'
|
||||
import { getI18n } from 'react-i18next'
|
||||
import { registerCommands, unregisterCommands } from './command-bus'
|
||||
|
||||
// Theme dependency types
|
||||
@ -32,6 +32,7 @@ const THEME_ITEMS = [
|
||||
] as const
|
||||
|
||||
const buildThemeCommands = (query: string, locale?: string): CommandSearchResult[] => {
|
||||
const i18n = getI18n()
|
||||
const q = query.toLowerCase()
|
||||
const list = THEME_ITEMS.filter(item =>
|
||||
!q
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import type { SlashCommandHandler } from './types'
|
||||
import { RiFullscreenLine } from '@remixicon/react'
|
||||
import * as React from 'react'
|
||||
import { getI18n } from 'react-i18next'
|
||||
import { isInWorkflowPage } from '@/app/components/workflow/constants'
|
||||
import i18n from '@/i18n-config/i18next-config'
|
||||
import { registerCommands, unregisterCommands } from './command-bus'
|
||||
|
||||
// Zen command dependency types - no external dependencies needed
|
||||
@ -32,6 +32,7 @@ export const zenCommand: SlashCommandHandler<ZenDeps> = {
|
||||
execute: toggleZenMode,
|
||||
|
||||
async search(_args: string, locale: string = 'en') {
|
||||
const i18n = getI18n()
|
||||
return [{
|
||||
id: 'zen',
|
||||
title: i18n.t('gotoAnything.actions.zenTitle', { ns: 'app', lng: locale }) || 'Zen Mode',
|
||||
|
||||
@ -15,7 +15,6 @@ import Divider from '@/app/components/base/divider'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import List from '@/app/components/plugins/marketplace/list'
|
||||
import ProviderCard from '@/app/components/plugins/provider-card'
|
||||
import { getLocaleOnClient } from '@/i18n-config'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import { getMarketplaceUrl } from '@/utils/var'
|
||||
import {
|
||||
@ -33,7 +32,6 @@ const InstallFromMarketplace = ({
|
||||
const { t } = useTranslation()
|
||||
const { theme } = useTheme()
|
||||
const [collapse, setCollapse] = useState(false)
|
||||
const locale = getLocaleOnClient()
|
||||
const {
|
||||
plugins: allPlugins,
|
||||
isLoading: isAllPluginsLoading,
|
||||
@ -70,7 +68,6 @@ const InstallFromMarketplace = ({
|
||||
marketplaceCollectionPluginsMap={{}}
|
||||
plugins={allPlugins}
|
||||
showInstallButton
|
||||
locale={locale}
|
||||
cardContainerClassName="grid grid-cols-2 gap-2"
|
||||
cardRender={cardRender}
|
||||
emptyClassName="h-auto"
|
||||
|
||||
@ -2,13 +2,13 @@
|
||||
|
||||
import type { Item } from '@/app/components/base/select'
|
||||
import type { Locale } from '@/i18n-config'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useContext } from 'use-context-selector'
|
||||
import { SimpleSelect } from '@/app/components/base/select'
|
||||
import { ToastContext } from '@/app/components/base/toast'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
|
||||
import { useLocale } from '@/context/i18n'
|
||||
import { setLocaleOnClient } from '@/i18n-config'
|
||||
import { languages } from '@/i18n-config/language'
|
||||
@ -25,6 +25,7 @@ export default function LanguagePage() {
|
||||
const { notify } = useContext(ToastContext)
|
||||
const [editing, setEditing] = useState(false)
|
||||
const { t } = useTranslation()
|
||||
const router = useRouter()
|
||||
|
||||
const handleSelectLanguage = async (item: Item) => {
|
||||
const url = '/account/interface-language'
|
||||
@ -35,7 +36,8 @@ export default function LanguagePage() {
|
||||
await updateUserProfile({ url, body: { [bodyKey]: item.value } })
|
||||
notify({ type: 'success', message: t('actionMsg.modifiedSuccessfully', { ns: 'common' }) })
|
||||
|
||||
setLocaleOnClient(item.value.toString() as Locale)
|
||||
setLocaleOnClient(item.value.toString() as Locale, false)
|
||||
router.refresh()
|
||||
}
|
||||
catch (e) {
|
||||
notify({ type: 'error', message: (e as Error).message })
|
||||
|
||||
@ -6,10 +6,8 @@ import {
|
||||
RiBrainLine,
|
||||
} from '@remixicon/react'
|
||||
import { useDebounce } from 'ahooks'
|
||||
import { useEffect, useMemo } from 'react'
|
||||
import { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { IS_CLOUD_EDITION } from '@/config'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { cn } from '@/utils/classnames'
|
||||
@ -22,7 +20,6 @@ import {
|
||||
} from './hooks'
|
||||
import InstallFromMarketplace from './install-from-marketplace'
|
||||
import ProviderAddedCard from './provider-added-card'
|
||||
import QuotaPanel from './provider-added-card/quota-panel'
|
||||
import SystemModelSelector from './system-model-selector'
|
||||
|
||||
type Props = {
|
||||
@ -34,16 +31,19 @@ const FixedModelProvider = ['langgenius/openai/openai', 'langgenius/anthropic/an
|
||||
const ModelProviderPage = ({ searchText }: Props) => {
|
||||
const debouncedSearchText = useDebounce(searchText, { wait: 500 })
|
||||
const { t } = useTranslation()
|
||||
const { mutateCurrentWorkspace, isValidatingCurrentWorkspace } = useAppContext()
|
||||
const { data: textGenerationDefaultModel } = useDefaultModel(ModelTypeEnum.textGeneration)
|
||||
const { data: embeddingsDefaultModel } = useDefaultModel(ModelTypeEnum.textEmbedding)
|
||||
const { data: rerankDefaultModel } = useDefaultModel(ModelTypeEnum.rerank)
|
||||
const { data: speech2textDefaultModel } = useDefaultModel(ModelTypeEnum.speech2text)
|
||||
const { data: ttsDefaultModel } = useDefaultModel(ModelTypeEnum.tts)
|
||||
const { data: textGenerationDefaultModel, isLoading: isTextGenerationDefaultModelLoading } = useDefaultModel(ModelTypeEnum.textGeneration)
|
||||
const { data: embeddingsDefaultModel, isLoading: isEmbeddingsDefaultModelLoading } = useDefaultModel(ModelTypeEnum.textEmbedding)
|
||||
const { data: rerankDefaultModel, isLoading: isRerankDefaultModelLoading } = useDefaultModel(ModelTypeEnum.rerank)
|
||||
const { data: speech2textDefaultModel, isLoading: isSpeech2textDefaultModelLoading } = useDefaultModel(ModelTypeEnum.speech2text)
|
||||
const { data: ttsDefaultModel, isLoading: isTTSDefaultModelLoading } = useDefaultModel(ModelTypeEnum.tts)
|
||||
const { modelProviders: providers } = useProviderContext()
|
||||
const { enable_marketplace } = useGlobalPublicStore(s => s.systemFeatures)
|
||||
const defaultModelNotConfigured = !textGenerationDefaultModel && !embeddingsDefaultModel && !speech2textDefaultModel && !rerankDefaultModel && !ttsDefaultModel
|
||||
|
||||
const isDefaultModelLoading = isTextGenerationDefaultModelLoading
|
||||
|| isEmbeddingsDefaultModelLoading
|
||||
|| isRerankDefaultModelLoading
|
||||
|| isSpeech2textDefaultModelLoading
|
||||
|| isTTSDefaultModelLoading
|
||||
const defaultModelNotConfigured = !isDefaultModelLoading && !textGenerationDefaultModel && !embeddingsDefaultModel && !speech2textDefaultModel && !rerankDefaultModel && !ttsDefaultModel
|
||||
const [configuredProviders, notConfiguredProviders] = useMemo(() => {
|
||||
const configuredProviders: ModelProvider[] = []
|
||||
const notConfiguredProviders: ModelProvider[] = []
|
||||
@ -88,10 +88,6 @@ const ModelProviderPage = ({ searchText }: Props) => {
|
||||
return [filteredConfiguredProviders, filteredNotConfiguredProviders]
|
||||
}, [configuredProviders, debouncedSearchText, notConfiguredProviders])
|
||||
|
||||
useEffect(() => {
|
||||
mutateCurrentWorkspace()
|
||||
}, [mutateCurrentWorkspace])
|
||||
|
||||
return (
|
||||
<div className="relative -mt-2 pt-1">
|
||||
<div className={cn('mb-2 flex items-center')}>
|
||||
@ -115,10 +111,10 @@ const ModelProviderPage = ({ searchText }: Props) => {
|
||||
rerankDefaultModel={rerankDefaultModel}
|
||||
speech2textDefaultModel={speech2textDefaultModel}
|
||||
ttsDefaultModel={ttsDefaultModel}
|
||||
isLoading={isDefaultModelLoading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{IS_CLOUD_EDITION && <QuotaPanel providers={providers} isLoading={isValidatingCurrentWorkspace} />}
|
||||
{!filteredConfiguredProviders?.length && (
|
||||
<div className="mb-2 rounded-[10px] bg-workflow-process-bg p-4">
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-[10px] border-[0.5px] border-components-card-border bg-components-card-bg shadow-lg backdrop-blur">
|
||||
|
||||
@ -14,7 +14,6 @@ import Divider from '@/app/components/base/divider'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import List from '@/app/components/plugins/marketplace/list'
|
||||
import ProviderCard from '@/app/components/plugins/provider-card'
|
||||
import { getLocaleOnClient } from '@/i18n-config'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import { getMarketplaceUrl } from '@/utils/var'
|
||||
import {
|
||||
@ -32,7 +31,6 @@ const InstallFromMarketplace = ({
|
||||
const { t } = useTranslation()
|
||||
const { theme } = useTheme()
|
||||
const [collapse, setCollapse] = useState(false)
|
||||
const locale = getLocaleOnClient()
|
||||
const {
|
||||
plugins: allPlugins,
|
||||
isLoading: isAllPluginsLoading,
|
||||
@ -69,7 +67,6 @@ const InstallFromMarketplace = ({
|
||||
marketplaceCollectionPluginsMap={{}}
|
||||
plugins={allPlugins}
|
||||
showInstallButton
|
||||
locale={locale}
|
||||
cardContainerClassName="grid grid-cols-2 gap-2"
|
||||
cardRender={cardRender}
|
||||
emptyClassName="h-auto"
|
||||
|
||||
@ -7,7 +7,6 @@ import { useToastContext } from '@/app/components/base/toast'
|
||||
import { ConfigProvider } from '@/app/components/header/account-setting/model-provider-page/model-auth'
|
||||
import { useCredentialStatus } from '@/app/components/header/account-setting/model-provider-page/model-auth/hooks'
|
||||
import Indicator from '@/app/components/header/indicator'
|
||||
import { IS_CLOUD_EDITION } from '@/config'
|
||||
import { useEventEmitterContextContext } from '@/context/event-emitter'
|
||||
import { changeModelProviderPriority } from '@/service/common'
|
||||
import { cn } from '@/utils/classnames'
|
||||
@ -115,7 +114,7 @@ const CredentialPanel = ({
|
||||
provider={provider}
|
||||
/>
|
||||
{
|
||||
systemConfig.enabled && isCustomConfigured && IS_CLOUD_EDITION && (
|
||||
systemConfig.enabled && isCustomConfigured && (
|
||||
<PrioritySelector
|
||||
value={priorityUseType}
|
||||
onSelect={handleChangePriority}
|
||||
@ -132,7 +131,7 @@ const CredentialPanel = ({
|
||||
)
|
||||
}
|
||||
{
|
||||
systemConfig.enabled && isCustomConfigured && !provider.provider_credential_schema && IS_CLOUD_EDITION && (
|
||||
systemConfig.enabled && isCustomConfigured && !provider.provider_credential_schema && (
|
||||
<div className="ml-1">
|
||||
<PrioritySelector
|
||||
value={priorityUseType}
|
||||
|
||||
@ -3,7 +3,6 @@ import type {
|
||||
ModelItem,
|
||||
ModelProvider,
|
||||
} from '../declarations'
|
||||
import type { ModelProviderQuotaGetPaid } from '../utils'
|
||||
import {
|
||||
RiArrowRightSLine,
|
||||
RiInformation2Fill,
|
||||
@ -29,6 +28,7 @@ import {
|
||||
} from '../utils'
|
||||
import CredentialPanel from './credential-panel'
|
||||
import ModelList from './model-list'
|
||||
import QuotaPanel from './quota-panel'
|
||||
|
||||
export const UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST = 'UPDATE_MODEL_PROVIDER_CUSTOM_MODEL_LIST'
|
||||
type ProviderAddedCardProps = {
|
||||
@ -49,7 +49,7 @@ const ProviderAddedCard: FC<ProviderAddedCardProps> = ({
|
||||
const systemConfig = provider.system_configuration
|
||||
const hasModelList = fetched && !!modelList.length
|
||||
const { isCurrentWorkspaceManager } = useAppContext()
|
||||
const showModelProvider = systemConfig.enabled && [...MODEL_PROVIDER_QUOTA_GET_PAID].includes(provider.provider as ModelProviderQuotaGetPaid) && !IS_CE_EDITION
|
||||
const showQuota = systemConfig.enabled && [...MODEL_PROVIDER_QUOTA_GET_PAID].includes(provider.provider) && !IS_CE_EDITION
|
||||
const showCredential = configurationMethods.includes(ConfigurationMethodEnum.predefinedModel) && isCurrentWorkspaceManager
|
||||
|
||||
const getModelList = async (providerName: string) => {
|
||||
@ -104,6 +104,13 @@ const ProviderAddedCard: FC<ProviderAddedCardProps> = ({
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
{
|
||||
showQuota && (
|
||||
<QuotaPanel
|
||||
provider={provider}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
showCredential && (
|
||||
<CredentialPanel
|
||||
@ -115,7 +122,7 @@ const ProviderAddedCard: FC<ProviderAddedCardProps> = ({
|
||||
{
|
||||
collapsed && (
|
||||
<div className="system-xs-medium group flex items-center justify-between border-t border-t-divider-subtle py-1.5 pl-2 pr-[11px] text-text-tertiary">
|
||||
{(showModelProvider || !notConfigured) && (
|
||||
{(showQuota || !notConfigured) && (
|
||||
<>
|
||||
<div className="flex h-6 items-center pl-1 pr-1.5 leading-6 group-hover:hidden">
|
||||
{
|
||||
@ -143,7 +150,7 @@ const ProviderAddedCard: FC<ProviderAddedCardProps> = ({
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{!showModelProvider && notConfigured && (
|
||||
{!showQuota && notConfigured && (
|
||||
<div className="flex h-6 items-center pl-1 pr-1.5">
|
||||
<RiInformation2Fill className="mr-1 h-4 w-4 text-text-accent" />
|
||||
<span className="system-xs-medium text-text-secondary">{t('modelProvider.configureTip', { ns: 'common' })}</span>
|
||||
|
||||
@ -1,163 +1,66 @@
|
||||
import type { FC } from 'react'
|
||||
import type { ModelProvider } from '../declarations'
|
||||
import type { Plugin } from '@/app/components/plugins/types'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import * as React from 'react'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { AnthropicShortLight, Deepseek, Gemini, Grok, OpenaiSmall, Tongyi } from '@/app/components/base/icons/src/public/llm'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import InstallFromMarketplace from '@/app/components/plugins/install-plugin/install-from-marketplace'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import useTimestamp from '@/hooks/use-timestamp'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import { formatNumber } from '@/utils/format'
|
||||
import { PreferredProviderTypeEnum } from '../declarations'
|
||||
import { useMarketplaceAllPlugins } from '../hooks'
|
||||
import { modelNameMap, ModelProviderQuotaGetPaid } from '../utils'
|
||||
|
||||
const allProviders = [
|
||||
{ key: ModelProviderQuotaGetPaid.OPENAI, Icon: OpenaiSmall },
|
||||
{ key: ModelProviderQuotaGetPaid.ANTHROPIC, Icon: AnthropicShortLight },
|
||||
{ key: ModelProviderQuotaGetPaid.GEMINI, Icon: Gemini },
|
||||
{ key: ModelProviderQuotaGetPaid.X, Icon: Grok },
|
||||
{ key: ModelProviderQuotaGetPaid.DEEPSEEK, Icon: Deepseek },
|
||||
{ key: ModelProviderQuotaGetPaid.TONGYI, Icon: Tongyi },
|
||||
] as const
|
||||
|
||||
// Map provider key to plugin ID
|
||||
// provider key format: langgenius/provider/model, plugin ID format: langgenius/provider
|
||||
const providerKeyToPluginId: Record<string, string> = {
|
||||
[ModelProviderQuotaGetPaid.OPENAI]: 'langgenius/openai',
|
||||
[ModelProviderQuotaGetPaid.ANTHROPIC]: 'langgenius/anthropic',
|
||||
[ModelProviderQuotaGetPaid.GEMINI]: 'langgenius/gemini',
|
||||
[ModelProviderQuotaGetPaid.X]: 'langgenius/x',
|
||||
[ModelProviderQuotaGetPaid.DEEPSEEK]: 'langgenius/deepseek',
|
||||
[ModelProviderQuotaGetPaid.TONGYI]: 'langgenius/tongyi',
|
||||
}
|
||||
import {
|
||||
CustomConfigurationStatusEnum,
|
||||
PreferredProviderTypeEnum,
|
||||
QuotaUnitEnum,
|
||||
} from '../declarations'
|
||||
import {
|
||||
MODEL_PROVIDER_QUOTA_GET_PAID,
|
||||
} from '../utils'
|
||||
import PriorityUseTip from './priority-use-tip'
|
||||
|
||||
type QuotaPanelProps = {
|
||||
providers: ModelProvider[]
|
||||
isLoading?: boolean
|
||||
provider: ModelProvider
|
||||
}
|
||||
const QuotaPanel: FC<QuotaPanelProps> = ({
|
||||
providers,
|
||||
isLoading = false,
|
||||
provider,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { currentWorkspace } = useAppContext()
|
||||
const credits = Math.max((currentWorkspace.trial_credits - currentWorkspace.trial_credits_used) || 0, 0)
|
||||
const providerMap = useMemo(() => new Map(
|
||||
providers.map(p => [p.provider, p.preferred_provider_type]),
|
||||
), [providers])
|
||||
const { formatTime } = useTimestamp()
|
||||
const {
|
||||
plugins: allPlugins,
|
||||
} = useMarketplaceAllPlugins(providers, '')
|
||||
const [selectedPlugin, setSelectedPlugin] = useState<Plugin | null>(null)
|
||||
const [isShowInstallModal, {
|
||||
setTrue: showInstallFromMarketplace,
|
||||
setFalse: hideInstallFromMarketplace,
|
||||
}] = useBoolean(false)
|
||||
const selectedPluginIdRef = useRef<string | null>(null)
|
||||
|
||||
const handleIconClick = useCallback((key: string) => {
|
||||
const providerType = providerMap.get(key)
|
||||
if (!providerType && allPlugins) {
|
||||
const pluginId = providerKeyToPluginId[key]
|
||||
const plugin = allPlugins.find(p => p.plugin_id === pluginId)
|
||||
if (plugin) {
|
||||
setSelectedPlugin(plugin)
|
||||
selectedPluginIdRef.current = pluginId
|
||||
showInstallFromMarketplace()
|
||||
}
|
||||
}
|
||||
}, [allPlugins, providerMap, showInstallFromMarketplace])
|
||||
|
||||
useEffect(() => {
|
||||
if (isShowInstallModal && selectedPluginIdRef.current) {
|
||||
const isInstalled = providers.some(p => p.provider.startsWith(selectedPluginIdRef.current!))
|
||||
if (isInstalled) {
|
||||
hideInstallFromMarketplace()
|
||||
selectedPluginIdRef.current = null
|
||||
}
|
||||
}
|
||||
}, [providers, isShowInstallModal, hideInstallFromMarketplace])
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="my-2 flex min-h-[72px] items-center justify-center rounded-xl border-[0.5px] border-components-panel-border bg-third-party-model-bg-default shadow-xs">
|
||||
<Loading />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
const customConfig = provider.custom_configuration
|
||||
const priorityUseType = provider.preferred_provider_type
|
||||
const systemConfig = provider.system_configuration
|
||||
const currentQuota = systemConfig.enabled && systemConfig.quota_configurations.find(item => item.quota_type === systemConfig.current_quota_type)
|
||||
const openaiOrAnthropic = MODEL_PROVIDER_QUOTA_GET_PAID.includes(provider.provider)
|
||||
|
||||
return (
|
||||
<div className={cn('my-2 min-w-[72px] shrink-0 rounded-xl border-[0.5px] pb-2.5 pl-4 pr-2.5 pt-3 shadow-xs', credits <= 0 ? 'border-state-destructive-border hover:bg-state-destructive-hover' : 'border-components-panel-border bg-third-party-model-bg-default')}>
|
||||
<div className="group relative min-w-[112px] shrink-0 rounded-lg border-[0.5px] border-components-panel-border bg-white/[0.18] px-3 py-2 shadow-xs">
|
||||
<div className="system-xs-medium-uppercase mb-2 flex h-4 items-center text-text-tertiary">
|
||||
{t('modelProvider.quota', { ns: 'common' })}
|
||||
<Tooltip popupContent={t('modelProvider.card.tip', { ns: 'common' })} />
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-1 text-xs text-text-tertiary">
|
||||
<span className="system-md-semibold-uppercase mr-0.5 text-text-secondary">{formatNumber(credits)}</span>
|
||||
<span>{t('modelProvider.credits', { ns: 'common' })}</span>
|
||||
{currentWorkspace.next_credit_reset_date
|
||||
? (
|
||||
<>
|
||||
<span>·</span>
|
||||
<span>
|
||||
{t('modelProvider.resetDate', {
|
||||
ns: 'common',
|
||||
date: formatTime(currentWorkspace.next_credit_reset_date, t('dateFormat', { ns: 'appLog' })),
|
||||
interpolation: { escapeValue: false },
|
||||
})}
|
||||
</span>
|
||||
</>
|
||||
)
|
||||
: null}
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
{allProviders.map(({ key, Icon }) => {
|
||||
const providerType = providerMap.get(key)
|
||||
const usingQuota = providerType === PreferredProviderTypeEnum.system
|
||||
const getTooltipKey = () => {
|
||||
if (usingQuota)
|
||||
return 'modelProvider.card.modelSupported'
|
||||
if (providerType === PreferredProviderTypeEnum.custom)
|
||||
return 'modelProvider.card.modelAPI'
|
||||
return 'modelProvider.card.modelNotSupported'
|
||||
}
|
||||
return (
|
||||
<Tooltip
|
||||
key={key}
|
||||
popupContent={t(getTooltipKey(), { modelName: modelNameMap[key], ns: 'common' })}
|
||||
>
|
||||
<div
|
||||
className={cn('relative h-6 w-6', !providerType && 'cursor-pointer hover:opacity-80')}
|
||||
onClick={() => handleIconClick(key)}
|
||||
>
|
||||
<Icon className="h-6 w-6 rounded-lg" />
|
||||
{!usingQuota && (
|
||||
<div className="absolute inset-0 rounded-lg border-[0.5px] border-components-panel-border-subtle bg-background-default-dodge opacity-30" />
|
||||
)}
|
||||
</div>
|
||||
</Tooltip>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
{isShowInstallModal && selectedPlugin && (
|
||||
<InstallFromMarketplace
|
||||
manifest={selectedPlugin}
|
||||
uniqueIdentifier={selectedPlugin.latest_package_identifier}
|
||||
onClose={hideInstallFromMarketplace}
|
||||
onSuccess={hideInstallFromMarketplace}
|
||||
<Tooltip popupContent={
|
||||
openaiOrAnthropic
|
||||
? t('modelProvider.card.tip', { ns: 'common' })
|
||||
: t('modelProvider.quotaTip', { ns: 'common' })
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{
|
||||
currentQuota && (
|
||||
<div className="flex h-4 items-center text-xs text-text-tertiary">
|
||||
<span className="system-md-semibold-uppercase mr-0.5 text-text-secondary">{formatNumber(Math.max((currentQuota?.quota_limit || 0) - (currentQuota?.quota_used || 0), 0))}</span>
|
||||
{
|
||||
currentQuota?.quota_unit === QuotaUnitEnum.tokens && 'Tokens'
|
||||
}
|
||||
{
|
||||
currentQuota?.quota_unit === QuotaUnitEnum.times && t('modelProvider.callTimes', { ns: 'common' })
|
||||
}
|
||||
{
|
||||
currentQuota?.quota_unit === QuotaUnitEnum.credits && t('modelProvider.credits', { ns: 'common' })
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
priorityUseType === PreferredProviderTypeEnum.system && customConfig.status === CustomConfigurationStatusEnum.active && (
|
||||
<PriorityUseTip />
|
||||
)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(QuotaPanel)
|
||||
export default QuotaPanel
|
||||
|
||||
@ -3,7 +3,7 @@ import type {
|
||||
DefaultModel,
|
||||
DefaultModelResponse,
|
||||
} from '../declarations'
|
||||
import { RiEqualizer2Line } from '@remixicon/react'
|
||||
import { RiEqualizer2Line, RiLoader2Line } from '@remixicon/react'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Button from '@/app/components/base/button'
|
||||
@ -32,6 +32,7 @@ type SystemModelSelectorProps = {
|
||||
speech2textDefaultModel: DefaultModelResponse | undefined
|
||||
ttsDefaultModel: DefaultModelResponse | undefined
|
||||
notConfigured: boolean
|
||||
isLoading?: boolean
|
||||
}
|
||||
const SystemModel: FC<SystemModelSelectorProps> = ({
|
||||
textGenerationDefaultModel,
|
||||
@ -40,6 +41,7 @@ const SystemModel: FC<SystemModelSelectorProps> = ({
|
||||
speech2textDefaultModel,
|
||||
ttsDefaultModel,
|
||||
notConfigured,
|
||||
isLoading,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { notify } = useToastContext()
|
||||
@ -129,13 +131,16 @@ const SystemModel: FC<SystemModelSelectorProps> = ({
|
||||
crossAxis: 8,
|
||||
}}
|
||||
>
|
||||
<PortalToFollowElemTrigger onClick={() => setOpen(v => !v)}>
|
||||
<PortalToFollowElemTrigger asChild onClick={() => setOpen(v => !v)}>
|
||||
<Button
|
||||
className="relative"
|
||||
variant={notConfigured ? 'primary' : 'secondary'}
|
||||
size="small"
|
||||
disabled={isLoading}
|
||||
>
|
||||
<RiEqualizer2Line className="mr-1 h-3.5 w-3.5" />
|
||||
{isLoading
|
||||
? <RiLoader2Line className="mr-1 h-3.5 w-3.5 animate-spin" />
|
||||
: <RiEqualizer2Line className="mr-1 h-3.5 w-3.5" />}
|
||||
{t('modelProvider.systemModelSettings', { ns: 'common' })}
|
||||
</Button>
|
||||
</PortalToFollowElemTrigger>
|
||||
|
||||
@ -17,25 +17,7 @@ import {
|
||||
ModelTypeEnum,
|
||||
} from './declarations'
|
||||
|
||||
export enum ModelProviderQuotaGetPaid {
|
||||
ANTHROPIC = 'langgenius/anthropic/anthropic',
|
||||
OPENAI = 'langgenius/openai/openai',
|
||||
// AZURE_OPENAI = 'langgenius/azure_openai/azure_openai',
|
||||
GEMINI = 'langgenius/gemini/google',
|
||||
X = 'langgenius/x/x',
|
||||
DEEPSEEK = 'langgenius/deepseek/deepseek',
|
||||
TONGYI = 'langgenius/tongyi/tongyi',
|
||||
}
|
||||
export const MODEL_PROVIDER_QUOTA_GET_PAID = [ModelProviderQuotaGetPaid.ANTHROPIC, ModelProviderQuotaGetPaid.OPENAI, ModelProviderQuotaGetPaid.GEMINI, ModelProviderQuotaGetPaid.X, ModelProviderQuotaGetPaid.DEEPSEEK, ModelProviderQuotaGetPaid.TONGYI]
|
||||
|
||||
export const modelNameMap = {
|
||||
[ModelProviderQuotaGetPaid.OPENAI]: 'OpenAI',
|
||||
[ModelProviderQuotaGetPaid.ANTHROPIC]: 'Anthropic',
|
||||
[ModelProviderQuotaGetPaid.GEMINI]: 'Gemini',
|
||||
[ModelProviderQuotaGetPaid.X]: 'xAI',
|
||||
[ModelProviderQuotaGetPaid.DEEPSEEK]: 'DeepSeek',
|
||||
[ModelProviderQuotaGetPaid.TONGYI]: 'TONGYI',
|
||||
}
|
||||
export const MODEL_PROVIDER_QUOTA_GET_PAID = ['langgenius/anthropic/anthropic', 'langgenius/openai/openai', 'langgenius/azure_openai/azure_openai']
|
||||
|
||||
export const isNullOrUndefined = (value: any) => {
|
||||
return value === undefined || value === null
|
||||
|
||||
@ -1,22 +0,0 @@
|
||||
import * as React from 'react'
|
||||
import { getLocaleOnServer } from '@/i18n-config/server'
|
||||
import { ToastProvider } from './base/toast'
|
||||
import I18N from './i18n'
|
||||
|
||||
export type II18NServerProps = {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
const I18NServer = async ({
|
||||
children,
|
||||
}: II18NServerProps) => {
|
||||
const locale = await getLocaleOnServer()
|
||||
|
||||
return (
|
||||
<I18N {...{ locale }}>
|
||||
<ToastProvider>{children}</ToastProvider>
|
||||
</I18N>
|
||||
)
|
||||
}
|
||||
|
||||
export default I18NServer
|
||||
@ -1,45 +0,0 @@
|
||||
'use client'
|
||||
|
||||
import type { FC } from 'react'
|
||||
import type { Locale } from '@/i18n-config'
|
||||
import { usePrefetchQuery } from '@tanstack/react-query'
|
||||
import { useHydrateAtoms } from 'jotai/utils'
|
||||
import * as React from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { localeAtom } from '@/context/i18n'
|
||||
import { setLocaleOnClient } from '@/i18n-config'
|
||||
import { getSystemFeatures } from '@/service/common'
|
||||
import Loading from './base/loading'
|
||||
|
||||
export type II18nProps = {
|
||||
locale: Locale
|
||||
children: React.ReactNode
|
||||
}
|
||||
const I18n: FC<II18nProps> = ({
|
||||
locale,
|
||||
children,
|
||||
}) => {
|
||||
useHydrateAtoms([[localeAtom, locale]])
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
usePrefetchQuery({
|
||||
queryKey: ['systemFeatures'],
|
||||
queryFn: getSystemFeatures,
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
setLocaleOnClient(locale, false).then(() => {
|
||||
setLoading(false)
|
||||
})
|
||||
}, [locale])
|
||||
|
||||
if (loading)
|
||||
return <div className="flex h-screen w-screen items-center justify-center"><Loading type="app" /></div>
|
||||
|
||||
return (
|
||||
<>
|
||||
{children}
|
||||
</>
|
||||
)
|
||||
}
|
||||
export default React.memo(I18n)
|
||||
@ -1,4 +1,5 @@
|
||||
import type { FC } from 'react'
|
||||
import { useTranslation } from '#i18n'
|
||||
import { RiAlertFill } from '@remixicon/react'
|
||||
import { camelCase } from 'es-toolkit/string'
|
||||
import Link from 'next/link'
|
||||
@ -6,14 +7,12 @@ import * as React from 'react'
|
||||
import { useMemo } from 'react'
|
||||
import { Trans } from 'react-i18next'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import { useMixedTranslation } from '../marketplace/hooks'
|
||||
|
||||
type DeprecationNoticeProps = {
|
||||
status: 'deleted' | 'active'
|
||||
deprecatedReason: string
|
||||
alternativePluginId: string
|
||||
alternativePluginURL: string
|
||||
locale?: string
|
||||
className?: string
|
||||
innerWrapperClassName?: string
|
||||
iconWrapperClassName?: string
|
||||
@ -34,13 +33,12 @@ const DeprecationNotice: FC<DeprecationNoticeProps> = ({
|
||||
deprecatedReason,
|
||||
alternativePluginId,
|
||||
alternativePluginURL,
|
||||
locale,
|
||||
className,
|
||||
innerWrapperClassName,
|
||||
iconWrapperClassName,
|
||||
textClassName,
|
||||
}) => {
|
||||
const { t } = useMixedTranslation(locale)
|
||||
const { t } = useTranslation()
|
||||
|
||||
const deprecatedReasonKey = useMemo(() => {
|
||||
if (!deprecatedReason)
|
||||
|
||||
@ -502,31 +502,6 @@ describe('Card', () => {
|
||||
})
|
||||
})
|
||||
|
||||
// ================================
|
||||
// Locale Tests
|
||||
// ================================
|
||||
describe('Locale', () => {
|
||||
it('should use locale from props when provided', () => {
|
||||
const plugin = createMockPlugin({
|
||||
label: { 'en-US': 'English Title', 'zh-Hans': '中文标题' },
|
||||
})
|
||||
|
||||
render(<Card payload={plugin} locale="zh-Hans" />)
|
||||
|
||||
expect(screen.getByText('中文标题')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should fallback to default locale when prop locale not found', () => {
|
||||
const plugin = createMockPlugin({
|
||||
label: { 'en-US': 'English Title' },
|
||||
})
|
||||
|
||||
render(<Card payload={plugin} locale="fr-FR" />)
|
||||
|
||||
expect(screen.getByText('English Title')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
// ================================
|
||||
// Memoization Tests
|
||||
// ================================
|
||||
|
||||
@ -1,15 +1,13 @@
|
||||
'use client'
|
||||
import type { Plugin } from '../types'
|
||||
import type { Locale } from '@/i18n-config'
|
||||
import { useTranslation } from '#i18n'
|
||||
import { RiAlertFill } from '@remixicon/react'
|
||||
import * as React from 'react'
|
||||
import { useMixedTranslation } from '@/app/components/plugins/marketplace/hooks'
|
||||
import { useGetLanguage } from '@/context/i18n'
|
||||
import useTheme from '@/hooks/use-theme'
|
||||
import {
|
||||
renderI18nObject,
|
||||
} from '@/i18n-config'
|
||||
import { getLanguage } from '@/i18n-config/language'
|
||||
import { Theme } from '@/types/app'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import Partner from '../base/badges/partner'
|
||||
@ -33,7 +31,6 @@ export type Props = {
|
||||
footer?: React.ReactNode
|
||||
isLoading?: boolean
|
||||
loadingFileName?: string
|
||||
locale?: Locale
|
||||
limitedInstall?: boolean
|
||||
}
|
||||
|
||||
@ -48,13 +45,11 @@ const Card = ({
|
||||
footer,
|
||||
isLoading = false,
|
||||
loadingFileName,
|
||||
locale: localeFromProps,
|
||||
limitedInstall = false,
|
||||
}: Props) => {
|
||||
const defaultLocale = useGetLanguage()
|
||||
const locale = localeFromProps ? getLanguage(localeFromProps) : defaultLocale
|
||||
const { t } = useMixedTranslation(localeFromProps)
|
||||
const { categoriesMap } = useCategories(t, true)
|
||||
const locale = useGetLanguage()
|
||||
const { t } = useTranslation()
|
||||
const { categoriesMap } = useCategories(true)
|
||||
const { category, type, name, org, label, brief, icon, icon_dark, verified, badges = [] } = payload
|
||||
const { theme } = useTheme()
|
||||
const iconSrc = theme === Theme.dark && icon_dark ? icon_dark : icon
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import type { TFunction } from 'i18next'
|
||||
import type { CategoryKey, TagKey } from './constants'
|
||||
import { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@ -13,9 +12,8 @@ export type Tag = {
|
||||
label: string
|
||||
}
|
||||
|
||||
export const useTags = (translateFromOut?: TFunction) => {
|
||||
const { t: translation } = useTranslation()
|
||||
const t = translateFromOut || translation
|
||||
export const useTags = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const tags = useMemo(() => {
|
||||
return tagKeys.map((tag) => {
|
||||
@ -53,9 +51,8 @@ type Category = {
|
||||
label: string
|
||||
}
|
||||
|
||||
export const useCategories = (translateFromOut?: TFunction, isSingle?: boolean) => {
|
||||
const { t: translation } = useTranslation()
|
||||
const t = translateFromOut || translation
|
||||
export const useCategories = (isSingle?: boolean) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const categories = useMemo(() => {
|
||||
return categoryKeys.map((category) => {
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
// Import component after mocks are set up
|
||||
import Description from './index'
|
||||
|
||||
// ================================
|
||||
@ -30,20 +28,18 @@ const commonTranslations: Record<string, string> = {
|
||||
'operation.in': 'in',
|
||||
}
|
||||
|
||||
// Mock getLocaleOnServer and translate
|
||||
vi.mock('@/i18n-config/server', () => ({
|
||||
getLocaleOnServer: vi.fn(() => Promise.resolve(mockDefaultLocale)),
|
||||
getTranslation: vi.fn((locale: string, ns: string) => {
|
||||
return Promise.resolve({
|
||||
t: (key: string) => {
|
||||
if (ns === 'plugin')
|
||||
return pluginTranslations[key] || key
|
||||
if (ns === 'common')
|
||||
return commonTranslations[key] || key
|
||||
return key
|
||||
},
|
||||
})
|
||||
}),
|
||||
// Mock i18n hooks
|
||||
vi.mock('#i18n', () => ({
|
||||
useLocale: vi.fn(() => mockDefaultLocale),
|
||||
useTranslation: vi.fn((ns: string) => ({
|
||||
t: (key: string) => {
|
||||
if (ns === 'plugin')
|
||||
return pluginTranslations[key] || key
|
||||
if (ns === 'common')
|
||||
return commonTranslations[key] || key
|
||||
return key
|
||||
},
|
||||
})),
|
||||
}))
|
||||
|
||||
// ================================
|
||||
@ -59,29 +55,29 @@ describe('Description', () => {
|
||||
// Rendering Tests
|
||||
// ================================
|
||||
describe('Rendering', () => {
|
||||
it('should render without crashing', async () => {
|
||||
const { container } = render(await Description({}))
|
||||
it('should render without crashing', () => {
|
||||
const { container } = render(<Description />)
|
||||
|
||||
expect(container.firstChild).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render h1 heading with empower text', async () => {
|
||||
render(await Description({}))
|
||||
it('should render h1 heading with empower text', () => {
|
||||
render(<Description />)
|
||||
|
||||
const heading = screen.getByRole('heading', { level: 1 })
|
||||
expect(heading).toBeInTheDocument()
|
||||
expect(heading).toHaveTextContent('Empower your AI development')
|
||||
})
|
||||
|
||||
it('should render h2 subheading', async () => {
|
||||
render(await Description({}))
|
||||
it('should render h2 subheading', () => {
|
||||
render(<Description />)
|
||||
|
||||
const subheading = screen.getByRole('heading', { level: 2 })
|
||||
expect(subheading).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should apply correct CSS classes to h1', async () => {
|
||||
render(await Description({}))
|
||||
it('should apply correct CSS classes to h1', () => {
|
||||
render(<Description />)
|
||||
|
||||
const heading = screen.getByRole('heading', { level: 1 })
|
||||
expect(heading).toHaveClass('title-4xl-semi-bold')
|
||||
@ -90,8 +86,8 @@ describe('Description', () => {
|
||||
expect(heading).toHaveClass('text-text-primary')
|
||||
})
|
||||
|
||||
it('should apply correct CSS classes to h2', async () => {
|
||||
render(await Description({}))
|
||||
it('should apply correct CSS classes to h2', () => {
|
||||
render(<Description />)
|
||||
|
||||
const subheading = screen.getByRole('heading', { level: 2 })
|
||||
expect(subheading).toHaveClass('body-md-regular')
|
||||
@ -104,14 +100,18 @@ describe('Description', () => {
|
||||
// Non-Chinese Locale Rendering Tests
|
||||
// ================================
|
||||
describe('Non-Chinese Locale Rendering', () => {
|
||||
it('should render discover text for en-US locale', async () => {
|
||||
render(await Description({ locale: 'en-US' }))
|
||||
beforeEach(() => {
|
||||
mockDefaultLocale = 'en-US'
|
||||
})
|
||||
|
||||
it('should render discover text for en-US locale', () => {
|
||||
render(<Description />)
|
||||
|
||||
expect(screen.getByText(/Discover/)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render all category names', async () => {
|
||||
render(await Description({ locale: 'en-US' }))
|
||||
it('should render all category names', () => {
|
||||
render(<Description />)
|
||||
|
||||
expect(screen.getByText('Models')).toBeInTheDocument()
|
||||
expect(screen.getByText('Tools')).toBeInTheDocument()
|
||||
@ -122,36 +122,36 @@ describe('Description', () => {
|
||||
expect(screen.getByText('Bundles')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render "and" conjunction text', async () => {
|
||||
render(await Description({ locale: 'en-US' }))
|
||||
it('should render "and" conjunction text', () => {
|
||||
render(<Description />)
|
||||
|
||||
const subheading = screen.getByRole('heading', { level: 2 })
|
||||
expect(subheading.textContent).toContain('and')
|
||||
})
|
||||
|
||||
it('should render "in" preposition at the end for non-Chinese locales', async () => {
|
||||
render(await Description({ locale: 'en-US' }))
|
||||
it('should render "in" preposition at the end for non-Chinese locales', () => {
|
||||
render(<Description />)
|
||||
|
||||
expect(screen.getByText('in')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render Dify Marketplace text at the end for non-Chinese locales', async () => {
|
||||
render(await Description({ locale: 'en-US' }))
|
||||
it('should render Dify Marketplace text at the end for non-Chinese locales', () => {
|
||||
render(<Description />)
|
||||
|
||||
const subheading = screen.getByRole('heading', { level: 2 })
|
||||
expect(subheading.textContent).toContain('Dify Marketplace')
|
||||
})
|
||||
|
||||
it('should render category spans with styled underline effect', async () => {
|
||||
const { container } = render(await Description({ locale: 'en-US' }))
|
||||
it('should render category spans with styled underline effect', () => {
|
||||
const { container } = render(<Description />)
|
||||
|
||||
const styledSpans = container.querySelectorAll('.body-md-medium.relative.z-\\[1\\]')
|
||||
// 7 category spans (models, tools, datasources, triggers, agents, extensions, bundles)
|
||||
expect(styledSpans.length).toBe(7)
|
||||
})
|
||||
|
||||
it('should apply text-text-secondary class to category spans', async () => {
|
||||
const { container } = render(await Description({ locale: 'en-US' }))
|
||||
it('should apply text-text-secondary class to category spans', () => {
|
||||
const { container } = render(<Description />)
|
||||
|
||||
const styledSpans = container.querySelectorAll('.text-text-secondary')
|
||||
expect(styledSpans.length).toBeGreaterThanOrEqual(7)
|
||||
@ -162,29 +162,33 @@ describe('Description', () => {
|
||||
// Chinese (zh-Hans) Locale Rendering Tests
|
||||
// ================================
|
||||
describe('Chinese (zh-Hans) Locale Rendering', () => {
|
||||
it('should render "in" text at the beginning for zh-Hans locale', async () => {
|
||||
render(await Description({ locale: 'zh-Hans' }))
|
||||
beforeEach(() => {
|
||||
mockDefaultLocale = 'zh-Hans'
|
||||
})
|
||||
|
||||
it('should render "in" text at the beginning for zh-Hans locale', () => {
|
||||
render(<Description />)
|
||||
|
||||
// In zh-Hans mode, "in" appears at the beginning
|
||||
const inElements = screen.getAllByText('in')
|
||||
expect(inElements.length).toBeGreaterThanOrEqual(1)
|
||||
})
|
||||
|
||||
it('should render Dify Marketplace text for zh-Hans locale', async () => {
|
||||
render(await Description({ locale: 'zh-Hans' }))
|
||||
it('should render Dify Marketplace text for zh-Hans locale', () => {
|
||||
render(<Description />)
|
||||
|
||||
const subheading = screen.getByRole('heading', { level: 2 })
|
||||
expect(subheading.textContent).toContain('Dify Marketplace')
|
||||
})
|
||||
|
||||
it('should render discover text for zh-Hans locale', async () => {
|
||||
render(await Description({ locale: 'zh-Hans' }))
|
||||
it('should render discover text for zh-Hans locale', () => {
|
||||
render(<Description />)
|
||||
|
||||
expect(screen.getByText(/Discover/)).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render all categories for zh-Hans locale', async () => {
|
||||
render(await Description({ locale: 'zh-Hans' }))
|
||||
it('should render all categories for zh-Hans locale', () => {
|
||||
render(<Description />)
|
||||
|
||||
expect(screen.getByText('Models')).toBeInTheDocument()
|
||||
expect(screen.getByText('Tools')).toBeInTheDocument()
|
||||
@ -195,8 +199,8 @@ describe('Description', () => {
|
||||
expect(screen.getByText('Bundles')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render both zh-Hans specific elements and shared elements', async () => {
|
||||
render(await Description({ locale: 'zh-Hans' }))
|
||||
it('should render both zh-Hans specific elements and shared elements', () => {
|
||||
render(<Description />)
|
||||
|
||||
// zh-Hans has specific element order: "in" -> Dify Marketplace -> Discover
|
||||
// then the same category list with "and" -> Bundles
|
||||
@ -206,61 +210,57 @@ describe('Description', () => {
|
||||
})
|
||||
|
||||
// ================================
|
||||
// Locale Prop Variations Tests
|
||||
// Locale Variations Tests
|
||||
// ================================
|
||||
describe('Locale Prop Variations', () => {
|
||||
it('should use default locale when locale prop is undefined', async () => {
|
||||
describe('Locale Variations', () => {
|
||||
it('should use en-US locale by default', () => {
|
||||
mockDefaultLocale = 'en-US'
|
||||
render(await Description({}))
|
||||
render(<Description />)
|
||||
|
||||
// Should use the default locale from getLocaleOnServer
|
||||
expect(screen.getByText('Empower your AI development')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should use provided locale prop instead of default', async () => {
|
||||
it('should handle ja-JP locale as non-Chinese', () => {
|
||||
mockDefaultLocale = 'ja-JP'
|
||||
render(await Description({ locale: 'en-US' }))
|
||||
|
||||
// The locale prop should be used, triggering non-Chinese rendering
|
||||
const subheading = screen.getByRole('heading', { level: 2 })
|
||||
expect(subheading).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should handle ja-JP locale as non-Chinese', async () => {
|
||||
render(await Description({ locale: 'ja-JP' }))
|
||||
render(<Description />)
|
||||
|
||||
// Should render in non-Chinese format (discover first, then "in Dify Marketplace" at end)
|
||||
const subheading = screen.getByRole('heading', { level: 2 })
|
||||
expect(subheading.textContent).toContain('Dify Marketplace')
|
||||
})
|
||||
|
||||
it('should handle ko-KR locale as non-Chinese', async () => {
|
||||
render(await Description({ locale: 'ko-KR' }))
|
||||
it('should handle ko-KR locale as non-Chinese', () => {
|
||||
mockDefaultLocale = 'ko-KR'
|
||||
render(<Description />)
|
||||
|
||||
// Should render in non-Chinese format
|
||||
expect(screen.getByText('Empower your AI development')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should handle de-DE locale as non-Chinese', async () => {
|
||||
render(await Description({ locale: 'de-DE' }))
|
||||
it('should handle de-DE locale as non-Chinese', () => {
|
||||
mockDefaultLocale = 'de-DE'
|
||||
render(<Description />)
|
||||
|
||||
expect(screen.getByText('Empower your AI development')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should handle fr-FR locale as non-Chinese', async () => {
|
||||
render(await Description({ locale: 'fr-FR' }))
|
||||
it('should handle fr-FR locale as non-Chinese', () => {
|
||||
mockDefaultLocale = 'fr-FR'
|
||||
render(<Description />)
|
||||
|
||||
expect(screen.getByText('Empower your AI development')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should handle pt-BR locale as non-Chinese', async () => {
|
||||
render(await Description({ locale: 'pt-BR' }))
|
||||
it('should handle pt-BR locale as non-Chinese', () => {
|
||||
mockDefaultLocale = 'pt-BR'
|
||||
render(<Description />)
|
||||
|
||||
expect(screen.getByText('Empower your AI development')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should handle es-ES locale as non-Chinese', async () => {
|
||||
render(await Description({ locale: 'es-ES' }))
|
||||
it('should handle es-ES locale as non-Chinese', () => {
|
||||
mockDefaultLocale = 'es-ES'
|
||||
render(<Description />)
|
||||
|
||||
expect(screen.getByText('Empower your AI development')).toBeInTheDocument()
|
||||
})
|
||||
@ -270,24 +270,27 @@ describe('Description', () => {
|
||||
// Conditional Rendering Tests
|
||||
// ================================
|
||||
describe('Conditional Rendering', () => {
|
||||
it('should render zh-Hans specific content when locale is zh-Hans', async () => {
|
||||
const { container } = render(await Description({ locale: 'zh-Hans' }))
|
||||
it('should render zh-Hans specific content when locale is zh-Hans', () => {
|
||||
mockDefaultLocale = 'zh-Hans'
|
||||
const { container } = render(<Description />)
|
||||
|
||||
// zh-Hans has additional span with mr-1 before "in" text at the start
|
||||
const mrSpan = container.querySelector('span.mr-1')
|
||||
expect(mrSpan).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render non-Chinese specific content when locale is not zh-Hans', async () => {
|
||||
render(await Description({ locale: 'en-US' }))
|
||||
it('should render non-Chinese specific content when locale is not zh-Hans', () => {
|
||||
mockDefaultLocale = 'en-US'
|
||||
render(<Description />)
|
||||
|
||||
// Non-Chinese has "in" and "Dify Marketplace" at the end
|
||||
const subheading = screen.getByRole('heading', { level: 2 })
|
||||
expect(subheading.textContent).toContain('Dify Marketplace')
|
||||
})
|
||||
|
||||
it('should not render zh-Hans intro content for non-Chinese locales', async () => {
|
||||
render(await Description({ locale: 'en-US' }))
|
||||
it('should not render zh-Hans intro content for non-Chinese locales', () => {
|
||||
mockDefaultLocale = 'en-US'
|
||||
render(<Description />)
|
||||
|
||||
// For en-US, the order should be Discover ... in Dify Marketplace
|
||||
// The "in" text should only appear once at the end
|
||||
@ -303,8 +306,9 @@ describe('Description', () => {
|
||||
expect(inIndex).toBeLessThan(marketplaceIndex)
|
||||
})
|
||||
|
||||
it('should render zh-Hans with proper word order', async () => {
|
||||
render(await Description({ locale: 'zh-Hans' }))
|
||||
it('should render zh-Hans with proper word order', () => {
|
||||
mockDefaultLocale = 'zh-Hans'
|
||||
render(<Description />)
|
||||
|
||||
const subheading = screen.getByRole('heading', { level: 2 })
|
||||
const content = subheading.textContent || ''
|
||||
@ -323,58 +327,58 @@ describe('Description', () => {
|
||||
// Category Styling Tests
|
||||
// ================================
|
||||
describe('Category Styling', () => {
|
||||
it('should apply underline effect with after pseudo-element styling', async () => {
|
||||
const { container } = render(await Description({}))
|
||||
it('should apply underline effect with after pseudo-element styling', () => {
|
||||
const { container } = render(<Description />)
|
||||
|
||||
const categorySpan = container.querySelector('.after\\:absolute')
|
||||
expect(categorySpan).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should apply correct after pseudo-element classes', async () => {
|
||||
const { container } = render(await Description({}))
|
||||
it('should apply correct after pseudo-element classes', () => {
|
||||
const { container } = render(<Description />)
|
||||
|
||||
// Check for the specific after pseudo-element classes
|
||||
const categorySpans = container.querySelectorAll('.after\\:bottom-\\[1\\.5px\\]')
|
||||
expect(categorySpans.length).toBe(7)
|
||||
})
|
||||
|
||||
it('should apply full width to after element', async () => {
|
||||
const { container } = render(await Description({}))
|
||||
it('should apply full width to after element', () => {
|
||||
const { container } = render(<Description />)
|
||||
|
||||
const categorySpans = container.querySelectorAll('.after\\:w-full')
|
||||
expect(categorySpans.length).toBe(7)
|
||||
})
|
||||
|
||||
it('should apply correct height to after element', async () => {
|
||||
const { container } = render(await Description({}))
|
||||
it('should apply correct height to after element', () => {
|
||||
const { container } = render(<Description />)
|
||||
|
||||
const categorySpans = container.querySelectorAll('.after\\:h-2')
|
||||
expect(categorySpans.length).toBe(7)
|
||||
})
|
||||
|
||||
it('should apply bg-text-text-selected to after element', async () => {
|
||||
const { container } = render(await Description({}))
|
||||
it('should apply bg-text-text-selected to after element', () => {
|
||||
const { container } = render(<Description />)
|
||||
|
||||
const categorySpans = container.querySelectorAll('.after\\:bg-text-text-selected')
|
||||
expect(categorySpans.length).toBe(7)
|
||||
})
|
||||
|
||||
it('should have z-index 1 on category spans', async () => {
|
||||
const { container } = render(await Description({}))
|
||||
it('should have z-index 1 on category spans', () => {
|
||||
const { container } = render(<Description />)
|
||||
|
||||
const categorySpans = container.querySelectorAll('.z-\\[1\\]')
|
||||
expect(categorySpans.length).toBe(7)
|
||||
})
|
||||
|
||||
it('should apply left margin to category spans', async () => {
|
||||
const { container } = render(await Description({}))
|
||||
it('should apply left margin to category spans', () => {
|
||||
const { container } = render(<Description />)
|
||||
|
||||
const categorySpans = container.querySelectorAll('.ml-1')
|
||||
expect(categorySpans.length).toBeGreaterThanOrEqual(7)
|
||||
})
|
||||
|
||||
it('should apply both left and right margin to specific spans', async () => {
|
||||
const { container } = render(await Description({}))
|
||||
it('should apply both left and right margin to specific spans', () => {
|
||||
const { container } = render(<Description />)
|
||||
|
||||
// Extensions and Bundles spans have both ml-1 and mr-1
|
||||
const extensionsBundlesSpans = container.querySelectorAll('.ml-1.mr-1')
|
||||
@ -386,28 +390,17 @@ describe('Description', () => {
|
||||
// Edge Cases Tests
|
||||
// ================================
|
||||
describe('Edge Cases', () => {
|
||||
it('should handle empty props object', async () => {
|
||||
const { container } = render(await Description({}))
|
||||
|
||||
expect(container.firstChild).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render fragment as root element', async () => {
|
||||
const { container } = render(await Description({}))
|
||||
it('should render fragment as root element', () => {
|
||||
const { container } = render(<Description />)
|
||||
|
||||
// Fragment renders h1 and h2 as direct children
|
||||
expect(container.querySelector('h1')).toBeInTheDocument()
|
||||
expect(container.querySelector('h2')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should handle locale prop with undefined value', async () => {
|
||||
render(await Description({ locale: undefined }))
|
||||
|
||||
expect(screen.getByRole('heading', { level: 1 })).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should handle zh-Hant as non-Chinese simplified', async () => {
|
||||
render(await Description({ locale: 'zh-Hant' }))
|
||||
it('should handle zh-Hant as non-Chinese simplified', () => {
|
||||
mockDefaultLocale = 'zh-Hant'
|
||||
render(<Description />)
|
||||
|
||||
// zh-Hant is different from zh-Hans, should use non-Chinese format
|
||||
const subheading = screen.getByRole('heading', { level: 2 })
|
||||
@ -426,8 +419,8 @@ describe('Description', () => {
|
||||
// Content Structure Tests
|
||||
// ================================
|
||||
describe('Content Structure', () => {
|
||||
it('should have comma separators between categories', async () => {
|
||||
render(await Description({}))
|
||||
it('should have comma separators between categories', () => {
|
||||
render(<Description />)
|
||||
|
||||
const subheading = screen.getByRole('heading', { level: 2 })
|
||||
const content = subheading.textContent || ''
|
||||
@ -436,8 +429,8 @@ describe('Description', () => {
|
||||
expect(content).toMatch(/Models[^\n\r,\u2028\u2029]*,.*Tools[^\n\r,\u2028\u2029]*,.*Data Sources[^\n\r,\u2028\u2029]*,.*Triggers[^\n\r,\u2028\u2029]*,.*Agent Strategies[^\n\r,\u2028\u2029]*,.*Extensions/)
|
||||
})
|
||||
|
||||
it('should have "and" before last category (Bundles)', async () => {
|
||||
render(await Description({}))
|
||||
it('should have "and" before last category (Bundles)', () => {
|
||||
render(<Description />)
|
||||
|
||||
const subheading = screen.getByRole('heading', { level: 2 })
|
||||
const content = subheading.textContent || ''
|
||||
@ -449,8 +442,9 @@ describe('Description', () => {
|
||||
expect(andIndex).toBeLessThan(bundlesIndex)
|
||||
})
|
||||
|
||||
it('should render all text elements in correct order for en-US', async () => {
|
||||
render(await Description({ locale: 'en-US' }))
|
||||
it('should render all text elements in correct order for en-US', () => {
|
||||
mockDefaultLocale = 'en-US'
|
||||
render(<Description />)
|
||||
|
||||
const subheading = screen.getByRole('heading', { level: 2 })
|
||||
const content = subheading.textContent || ''
|
||||
@ -477,8 +471,9 @@ describe('Description', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('should render all text elements in correct order for zh-Hans', async () => {
|
||||
render(await Description({ locale: 'zh-Hans' }))
|
||||
it('should render all text elements in correct order for zh-Hans', () => {
|
||||
mockDefaultLocale = 'zh-Hans'
|
||||
render(<Description />)
|
||||
|
||||
const subheading = screen.getByRole('heading', { level: 2 })
|
||||
const content = subheading.textContent || ''
|
||||
@ -499,82 +494,48 @@ describe('Description', () => {
|
||||
// Layout Tests
|
||||
// ================================
|
||||
describe('Layout', () => {
|
||||
it('should have shrink-0 on h1 heading', async () => {
|
||||
render(await Description({}))
|
||||
it('should have shrink-0 on h1 heading', () => {
|
||||
render(<Description />)
|
||||
|
||||
const heading = screen.getByRole('heading', { level: 1 })
|
||||
expect(heading).toHaveClass('shrink-0')
|
||||
})
|
||||
|
||||
it('should have shrink-0 on h2 subheading', async () => {
|
||||
render(await Description({}))
|
||||
it('should have shrink-0 on h2 subheading', () => {
|
||||
render(<Description />)
|
||||
|
||||
const subheading = screen.getByRole('heading', { level: 2 })
|
||||
expect(subheading).toHaveClass('shrink-0')
|
||||
})
|
||||
|
||||
it('should have flex layout on h2', async () => {
|
||||
render(await Description({}))
|
||||
it('should have flex layout on h2', () => {
|
||||
render(<Description />)
|
||||
|
||||
const subheading = screen.getByRole('heading', { level: 2 })
|
||||
expect(subheading).toHaveClass('flex')
|
||||
})
|
||||
|
||||
it('should have items-center on h2', async () => {
|
||||
render(await Description({}))
|
||||
it('should have items-center on h2', () => {
|
||||
render(<Description />)
|
||||
|
||||
const subheading = screen.getByRole('heading', { level: 2 })
|
||||
expect(subheading).toHaveClass('items-center')
|
||||
})
|
||||
|
||||
it('should have justify-center on h2', async () => {
|
||||
render(await Description({}))
|
||||
it('should have justify-center on h2', () => {
|
||||
render(<Description />)
|
||||
|
||||
const subheading = screen.getByRole('heading', { level: 2 })
|
||||
expect(subheading).toHaveClass('justify-center')
|
||||
})
|
||||
})
|
||||
|
||||
// ================================
|
||||
// Translation Function Tests
|
||||
// ================================
|
||||
describe('Translation Functions', () => {
|
||||
it('should call getTranslation for plugin namespace', async () => {
|
||||
const { getTranslation } = await import('@/i18n-config/server')
|
||||
render(await Description({ locale: 'en-US' }))
|
||||
|
||||
expect(getTranslation).toHaveBeenCalledWith('en-US', 'plugin')
|
||||
})
|
||||
|
||||
it('should call getTranslation for common namespace', async () => {
|
||||
const { getTranslation } = await import('@/i18n-config/server')
|
||||
render(await Description({ locale: 'en-US' }))
|
||||
|
||||
expect(getTranslation).toHaveBeenCalledWith('en-US', 'common')
|
||||
})
|
||||
|
||||
it('should call getLocaleOnServer when locale prop is undefined', async () => {
|
||||
const { getLocaleOnServer } = await import('@/i18n-config/server')
|
||||
render(await Description({}))
|
||||
|
||||
expect(getLocaleOnServer).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should use locale prop when provided', async () => {
|
||||
const { getTranslation } = await import('@/i18n-config/server')
|
||||
render(await Description({ locale: 'ja-JP' }))
|
||||
|
||||
expect(getTranslation).toHaveBeenCalledWith('ja-JP', 'plugin')
|
||||
expect(getTranslation).toHaveBeenCalledWith('ja-JP', 'common')
|
||||
})
|
||||
})
|
||||
|
||||
// ================================
|
||||
// Accessibility Tests
|
||||
// ================================
|
||||
describe('Accessibility', () => {
|
||||
it('should have proper heading hierarchy', async () => {
|
||||
render(await Description({}))
|
||||
it('should have proper heading hierarchy', () => {
|
||||
render(<Description />)
|
||||
|
||||
const h1 = screen.getByRole('heading', { level: 1 })
|
||||
const h2 = screen.getByRole('heading', { level: 2 })
|
||||
@ -583,22 +544,22 @@ describe('Description', () => {
|
||||
expect(h2).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should have readable text content', async () => {
|
||||
render(await Description({}))
|
||||
it('should have readable text content', () => {
|
||||
render(<Description />)
|
||||
|
||||
const h1 = screen.getByRole('heading', { level: 1 })
|
||||
expect(h1.textContent).not.toBe('')
|
||||
})
|
||||
|
||||
it('should have visible h1 heading', async () => {
|
||||
render(await Description({}))
|
||||
it('should have visible h1 heading', () => {
|
||||
render(<Description />)
|
||||
|
||||
const heading = screen.getByRole('heading', { level: 1 })
|
||||
expect(heading).toBeVisible()
|
||||
})
|
||||
|
||||
it('should have visible h2 heading', async () => {
|
||||
render(await Description({}))
|
||||
it('should have visible h2 heading', () => {
|
||||
render(<Description />)
|
||||
|
||||
const subheading = screen.getByRole('heading', { level: 2 })
|
||||
expect(subheading).toBeVisible()
|
||||
@ -615,8 +576,8 @@ describe('Description Integration', () => {
|
||||
mockDefaultLocale = 'en-US'
|
||||
})
|
||||
|
||||
it('should render complete component structure', async () => {
|
||||
const { container } = render(await Description({ locale: 'en-US' }))
|
||||
it('should render complete component structure', () => {
|
||||
const { container } = render(<Description />)
|
||||
|
||||
// Main headings
|
||||
expect(container.querySelector('h1')).toBeInTheDocument()
|
||||
@ -627,8 +588,9 @@ describe('Description Integration', () => {
|
||||
expect(categorySpans.length).toBe(7)
|
||||
})
|
||||
|
||||
it('should render complete zh-Hans structure', async () => {
|
||||
const { container } = render(await Description({ locale: 'zh-Hans' }))
|
||||
it('should render complete zh-Hans structure', () => {
|
||||
mockDefaultLocale = 'zh-Hans'
|
||||
const { container } = render(<Description />)
|
||||
|
||||
// Main headings
|
||||
expect(container.querySelector('h1')).toBeInTheDocument()
|
||||
@ -639,14 +601,16 @@ describe('Description Integration', () => {
|
||||
expect(categorySpans.length).toBe(7)
|
||||
})
|
||||
|
||||
it('should correctly switch between zh-Hans and en-US layouts', async () => {
|
||||
it('should correctly differentiate between zh-Hans and en-US layouts', () => {
|
||||
// Render en-US
|
||||
const { container: enContainer, unmount: unmountEn } = render(await Description({ locale: 'en-US' }))
|
||||
mockDefaultLocale = 'en-US'
|
||||
const { container: enContainer, unmount: unmountEn } = render(<Description />)
|
||||
const enContent = enContainer.querySelector('h2')?.textContent || ''
|
||||
unmountEn()
|
||||
|
||||
// Render zh-Hans
|
||||
const { container: zhContainer } = render(await Description({ locale: 'zh-Hans' }))
|
||||
mockDefaultLocale = 'zh-Hans'
|
||||
const { container: zhContainer } = render(<Description />)
|
||||
const zhContent = zhContainer.querySelector('h2')?.textContent || ''
|
||||
|
||||
// Both should have all categories
|
||||
@ -666,14 +630,16 @@ describe('Description Integration', () => {
|
||||
expect(zhMarketplaceIndex).toBeLessThan(zhDiscoverIndex)
|
||||
})
|
||||
|
||||
it('should maintain consistent styling across locales', async () => {
|
||||
it('should maintain consistent styling across locales', () => {
|
||||
// Render en-US
|
||||
const { container: enContainer, unmount: unmountEn } = render(await Description({ locale: 'en-US' }))
|
||||
mockDefaultLocale = 'en-US'
|
||||
const { container: enContainer, unmount: unmountEn } = render(<Description />)
|
||||
const enCategoryCount = enContainer.querySelectorAll('.body-md-medium').length
|
||||
unmountEn()
|
||||
|
||||
// Render zh-Hans
|
||||
const { container: zhContainer } = render(await Description({ locale: 'zh-Hans' }))
|
||||
mockDefaultLocale = 'zh-Hans'
|
||||
const { container: zhContainer } = render(<Description />)
|
||||
const zhCategoryCount = zhContainer.querySelectorAll('.body-md-medium').length
|
||||
|
||||
// Both should have same number of styled category spans
|
||||
|
||||
@ -1,17 +1,11 @@
|
||||
/* eslint-disable dify-i18n/require-ns-option */
|
||||
import type { Locale } from '@/i18n-config'
|
||||
import { getLocaleOnServer, getTranslation } from '@/i18n-config/server'
|
||||
import { useLocale, useTranslation } from '#i18n'
|
||||
|
||||
type DescriptionProps = {
|
||||
locale?: Locale
|
||||
}
|
||||
const Description = async ({
|
||||
locale: localeFromProps,
|
||||
}: DescriptionProps) => {
|
||||
const localeDefault = await getLocaleOnServer()
|
||||
const { t } = await getTranslation(localeFromProps || localeDefault, 'plugin')
|
||||
const { t: tCommon } = await getTranslation(localeFromProps || localeDefault, 'common')
|
||||
const isZhHans = localeFromProps === 'zh-Hans'
|
||||
const Description = () => {
|
||||
const { t } = useTranslation('plugin')
|
||||
const { t: tCommon } = useTranslation('common')
|
||||
const locale = useLocale()
|
||||
|
||||
const isZhHans = locale === 'zh-Hans'
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@ -7,9 +7,9 @@ import Line from './line'
|
||||
// Mock external dependencies only
|
||||
// ================================
|
||||
|
||||
// Mock useMixedTranslation hook
|
||||
vi.mock('../hooks', () => ({
|
||||
useMixedTranslation: (_locale?: string) => ({
|
||||
// Mock i18n translation hook
|
||||
vi.mock('#i18n', () => ({
|
||||
useTranslation: () => ({
|
||||
t: (key: string, options?: { ns?: string }) => {
|
||||
// Build full key with namespace prefix if provided
|
||||
const fullKey = options?.ns ? `${options.ns}.${key}` : key
|
||||
@ -471,36 +471,6 @@ describe('Empty', () => {
|
||||
})
|
||||
})
|
||||
|
||||
// ================================
|
||||
// Locale Prop Tests
|
||||
// ================================
|
||||
describe('Locale Prop', () => {
|
||||
it('should pass locale to useMixedTranslation', () => {
|
||||
render(<Empty locale="zh-CN" />)
|
||||
|
||||
// Translation should still work
|
||||
expect(screen.getByText('No plugin found')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should handle undefined locale', () => {
|
||||
render(<Empty locale={undefined} />)
|
||||
|
||||
expect(screen.getByText('No plugin found')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should handle en-US locale', () => {
|
||||
render(<Empty locale="en-US" />)
|
||||
|
||||
expect(screen.getByText('No plugin found')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should handle ja-JP locale', () => {
|
||||
render(<Empty locale="ja-JP" />)
|
||||
|
||||
expect(screen.getByText('No plugin found')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
// ================================
|
||||
// Placeholder Cards Layout Tests
|
||||
// ================================
|
||||
@ -634,7 +604,6 @@ describe('Empty', () => {
|
||||
text="Custom message"
|
||||
lightCard
|
||||
className="custom-wrapper"
|
||||
locale="en-US"
|
||||
/>,
|
||||
)
|
||||
|
||||
@ -695,12 +664,6 @@ describe('Empty', () => {
|
||||
expect(container.querySelector('.only-class')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render with only locale prop', () => {
|
||||
render(<Empty locale="zh-CN" />)
|
||||
|
||||
expect(screen.getByText('No plugin found')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should handle text with unicode characters', () => {
|
||||
render(<Empty text="没有找到插件 🔍" />)
|
||||
|
||||
@ -813,7 +776,7 @@ describe('Empty and Line Integration', () => {
|
||||
})
|
||||
|
||||
it('should render complete Empty component structure', () => {
|
||||
const { container } = render(<Empty text="Test" lightCard className="test" locale="en-US" />)
|
||||
const { container } = render(<Empty text="Test" lightCard className="test" />)
|
||||
|
||||
// Container
|
||||
expect(container.querySelector('.test')).toBeInTheDocument()
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
import { useTranslation } from '#i18n'
|
||||
import { Group } from '@/app/components/base/icons/src/vender/other'
|
||||
import { useMixedTranslation } from '@/app/components/plugins/marketplace/hooks'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import Line from './line'
|
||||
|
||||
@ -8,16 +8,14 @@ type Props = {
|
||||
text?: string
|
||||
lightCard?: boolean
|
||||
className?: string
|
||||
locale?: string
|
||||
}
|
||||
|
||||
const Empty = ({
|
||||
text,
|
||||
lightCard,
|
||||
className,
|
||||
locale,
|
||||
}: Props) => {
|
||||
const { t } = useMixedTranslation(locale)
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div
|
||||
|
||||
@ -18,8 +18,6 @@ import {
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import i18n from '@/i18n-config/i18next-config'
|
||||
import { postMarketplace } from '@/service/base'
|
||||
import { SCROLL_BOTTOM_THRESHOLD } from './constants'
|
||||
import {
|
||||
@ -218,21 +216,6 @@ export const useMarketplacePlugins = () => {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ! Support zh-Hans, pt-BR, ja-JP and en-US for Marketplace page
|
||||
* ! For other languages, use en-US as fallback
|
||||
*/
|
||||
export const useMixedTranslation = (localeFromOuter?: string) => {
|
||||
let t = useTranslation().t
|
||||
|
||||
if (localeFromOuter)
|
||||
t = i18n.getFixedT(localeFromOuter)
|
||||
|
||||
return {
|
||||
t,
|
||||
}
|
||||
}
|
||||
|
||||
export const useMarketplaceContainerScroll = (
|
||||
callback: () => void,
|
||||
scrollContainerId = 'marketplace-container',
|
||||
|
||||
@ -11,7 +11,6 @@ import { PluginCategoryEnum } from '@/app/components/plugins/types'
|
||||
// Note: Import after mocks are set up
|
||||
import { DEFAULT_SORT, SCROLL_BOTTOM_THRESHOLD } from './constants'
|
||||
import { MarketplaceContext, MarketplaceContextProvider, useMarketplaceContext } from './context'
|
||||
import { useMixedTranslation } from './hooks'
|
||||
import PluginTypeSwitch, { PLUGIN_TYPE_SEARCH_MAP } from './plugin-type-switch'
|
||||
import StickySearchAndSwitchWrapper from './sticky-search-and-switch-wrapper'
|
||||
import {
|
||||
@ -602,48 +601,6 @@ describe('utils', () => {
|
||||
})
|
||||
})
|
||||
|
||||
// ================================
|
||||
// Hooks Tests
|
||||
// ================================
|
||||
describe('hooks', () => {
|
||||
describe('useMixedTranslation', () => {
|
||||
it('should return translation function', () => {
|
||||
const { result } = renderHook(() => useMixedTranslation())
|
||||
|
||||
expect(result.current.t).toBeDefined()
|
||||
expect(typeof result.current.t).toBe('function')
|
||||
})
|
||||
|
||||
it('should return translation key when no translation found', () => {
|
||||
const { result } = renderHook(() => useMixedTranslation())
|
||||
|
||||
// The global mock returns key with namespace prefix
|
||||
expect(result.current.t('category.all', { ns: 'plugin' })).toBe('plugin.category.all')
|
||||
})
|
||||
|
||||
it('should use locale from outer when provided', () => {
|
||||
const { result } = renderHook(() => useMixedTranslation('zh-Hans'))
|
||||
|
||||
expect(result.current.t).toBeDefined()
|
||||
})
|
||||
|
||||
it('should handle different locale values', () => {
|
||||
const locales = ['en-US', 'zh-Hans', 'ja-JP', 'pt-BR']
|
||||
locales.forEach((locale) => {
|
||||
const { result } = renderHook(() => useMixedTranslation(locale))
|
||||
expect(result.current.t).toBeDefined()
|
||||
expect(typeof result.current.t).toBe('function')
|
||||
})
|
||||
})
|
||||
|
||||
it('should use getFixedT when localeFromOuter is provided', () => {
|
||||
const { result } = renderHook(() => useMixedTranslation('fr-FR'))
|
||||
// The global mock returns key with namespace prefix
|
||||
expect(result.current.t('search', { ns: 'plugin' })).toBe('plugin.search')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// ================================
|
||||
// useMarketplaceCollectionsAndPlugins Tests
|
||||
// ================================
|
||||
@ -2088,17 +2045,6 @@ describe('StickySearchAndSwitchWrapper', () => {
|
||||
})
|
||||
|
||||
describe('Props', () => {
|
||||
it('should accept locale prop', () => {
|
||||
render(
|
||||
<MarketplaceContextProvider>
|
||||
<StickySearchAndSwitchWrapper locale="zh-Hans" />
|
||||
</MarketplaceContextProvider>,
|
||||
)
|
||||
|
||||
// Component should render without errors
|
||||
expect(screen.getByTestId('portal-elem')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should accept showSearchParams prop', () => {
|
||||
render(
|
||||
<MarketplaceContextProvider>
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import type { MarketplaceCollection, SearchParams } from './types'
|
||||
import type { Plugin } from '@/app/components/plugins/types'
|
||||
import type { Locale } from '@/i18n-config'
|
||||
import { TanstackQueryInitializer } from '@/context/query-client'
|
||||
import { MarketplaceContextProvider } from './context'
|
||||
import Description from './description'
|
||||
@ -9,7 +8,6 @@ import StickySearchAndSwitchWrapper from './sticky-search-and-switch-wrapper'
|
||||
import { getMarketplaceCollectionsAndPlugins } from './utils'
|
||||
|
||||
type MarketplaceProps = {
|
||||
locale: Locale
|
||||
showInstallButton?: boolean
|
||||
shouldExclude?: boolean
|
||||
searchParams?: SearchParams
|
||||
@ -18,7 +16,6 @@ type MarketplaceProps = {
|
||||
showSearchParams?: boolean
|
||||
}
|
||||
const Marketplace = async ({
|
||||
locale,
|
||||
showInstallButton = true,
|
||||
shouldExclude,
|
||||
searchParams,
|
||||
@ -42,14 +39,12 @@ const Marketplace = async ({
|
||||
scrollContainerId={scrollContainerId}
|
||||
showSearchParams={showSearchParams}
|
||||
>
|
||||
<Description locale={locale} />
|
||||
<Description />
|
||||
<StickySearchAndSwitchWrapper
|
||||
locale={locale}
|
||||
pluginTypeSwitchClassName={pluginTypeSwitchClassName}
|
||||
showSearchParams={showSearchParams}
|
||||
/>
|
||||
<ListWrapper
|
||||
locale={locale}
|
||||
marketplaceCollections={marketplaceCollections}
|
||||
marketplaceCollectionPluginsMap={marketplaceCollectionPluginsMap}
|
||||
showInstallButton={showInstallButton}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
import type { Plugin } from '@/app/components/plugins/types'
|
||||
import type { Locale } from '@/i18n-config'
|
||||
import { useLocale, useTranslation } from '#i18n'
|
||||
import { RiArrowRightUpLine } from '@remixicon/react'
|
||||
import { useBoolean } from 'ahooks'
|
||||
import { useTheme } from 'next-themes'
|
||||
@ -11,34 +11,30 @@ import Card from '@/app/components/plugins/card'
|
||||
import CardMoreInfo from '@/app/components/plugins/card/card-more-info'
|
||||
import { useTags } from '@/app/components/plugins/hooks'
|
||||
import InstallFromMarketplace from '@/app/components/plugins/install-plugin/install-from-marketplace'
|
||||
import { useMixedTranslation } from '@/app/components/plugins/marketplace/hooks'
|
||||
import { useLocale } from '@/context/i18n'
|
||||
import { getPluginDetailLinkInMarketplace, getPluginLinkInMarketplace } from '../utils'
|
||||
|
||||
type CardWrapperProps = {
|
||||
plugin: Plugin
|
||||
showInstallButton?: boolean
|
||||
locale?: Locale
|
||||
}
|
||||
const CardWrapperComponent = ({
|
||||
plugin,
|
||||
showInstallButton,
|
||||
locale,
|
||||
}: CardWrapperProps) => {
|
||||
const { t } = useMixedTranslation(locale)
|
||||
const { t } = useTranslation()
|
||||
const { theme } = useTheme()
|
||||
const [isShowInstallFromMarketplace, {
|
||||
setTrue: showInstallFromMarketplace,
|
||||
setFalse: hideInstallFromMarketplace,
|
||||
}] = useBoolean(false)
|
||||
const localeFromLocale = useLocale()
|
||||
const { getTagLabel } = useTags(t)
|
||||
const locale = useLocale()
|
||||
const { getTagLabel } = useTags()
|
||||
|
||||
// Memoize marketplace link params to prevent unnecessary re-renders
|
||||
const marketplaceLinkParams = useMemo(() => ({
|
||||
language: localeFromLocale,
|
||||
language: locale,
|
||||
theme,
|
||||
}), [localeFromLocale, theme])
|
||||
}), [locale, theme])
|
||||
|
||||
// Memoize tag labels to prevent recreating array on every render
|
||||
const tagLabels = useMemo(() =>
|
||||
@ -52,7 +48,6 @@ const CardWrapperComponent = ({
|
||||
<Card
|
||||
key={plugin.name}
|
||||
payload={plugin}
|
||||
locale={locale}
|
||||
footer={(
|
||||
<CardMoreInfo
|
||||
downloadCount={plugin.install_count}
|
||||
@ -99,7 +94,6 @@ const CardWrapperComponent = ({
|
||||
<Card
|
||||
key={plugin.name}
|
||||
payload={plugin}
|
||||
locale={locale}
|
||||
footer={(
|
||||
<CardMoreInfo
|
||||
downloadCount={plugin.install_count}
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import type { MarketplaceCollection, SearchParamsFromCollection } from '../types'
|
||||
import type { Plugin } from '@/app/components/plugins/types'
|
||||
import type { Locale } from '@/i18n-config'
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { PluginCategoryEnum } from '@/app/components/plugins/types'
|
||||
@ -12,9 +11,9 @@ import ListWrapper from './list-wrapper'
|
||||
// Mock External Dependencies Only
|
||||
// ================================
|
||||
|
||||
// Mock useMixedTranslation hook
|
||||
vi.mock('../hooks', () => ({
|
||||
useMixedTranslation: (_locale?: string) => ({
|
||||
// Mock i18n translation hook
|
||||
vi.mock('#i18n', () => ({
|
||||
useTranslation: () => ({
|
||||
t: (key: string, options?: { ns?: string, num?: number }) => {
|
||||
// Build full key with namespace prefix if provided
|
||||
const fullKey = options?.ns ? `${options.ns}.${key}` : key
|
||||
@ -28,6 +27,7 @@ vi.mock('../hooks', () => ({
|
||||
return translations[fullKey] || key
|
||||
},
|
||||
}),
|
||||
useLocale: () => 'en-US',
|
||||
}))
|
||||
|
||||
// Mock useMarketplaceContext with controllable values
|
||||
@ -148,15 +148,15 @@ vi.mock('@/app/components/plugins/install-plugin/install-from-marketplace', () =
|
||||
|
||||
// Mock SortDropdown component
|
||||
vi.mock('../sort-dropdown', () => ({
|
||||
default: ({ locale }: { locale: Locale }) => (
|
||||
<div data-testid="sort-dropdown" data-locale={locale}>Sort</div>
|
||||
default: () => (
|
||||
<div data-testid="sort-dropdown">Sort</div>
|
||||
),
|
||||
}))
|
||||
|
||||
// Mock Empty component
|
||||
vi.mock('../empty', () => ({
|
||||
default: ({ className, locale }: { className?: string, locale?: string }) => (
|
||||
<div data-testid="empty-component" className={className} data-locale={locale}>
|
||||
default: ({ className }: { className?: string }) => (
|
||||
<div data-testid="empty-component" className={className}>
|
||||
No plugins found
|
||||
</div>
|
||||
),
|
||||
@ -233,7 +233,6 @@ describe('List', () => {
|
||||
marketplaceCollectionPluginsMap: {} as Record<string, Plugin[]>,
|
||||
plugins: undefined,
|
||||
showInstallButton: false,
|
||||
locale: 'en-US' as Locale,
|
||||
cardContainerClassName: '',
|
||||
cardRender: undefined,
|
||||
onMoreClick: undefined,
|
||||
@ -351,18 +350,6 @@ describe('List', () => {
|
||||
expect(screen.getByTestId('empty-component')).toHaveClass('custom-empty-class')
|
||||
})
|
||||
|
||||
it('should pass locale to Empty component', () => {
|
||||
render(
|
||||
<List
|
||||
{...defaultProps}
|
||||
plugins={[]}
|
||||
locale={'zh-CN' as Locale}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByTestId('empty-component')).toHaveAttribute('data-locale', 'zh-CN')
|
||||
})
|
||||
|
||||
it('should pass showInstallButton to CardWrapper', () => {
|
||||
const plugins = createMockPluginList(1)
|
||||
|
||||
@ -508,7 +495,6 @@ describe('ListWithCollection', () => {
|
||||
marketplaceCollections: [] as MarketplaceCollection[],
|
||||
marketplaceCollectionPluginsMap: {} as Record<string, Plugin[]>,
|
||||
showInstallButton: false,
|
||||
locale: 'en-US' as Locale,
|
||||
cardContainerClassName: '',
|
||||
cardRender: undefined,
|
||||
onMoreClick: undefined,
|
||||
@ -820,7 +806,6 @@ describe('ListWrapper', () => {
|
||||
marketplaceCollections: [] as MarketplaceCollection[],
|
||||
marketplaceCollectionPluginsMap: {} as Record<string, Plugin[]>,
|
||||
showInstallButton: false,
|
||||
locale: 'en-US' as Locale,
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
@ -901,14 +886,6 @@ describe('ListWrapper', () => {
|
||||
|
||||
expect(screen.queryByTestId('sort-dropdown')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should pass locale to SortDropdown', () => {
|
||||
mockContextValues.plugins = createMockPluginList(1)
|
||||
|
||||
render(<ListWrapper {...defaultProps} locale={'zh-CN' as Locale} />)
|
||||
|
||||
expect(screen.getByTestId('sort-dropdown')).toHaveAttribute('data-locale', 'zh-CN')
|
||||
})
|
||||
})
|
||||
|
||||
// ================================
|
||||
@ -1169,7 +1146,6 @@ describe('CardWrapper (via List integration)', () => {
|
||||
marketplaceCollections={[]}
|
||||
marketplaceCollectionPluginsMap={{}}
|
||||
plugins={[plugin]}
|
||||
locale="en-US"
|
||||
/>,
|
||||
)
|
||||
|
||||
@ -1188,7 +1164,6 @@ describe('CardWrapper (via List integration)', () => {
|
||||
marketplaceCollections={[]}
|
||||
marketplaceCollectionPluginsMap={{}}
|
||||
plugins={[plugin]}
|
||||
locale="en-US"
|
||||
/>,
|
||||
)
|
||||
|
||||
@ -1209,7 +1184,6 @@ describe('CardWrapper (via List integration)', () => {
|
||||
marketplaceCollections={[]}
|
||||
marketplaceCollectionPluginsMap={{}}
|
||||
plugins={plugins}
|
||||
locale="en-US"
|
||||
/>,
|
||||
)
|
||||
|
||||
@ -1231,7 +1205,6 @@ describe('CardWrapper (via List integration)', () => {
|
||||
marketplaceCollectionPluginsMap={{}}
|
||||
plugins={[plugin]}
|
||||
showInstallButton={true}
|
||||
locale="en-US"
|
||||
/>,
|
||||
)
|
||||
|
||||
@ -1252,7 +1225,6 @@ describe('CardWrapper (via List integration)', () => {
|
||||
marketplaceCollectionPluginsMap={{}}
|
||||
plugins={[plugin]}
|
||||
showInstallButton={true}
|
||||
locale="en-US"
|
||||
/>,
|
||||
)
|
||||
|
||||
@ -1274,7 +1246,6 @@ describe('CardWrapper (via List integration)', () => {
|
||||
marketplaceCollectionPluginsMap={{}}
|
||||
plugins={[plugin]}
|
||||
showInstallButton={true}
|
||||
locale="en-US"
|
||||
/>,
|
||||
)
|
||||
|
||||
@ -1293,7 +1264,6 @@ describe('CardWrapper (via List integration)', () => {
|
||||
marketplaceCollectionPluginsMap={{}}
|
||||
plugins={[plugin]}
|
||||
showInstallButton={true}
|
||||
locale="en-US"
|
||||
/>,
|
||||
)
|
||||
|
||||
@ -1310,7 +1280,6 @@ describe('CardWrapper (via List integration)', () => {
|
||||
marketplaceCollectionPluginsMap={{}}
|
||||
plugins={[plugin]}
|
||||
showInstallButton={true}
|
||||
locale="en-US"
|
||||
/>,
|
||||
)
|
||||
|
||||
@ -1327,7 +1296,6 @@ describe('CardWrapper (via List integration)', () => {
|
||||
marketplaceCollectionPluginsMap={{}}
|
||||
plugins={[plugin]}
|
||||
showInstallButton={true}
|
||||
locale="en-US"
|
||||
/>,
|
||||
)
|
||||
|
||||
@ -1354,7 +1322,6 @@ describe('CardWrapper (via List integration)', () => {
|
||||
marketplaceCollectionPluginsMap={{}}
|
||||
plugins={[plugin]}
|
||||
showInstallButton={false}
|
||||
locale="en-US"
|
||||
/>,
|
||||
)
|
||||
|
||||
@ -1375,7 +1342,6 @@ describe('CardWrapper (via List integration)', () => {
|
||||
marketplaceCollectionPluginsMap={{}}
|
||||
plugins={[plugin]}
|
||||
showInstallButton={false}
|
||||
locale="en-US"
|
||||
/>,
|
||||
)
|
||||
|
||||
@ -1390,7 +1356,6 @@ describe('CardWrapper (via List integration)', () => {
|
||||
marketplaceCollections={[]}
|
||||
marketplaceCollectionPluginsMap={{}}
|
||||
plugins={[plugin]}
|
||||
locale="en-US"
|
||||
/>,
|
||||
)
|
||||
|
||||
@ -1414,7 +1379,6 @@ describe('CardWrapper (via List integration)', () => {
|
||||
marketplaceCollections={[]}
|
||||
marketplaceCollectionPluginsMap={{}}
|
||||
plugins={[plugin]}
|
||||
locale="en-US"
|
||||
/>,
|
||||
)
|
||||
|
||||
@ -1432,7 +1396,6 @@ describe('CardWrapper (via List integration)', () => {
|
||||
marketplaceCollections={[]}
|
||||
marketplaceCollectionPluginsMap={{}}
|
||||
plugins={[plugin]}
|
||||
locale="en-US"
|
||||
/>,
|
||||
)
|
||||
|
||||
@ -1450,7 +1413,6 @@ describe('CardWrapper (via List integration)', () => {
|
||||
marketplaceCollections={[]}
|
||||
marketplaceCollectionPluginsMap={{}}
|
||||
plugins={[plugin]}
|
||||
locale="en-US"
|
||||
/>,
|
||||
)
|
||||
|
||||
@ -1482,7 +1444,6 @@ describe('Combined Workflows', () => {
|
||||
<ListWrapper
|
||||
marketplaceCollections={[]}
|
||||
marketplaceCollectionPluginsMap={{}}
|
||||
locale="en-US"
|
||||
/>,
|
||||
)
|
||||
|
||||
@ -1501,7 +1462,6 @@ describe('Combined Workflows', () => {
|
||||
<ListWrapper
|
||||
marketplaceCollections={[]}
|
||||
marketplaceCollectionPluginsMap={{}}
|
||||
locale="en-US"
|
||||
/>,
|
||||
)
|
||||
|
||||
@ -1521,7 +1481,6 @@ describe('Combined Workflows', () => {
|
||||
<ListWrapper
|
||||
marketplaceCollections={[]}
|
||||
marketplaceCollectionPluginsMap={{}}
|
||||
locale="en-US"
|
||||
/>,
|
||||
)
|
||||
|
||||
@ -1535,7 +1494,6 @@ describe('Combined Workflows', () => {
|
||||
<ListWrapper
|
||||
marketplaceCollections={[]}
|
||||
marketplaceCollectionPluginsMap={{}}
|
||||
locale="en-US"
|
||||
/>,
|
||||
)
|
||||
|
||||
@ -1551,7 +1509,6 @@ describe('Combined Workflows', () => {
|
||||
<ListWrapper
|
||||
marketplaceCollections={[]}
|
||||
marketplaceCollectionPluginsMap={{}}
|
||||
locale="en-US"
|
||||
/>,
|
||||
)
|
||||
|
||||
@ -1569,7 +1526,6 @@ describe('Combined Workflows', () => {
|
||||
<ListWrapper
|
||||
marketplaceCollections={[]}
|
||||
marketplaceCollectionPluginsMap={{}}
|
||||
locale="en-US"
|
||||
/>,
|
||||
)
|
||||
|
||||
@ -1601,7 +1557,6 @@ describe('Accessibility', () => {
|
||||
<ListWithCollection
|
||||
marketplaceCollections={collections}
|
||||
marketplaceCollectionPluginsMap={pluginsMap}
|
||||
locale="en-US"
|
||||
/>,
|
||||
)
|
||||
|
||||
@ -1625,7 +1580,6 @@ describe('Accessibility', () => {
|
||||
marketplaceCollections={collections}
|
||||
marketplaceCollectionPluginsMap={pluginsMap}
|
||||
onMoreClick={onMoreClick}
|
||||
locale="en-US"
|
||||
/>,
|
||||
)
|
||||
|
||||
@ -1642,7 +1596,6 @@ describe('Accessibility', () => {
|
||||
marketplaceCollections={[]}
|
||||
marketplaceCollectionPluginsMap={{}}
|
||||
plugins={plugins}
|
||||
locale="en-US"
|
||||
/>,
|
||||
)
|
||||
|
||||
@ -1668,7 +1621,6 @@ describe('Performance', () => {
|
||||
marketplaceCollections={[]}
|
||||
marketplaceCollectionPluginsMap={{}}
|
||||
plugins={plugins}
|
||||
locale="en-US"
|
||||
/>,
|
||||
)
|
||||
const endTime = performance.now()
|
||||
@ -1689,7 +1641,6 @@ describe('Performance', () => {
|
||||
<ListWithCollection
|
||||
marketplaceCollections={collections}
|
||||
marketplaceCollectionPluginsMap={pluginsMap}
|
||||
locale="en-US"
|
||||
/>,
|
||||
)
|
||||
const endTime = performance.now()
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
'use client'
|
||||
import type { Plugin } from '../../types'
|
||||
import type { MarketplaceCollection } from '../types'
|
||||
import type { Locale } from '@/i18n-config'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import Empty from '../empty'
|
||||
import CardWrapper from './card-wrapper'
|
||||
@ -12,7 +11,6 @@ type ListProps = {
|
||||
marketplaceCollectionPluginsMap: Record<string, Plugin[]>
|
||||
plugins?: Plugin[]
|
||||
showInstallButton?: boolean
|
||||
locale: Locale
|
||||
cardContainerClassName?: string
|
||||
cardRender?: (plugin: Plugin) => React.JSX.Element | null
|
||||
onMoreClick?: () => void
|
||||
@ -23,7 +21,6 @@ const List = ({
|
||||
marketplaceCollectionPluginsMap,
|
||||
plugins,
|
||||
showInstallButton,
|
||||
locale,
|
||||
cardContainerClassName,
|
||||
cardRender,
|
||||
onMoreClick,
|
||||
@ -37,7 +34,6 @@ const List = ({
|
||||
marketplaceCollections={marketplaceCollections}
|
||||
marketplaceCollectionPluginsMap={marketplaceCollectionPluginsMap}
|
||||
showInstallButton={showInstallButton}
|
||||
locale={locale}
|
||||
cardContainerClassName={cardContainerClassName}
|
||||
cardRender={cardRender}
|
||||
onMoreClick={onMoreClick}
|
||||
@ -61,7 +57,6 @@ const List = ({
|
||||
key={`${plugin.org}/${plugin.name}`}
|
||||
plugin={plugin}
|
||||
showInstallButton={showInstallButton}
|
||||
locale={locale}
|
||||
/>
|
||||
)
|
||||
})
|
||||
@ -71,7 +66,7 @@ const List = ({
|
||||
}
|
||||
{
|
||||
plugins && !plugins.length && (
|
||||
<Empty className={emptyClassName} locale={locale} />
|
||||
<Empty className={emptyClassName} />
|
||||
)
|
||||
}
|
||||
</>
|
||||
|
||||
@ -3,9 +3,8 @@
|
||||
import type { MarketplaceCollection } from '../types'
|
||||
import type { SearchParamsFromCollection } from '@/app/components/plugins/marketplace/types'
|
||||
import type { Plugin } from '@/app/components/plugins/types'
|
||||
import type { Locale } from '@/i18n-config'
|
||||
import { useLocale, useTranslation } from '#i18n'
|
||||
import { RiArrowRightSLine } from '@remixicon/react'
|
||||
import { useMixedTranslation } from '@/app/components/plugins/marketplace/hooks'
|
||||
import { getLanguage } from '@/i18n-config/language'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import CardWrapper from './card-wrapper'
|
||||
@ -14,7 +13,6 @@ type ListWithCollectionProps = {
|
||||
marketplaceCollections: MarketplaceCollection[]
|
||||
marketplaceCollectionPluginsMap: Record<string, Plugin[]>
|
||||
showInstallButton?: boolean
|
||||
locale: Locale
|
||||
cardContainerClassName?: string
|
||||
cardRender?: (plugin: Plugin) => React.JSX.Element | null
|
||||
onMoreClick?: (searchParams?: SearchParamsFromCollection) => void
|
||||
@ -23,12 +21,12 @@ const ListWithCollection = ({
|
||||
marketplaceCollections,
|
||||
marketplaceCollectionPluginsMap,
|
||||
showInstallButton,
|
||||
locale,
|
||||
cardContainerClassName,
|
||||
cardRender,
|
||||
onMoreClick,
|
||||
}: ListWithCollectionProps) => {
|
||||
const { t } = useMixedTranslation(locale)
|
||||
const { t } = useTranslation()
|
||||
const locale = useLocale()
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -72,7 +70,6 @@ const ListWithCollection = ({
|
||||
key={plugin.plugin_id}
|
||||
plugin={plugin}
|
||||
showInstallButton={showInstallButton}
|
||||
locale={locale}
|
||||
/>
|
||||
)
|
||||
})
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
'use client'
|
||||
import type { Plugin } from '../../types'
|
||||
import type { MarketplaceCollection } from '../types'
|
||||
import type { Locale } from '@/i18n-config'
|
||||
import { useTranslation } from '#i18n'
|
||||
import { useEffect } from 'react'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import { useMixedTranslation } from '@/app/components/plugins/marketplace/hooks'
|
||||
import { useMarketplaceContext } from '../context'
|
||||
import SortDropdown from '../sort-dropdown'
|
||||
import List from './index'
|
||||
@ -13,15 +12,13 @@ type ListWrapperProps = {
|
||||
marketplaceCollections: MarketplaceCollection[]
|
||||
marketplaceCollectionPluginsMap: Record<string, Plugin[]>
|
||||
showInstallButton?: boolean
|
||||
locale: Locale
|
||||
}
|
||||
const ListWrapper = ({
|
||||
marketplaceCollections,
|
||||
marketplaceCollectionPluginsMap,
|
||||
showInstallButton,
|
||||
locale,
|
||||
}: ListWrapperProps) => {
|
||||
const { t } = useMixedTranslation(locale)
|
||||
const { t } = useTranslation()
|
||||
const plugins = useMarketplaceContext(v => v.plugins)
|
||||
const pluginsTotal = useMarketplaceContext(v => v.pluginsTotal)
|
||||
const marketplaceCollectionsFromClient = useMarketplaceContext(v => v.marketplaceCollectionsFromClient)
|
||||
@ -55,7 +52,7 @@ const ListWrapper = ({
|
||||
<div className="mb-4 flex items-center pt-3">
|
||||
<div className="title-xl-semi-bold text-text-primary">{t('marketplace.pluginsResult', { ns: 'plugin', num: pluginsTotal })}</div>
|
||||
<div className="mx-3 h-3.5 w-[1px] bg-divider-regular"></div>
|
||||
<SortDropdown locale={locale} />
|
||||
<SortDropdown />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -73,7 +70,6 @@ const ListWrapper = ({
|
||||
marketplaceCollectionPluginsMap={marketplaceCollectionPluginsMapFromClient || marketplaceCollectionPluginsMap}
|
||||
plugins={plugins}
|
||||
showInstallButton={showInstallButton}
|
||||
locale={locale}
|
||||
onMoreClick={handleMoreClick}
|
||||
/>
|
||||
)
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
'use client'
|
||||
import { useTranslation } from '#i18n'
|
||||
import {
|
||||
RiArchive2Line,
|
||||
RiBrain2Line,
|
||||
@ -12,7 +13,6 @@ import { Trigger as TriggerIcon } from '@/app/components/base/icons/src/vender/p
|
||||
import { cn } from '@/utils/classnames'
|
||||
import { PluginCategoryEnum } from '../types'
|
||||
import { useMarketplaceContext } from './context'
|
||||
import { useMixedTranslation } from './hooks'
|
||||
|
||||
export const PLUGIN_TYPE_SEARCH_MAP = {
|
||||
all: 'all',
|
||||
@ -25,16 +25,14 @@ export const PLUGIN_TYPE_SEARCH_MAP = {
|
||||
bundle: 'bundle',
|
||||
}
|
||||
type PluginTypeSwitchProps = {
|
||||
locale?: string
|
||||
className?: string
|
||||
showSearchParams?: boolean
|
||||
}
|
||||
const PluginTypeSwitch = ({
|
||||
locale,
|
||||
className,
|
||||
showSearchParams,
|
||||
}: PluginTypeSwitchProps) => {
|
||||
const { t } = useMixedTranslation(locale)
|
||||
const { t } = useTranslation()
|
||||
const activePluginType = useMarketplaceContext(s => s.activePluginType)
|
||||
const handleActivePluginTypeChange = useMarketplaceContext(s => s.handleActivePluginTypeChange)
|
||||
|
||||
|
||||
@ -10,9 +10,9 @@ import ToolSelectorTrigger from './trigger/tool-selector'
|
||||
// Mock external dependencies only
|
||||
// ================================
|
||||
|
||||
// Mock useMixedTranslation hook
|
||||
vi.mock('../hooks', () => ({
|
||||
useMixedTranslation: (_locale?: string) => ({
|
||||
// Mock i18n translation hook
|
||||
vi.mock('#i18n', () => ({
|
||||
useTranslation: () => ({
|
||||
t: (key: string, options?: { ns?: string }) => {
|
||||
// Build full key with namespace prefix if provided
|
||||
const fullKey = options?.ns ? `${options.ns}.${key}` : key
|
||||
@ -364,13 +364,6 @@ describe('SearchBox', () => {
|
||||
expect(container.querySelector('.custom-input-class')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should pass locale to TagsFilter', () => {
|
||||
render(<SearchBox {...defaultProps} locale="zh-CN" />)
|
||||
|
||||
// TagsFilter should be rendered with locale
|
||||
expect(screen.getByTestId('portal-elem')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should handle empty placeholder', () => {
|
||||
render(<SearchBox {...defaultProps} placeholder="" />)
|
||||
|
||||
@ -449,12 +442,6 @@ describe('SearchBoxWrapper', () => {
|
||||
expect(screen.getByRole('textbox')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render with locale prop', () => {
|
||||
render(<SearchBoxWrapper locale="en-US" />)
|
||||
|
||||
expect(screen.getByRole('textbox')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render in marketplace mode', () => {
|
||||
const { container } = render(<SearchBoxWrapper />)
|
||||
|
||||
@ -500,13 +487,6 @@ describe('SearchBoxWrapper', () => {
|
||||
|
||||
expect(screen.getByPlaceholderText('Search plugins')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should pass locale to useMixedTranslation', () => {
|
||||
render(<SearchBoxWrapper locale="zh-CN" />)
|
||||
|
||||
// Translation should still work
|
||||
expect(screen.getByPlaceholderText('Search plugins')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -665,12 +645,6 @@ describe('MarketplaceTrigger', () => {
|
||||
})
|
||||
|
||||
describe('Props Variations', () => {
|
||||
it('should handle locale prop', () => {
|
||||
render(<MarketplaceTrigger {...defaultProps} locale="zh-CN" />)
|
||||
|
||||
expect(screen.getByText('All Tags')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should handle empty tagsMap', () => {
|
||||
const { container } = render(
|
||||
<MarketplaceTrigger {...defaultProps} tagsMap={{}} tags={[]} />,
|
||||
@ -1251,7 +1225,6 @@ describe('Combined Workflows', () => {
|
||||
supportAddCustomTool
|
||||
onShowAddCustomCollectionModal={vi.fn()}
|
||||
placeholder="Search plugins"
|
||||
locale="en-US"
|
||||
wrapperClassName="custom-wrapper"
|
||||
inputClassName="custom-input"
|
||||
autoFocus={false}
|
||||
|
||||
@ -13,7 +13,6 @@ type SearchBoxProps = {
|
||||
tags: string[]
|
||||
onTagsChange: (tags: string[]) => void
|
||||
placeholder?: string
|
||||
locale?: string
|
||||
supportAddCustomTool?: boolean
|
||||
usedInMarketplace?: boolean
|
||||
onShowAddCustomCollectionModal?: () => void
|
||||
@ -28,7 +27,6 @@ const SearchBox = ({
|
||||
tags,
|
||||
onTagsChange,
|
||||
placeholder = '',
|
||||
locale,
|
||||
usedInMarketplace = false,
|
||||
supportAddCustomTool,
|
||||
onShowAddCustomCollectionModal,
|
||||
@ -49,7 +47,6 @@ const SearchBox = ({
|
||||
tags={tags}
|
||||
onTagsChange={onTagsChange}
|
||||
usedInMarketplace
|
||||
locale={locale}
|
||||
/>
|
||||
<Divider type="vertical" className="mx-1 h-3.5" />
|
||||
<div className="flex grow items-center gap-x-2 p-1">
|
||||
@ -109,7 +106,6 @@ const SearchBox = ({
|
||||
<TagsFilter
|
||||
tags={tags}
|
||||
onTagsChange={onTagsChange}
|
||||
locale={locale}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
|
||||
@ -1,16 +1,11 @@
|
||||
'use client'
|
||||
|
||||
import { useTranslation } from '#i18n'
|
||||
import { useMarketplaceContext } from '../context'
|
||||
import { useMixedTranslation } from '../hooks'
|
||||
import SearchBox from './index'
|
||||
|
||||
type SearchBoxWrapperProps = {
|
||||
locale?: string
|
||||
}
|
||||
const SearchBoxWrapper = ({
|
||||
locale,
|
||||
}: SearchBoxWrapperProps) => {
|
||||
const { t } = useMixedTranslation(locale)
|
||||
const SearchBoxWrapper = () => {
|
||||
const { t } = useTranslation()
|
||||
const searchPluginText = useMarketplaceContext(v => v.searchPluginText)
|
||||
const handleSearchPluginTextChange = useMarketplaceContext(v => v.handleSearchPluginTextChange)
|
||||
const filterPluginTags = useMarketplaceContext(v => v.filterPluginTags)
|
||||
@ -24,7 +19,6 @@ const SearchBoxWrapper = ({
|
||||
onSearchChange={handleSearchPluginTextChange}
|
||||
tags={filterPluginTags}
|
||||
onTagsChange={handleFilterPluginTagsChange}
|
||||
locale={locale}
|
||||
placeholder={t('searchPlugins', { ns: 'plugin' })}
|
||||
usedInMarketplace
|
||||
/>
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import { useTranslation } from '#i18n'
|
||||
import { useState } from 'react'
|
||||
import Checkbox from '@/app/components/base/checkbox'
|
||||
import Input from '@/app/components/base/input'
|
||||
@ -9,7 +10,6 @@ import {
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import { useTags } from '@/app/components/plugins/hooks'
|
||||
import { useMixedTranslation } from '@/app/components/plugins/marketplace/hooks'
|
||||
import MarketplaceTrigger from './trigger/marketplace'
|
||||
import ToolSelectorTrigger from './trigger/tool-selector'
|
||||
|
||||
@ -17,18 +17,16 @@ type TagsFilterProps = {
|
||||
tags: string[]
|
||||
onTagsChange: (tags: string[]) => void
|
||||
usedInMarketplace?: boolean
|
||||
locale?: string
|
||||
}
|
||||
const TagsFilter = ({
|
||||
tags,
|
||||
onTagsChange,
|
||||
usedInMarketplace = false,
|
||||
locale,
|
||||
}: TagsFilterProps) => {
|
||||
const { t } = useMixedTranslation(locale)
|
||||
const { t } = useTranslation()
|
||||
const [open, setOpen] = useState(false)
|
||||
const [searchText, setSearchText] = useState('')
|
||||
const { tags: options, tagsMap } = useTags(t)
|
||||
const { tags: options, tagsMap } = useTags()
|
||||
const filteredOptions = options.filter(option => option.label.toLowerCase().includes(searchText.toLowerCase()))
|
||||
const handleCheck = (id: string) => {
|
||||
if (tags.includes(id))
|
||||
@ -59,7 +57,6 @@ const TagsFilter = ({
|
||||
open={open}
|
||||
tags={tags}
|
||||
tagsMap={tagsMap}
|
||||
locale={locale}
|
||||
onTagsChange={onTagsChange}
|
||||
/>
|
||||
)
|
||||
|
||||
@ -1,15 +1,14 @@
|
||||
import type { Tag } from '../../../hooks'
|
||||
import { useTranslation } from '#i18n'
|
||||
import { RiArrowDownSLine, RiCloseCircleFill, RiFilter3Line } from '@remixicon/react'
|
||||
import * as React from 'react'
|
||||
import { cn } from '@/utils/classnames'
|
||||
import { useMixedTranslation } from '../../hooks'
|
||||
|
||||
type MarketplaceTriggerProps = {
|
||||
selectedTagsLength: number
|
||||
open: boolean
|
||||
tags: string[]
|
||||
tagsMap: Record<string, Tag>
|
||||
locale?: string
|
||||
onTagsChange: (tags: string[]) => void
|
||||
}
|
||||
|
||||
@ -18,10 +17,9 @@ const MarketplaceTrigger = ({
|
||||
open,
|
||||
tags,
|
||||
tagsMap,
|
||||
locale,
|
||||
onTagsChange,
|
||||
}: MarketplaceTriggerProps) => {
|
||||
const { t } = useMixedTranslation(locale)
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user