From 23539c5bccc3855668cd6fc956c0268a84e67a51 Mon Sep 17 00:00:00 2001 From: yyh <92089059+lyzno1@users.noreply.github.com> Date: Mon, 25 May 2026 16:31:52 +0800 Subject: [PATCH] feat(dify-ui): add status and progress primitives (#36615) --- eslint-suppressions.json | 12 +- packages/dify-ui/package.json | 8 + .../src/progress/__tests__/index.spec.tsx | 80 +++++++++ .../dify-ui/src/progress/index.stories.tsx | 77 ++++++++ packages/dify-ui/src/progress/index.tsx | 167 ++++++++++++++++++ .../src/status-dot/__tests__/index.spec.tsx | 57 ++++++ .../dify-ui/src/status-dot/index.stories.tsx | 62 +++++++ packages/dify-ui/src/status-dot/index.tsx | 108 +++++++++++ .../tools/tool-provider-detail-flow.test.tsx | 6 +- .../[appId]/overview/tracing/config-popup.tsx | 4 +- .../[appId]/overview/tracing/panel.tsx | 4 +- .../config/agent/agent-tools/index.tsx | 4 +- web/app/components/app/log/list.tsx | 10 +- web/app/components/app/overview/app-card.tsx | 4 +- .../components/app/overview/trigger-card.tsx | 21 +-- web/app/components/app/workflow-log/list.tsx | 14 +- .../file-uploader-in-attachment/file-item.tsx | 7 +- .../file-image-item.tsx | 10 +- .../file-uploader-in-chat-input/file-item.tsx | 6 +- .../__tests__/progress-circle.spec.tsx | 89 ---------- .../progress-bar/progress-circle.stories.tsx | 90 ---------- .../base/progress-bar/progress-circle.tsx | 64 ------- .../image-uploader-in-chunk/image-item.tsx | 12 +- .../image-item.tsx | 12 +- .../status-item/__tests__/hooks.spec.ts | 42 +++-- .../status-item/__tests__/index.spec.tsx | 16 +- .../datasets/documents/status-item/hooks.ts | 19 +- .../datasets/documents/status-item/index.tsx | 24 +-- .../datasets/extra-info/api-access/card.tsx | 6 +- .../datasets/extra-info/api-access/index.tsx | 6 +- .../datasets/extra-info/service-api/card.tsx | 8 +- .../datasets/extra-info/service-api/index.tsx | 8 +- .../account-dropdown/__tests__/index.spec.tsx | 6 +- .../header/account-dropdown/index.tsx | 4 +- .../data-source-page-new/item.tsx | 4 +- .../account-setting/key-validator/Operate.tsx | 6 +- .../__tests__/config-model.spec.tsx | 8 +- .../__tests__/credential-selector.spec.tsx | 4 +- ...itch-credential-in-load-balancing.spec.tsx | 16 +- .../__tests__/credential-item.spec.tsx | 4 +- .../model-auth/authorized/credential-item.tsx | 4 +- .../model-auth/config-model.tsx | 6 +- .../model-auth/credential-selector.tsx | 4 +- .../switch-credential-in-load-balancing.tsx | 11 +- .../configuration-button.tsx | 5 +- .../model-selector/popup-item.tsx | 5 +- .../__tests__/credential-panel.spec.tsx | 10 +- .../provider-added-card/credential-panel.tsx | 10 +- .../model-load-balancing-configs.tsx | 4 +- .../header/indicator/__tests__/index.spec.tsx | 79 --------- web/app/components/header/indicator/index.tsx | 54 ------ .../plugins-nav/__tests__/index.spec.tsx | 11 +- .../components/header/plugins-nav/index.tsx | 6 +- .../authorized-in-data-source-node.spec.tsx | 6 +- .../authorized-in-data-source-node.tsx | 6 +- .../plugin-auth/authorized-in-node.tsx | 15 +- .../authorized/__tests__/index.spec.tsx | 4 +- .../plugins/plugin-auth/authorized/index.tsx | 4 +- .../plugins/plugin-auth/authorized/item.tsx | 6 +- .../plugin-auth/plugin-auth-in-agent.tsx | 13 +- .../__tests__/endpoint-card.spec.tsx | 8 +- .../datasource-action-list.tsx | 4 +- .../plugin-detail-panel/endpoint-card.tsx | 6 +- .../tool-selector/components/tool-item.tsx | 6 +- .../__tests__/task-status-indicator.spec.tsx | 12 +- .../components/task-status-indicator.tsx | 13 +- .../components/tools/mcp/detail/content.tsx | 4 +- .../components/tools/mcp/mcp-service-card.tsx | 4 +- .../components/tools/mcp/provider-card.tsx | 6 +- .../tools/provider/__tests__/detail.spec.tsx | 4 +- web/app/components/tools/provider/detail.tsx | 4 +- .../tools/workflow-tool/configure-button.tsx | 4 +- .../nodes/_base/components/setting-item.tsx | 9 +- .../agent/__tests__/integration.spec.tsx | 14 +- .../components/__tests__/model-bar.spec.tsx | 10 +- .../components/__tests__/tool-icon.spec.tsx | 8 +- .../nodes/agent/components/model-bar.tsx | 6 +- .../nodes/agent/components/tool-icon.tsx | 6 +- .../delivery-method/method-item.tsx | 4 +- web/app/components/workflow/run/status.tsx | 16 +- 80 files changed, 841 insertions(+), 679 deletions(-) create mode 100644 packages/dify-ui/src/progress/__tests__/index.spec.tsx create mode 100644 packages/dify-ui/src/progress/index.stories.tsx create mode 100644 packages/dify-ui/src/progress/index.tsx create mode 100644 packages/dify-ui/src/status-dot/__tests__/index.spec.tsx create mode 100644 packages/dify-ui/src/status-dot/index.stories.tsx create mode 100644 packages/dify-ui/src/status-dot/index.tsx delete mode 100644 web/app/components/base/progress-bar/__tests__/progress-circle.spec.tsx delete mode 100644 web/app/components/base/progress-bar/progress-circle.stories.tsx delete mode 100644 web/app/components/base/progress-bar/progress-circle.tsx delete mode 100644 web/app/components/header/indicator/__tests__/index.spec.tsx delete mode 100644 web/app/components/header/indicator/index.tsx diff --git a/eslint-suppressions.json b/eslint-suppressions.json index f1fe2cd252..2624746723 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -2534,7 +2534,7 @@ }, "web/app/components/header/account-setting/model-provider-page/model-auth/switch-credential-in-load-balancing.tsx": { "ts/no-explicit-any": { - "count": 3 + "count": 2 } }, "web/app/components/header/account-setting/model-provider-page/model-modal/Input.tsx": { @@ -2653,11 +2653,6 @@ "count": 1 } }, - "web/app/components/plugins/plugin-auth/authorized-in-node.tsx": { - "ts/no-explicit-any": { - "count": 1 - } - }, "web/app/components/plugins/plugin-auth/authorized/index.tsx": { "no-restricted-imports": { "count": 1 @@ -2684,11 +2679,6 @@ "count": 3 } }, - "web/app/components/plugins/plugin-auth/plugin-auth-in-agent.tsx": { - "ts/no-explicit-any": { - "count": 1 - } - }, "web/app/components/plugins/plugin-auth/types.ts": { "erasable-syntax-only/enums": { "count": 2 diff --git a/packages/dify-ui/package.json b/packages/dify-ui/package.json index d98543960c..c517153bf6 100644 --- a/packages/dify-ui/package.json +++ b/packages/dify-ui/package.json @@ -73,6 +73,10 @@ "types": "./src/meter/index.tsx", "import": "./src/meter/index.tsx" }, + "./progress": { + "types": "./src/progress/index.tsx", + "import": "./src/progress/index.tsx" + }, "./number-field": { "types": "./src/number-field/index.tsx", "import": "./src/number-field/index.tsx" @@ -105,6 +109,10 @@ "types": "./src/select/index.tsx", "import": "./src/select/index.tsx" }, + "./status-dot": { + "types": "./src/status-dot/index.tsx", + "import": "./src/status-dot/index.tsx" + }, "./slider": { "types": "./src/slider/index.tsx", "import": "./src/slider/index.tsx" diff --git a/packages/dify-ui/src/progress/__tests__/index.spec.tsx b/packages/dify-ui/src/progress/__tests__/index.spec.tsx new file mode 100644 index 0000000000..07a052d50b --- /dev/null +++ b/packages/dify-ui/src/progress/__tests__/index.spec.tsx @@ -0,0 +1,80 @@ +import { render } from 'vitest-browser-react' +import { ProgressCircle } from '../index' + +describe('ProgressCircle', () => { + it('exposes progressbar semantics through Base UI Progress', async () => { + const screen = await render() + + const progress = screen.getByLabelText('Uploading') + + await expect.element(progress).toHaveAttribute('role', 'progressbar') + await expect.element(progress).toHaveAttribute('aria-valuemin', '0') + await expect.element(progress).toHaveAttribute('aria-valuemax', '100') + await expect.element(progress).toHaveAttribute('aria-valuenow', '40') + }) + + it('supports custom min and max', async () => { + const screen = await render() + + const progress = screen.getByLabelText('Installing') + + await expect.element(progress).toHaveAttribute('aria-valuemin', '1') + await expect.element(progress).toHaveAttribute('aria-valuemax', '5') + await expect.element(progress).toHaveAttribute('aria-valuenow', '3') + }) + + it('renders indeterminate state when value is null', async () => { + const screen = await render() + + await expect.element(screen.getByTestId('progress')).toHaveAttribute('data-indeterminate') + await expect.element(screen.getByTestId('progress')).not.toHaveAttribute('aria-valuenow') + expect(screen.getByTestId('progress').element().querySelector('path')).toBeNull() + }) + + it('does not render a progress sector for zero progress', async () => { + const screen = await render() + + expect(screen.getByTestId('progress').element().querySelector('path')).toBeNull() + }) + + it('applies design kit size variants', async () => { + const screen = await render() + + const root = screen.getByTestId('progress').element() as HTMLElement + const svg = root.querySelector('svg')! + + expect(root.className).toContain('size-5') + expect(svg.getAttribute('width')).toBe('21') + expect(svg.getAttribute('height')).toBe('21') + }) + + it('applies color tokens to circle and sector', async () => { + const screen = await render() + + const root = screen.getByTestId('progress').element() as HTMLElement + const circle = root.querySelector('circle')! + const path = root.querySelector('path')! + + expect(circle.getAttribute('class')).toContain('fill-components-progress-error-bg') + expect(circle.getAttribute('class')).toContain('stroke-components-progress-error-border') + expect(path.getAttribute('class')).toContain('fill-components-progress-error-progress') + }) + + it('renders a deterministic progress sector', async () => { + const screen = await render() + + const path = screen.getByTestId('progress').element().querySelector('path')! + + expect(path.getAttribute('d')).toContain('A 6,6 0 1 1') + }) + + it('renders a closed circle sector for complete progress', async () => { + const screen = await render() + + const path = screen.getByTestId('progress').element().querySelector('path')! + const pathData = path.getAttribute('d')! + + expect(pathData).toContain('A 6,6 0 1 1 6,12') + expect(pathData).toContain('A 6,6 0 1 1 6,0') + }) +}) diff --git a/packages/dify-ui/src/progress/index.stories.tsx b/packages/dify-ui/src/progress/index.stories.tsx new file mode 100644 index 0000000000..eb9a3326ba --- /dev/null +++ b/packages/dify-ui/src/progress/index.stories.tsx @@ -0,0 +1,77 @@ +import type { Meta, StoryObj } from '@storybook/react-vite' +import type { ProgressCircleColor, ProgressCircleSize } from '.' +import { Fragment } from 'react' +import { ProgressCircle } from '.' + +const colors: ProgressCircleColor[] = ['gray', 'white', 'blue', 'warning', 'error'] +const sizes: ProgressCircleSize[] = ['small', 'medium', 'large'] + +const meta = { + title: 'Base/UI/Progress', + component: ProgressCircle, + parameters: { + layout: 'centered', + docs: { + description: { + component: + 'Task progress primitives. ProgressCircle matches the Dify Design Kit circular Progress component and uses Base UI Progress semantics.', + }, + }, + }, + tags: ['autodocs'], +} satisfies Meta + +export default meta + +type Story = StoryObj + +export const Circle: Story = { + args: { + 'value': 42, + 'color': 'blue', + 'size': 'small', + 'aria-label': 'Uploading', + }, +} + +export const CircleMatrix: Story = { + args: { + 'value': 62, + 'aria-label': 'Progress', + }, + render: () => ( +
+
+ {sizes.map(size => ( +
+ {size} +
+ ))} + {colors.map(color => ( + +
+ {color} +
+ {sizes.map(size => ( + + ))} +
+ ))} +
+ ), +} + +export const Indeterminate: Story = { + args: { + 'value': null, + 'color': 'gray', + 'size': 'medium', + 'aria-label': 'Processing', + }, +} diff --git a/packages/dify-ui/src/progress/index.tsx b/packages/dify-ui/src/progress/index.tsx new file mode 100644 index 0000000000..05bfd8c355 --- /dev/null +++ b/packages/dify-ui/src/progress/index.tsx @@ -0,0 +1,167 @@ +'use client' + +import type { VariantProps } from 'class-variance-authority' +import { Progress as BaseProgress } from '@base-ui/react/progress' +import { cva } from 'class-variance-authority' +import { cn } from '../cn' + +const progressCircleRootVariants = cva( + 'inline-flex shrink-0 items-center justify-center', + { + variants: { + size: { + small: 'size-3', + medium: 'size-4', + large: 'size-5', + }, + }, + defaultVariants: { + size: 'small', + }, + }, +) + +const progressCircleColorClasses = { + gray: { + stroke: 'stroke-components-progress-gray-border', + fill: 'fill-components-progress-gray-bg', + sector: 'fill-components-progress-gray-progress', + }, + white: { + stroke: 'stroke-components-progress-white-border', + fill: 'fill-components-progress-white-bg', + sector: 'fill-components-progress-white-progress', + }, + blue: { + stroke: 'stroke-components-progress-brand-border', + fill: 'fill-components-progress-brand-bg', + sector: 'fill-components-progress-brand-progress', + }, + warning: { + stroke: 'stroke-components-progress-warning-border', + fill: 'fill-components-progress-warning-bg', + sector: 'fill-components-progress-warning-progress', + }, + error: { + stroke: 'stroke-components-progress-error-border', + fill: 'fill-components-progress-error-bg', + sector: 'fill-components-progress-error-progress', + }, +} as const + +export type ProgressCircleSize = NonNullable['size']> +export type ProgressCircleColor = keyof typeof progressCircleColorClasses + +const progressCircleSizeValues = { + small: 12, + medium: 16, + large: 20, +} as const satisfies Record + +type ProgressCircleAccessibleNameProps + = | { + 'aria-label': string + 'aria-labelledby'?: never + } + | { + 'aria-label'?: never + 'aria-labelledby': string + } + +export type ProgressCircleProps + = Omit + & ProgressCircleAccessibleNameProps + & { + className?: string + color?: ProgressCircleColor + size?: ProgressCircleSize + circleStrokeWidth?: number + } + +function getProgressPercentage(value: number | null, min: number, max: number) { + if (value === null || !Number.isFinite(value) || max <= min) + return null + + return Math.min(100, Math.max(0, ((value - min) / (max - min)) * 100)) +} + +function getSectorPath(size: number, percentage: number | null) { + if (percentage === null || percentage <= 0) + return '' + + const radius = size / 2 + const center = size / 2 + + if (percentage >= 100) { + return ` + M ${center},${center - radius} + A ${radius},${radius} 0 1 1 ${center},${center + radius} + A ${radius},${radius} 0 1 1 ${center},${center - radius} + Z + ` + } + + const angle = (percentage / 100) * 360 + const radians = (angle * Math.PI) / 180 + const x = center + radius * Math.cos(radians - Math.PI / 2) + const y = center + radius * Math.sin(radians - Math.PI / 2) + const largeArcFlag = percentage > 50 ? 1 : 0 + + return ` + M ${center},${center} + L ${center},${center - radius} + A ${radius},${radius} 0 ${largeArcFlag} 1 ${x},${y} + Z + ` +} + +export function ProgressCircle({ + className, + color = 'blue', + size = 'small', + circleStrokeWidth = 1, + value, + min = 0, + max = 100, + ...props +}: ProgressCircleProps) { + const numericSize = progressCircleSizeValues[size] + const percentage = getProgressPercentage(value, min, max) + const radius = numericSize / 2 + const center = numericSize / 2 + const pathData = getSectorPath(numericSize, percentage) + const colors = progressCircleColorClasses[color] + + return ( + + + + ) +} diff --git a/packages/dify-ui/src/status-dot/__tests__/index.spec.tsx b/packages/dify-ui/src/status-dot/__tests__/index.spec.tsx new file mode 100644 index 0000000000..c4b46d50a0 --- /dev/null +++ b/packages/dify-ui/src/status-dot/__tests__/index.spec.tsx @@ -0,0 +1,57 @@ +import { render } from 'vitest-browser-react' +import { StatusDot, StatusDotSkeleton } from '../index' + +describe('StatusDot', () => { + it('renders a medium success dot by default', async () => { + const screen = await render() + + const root = screen.getByTestId('dot').element() as HTMLElement + + await expect.element(screen.getByTestId('dot')).toHaveAttribute('aria-hidden', 'true') + expect(root.className).toContain('size-2') + expect(root.className).toContain('bg-components-badge-status-light-success-bg') + expect(root.className).toContain('border-components-badge-status-light-success-border-inner') + expect(root.className).toContain('shadow-status-indicator-green-shadow') + }) + + it('uses small dot geometry', async () => { + const screen = await render() + + const root = screen.getByTestId('dot').element() as HTMLElement + + expect(root.className).toContain('size-1.5') + expect(root.className).toContain('rounded-xs') + }) + + it.each([ + ['warning', 'bg-components-badge-status-light-warning-bg', 'border-components-badge-status-light-warning-border-inner'], + ['error', 'bg-components-badge-status-light-error-bg', 'border-components-badge-status-light-error-border-inner'], + ['normal', 'bg-components-badge-status-light-normal-bg', 'border-components-badge-status-light-normal-border-inner'], + ['disabled', 'bg-components-badge-status-light-disabled-bg', 'border-components-badge-status-light-disabled-border-inner'], + ] as const)('applies %s status tokens', async (status, backgroundClass, borderClass) => { + const screen = await render() + + const dot = screen.getByTestId('dot').element() as HTMLElement + + expect(dot.className).toContain(backgroundClass) + expect(dot.className).toContain(borderClass) + }) + + it('keeps an explicit accessible label visible to assistive tech', async () => { + const screen = await render() + + await expect.element(screen.getByTestId('dot')).toHaveAttribute('aria-label', 'Active') + await expect.element(screen.getByTestId('dot')).not.toHaveAttribute('aria-hidden') + }) + + it('renders skeleton styling without status color', async () => { + const screen = await render() + + const dot = screen.getByTestId('dot').element() as HTMLElement + + expect(dot.className).toContain('bg-text-primary') + expect(dot.className).toContain('opacity-30') + expect(dot.className).not.toContain('bg-components-badge-status-light-success-bg') + expect(dot.className).not.toContain('border-components-badge-status-light-success-border-inner') + }) +}) diff --git a/packages/dify-ui/src/status-dot/index.stories.tsx b/packages/dify-ui/src/status-dot/index.stories.tsx new file mode 100644 index 0000000000..b44deb9682 --- /dev/null +++ b/packages/dify-ui/src/status-dot/index.stories.tsx @@ -0,0 +1,62 @@ +import type { Meta, StoryObj } from '@storybook/react-vite' +import type { StatusDotSize, StatusDotStatus } from '.' +import { Fragment } from 'react' +import { StatusDot, StatusDotSkeleton } from '.' + +const statuses: StatusDotStatus[] = ['success', 'warning', 'error', 'normal', 'disabled'] +const sizes: StatusDotSize[] = ['small', 'medium'] + +const meta = { + title: 'Base/UI/StatusDot', + component: StatusDot, + parameters: { + layout: 'centered', + docs: { + description: { + component: + 'Status Dot primitive from the Dify Design Kit. Use it for compact visual status indicators; provide an accessible label only when the dot is the sole status representation.', + }, + }, + }, + tags: ['autodocs'], +} satisfies Meta + +export default meta + +type Story = StoryObj + +export const Default: Story = { + args: { + status: 'success', + size: 'medium', + }, +} + +export const Matrix: Story = { + render: () => ( +
+
+
Small
+
Medium
+ {statuses.map(status => ( + +
+ {status} +
+ {sizes.map(size => ( + + ))} +
+ ))} +
+ ), +} + +export const Skeleton: Story = { + render: () => ( +
+ + +
+ ), +} diff --git a/packages/dify-ui/src/status-dot/index.tsx b/packages/dify-ui/src/status-dot/index.tsx new file mode 100644 index 0000000000..087f3da4ba --- /dev/null +++ b/packages/dify-ui/src/status-dot/index.tsx @@ -0,0 +1,108 @@ +'use client' + +import type { VariantProps } from 'class-variance-authority' +import type { ComponentProps } from 'react' +import { cva } from 'class-variance-authority' +import { cn } from '../cn' + +const statusDotVariants = cva( + 'block shrink-0 border border-solid', + { + variants: { + status: { + success: 'border-components-badge-status-light-success-border-inner bg-components-badge-status-light-success-bg shadow-status-indicator-green-shadow', + warning: 'border-components-badge-status-light-warning-border-inner bg-components-badge-status-light-warning-bg shadow-status-indicator-warning-shadow', + error: 'border-components-badge-status-light-error-border-inner bg-components-badge-status-light-error-bg shadow-status-indicator-red-shadow', + normal: 'border-components-badge-status-light-normal-border-inner bg-components-badge-status-light-normal-bg shadow-status-indicator-blue-shadow', + disabled: 'border-components-badge-status-light-disabled-border-inner bg-components-badge-status-light-disabled-bg shadow-status-indicator-gray-shadow', + }, + size: { + small: 'size-1.5 rounded-xs', + medium: 'size-2 rounded-[3px]', + }, + }, + defaultVariants: { + status: 'success', + size: 'medium', + }, + }, +) + +const statusDotSkeletonVariants = cva( + 'block shrink-0 border border-transparent bg-text-primary opacity-30', + { + variants: { + size: { + small: 'size-1.5 rounded-xs', + medium: 'size-2 rounded-[3px]', + }, + }, + defaultVariants: { + size: 'medium', + }, + }, +) + +type StatusDotVariants = VariantProps + +export type StatusDotStatus = NonNullable +export type StatusDotSize = NonNullable + +export type StatusDotProps + = Omit, 'children'> + & { + status?: StatusDotStatus + size?: StatusDotSize + } + +export type StatusDotSkeletonProps + = Omit, 'children'> + & { + size?: StatusDotSize + } + +export function StatusDot({ + className, + status = 'success', + size = 'medium', + 'aria-hidden': ariaHidden, + 'aria-label': ariaLabel, + 'aria-labelledby': ariaLabelledBy, + ...props +}: StatusDotProps) { + const hidden = ariaHidden ?? (ariaLabel || ariaLabelledBy ? undefined : true) + + return ( + + ) +} + +export function StatusDotSkeleton({ + className, + size = 'medium', + 'aria-hidden': ariaHidden, + 'aria-label': ariaLabel, + 'aria-labelledby': ariaLabelledBy, + ...props +}: StatusDotSkeletonProps) { + const hidden = ariaHidden ?? (ariaLabel || ariaLabelledBy ? undefined : true) + + return ( + + ) +} diff --git a/web/__tests__/tools/tool-provider-detail-flow.test.tsx b/web/__tests__/tools/tool-provider-detail-flow.test.tsx index 5e3e94d4ff..7b873601d4 100644 --- a/web/__tests__/tools/tool-provider-detail-flow.test.tsx +++ b/web/__tests__/tools/tool-provider-detail-flow.test.tsx @@ -143,10 +143,6 @@ vi.mock('@/app/components/header/account-setting/model-provider-page/declaration ConfigurationMethodEnum: { predefinedModel: 'predefined-model' }, })) -vi.mock('@/app/components/header/indicator', () => ({ - default: ({ color }: { color: string }) => , -})) - vi.mock('@/app/components/plugins/card/base/card-icon', () => ({ default: ({ src }: { src: string }) =>
, })) @@ -282,7 +278,7 @@ describe('Tool Provider Detail Flow Integration', () => { await waitFor(() => { expect(screen.getByText('Authorized')).toBeInTheDocument() - expect(screen.getByTestId('indicator-green')).toBeInTheDocument() + expect(document.querySelector('.shadow-status-indicator-green-shadow')).toBeInTheDocument() }) }) diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-popup.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-popup.tsx index 2b0f978906..fa2a176b10 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-popup.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-popup.tsx @@ -2,6 +2,7 @@ import type { FC, JSX } from 'react' import type { AliyunConfig, ArizeConfig, DatabricksConfig, LangFuseConfig, LangSmithConfig, MLflowConfig, OpikConfig, PhoenixConfig, TencentConfig, WeaveConfig } from './type' import { cn } from '@langgenius/dify-ui/cn' +import { StatusDot } from '@langgenius/dify-ui/status-dot' import { Switch } from '@langgenius/dify-ui/switch' import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip' import { useBoolean } from 'ahooks' @@ -9,7 +10,6 @@ import * as React from 'react' import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' import Divider from '@/app/components/base/divider' -import Indicator from '@/app/components/header/indicator' import ProviderConfigModal from './provider-config-modal' import ProviderPanel from './provider-panel' import TracingIcon from './tracing-icon' @@ -330,7 +330,7 @@ const ConfigPopup: FC = ({
{t(`${I18N_PREFIX}.tracing`, { ns: 'app' })}
- +
{t(`${I18N_PREFIX}.${enabled ? 'enabled' : 'disabled'}`, { ns: 'app' })}
diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/panel.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/panel.tsx index 2443641b8a..b03fd6e30d 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/panel.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/panel.tsx @@ -3,6 +3,7 @@ import type { FC } from 'react' import type { AliyunConfig, ArizeConfig, DatabricksConfig, LangFuseConfig, LangSmithConfig, MLflowConfig, OpikConfig, PhoenixConfig, TencentConfig, WeaveConfig } from './type' import type { TracingStatus } from '@/models/app' import { cn } from '@langgenius/dify-ui/cn' +import { StatusDot } from '@langgenius/dify-ui/status-dot' import { toast } from '@langgenius/dify-ui/toast' import { RiArrowDownDoubleLine, @@ -15,7 +16,6 @@ import { useTranslation } from 'react-i18next' import Divider from '@/app/components/base/divider' import { AliyunIcon, ArizeIcon, DatabricksIcon, LangfuseIcon, LangsmithIcon, MlflowIcon, OpikIcon, PhoenixIcon, TencentIcon, WeaveIcon } from '@/app/components/base/icons/src/public/tracing' import Loading from '@/app/components/base/loading' -import Indicator from '@/app/components/header/indicator' import { useAppContext } from '@/context/app-context' import { usePathname } from '@/next/navigation' import { fetchTracingConfig as doFetchTracingConfig, fetchTracingStatus, updateTracingStatus } from '@/service/apps' @@ -290,7 +290,7 @@ const Panel: FC = () => { )} >
- +
{t(`${I18N_PREFIX}.${enabled ? 'enabled' : 'disabled'}`, { ns: 'app' })}
diff --git a/web/app/components/app/configuration/config/agent/agent-tools/index.tsx b/web/app/components/app/configuration/config/agent/agent-tools/index.tsx index cb86c8825a..f918ddd7e7 100644 --- a/web/app/components/app/configuration/config/agent/agent-tools/index.tsx +++ b/web/app/components/app/configuration/config/agent/agent-tools/index.tsx @@ -7,6 +7,7 @@ import type { AgentTool } from '@/types/app' import { Button } from '@langgenius/dify-ui/button' import { cn } from '@langgenius/dify-ui/cn' import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover' +import { StatusDot } from '@langgenius/dify-ui/status-dot' import { Switch } from '@langgenius/dify-ui/switch' import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip' import { @@ -24,7 +25,6 @@ import AppIcon from '@/app/components/base/app-icon' import { DefaultToolIcon } from '@/app/components/base/icons/src/public/other' import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback' import { Infotip } from '@/app/components/base/infotip' -import Indicator from '@/app/components/header/indicator' import { CollectionType } from '@/app/components/tools/types' import { addDefaultValue, toolParametersToFormSchemas } from '@/app/components/tools/utils/to-form-schema' import ToolPicker from '@/app/components/workflow/block-selector/tool-picker' @@ -340,7 +340,7 @@ const AgentTools: FC = () => { }} > {t('notAuthorized', { ns: 'tools' })} - + )}
diff --git a/web/app/components/app/log/list.tsx b/web/app/components/app/log/list.tsx index 609a52efc3..e28f5473f6 100644 --- a/web/app/components/app/log/list.tsx +++ b/web/app/components/app/log/list.tsx @@ -17,6 +17,7 @@ import { DrawerPortal, DrawerViewport, } from '@langgenius/dify-ui/drawer' +import { StatusDot } from '@langgenius/dify-ui/status-dot' import { toast } from '@langgenius/dify-ui/toast' import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip' import { RiCloseLine, RiEditFill } from '@remixicon/react' @@ -47,7 +48,6 @@ import { AppSourceType } from '@/service/share' import { useChatConversationDetail, useCompletionConversationDetail } from '@/service/use-log' import { AppModeEnum } from '@/types/app' import PromptLogModal from '../../base/prompt-log-modal' -import Indicator from '../../header/indicator' import { applyAnnotationAdded, applyAnnotationEdited, @@ -114,7 +114,7 @@ const statusTdRender = (statusCount: StatusCount) => { if (statusCount.paused > 0) { return (
- + Pending
) @@ -122,7 +122,7 @@ const statusTdRender = (statusCount: StatusCount) => { else if (statusCount.partial_success + statusCount.failed === 0) { return (
- + Success
) @@ -130,7 +130,7 @@ const statusTdRender = (statusCount: StatusCount) => { else if (statusCount.failed === 0) { return (
- + Partial Success
) @@ -138,7 +138,7 @@ const statusTdRender = (statusCount: StatusCount) => { else { return (
- + {statusCount.failed} {' '} diff --git a/web/app/components/app/overview/app-card.tsx b/web/app/components/app/overview/app-card.tsx index 9b1fc3a032..3bff802c91 100644 --- a/web/app/components/app/overview/app-card.tsx +++ b/web/app/components/app/overview/app-card.tsx @@ -4,6 +4,7 @@ import type { ConfigParams } from './settings' import type { AppDetailResponse } from '@/models/app' import type { AppSSO } from '@/types/app' import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover' +import { StatusDot } from '@langgenius/dify-ui/status-dot' import { Switch } from '@langgenius/dify-ui/switch' import { useSuspenseQuery } from '@tanstack/react-query' import * as React from 'react' @@ -12,7 +13,6 @@ import { useTranslation } from 'react-i18next' import AppBasic from '@/app/components/app-sidebar/basic' import { useStore as useAppStore } from '@/app/components/app/store' import SecretKeyButton from '@/app/components/develop/secret-key/secret-key-button' -import Indicator from '@/app/components/header/indicator' import { useAppContext } from '@/context/app-context' import { useDocLink } from '@/context/i18n' import { AccessMode } from '@/models/access-control' @@ -296,7 +296,7 @@ function AppCard({ } />
- +
{cardState.runningStatus ? t('overview.status.running', { ns: 'appOverview' }) diff --git a/web/app/components/app/overview/trigger-card.tsx b/web/app/components/app/overview/trigger-card.tsx index 05d97312f6..d6bcb9efcf 100644 --- a/web/app/components/app/overview/trigger-card.tsx +++ b/web/app/components/app/overview/trigger-card.tsx @@ -3,6 +3,7 @@ import type { AppDetailResponse } from '@/models/app' import type { AppTrigger } from '@/service/use-tools' import type { AppSSO } from '@/types/app' import type { I18nKeysByPrefix } from '@/types/i18n' +import { StatusDot } from '@langgenius/dify-ui/status-dot' import { Switch } from '@langgenius/dify-ui/switch' import * as React from 'react' import { useTranslation } from 'react-i18next' @@ -30,20 +31,6 @@ type ITriggerCardProps = { const getTriggerIcon = (trigger: AppTrigger, triggerPlugins: any[]) => { const { trigger_type, status, provider_name } = trigger - // Status dot styling based on trigger status - const getStatusDot = () => { - if (status === 'enabled') { - return ( -
- ) - } - else { - return ( -
- ) - } - } - // Get BlockEnum type from trigger_type let blockType: BlockEnum switch (trigger_type) { @@ -78,7 +65,11 @@ const getTriggerIcon = (trigger: AppTrigger, triggerPlugins: any[]) => { size="md" toolIcon={triggerIcon} /> - {getStatusDot()} +
) } diff --git a/web/app/components/app/workflow-log/list.tsx b/web/app/components/app/workflow-log/list.tsx index 7b3dae3ab0..402c383a39 100644 --- a/web/app/components/app/workflow-log/list.tsx +++ b/web/app/components/app/workflow-log/list.tsx @@ -12,11 +12,11 @@ import { DrawerPortal, DrawerViewport, } from '@langgenius/dify-ui/drawer' +import { StatusDot } from '@langgenius/dify-ui/status-dot' import * as React from 'react' import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import Loading from '@/app/components/base/loading' -import Indicator from '@/app/components/header/indicator' import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints' import useTimestamp from '@/hooks/use-timestamp' import { AppModeEnum } from '@/types/app' @@ -67,7 +67,7 @@ const WorkflowAppLogList: FC = ({ logs, appDetail, onRefresh }) => { if (status === 'succeeded') { return (
- + Success
) @@ -75,7 +75,7 @@ const WorkflowAppLogList: FC = ({ logs, appDetail, onRefresh }) => { if (status === 'failed') { return (
- + Failure
) @@ -83,7 +83,7 @@ const WorkflowAppLogList: FC = ({ logs, appDetail, onRefresh }) => { if (status === 'stopped') { return (
- + Stop
) @@ -91,7 +91,7 @@ const WorkflowAppLogList: FC = ({ logs, appDetail, onRefresh }) => { if (status === 'paused') { return (
- + Pending
) @@ -99,7 +99,7 @@ const WorkflowAppLogList: FC = ({ logs, appDetail, onRefresh }) => { if (status === 'running') { return (
- + Running
) @@ -107,7 +107,7 @@ const WorkflowAppLogList: FC = ({ logs, appDetail, onRefresh }) => { if (status === 'partial-succeeded') { return (
- + Partial Success
) diff --git a/web/app/components/base/file-uploader/file-uploader-in-attachment/file-item.tsx b/web/app/components/base/file-uploader/file-uploader-in-attachment/file-item.tsx index b8521e8799..93ffe2500e 100644 --- a/web/app/components/base/file-uploader/file-uploader-in-attachment/file-item.tsx +++ b/web/app/components/base/file-uploader/file-uploader-in-attachment/file-item.tsx @@ -1,5 +1,6 @@ import type { FileEntity } from '../types' import { cn } from '@langgenius/dify-ui/cn' +import { ProgressCircle } from '@langgenius/dify-ui/progress' import { RiDeleteBinLine, RiDownloadLine, @@ -9,11 +10,11 @@ import { memo, useState, } from 'react' +import { useTranslation } from 'react-i18next' import ActionButton from '@/app/components/base/action-button' import { PreviewMode } from '@/app/components/base/features/types' import { ReplayLine } from '@/app/components/base/icons/src/vender/other' import ImagePreview from '@/app/components/base/image-uploader/image-preview' -import ProgressCircle from '@/app/components/base/progress-bar/progress-circle' import { SupportUploadFileTypes } from '@/app/components/workflow/types' import { downloadUrl } from '@/utils/download' import { formatFileSize } from '@/utils/format' @@ -43,6 +44,7 @@ const FileInAttachmentItem = ({ canPreview, previewMode = PreviewMode.CurrentPage, }: FileInAttachmentItemProps) => { + const { t } = useTranslation() const { id, name, type, progress, supportFileType, base64Url, url, isRemote } = file const ext = getFileExtension(name, type, isRemote) const isImageFile = supportFileType === SupportUploadFileTypes.image @@ -108,7 +110,8 @@ const FileInAttachmentItem = ({ progress >= 0 && !fileIsUploaded(file) && ( ) } diff --git a/web/app/components/base/file-uploader/file-uploader-in-chat-input/file-image-item.tsx b/web/app/components/base/file-uploader/file-uploader-in-chat-input/file-image-item.tsx index dbf721b941..8c3b2f8366 100644 --- a/web/app/components/base/file-uploader/file-uploader-in-chat-input/file-image-item.tsx +++ b/web/app/components/base/file-uploader/file-uploader-in-chat-input/file-image-item.tsx @@ -1,5 +1,6 @@ import type { FileEntity } from '../types' import { Button } from '@langgenius/dify-ui/button' +import { ProgressCircle } from '@langgenius/dify-ui/progress' import { RiCloseLine, RiDownloadLine, @@ -8,7 +9,6 @@ import { useState } from 'react' import { useTranslation } from 'react-i18next' import { ReplayLine } from '@/app/components/base/icons/src/vender/other' import ImagePreview from '@/app/components/base/image-uploader/image-preview' -import ProgressCircle from '@/app/components/base/progress-bar/progress-circle' import { downloadUrl } from '@/utils/download' import FileImageRender from '../file-image-render' import { @@ -65,11 +65,9 @@ const FileImageItem = ({ progress >= 0 && !fileIsUploaded(file) && (
) diff --git a/web/app/components/base/file-uploader/file-uploader-in-chat-input/file-item.tsx b/web/app/components/base/file-uploader/file-uploader-in-chat-input/file-item.tsx index e8599f1a0b..f4fd96db5b 100644 --- a/web/app/components/base/file-uploader/file-uploader-in-chat-input/file-item.tsx +++ b/web/app/components/base/file-uploader/file-uploader-in-chat-input/file-item.tsx @@ -1,13 +1,13 @@ import type { FileEntity } from '../types' import { Button } from '@langgenius/dify-ui/button' import { cn } from '@langgenius/dify-ui/cn' +import { ProgressCircle } from '@langgenius/dify-ui/progress' import { useState } from 'react' import { useTranslation } from 'react-i18next' import ActionButton from '@/app/components/base/action-button' import AudioPreview from '@/app/components/base/file-uploader/audio-preview' import PdfPreview from '@/app/components/base/file-uploader/dynamic-pdf-preview' import VideoPreview from '@/app/components/base/file-uploader/video-preview' -import ProgressCircle from '@/app/components/base/progress-bar/progress-circle' import { downloadUrl } from '@/utils/download' import { formatFileSize } from '@/utils/format' import FileTypeIcon from '../file-type-icon' @@ -110,9 +110,9 @@ const FileItem = ({ { progress >= 0 && !fileIsUploaded(file) && ( ) } diff --git a/web/app/components/base/progress-bar/__tests__/progress-circle.spec.tsx b/web/app/components/base/progress-bar/__tests__/progress-circle.spec.tsx deleted file mode 100644 index 14bb9896e3..0000000000 --- a/web/app/components/base/progress-bar/__tests__/progress-circle.spec.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import { render } from '@testing-library/react' -import ProgressCircle from '../progress-circle' - -const extractLargeArcFlag = (pathData: string): string => { - const afterA = pathData.slice(pathData.indexOf('A') + 1) - const tokens = afterA.replace(/,/g, ' ').trim().split(/\s+/) - // Arc syntax: A rx ry x-axis-rotation large-arc-flag sweep-flag x y - return tokens[3]! -} - -describe('ProgressCircle', () => { - describe('Render', () => { - it('renders an SVG with default props', () => { - const { container } = render() - - const svg = container.querySelector('svg') - const circle = container.querySelector('circle') - const path = container.querySelector('path') - - expect(svg)!.toBeInTheDocument() - expect(circle)!.toBeInTheDocument() - expect(path)!.toBeInTheDocument() - }) - }) - - describe('Props', () => { - it('applies correct size and viewBox when size is provided', () => { - const size = 24 - const strokeWidth = 2 - - const { container } = render( - , - ) - - const svg = container.querySelector('svg') as SVGElement - - expect(svg)!.toHaveAttribute('width', String(size + strokeWidth)) - expect(svg)!.toHaveAttribute('height', String(size + strokeWidth)) - expect(svg)!.toHaveAttribute( - 'viewBox', - `0 0 ${size + strokeWidth} ${size + strokeWidth}`, - ) - }) - - it('applies custom stroke and fill classes to the circle', () => { - const { container } = render( - , - ) - const circle = container.querySelector('circle')! - expect(circle!)!.toHaveClass('stroke-red-500') - expect(circle!)!.toHaveClass('fill-red-100') - }) - - it('applies custom sector fill color to the path', () => { - const { container } = render( - , - ) - const path = container.querySelector('path')! - expect(path!)!.toHaveClass('fill-blue-500') - }) - - it('uses large arc flag when percentage is greater than 50', () => { - const { container } = render() - const path = container.querySelector('path')! - const d = path.getAttribute('d') || '' - expect(d).toContain('A') - expect(extractLargeArcFlag(d)).toBe('1') - }) - - it('uses small arc flag when percentage is 50 or less', () => { - const { container } = render() - const path = container.querySelector('path')! - const d = path.getAttribute('d') || '' - expect(d).toContain('A') - expect(extractLargeArcFlag(d)).toBe('0') - }) - - it('uses small arc flag when percentage is exactly 50', () => { - const { container } = render() - const path = container.querySelector('path')! - const d = path.getAttribute('d') || '' - expect(d).toContain('A') - expect(extractLargeArcFlag(d)).toBe('0') - }) - }) -}) diff --git a/web/app/components/base/progress-bar/progress-circle.stories.tsx b/web/app/components/base/progress-bar/progress-circle.stories.tsx deleted file mode 100644 index 2c0e70b4a1..0000000000 --- a/web/app/components/base/progress-bar/progress-circle.stories.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/nextjs-vite' -import { useState } from 'react' -import ProgressCircle from './progress-circle' - -const ProgressCircleDemo = ({ - initialPercentage = 42, - size = 24, -}: { - initialPercentage?: number - size?: number -}) => { - const [percentage, setPercentage] = useState(initialPercentage) - - return ( -
-
- Upload progress - - {percentage} - % - -
-
- - setPercentage(Number.parseInt(event.target.value, 10))} - className="h-2 w-full cursor-pointer appearance-none rounded-full bg-divider-subtle accent-primary-600" - /> -
-
- -
-
- ProgressCircle renders a deterministic SVG slice. Advance the slider to preview how the arc grows for upload indicators. -
-
- ) -} - -const meta = { - title: 'Base/Feedback/ProgressCircle', - component: ProgressCircleDemo, - parameters: { - layout: 'centered', - docs: { - description: { - component: 'Compact radial progress indicator wired to upload flows. The story provides a slider to scrub through percentages.', - }, - }, - }, - argTypes: { - initialPercentage: { - control: { type: 'range', min: 0, max: 100, step: 1 }, - }, - size: { - control: { type: 'number', min: 12, max: 48, step: 2 }, - }, - }, - args: { - initialPercentage: 42, - size: 24, - }, - tags: ['autodocs'], -} satisfies Meta - -export default meta -type Story = StoryObj - -export const Playground: Story = {} - -export const NearComplete: Story = { - args: { - initialPercentage: 92, - }, -} diff --git a/web/app/components/base/progress-bar/progress-circle.tsx b/web/app/components/base/progress-bar/progress-circle.tsx deleted file mode 100644 index 85113277c8..0000000000 --- a/web/app/components/base/progress-bar/progress-circle.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { cn } from '@langgenius/dify-ui/cn' -import { memo } from 'react' - -type ProgressCircleProps = { - className?: string - percentage?: number - size?: number - circleStrokeWidth?: number - circleStrokeColor?: string - circleFillColor?: string - sectorFillColor?: string -} - -const ProgressCircle: React.FC = ({ - className, - percentage = 0, - size = 12, - circleStrokeWidth = 1, - circleStrokeColor = 'stroke-components-progress-brand-border', - circleFillColor = 'fill-components-progress-brand-bg', - sectorFillColor = 'fill-components-progress-brand-progress', -}) => { - const radius = size / 2 - const center = size / 2 - const angle = (percentage / 101) * 360 - const radians = (angle * Math.PI) / 180 - const x = center + radius * Math.cos(radians - Math.PI / 2) - const y = center + radius * Math.sin(radians - Math.PI / 2) - const largeArcFlag = percentage > 50 ? 1 : 0 - - const pathData = ` - M ${center},${center} - L ${center},${center - radius} - A ${radius},${radius} 0 ${largeArcFlag} 1 ${x},${y} - Z - ` - - return ( - - - - - ) -} - -export default memo(ProgressCircle) diff --git a/web/app/components/datasets/common/image-uploader/image-uploader-in-chunk/image-item.tsx b/web/app/components/datasets/common/image-uploader/image-uploader-in-chunk/image-item.tsx index b5a744daac..67ec37e739 100644 --- a/web/app/components/datasets/common/image-uploader/image-uploader-in-chunk/image-item.tsx +++ b/web/app/components/datasets/common/image-uploader/image-uploader-in-chunk/image-item.tsx @@ -1,5 +1,6 @@ import type { FileEntity } from '../types' import { Button } from '@langgenius/dify-ui/button' +import { ProgressCircle } from '@langgenius/dify-ui/progress' import { RiCloseLine, } from '@remixicon/react' @@ -7,9 +8,9 @@ import { memo, useCallback, } from 'react' +import { useTranslation } from 'react-i18next' import FileImageRender from '@/app/components/base/file-uploader/file-image-render' import { ReplayLine } from '@/app/components/base/icons/src/vender/other' -import ProgressCircle from '@/app/components/base/progress-bar/progress-circle' import { fileIsUploaded } from '../utils' type ImageItemProps = { @@ -26,6 +27,7 @@ const ImageItem = ({ onReUpload, onPreview, }: ImageItemProps) => { + const { t } = useTranslation() const { id, progress, base64Url, sourceUrl } = file const handlePreview = useCallback((e: React.MouseEvent) => { @@ -69,11 +71,9 @@ const ImageItem = ({ progress >= 0 && !fileIsUploaded(file) && (
) diff --git a/web/app/components/datasets/common/image-uploader/image-uploader-in-retrieval-testing/image-item.tsx b/web/app/components/datasets/common/image-uploader/image-uploader-in-retrieval-testing/image-item.tsx index 1e9ad455ad..35026260c9 100644 --- a/web/app/components/datasets/common/image-uploader/image-uploader-in-retrieval-testing/image-item.tsx +++ b/web/app/components/datasets/common/image-uploader/image-uploader-in-retrieval-testing/image-item.tsx @@ -1,5 +1,6 @@ import type { FileEntity } from '../types' import { Button } from '@langgenius/dify-ui/button' +import { ProgressCircle } from '@langgenius/dify-ui/progress' import { RiCloseLine, } from '@remixicon/react' @@ -7,9 +8,9 @@ import { memo, useCallback, } from 'react' +import { useTranslation } from 'react-i18next' import FileImageRender from '@/app/components/base/file-uploader/file-image-render' import { ReplayLine } from '@/app/components/base/icons/src/vender/other' -import ProgressCircle from '@/app/components/base/progress-bar/progress-circle' import { fileIsUploaded } from '../utils' type ImageItemProps = { @@ -26,6 +27,7 @@ const ImageItem = ({ onReUpload, onPreview, }: ImageItemProps) => { + const { t } = useTranslation() const { id, progress, base64Url, sourceUrl } = file const handlePreview = useCallback((e: React.MouseEvent) => { @@ -69,11 +71,9 @@ const ImageItem = ({ progress >= 0 && !fileIsUploaded(file) && (
) diff --git a/web/app/components/datasets/documents/status-item/__tests__/hooks.spec.ts b/web/app/components/datasets/documents/status-item/__tests__/hooks.spec.ts index 9b89cab7a0..6d1a0d6881 100644 --- a/web/app/components/datasets/documents/status-item/__tests__/hooks.spec.ts +++ b/web/app/components/datasets/documents/status-item/__tests__/hooks.spec.ts @@ -19,46 +19,45 @@ describe('useIndexStatus', () => { expect(keys).toEqual(expect.arrayContaining(expectedKeys)) }) - // Verify each status entry has the correct color - describe('colors', () => { - it('should return orange color for queuing', () => { + describe('status variants', () => { + it('should return warning status for queuing', () => { const { result } = renderHook(() => useIndexStatus()) - expect(result.current.queuing.color).toBe('orange') + expect(result.current.queuing.status).toBe('warning') }) - it('should return blue color for indexing', () => { + it('should return normal status for indexing', () => { const { result } = renderHook(() => useIndexStatus()) - expect(result.current.indexing.color).toBe('blue') + expect(result.current.indexing.status).toBe('normal') }) - it('should return orange color for paused', () => { + it('should return warning status for paused', () => { const { result } = renderHook(() => useIndexStatus()) - expect(result.current.paused.color).toBe('orange') + expect(result.current.paused.status).toBe('warning') }) - it('should return red color for error', () => { + it('should return error status for error', () => { const { result } = renderHook(() => useIndexStatus()) - expect(result.current.error.color).toBe('red') + expect(result.current.error.status).toBe('error') }) - it('should return green color for available', () => { + it('should return success status for available', () => { const { result } = renderHook(() => useIndexStatus()) - expect(result.current.available.color).toBe('green') + expect(result.current.available.status).toBe('success') }) - it('should return green color for enabled', () => { + it('should return success status for enabled', () => { const { result } = renderHook(() => useIndexStatus()) - expect(result.current.enabled.color).toBe('green') + expect(result.current.enabled.status).toBe('success') }) - it('should return gray color for disabled', () => { + it('should return disabled status for disabled', () => { const { result } = renderHook(() => useIndexStatus()) - expect(result.current.disabled.color).toBe('gray') + expect(result.current.disabled.status).toBe('disabled') }) - it('should return gray color for archived', () => { + it('should return disabled status for archived', () => { const { result } = renderHook(() => useIndexStatus()) - expect(result.current.archived.color).toBe('gray') + expect(result.current.archived.status).toBe('disabled') }) }) @@ -105,14 +104,13 @@ describe('useIndexStatus', () => { }) }) - // Verify each entry has both color and text properties - it('should return objects with color and text properties for every status', () => { + it('should return objects with status and text properties for every status', () => { const { result } = renderHook(() => useIndexStatus()) for (const key of Object.keys(result.current) as Array) { - expect(result.current[key]).toHaveProperty('color') + expect(result.current[key]).toHaveProperty('status') expect(result.current[key]).toHaveProperty('text') - expect(typeof result.current[key].color).toBe('string') + expect(typeof result.current[key].status).toBe('string') expect(typeof result.current[key].text).toBe('string') } }) diff --git a/web/app/components/datasets/documents/status-item/__tests__/index.spec.tsx b/web/app/components/datasets/documents/status-item/__tests__/index.spec.tsx index 1e651929fe..324e714242 100644 --- a/web/app/components/datasets/documents/status-item/__tests__/index.spec.tsx +++ b/web/app/components/datasets/documents/status-item/__tests__/index.spec.tsx @@ -34,14 +34,14 @@ vi.mock('@langgenius/dify-ui/toast', () => ({ // Mock useIndexStatus hook vi.mock('../hooks', () => ({ useIndexStatus: () => ({ - queuing: { text: 'Queuing', color: 'orange' }, - indexing: { text: 'Indexing', color: 'blue' }, - paused: { text: 'Paused', color: 'yellow' }, - error: { text: 'Error', color: 'red' }, - available: { text: 'Available', color: 'green' }, - enabled: { text: 'Enabled', color: 'green' }, - disabled: { text: 'Disabled', color: 'gray' }, - archived: { text: 'Archived', color: 'gray' }, + queuing: { text: 'Queuing', status: 'warning' }, + indexing: { text: 'Indexing', status: 'normal' }, + paused: { text: 'Paused', status: 'warning' }, + error: { text: 'Error', status: 'error' }, + available: { text: 'Available', status: 'success' }, + enabled: { text: 'Enabled', status: 'success' }, + disabled: { text: 'Disabled', status: 'disabled' }, + archived: { text: 'Archived', status: 'disabled' }, }), })) diff --git a/web/app/components/datasets/documents/status-item/hooks.ts b/web/app/components/datasets/documents/status-item/hooks.ts index 270aa36023..69423ff4c4 100644 --- a/web/app/components/datasets/documents/status-item/hooks.ts +++ b/web/app/components/datasets/documents/status-item/hooks.ts @@ -1,15 +1,16 @@ +import type { StatusDotStatus } from '@langgenius/dify-ui/status-dot' import { useTranslation } from 'react-i18next' export const useIndexStatus = () => { const { t } = useTranslation() return { - queuing: { color: 'orange', text: t('list.status.queuing', { ns: 'datasetDocuments' }) }, // waiting - indexing: { color: 'blue', text: t('list.status.indexing', { ns: 'datasetDocuments' }) }, // indexing splitting parsing cleaning - paused: { color: 'orange', text: t('list.status.paused', { ns: 'datasetDocuments' }) }, // paused - error: { color: 'red', text: t('list.status.error', { ns: 'datasetDocuments' }) }, // error - available: { color: 'green', text: t('list.status.available', { ns: 'datasetDocuments' }) }, // completed,archived = false,enabled = true - enabled: { color: 'green', text: t('list.status.enabled', { ns: 'datasetDocuments' }) }, // completed,archived = false,enabled = true - disabled: { color: 'gray', text: t('list.status.disabled', { ns: 'datasetDocuments' }) }, // completed,archived = false,enabled = false - archived: { color: 'gray', text: t('list.status.archived', { ns: 'datasetDocuments' }) }, // completed,archived = true - } + queuing: { status: 'warning', text: t('list.status.queuing', { ns: 'datasetDocuments' }) }, + indexing: { status: 'normal', text: t('list.status.indexing', { ns: 'datasetDocuments' }) }, + paused: { status: 'warning', text: t('list.status.paused', { ns: 'datasetDocuments' }) }, + error: { status: 'error', text: t('list.status.error', { ns: 'datasetDocuments' }) }, + available: { status: 'success', text: t('list.status.available', { ns: 'datasetDocuments' }) }, + enabled: { status: 'success', text: t('list.status.enabled', { ns: 'datasetDocuments' }) }, + disabled: { status: 'disabled', text: t('list.status.disabled', { ns: 'datasetDocuments' }) }, + archived: { status: 'disabled', text: t('list.status.archived', { ns: 'datasetDocuments' }) }, + } satisfies Record } diff --git a/web/app/components/datasets/documents/status-item/index.tsx b/web/app/components/datasets/documents/status-item/index.tsx index 911b1df55e..fb875fee57 100644 --- a/web/app/components/datasets/documents/status-item/index.tsx +++ b/web/app/components/datasets/documents/status-item/index.tsx @@ -1,8 +1,9 @@ +import type { StatusDotStatus } from '@langgenius/dify-ui/status-dot' import type { OperationName } from '../types' -import type { ColorMap, IndicatorProps } from '@/app/components/header/indicator' import type { CommonResponse } from '@/models/common' import type { DocumentDisplayStatus } from '@/models/datasets' import { cn } from '@langgenius/dify-ui/cn' +import { StatusDot } from '@langgenius/dify-ui/status-dot' import { Switch } from '@langgenius/dify-ui/switch' import { toast } from '@langgenius/dify-ui/toast' import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip' @@ -11,19 +12,17 @@ import * as React from 'react' import { useMemo } from 'react' import { useTranslation } from 'react-i18next' import { Infotip } from '@/app/components/base/infotip' -import Indicator from '@/app/components/header/indicator' import { useDocumentDelete, useDocumentDisable, useDocumentEnable } from '@/service/knowledge/use-document' import { asyncRunSafe } from '@/utils' import s from '../style.module.css' import { useIndexStatus } from './hooks' -const STATUS_TEXT_COLOR_MAP: ColorMap = { - green: 'text-util-colors-green-green-600', - orange: 'text-util-colors-warning-warning-600', - red: 'text-util-colors-red-red-600', - blue: 'text-util-colors-blue-light-blue-light-600', - yellow: 'text-util-colors-warning-warning-600', - gray: 'text-text-tertiary', +const STATUS_TEXT_COLOR_MAP: Record = { + success: 'text-util-colors-green-green-600', + warning: 'text-util-colors-warning-warning-600', + error: 'text-util-colors-red-red-600', + normal: 'text-util-colors-blue-light-blue-light-600', + disabled: 'text-text-tertiary', } type StatusItemProps = { status: DocumentDisplayStatus @@ -43,6 +42,7 @@ const StatusItem = ({ status, reverse = false, scene = 'list', textCls = '', err const { t } = useTranslation() const DOC_INDEX_STATUS_MAP = useIndexStatus() const localStatus = status.toLowerCase() as keyof typeof DOC_INDEX_STATUS_MAP + const statusItem = DOC_INDEX_STATUS_MAP[localStatus] const { enabled = false, archived = false, id = '' } = detail || {} const { mutateAsync: enableDocument } = useDocumentEnable() const { mutateAsync: disableDocument } = useDocumentDisable() @@ -78,9 +78,9 @@ const StatusItem = ({ status, reverse = false, scene = 'list', textCls = '', err }, [localStatus]) return (
- - - {DOC_INDEX_STATUS_MAP[localStatus]?.text} + + + {statusItem.text} {errorMessage && (
-
{expand &&
{t('appMenus.apiAccess', { ns: 'common' })}
} -
diff --git a/web/app/components/datasets/extra-info/service-api/card.tsx b/web/app/components/datasets/extra-info/service-api/card.tsx index efa5cf7f6a..c178f875fd 100644 --- a/web/app/components/datasets/extra-info/service-api/card.tsx +++ b/web/app/components/datasets/extra-info/service-api/card.tsx @@ -1,11 +1,11 @@ import { Button } from '@langgenius/dify-ui/button' import { PopoverClose } from '@langgenius/dify-ui/popover' +import { StatusDot } from '@langgenius/dify-ui/status-dot' import { RiBookOpenLine, RiKey2Line } from '@remixicon/react' import * as React from 'react' import { useTranslation } from 'react-i18next' import CopyFeedback from '@/app/components/base/copy-feedback' import { ApiAggregate } from '@/app/components/base/icons/src/vender/knowledge' -import Indicator from '@/app/components/header/indicator' import { useDatasetApiAccessUrl } from '@/hooks/use-api-access-url' import Link from '@/next/link' @@ -35,10 +35,10 @@ const Card = ({
-
-
{t('serviceApi.title', { ns: 'dataset' })}
diff --git a/web/app/components/header/account-dropdown/__tests__/index.spec.tsx b/web/app/components/header/account-dropdown/__tests__/index.spec.tsx index f23cb2efd4..107ebd7028 100644 --- a/web/app/components/header/account-dropdown/__tests__/index.spec.tsx +++ b/web/app/components/header/account-dropdown/__tests__/index.spec.tsx @@ -353,8 +353,7 @@ describe('AccountDropdown', () => { fireEvent.click(screen.getByRole('button')) // Assert - const indicator = screen.getByTestId('status-indicator') - expect(indicator).toHaveClass('bg-components-badge-status-light-warning-bg') + expect(document.querySelector('.bg-components-badge-status-light-warning-bg')).toBeInTheDocument() }) it('should show green indicator when version is latest', () => { @@ -374,8 +373,7 @@ describe('AccountDropdown', () => { fireEvent.click(screen.getByRole('button')) // Assert - const indicator = screen.getByTestId('status-indicator') - expect(indicator).toHaveClass('bg-components-badge-status-light-success-bg') + expect(document.querySelector('.bg-components-badge-status-light-success-bg')).toBeInTheDocument() }) }) }) diff --git a/web/app/components/header/account-dropdown/index.tsx b/web/app/components/header/account-dropdown/index.tsx index 3744db6e81..a64fc5f5a7 100644 --- a/web/app/components/header/account-dropdown/index.tsx +++ b/web/app/components/header/account-dropdown/index.tsx @@ -3,6 +3,7 @@ import type { MouseEventHandler, ReactNode } from 'react' import { Avatar } from '@langgenius/dify-ui/avatar' import { DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuLinkItem, DropdownMenuSeparator, DropdownMenuTrigger } from '@langgenius/dify-ui/dropdown-menu' +import { StatusDot } from '@langgenius/dify-ui/status-dot' import { useSuspenseQuery } from '@tanstack/react-query' import { useState } from 'react' import { useTranslation } from 'react-i18next' @@ -22,7 +23,6 @@ import { systemFeaturesQueryOptions } from '@/service/system-features' import { useLogout } from '@/service/use-common' import AccountAbout from '../account-about' import GithubStar from '../github-star' -import Indicator from '../indicator' import Compliance from './compliance' import { ExternalLinkIndicator, MenuItemContent } from './menu-item-content' import Support from './support' @@ -216,7 +216,7 @@ export default function AppSelector() { trailing={(
{langGeniusVersionInfo.current_version}
- +
)} /> diff --git a/web/app/components/header/account-setting/data-source-page-new/item.tsx b/web/app/components/header/account-setting/data-source-page-new/item.tsx index 8fb3ca305a..167d055f1d 100644 --- a/web/app/components/header/account-setting/data-source-page-new/item.tsx +++ b/web/app/components/header/account-setting/data-source-page-new/item.tsx @@ -2,13 +2,13 @@ import type { DataSourceCredential, } from './types' import { Button } from '@langgenius/dify-ui/button' +import { StatusDot } from '@langgenius/dify-ui/status-dot' import { memo, useState, } from 'react' import { useTranslation } from 'react-i18next' import Input from '@/app/components/base/input' -import Indicator from '@/app/components/header/indicator' import Operator from './operator' type ItemProps = { @@ -76,7 +76,7 @@ const Item = ({ }
- +
connected diff --git a/web/app/components/header/account-setting/key-validator/Operate.tsx b/web/app/components/header/account-setting/key-validator/Operate.tsx index 7f09f32f64..984f572a0d 100644 --- a/web/app/components/header/account-setting/key-validator/Operate.tsx +++ b/web/app/components/header/account-setting/key-validator/Operate.tsx @@ -1,6 +1,6 @@ import type { Status } from './declarations' +import { StatusDot } from '@langgenius/dify-ui/status-dot' import { useTranslation } from 'react-i18next' -import Indicator from '../../indicator' type OperateProps = { isOpen: boolean @@ -71,13 +71,13 @@ const Operate = ({ status === 'fail' && (
{t('provider.invalidApiKey', { ns: 'common' })}
- +
) } { status === 'success' && ( - + ) }
({ })) // Mock Indicator -vi.mock('@/app/components/header/indicator', () => ({ - default: ({ color }: { color: string }) =>
, +vi.mock('@langgenius/dify-ui/status-dot', () => ({ + StatusDot: ({ status }: { status: string }) =>
, })) describe('ConfigModel', () => { @@ -19,7 +19,7 @@ describe('ConfigModel', () => { expect(screen.getByText(/modelProvider.auth.authorizationError/)).toBeInTheDocument() expect(screen.getByTestId('scales-icon')).toBeInTheDocument() - expect(screen.getByTestId('indicator-orange')).toBeInTheDocument() + expect(screen.getByTestId('indicator-warning')).toBeInTheDocument() fireEvent.click(screen.getByText(/modelProvider.auth.authorizationError/)) expect(onClick).toHaveBeenCalled() @@ -29,7 +29,7 @@ describe('ConfigModel', () => { render() expect(screen.getByText(/modelProvider.auth.credentialRemoved/)).toBeInTheDocument() - expect(screen.getByTestId('indicator-red')).toBeInTheDocument() + expect(screen.getByTestId('indicator-error')).toBeInTheDocument() }) it('should render standard config message when no flags enabled', () => { diff --git a/web/app/components/header/account-setting/model-provider-page/model-auth/__tests__/credential-selector.spec.tsx b/web/app/components/header/account-setting/model-provider-page/model-auth/__tests__/credential-selector.spec.tsx index 720bdc2ff3..408465fbb2 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-auth/__tests__/credential-selector.spec.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-auth/__tests__/credential-selector.spec.tsx @@ -10,8 +10,8 @@ vi.mock('../authorized/credential-item', () => ({ ), })) -vi.mock('@/app/components/header/indicator', () => ({ - default: () =>
, +vi.mock('@langgenius/dify-ui/status-dot', () => ({ + StatusDot: () =>
, })) vi.mock('@remixicon/react', () => ({ diff --git a/web/app/components/header/account-setting/model-provider-page/model-auth/__tests__/switch-credential-in-load-balancing.spec.tsx b/web/app/components/header/account-setting/model-provider-page/model-auth/__tests__/switch-credential-in-load-balancing.spec.tsx index 73aa8f9bfc..e135c23764 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-auth/__tests__/switch-credential-in-load-balancing.spec.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-auth/__tests__/switch-credential-in-load-balancing.spec.tsx @@ -15,8 +15,8 @@ vi.mock('../authorized', () => ({ ), })) -vi.mock('@/app/components/header/indicator', () => ({ - default: ({ color }: { color: string }) =>
, +vi.mock('@langgenius/dify-ui/status-dot', () => ({ + StatusDot: ({ status }: { status: string }) =>
, })) vi.mock('@remixicon/react', () => ({ @@ -58,7 +58,7 @@ describe('SwitchCredentialInLoadBalancing', () => { ) expect(screen.getByText('Key 1'))!.toBeInTheDocument() - expect(screen.getByTestId('indicator-green'))!.toBeInTheDocument() + expect(screen.getByTestId('indicator-success'))!.toBeInTheDocument() }) it('should render auth removed status when selected credential is not in list', () => { @@ -73,7 +73,7 @@ describe('SwitchCredentialInLoadBalancing', () => { ) expect(screen.getByText(/modelProvider.auth.authRemoved/))!.toBeInTheDocument() - expect(screen.getByTestId('indicator-red'))!.toBeInTheDocument() + expect(screen.getByTestId('indicator-error'))!.toBeInTheDocument() }) it('should render unavailable status when credentials list is empty', () => { @@ -156,7 +156,7 @@ describe('SwitchCredentialInLoadBalancing', () => { />, ) - expect(screen.getByTestId('indicator-red'))!.toBeInTheDocument() + expect(screen.getByTestId('indicator-error'))!.toBeInTheDocument() expect(screen.getByText(/auth.credentialUnavailableInButton/))!.toBeInTheDocument() }) @@ -244,9 +244,9 @@ describe('SwitchCredentialInLoadBalancing', () => { />, ) - // indicator-green shown (not authRemoved, not unavailable, not empty) - // indicator-green shown (not authRemoved, not unavailable, not empty) - expect(screen.getByTestId('indicator-green'))!.toBeInTheDocument() + // indicator-success shown (not authRemoved, not unavailable, not empty) + // indicator-success shown (not authRemoved, not unavailable, not empty) + expect(screen.getByTestId('indicator-success'))!.toBeInTheDocument() // credential_name is empty so nothing printed for name // credential_name is empty so nothing printed for name // credential_name is empty so nothing printed for name diff --git a/web/app/components/header/account-setting/model-provider-page/model-auth/authorized/__tests__/credential-item.spec.tsx b/web/app/components/header/account-setting/model-provider-page/model-auth/authorized/__tests__/credential-item.spec.tsx index c5ef0028fb..0b96f03c7d 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-auth/authorized/__tests__/credential-item.spec.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-auth/authorized/__tests__/credential-item.spec.tsx @@ -2,8 +2,8 @@ import type { Credential } from '../../../declarations' import { fireEvent, render, screen } from '@testing-library/react' import CredentialItem from '../credential-item' -vi.mock('@/app/components/header/indicator', () => ({ - default: () =>
, +vi.mock('@langgenius/dify-ui/status-dot', () => ({ + StatusDot: () =>
, })) describe('CredentialItem', () => { diff --git a/web/app/components/header/account-setting/model-provider-page/model-auth/authorized/credential-item.tsx b/web/app/components/header/account-setting/model-provider-page/model-auth/authorized/credential-item.tsx index 8e315ccd75..610e94890c 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-auth/authorized/credential-item.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-auth/authorized/credential-item.tsx @@ -1,5 +1,6 @@ import type { Credential } from '../../declarations' import { cn } from '@langgenius/dify-ui/cn' +import { StatusDot } from '@langgenius/dify-ui/status-dot' import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip' import { memo, @@ -8,7 +9,6 @@ import { import { useTranslation } from 'react-i18next' import ActionButton from '@/app/components/base/action-button' import Badge from '@/app/components/base/badge' -import Indicator from '@/app/components/header/indicator' type CredentialItemProps = { credential: Credential @@ -71,7 +71,7 @@ const CredentialItem = ({
) } - +
void @@ -30,7 +30,7 @@ const ConfigModel = ({ > {t('modelProvider.auth.authorizationError', { ns: 'common' })} - +
) } @@ -49,7 +49,7 @@ const ConfigModel = ({ credentialRemoved && ( <> {t('modelProvider.auth.credentialRemoved', { ns: 'common' })} - + ) } diff --git a/web/app/components/header/account-setting/model-provider-page/model-auth/credential-selector.tsx b/web/app/components/header/account-setting/model-provider-page/model-auth/credential-selector.tsx index 85ff918847..df1287113f 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-auth/credential-selector.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-auth/credential-selector.tsx @@ -4,6 +4,7 @@ import { PopoverContent, PopoverTrigger, } from '@langgenius/dify-ui/popover' +import { StatusDot } from '@langgenius/dify-ui/status-dot' import { RiAddLine, RiArrowDownSLine, @@ -15,7 +16,6 @@ import { } from 'react' import { useTranslation } from 'react-i18next' import Badge from '@/app/components/base/badge' -import Indicator from '@/app/components/header/indicator' import CredentialItem from './authorized/credential-item' type CredentialSelectorProps = { @@ -60,7 +60,7 @@ const CredentialSelector = ({ selectedCredential && (
{ - !selectedCredential.addNewCredential && + !selectedCredential.addNewCredential && }
{selectedCredential.credential_name}
{ diff --git a/web/app/components/header/account-setting/model-provider-page/model-auth/switch-credential-in-load-balancing.tsx b/web/app/components/header/account-setting/model-provider-page/model-auth/switch-credential-in-load-balancing.tsx index 30bc17d159..aaaa8f5dfa 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-auth/switch-credential-in-load-balancing.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-auth/switch-credential-in-load-balancing.tsx @@ -1,3 +1,4 @@ +import type { StatusDotStatus } from '@langgenius/dify-ui/status-dot' import type { Dispatch, SetStateAction } from 'react' import type { Credential, @@ -6,6 +7,7 @@ import type { } from '../declarations' import { Button } from '@langgenius/dify-ui/button' import { cn } from '@langgenius/dify-ui/cn' +import { StatusDot } from '@langgenius/dify-ui/status-dot' import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip' import { RiArrowDownSLine } from '@remixicon/react' import { @@ -15,7 +17,6 @@ import { import { useTranslation } from 'react-i18next' import Badge from '@/app/components/base/badge' import { ConfigurationMethodEnum, ModelModalModeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' -import Indicator from '@/app/components/header/indicator' import Authorized from './authorized' type SwitchCredentialInLoadBalancingProps = { @@ -49,9 +50,9 @@ const SwitchCredentialInLoadBalancing = ({ const authRemoved = selectedCredentialId && !currentCredential && !empty const unavailable = currentCredential?.not_allowed_to_use - let color = 'green' + let color: StatusDotStatus = 'success' if (authRemoved || unavailable) - color = 'red' + color = 'error' const Item = (
-
+
) diff --git a/web/app/components/header/account-setting/model-provider-page/model-selector/popup-item.tsx b/web/app/components/header/account-setting/model-provider-page/model-selector/popup-item.tsx index 0a936d1aa5..24a91f5599 100644 --- a/web/app/components/header/account-setting/model-provider-page/model-selector/popup-item.tsx +++ b/web/app/components/header/account-setting/model-provider-page/model-selector/popup-item.tsx @@ -4,6 +4,7 @@ import { cn } from '@langgenius/dify-ui/cn' import { ComboboxGroup, ComboboxItem, ComboboxItemIndicator } from '@langgenius/dify-ui/combobox' import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover' import { PreviewCardTrigger } from '@langgenius/dify-ui/preview-card' +import { StatusDot } from '@langgenius/dify-ui/status-dot' import { useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { CreditsCoin } from '@/app/components/base/icons/src/vender/line/financeAndECommerce' @@ -122,13 +123,13 @@ function PopupItem({ : credentialName ? ( <> - + {credentialName} ) : ( <> - + {t('modelProvider.selector.configureRequired', { ns: 'common' })} )} diff --git a/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/credential-panel.spec.tsx b/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/credential-panel.spec.tsx index 9d158a019e..257b0128ab 100644 --- a/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/credential-panel.spec.tsx +++ b/web/app/components/header/account-setting/model-provider-page/provider-added-card/__tests__/credential-panel.spec.tsx @@ -85,8 +85,8 @@ vi.mock('../model-auth-dropdown', () => ({ ), })) -vi.mock('@/app/components/header/indicator', () => ({ - default: ({ color }: { color: string }) =>
, +vi.mock('@langgenius/dify-ui/status-dot', () => ({ + StatusDot: ({ status }: { status: string }) =>
, })) vi.mock('@/app/components/base/icons/src/vender/line/alertsAndFeedback/Warning', () => ({ @@ -192,7 +192,7 @@ describe('CredentialPanel', () => { it('should show green indicator and credential name for api-fallback (exhausted + authorized key)', () => { mockTrialCredits.isExhausted = true renderWithQueryClient(createProvider()) - expect(screen.getByTestId('indicator')).toHaveAttribute('data-color', 'green') + expect(screen.getByTestId('indicator')).toHaveAttribute('data-status', 'success') expect(screen.getByText('test-credential')).toBeInTheDocument() }) @@ -206,7 +206,7 @@ describe('CredentialPanel', () => { renderWithQueryClient(createProvider({ preferred_provider_type: PreferredProviderTypeEnum.custom, })) - expect(screen.getByTestId('indicator')).toHaveAttribute('data-color', 'green') + expect(screen.getByTestId('indicator')).toHaveAttribute('data-status', 'success') expect(screen.getByText('test-credential')).toBeInTheDocument() }) @@ -228,7 +228,7 @@ describe('CredentialPanel', () => { available_credentials: [{ credential_id: 'cred-1', credential_name: 'Bad Key' }], }, })) - expect(screen.getByTestId('indicator')).toHaveAttribute('data-color', 'red') + expect(screen.getByTestId('indicator')).toHaveAttribute('data-status', 'error') expect(screen.getByText('Bad Key')).toBeInTheDocument() }) }) diff --git a/web/app/components/header/account-setting/model-provider-page/provider-added-card/credential-panel.tsx b/web/app/components/header/account-setting/model-provider-page/provider-added-card/credential-panel.tsx index 190b05209f..25234a582f 100644 --- a/web/app/components/header/account-setting/model-provider-page/provider-added-card/credential-panel.tsx +++ b/web/app/components/header/account-setting/model-provider-page/provider-added-card/credential-panel.tsx @@ -1,9 +1,9 @@ import type { ModelProvider } from '../declarations' import type { CardVariant } from './use-credential-panel-state' +import { StatusDot } from '@langgenius/dify-ui/status-dot' import { memo } from 'react' import { useTranslation } from 'react-i18next' import Warning from '@/app/components/base/icons/src/vender/line/alertsAndFeedback/Warning' -import Indicator from '@/app/components/header/indicator' import ModelAuthDropdown from './model-auth-dropdown' import SystemQuotaCard from './system-quota-card' import { useChangeProviderPriority } from './use-change-provider-priority' @@ -38,7 +38,7 @@ const CredentialPanel = ({ {isTextLabel ? - : } + : } - + + )} /> diff --git a/web/app/components/header/indicator/__tests__/index.spec.tsx b/web/app/components/header/indicator/__tests__/index.spec.tsx deleted file mode 100644 index ffb2ade8d3..0000000000 --- a/web/app/components/header/indicator/__tests__/index.spec.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import { render, screen } from '@testing-library/react' -import Indicator from '../index' - -describe('Indicator', () => { - it('should render with default props', () => { - render() - const indicator = screen.getByTestId('status-indicator') - expect(indicator).toBeInTheDocument() - expect(indicator).toHaveClass( - 'bg-components-badge-status-light-success-bg', - ) - expect(indicator).toHaveClass( - 'border-components-badge-status-light-success-border-inner', - ) - expect(indicator).toHaveClass('shadow-status-indicator-green-shadow') - }) - - it('should render with orange color', () => { - render() - const indicator = screen.getByTestId('status-indicator') - expect(indicator).toHaveClass( - 'bg-components-badge-status-light-warning-bg', - ) - expect(indicator).toHaveClass( - 'border-components-badge-status-light-warning-border-inner', - ) - expect(indicator).toHaveClass('shadow-status-indicator-warning-shadow') - }) - - it('should render with red color', () => { - render() - const indicator = screen.getByTestId('status-indicator') - expect(indicator).toHaveClass('bg-components-badge-status-light-error-bg') - expect(indicator).toHaveClass( - 'border-components-badge-status-light-error-border-inner', - ) - expect(indicator).toHaveClass('shadow-status-indicator-red-shadow') - }) - - it('should render with blue color', () => { - render() - const indicator = screen.getByTestId('status-indicator') - expect(indicator).toHaveClass('bg-components-badge-status-light-normal-bg') - expect(indicator).toHaveClass( - 'border-components-badge-status-light-normal-border-inner', - ) - expect(indicator).toHaveClass('shadow-status-indicator-blue-shadow') - }) - - it('should render with yellow color', () => { - render() - const indicator = screen.getByTestId('status-indicator') - expect(indicator).toHaveClass( - 'bg-components-badge-status-light-warning-bg', - ) - expect(indicator).toHaveClass( - 'border-components-badge-status-light-warning-border-inner', - ) - expect(indicator).toHaveClass('shadow-status-indicator-warning-shadow') - }) - - it('should render with gray color', () => { - render() - const indicator = screen.getByTestId('status-indicator') - expect(indicator).toHaveClass( - 'bg-components-badge-status-light-disabled-bg', - ) - expect(indicator).toHaveClass( - 'border-components-badge-status-light-disabled-border-inner', - ) - expect(indicator).toHaveClass('shadow-status-indicator-gray-shadow') - }) - - it('should apply custom className', () => { - render() - const indicator = screen.getByTestId('status-indicator') - expect(indicator).toHaveClass('custom-class') - }) -}) diff --git a/web/app/components/header/indicator/index.tsx b/web/app/components/header/indicator/index.tsx deleted file mode 100644 index c60ebcb7bd..0000000000 --- a/web/app/components/header/indicator/index.tsx +++ /dev/null @@ -1,54 +0,0 @@ -'use client' - -import { cn } from '@langgenius/dify-ui/cn' - -export type IndicatorProps = { - color?: 'green' | 'orange' | 'red' | 'blue' | 'yellow' | 'gray' - className?: string -} - -export type ColorMap = { - green: string - orange: string - red: string - blue: string - yellow: string - gray: string -} - -const BACKGROUND_MAP: ColorMap = { - green: 'bg-components-badge-status-light-success-bg', - orange: 'bg-components-badge-status-light-warning-bg', - red: 'bg-components-badge-status-light-error-bg', - blue: 'bg-components-badge-status-light-normal-bg', - yellow: 'bg-components-badge-status-light-warning-bg', - gray: 'bg-components-badge-status-light-disabled-bg', -} -const BORDER_MAP: ColorMap = { - green: 'border-components-badge-status-light-success-border-inner', - orange: 'border-components-badge-status-light-warning-border-inner', - red: 'border-components-badge-status-light-error-border-inner', - blue: 'border-components-badge-status-light-normal-border-inner', - yellow: 'border-components-badge-status-light-warning-border-inner', - gray: 'border-components-badge-status-light-disabled-border-inner', -} -const SHADOW_MAP: ColorMap = { - green: 'shadow-status-indicator-green-shadow', - orange: 'shadow-status-indicator-warning-shadow', - red: 'shadow-status-indicator-red-shadow', - blue: 'shadow-status-indicator-blue-shadow', - yellow: 'shadow-status-indicator-warning-shadow', - gray: 'shadow-status-indicator-gray-shadow', -} - -export default function Indicator({ - color = 'green', - className = '', -}: IndicatorProps) { - return ( -
- ) -} diff --git a/web/app/components/header/plugins-nav/__tests__/index.spec.tsx b/web/app/components/header/plugins-nav/__tests__/index.spec.tsx index ab55225641..ff32260811 100644 --- a/web/app/components/header/plugins-nav/__tests__/index.spec.tsx +++ b/web/app/components/header/plugins-nav/__tests__/index.spec.tsx @@ -5,6 +5,9 @@ import { useSelectedLayoutSegment } from '@/next/navigation' import PluginsNav from '../index' +const queryErrorStatusDot = (container: HTMLElement) => + container.querySelector('.shadow-status-indicator-red-shadow') + vi.mock('@/next/navigation', () => ({ useSelectedLayoutSegment: vi.fn(), })) @@ -38,7 +41,7 @@ describe('PluginsNav', () => { const svg = linkElement.querySelector('svg') expect(svg).toBeInTheDocument() - expect(screen.queryByTestId('status-indicator')).not.toBeInTheDocument() + expect(queryErrorStatusDot(linkElement)).not.toBeInTheDocument() }) describe('Active State', () => { @@ -70,7 +73,7 @@ describe('PluginsNav', () => { expect(svgs.length).toBe(1) expect(svgs[0]).toHaveClass('install-icon') - expect(screen.queryByTestId('status-indicator')).not.toBeInTheDocument() + expect(queryErrorStatusDot(container)).not.toBeInTheDocument() }) it('renders Installing With Error state (Inactive)', () => { @@ -81,7 +84,7 @@ describe('PluginsNav', () => { const downloadingIcon = container.querySelector('.install-icon') expect(downloadingIcon).toBeInTheDocument() - expect(screen.getByTestId('status-indicator')).toBeInTheDocument() + expect(queryErrorStatusDot(container)).toBeInTheDocument() }) it('renders Failed state (Inactive)', () => { @@ -93,7 +96,7 @@ describe('PluginsNav', () => { expect(svg).toBeInTheDocument() expect(svg).not.toHaveClass('install-icon') - expect(screen.getByTestId('status-indicator')).toBeInTheDocument() + expect(queryErrorStatusDot(container)).toBeInTheDocument() }) it('renders Default icon when Active even if installing', () => { diff --git a/web/app/components/header/plugins-nav/index.tsx b/web/app/components/header/plugins-nav/index.tsx index ca3ebdbbb6..8e5ef258b2 100644 --- a/web/app/components/header/plugins-nav/index.tsx +++ b/web/app/components/header/plugins-nav/index.tsx @@ -1,9 +1,9 @@ 'use client' import { cn } from '@langgenius/dify-ui/cn' +import { StatusDot } from '@langgenius/dify-ui/status-dot' import { useTranslation } from 'react-i18next' import { Group } from '@/app/components/base/icons/src/vender/other' -import Indicator from '@/app/components/header/indicator' import { usePluginTaskStatus } from '@/app/components/plugins/plugin-page/plugin-tasks/hooks' import Link from '@/next/link' import { useSelectedLayoutSegment } from '@/next/navigation' @@ -37,8 +37,8 @@ const PluginsNav = ({ > { (isFailed || isInstallingWithError) && !activated && ( - ) diff --git a/web/app/components/plugins/plugin-auth/__tests__/authorized-in-data-source-node.spec.tsx b/web/app/components/plugins/plugin-auth/__tests__/authorized-in-data-source-node.spec.tsx index d5cea7a495..76adaf5721 100644 --- a/web/app/components/plugins/plugin-auth/__tests__/authorized-in-data-source-node.spec.tsx +++ b/web/app/components/plugins/plugin-auth/__tests__/authorized-in-data-source-node.spec.tsx @@ -2,8 +2,8 @@ import { cleanup, fireEvent, render, screen } from '@testing-library/react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import AuthorizedInDataSourceNode from '../authorized-in-data-source-node' -vi.mock('@/app/components/header/indicator', () => ({ - default: ({ color }: { color: string }) => , +vi.mock('@langgenius/dify-ui/status-dot', () => ({ + StatusDot: ({ status }: { status: string }) => , })) describe('AuthorizedInDataSourceNode', () => { @@ -19,7 +19,7 @@ describe('AuthorizedInDataSourceNode', () => { it('renders with green indicator', () => { render() - expect(screen.getByTestId('indicator')).toHaveAttribute('data-color', 'green') + expect(screen.getByTestId('indicator')).toHaveAttribute('data-status', 'success') }) it('renders singular text for 1 authorization', () => { diff --git a/web/app/components/plugins/plugin-auth/authorized-in-data-source-node.tsx b/web/app/components/plugins/plugin-auth/authorized-in-data-source-node.tsx index d8b1245c16..c8dda200bc 100644 --- a/web/app/components/plugins/plugin-auth/authorized-in-data-source-node.tsx +++ b/web/app/components/plugins/plugin-auth/authorized-in-data-source-node.tsx @@ -1,11 +1,11 @@ import { Button } from '@langgenius/dify-ui/button' import { cn } from '@langgenius/dify-ui/cn' +import { StatusDot } from '@langgenius/dify-ui/status-dot' import { RiEqualizer2Line } from '@remixicon/react' import { memo, } from 'react' import { useTranslation } from 'react-i18next' -import Indicator from '@/app/components/header/indicator' type AuthorizedInDataSourceNodeProps = { authorizationsNum: number @@ -22,9 +22,9 @@ const AuthorizedInDataSourceNode = ({ size="small" onClick={onJumpToDataSourcePage} > - { authorizationsNum > 1 diff --git a/web/app/components/plugins/plugin-auth/authorized-in-node.tsx b/web/app/components/plugins/plugin-auth/authorized-in-node.tsx index 12a84b56e3..0831fe9a15 100644 --- a/web/app/components/plugins/plugin-auth/authorized-in-node.tsx +++ b/web/app/components/plugins/plugin-auth/authorized-in-node.tsx @@ -1,9 +1,11 @@ +import type { StatusDotStatus } from '@langgenius/dify-ui/status-dot' import type { Credential, PluginPayload, } from './types' import { Button } from '@langgenius/dify-ui/button' import { cn } from '@langgenius/dify-ui/cn' +import { StatusDot } from '@langgenius/dify-ui/status-dot' import { RiArrowDownSLine } from '@remixicon/react' import { memo, @@ -11,7 +13,6 @@ import { useState, } from 'react' import { useTranslation } from 'react-i18next' -import Indicator from '@/app/components/header/indicator' import { Authorized, usePluginAuth, @@ -41,7 +42,7 @@ const AuthorizedInNode = ({ let label = '' let removed = false let unavailable = false - let color = 'green' + let color: StatusDotStatus = 'success' let defaultUnavailable = false if (!credentialId) { label = t('auth.workspaceDefault', { ns: 'plugin' }) @@ -49,7 +50,7 @@ const AuthorizedInNode = ({ const defaultCredential = credentials.find(c => c.is_default) if (defaultCredential?.not_allowed_to_use) { - color = 'gray' + color = 'disabled' defaultUnavailable = true } } @@ -60,9 +61,9 @@ const AuthorizedInNode = ({ unavailable = !!credential?.not_allowed_to_use && !credential?.from_enterprise if (removed) - color = 'red' + color = 'error' else if (unavailable) - color = 'gray' + color = 'disabled' } return (
) } -
- {label} { diff --git a/web/app/components/plugins/plugin-detail-panel/__tests__/endpoint-card.spec.tsx b/web/app/components/plugins/plugin-detail-panel/__tests__/endpoint-card.spec.tsx index 1dab8bdf84..d2ac595c87 100644 --- a/web/app/components/plugins/plugin-detail-panel/__tests__/endpoint-card.spec.tsx +++ b/web/app/components/plugins/plugin-detail-panel/__tests__/endpoint-card.spec.tsx @@ -72,8 +72,8 @@ vi.mock('@/service/use-endpoints', () => ({ }), })) -vi.mock('@/app/components/header/indicator', () => ({ - default: ({ color }: { color: string }) => , +vi.mock('@langgenius/dify-ui/status-dot', () => ({ + StatusDot: ({ status }: { status: string }) => , })) vi.mock('@/app/components/tools/utils/to-form-schema', () => ({ @@ -176,7 +176,7 @@ describe('EndpointCard', () => { render() expect(screen.getByText('plugin.detailPanel.serviceOk'))!.toBeInTheDocument() - expect(screen.getByTestId('indicator'))!.toHaveAttribute('data-color', 'green') + expect(screen.getByTestId('indicator'))!.toHaveAttribute('data-status', 'success') }) it('should show disabled status when not enabled', () => { @@ -184,7 +184,7 @@ describe('EndpointCard', () => { render() expect(screen.getByText('plugin.detailPanel.disabled'))!.toBeInTheDocument() - expect(screen.getByTestId('indicator'))!.toHaveAttribute('data-color', 'gray') + expect(screen.getByTestId('indicator'))!.toHaveAttribute('data-status', 'disabled') }) }) diff --git a/web/app/components/plugins/plugin-detail-panel/datasource-action-list.tsx b/web/app/components/plugins/plugin-detail-panel/datasource-action-list.tsx index 5bba7b823b..b8768b16a1 100644 --- a/web/app/components/plugins/plugin-detail-panel/datasource-action-list.tsx +++ b/web/app/components/plugins/plugin-detail-panel/datasource-action-list.tsx @@ -1,6 +1,6 @@ // import { useAppContext } from '@/context/app-context' // import { Button } from '@langgenius/dify-ui/button' -// import Indicator from '@/app/components/header/indicator' +// import { StatusDot } from '@langgenius/dify-ui/status-dot' // import ToolItem from '@/app/components/tools/provider/tool-item' // import ConfigCredential from '@/app/components/tools/setting/build-in/config-credentials' import type { PluginDetail } from '@/app/components/plugins/types' @@ -60,7 +60,7 @@ const ActionList = ({ onClick={() => setShowSettingAuth(true)} disabled={!isCurrentWorkspaceManager} > - + {t('tools.auth.authorized')} )} */} diff --git a/web/app/components/plugins/plugin-detail-panel/endpoint-card.tsx b/web/app/components/plugins/plugin-detail-panel/endpoint-card.tsx index 9cea1defbc..8fff7c7da5 100644 --- a/web/app/components/plugins/plugin-detail-panel/endpoint-card.tsx +++ b/web/app/components/plugins/plugin-detail-panel/endpoint-card.tsx @@ -8,6 +8,7 @@ import { AlertDialogContent, AlertDialogTitle, } from '@langgenius/dify-ui/alert-dialog' +import { StatusDot } from '@langgenius/dify-ui/status-dot' import { Switch } from '@langgenius/dify-ui/switch' import { toast } from '@langgenius/dify-ui/toast' import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip' @@ -18,7 +19,6 @@ import { useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import ActionButton from '@/app/components/base/action-button' import { CopyCheck } from '@/app/components/base/icons/src/vender/line/files' -import Indicator from '@/app/components/header/indicator' import { addDefaultValue, toolCredentialToFormSchemas } from '@/app/components/tools/utils/to-form-schema' import { useDeleteEndpoint, @@ -199,13 +199,13 @@ const EndpointCard = ({
{active && (
- + {t('detailPanel.serviceOk', { ns: 'plugin' })}
)} {!active && (
- + {t('detailPanel.disabled', { ns: 'plugin' })}
)} diff --git a/web/app/components/plugins/plugin-detail-panel/tool-selector/components/tool-item.tsx b/web/app/components/plugins/plugin-detail-panel/tool-selector/components/tool-item.tsx index 2bb690777c..06735865f7 100644 --- a/web/app/components/plugins/plugin-detail-panel/tool-selector/components/tool-item.tsx +++ b/web/app/components/plugins/plugin-detail-panel/tool-selector/components/tool-item.tsx @@ -2,6 +2,7 @@ import { Button } from '@langgenius/dify-ui/button' import { cn } from '@langgenius/dify-ui/cn' import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover' +import { StatusDot } from '@langgenius/dify-ui/status-dot' import { Switch } from '@langgenius/dify-ui/switch' import { RiDeleteBinLine, @@ -14,7 +15,6 @@ import { useTranslation } from 'react-i18next' import ActionButton from '@/app/components/base/action-button' import AppIcon from '@/app/components/base/app-icon' import { Group } from '@/app/components/base/icons/src/vender/other' -import Indicator from '@/app/components/header/indicator' import { InstallPluginButton } from '@/app/components/workflow/nodes/_base/components/install-plugin-button' import { useMCPToolAvailability } from '@/app/components/workflow/nodes/_base/components/mcp-tool-availability' import McpToolNotSupportTooltip from '@/app/components/workflow/nodes/_base/components/mcp-tool-not-support-tooltip' @@ -128,13 +128,13 @@ const ToolItem = ({ {!isError && !uninstalled && !versionMismatch && noAuth && ( )} {!isError && !uninstalled && !versionMismatch && authRemoved && ( )} {!isError && !uninstalled && versionMismatch && installInfo && ( diff --git a/web/app/components/plugins/plugin-page/plugin-tasks/components/__tests__/task-status-indicator.spec.tsx b/web/app/components/plugins/plugin-page/plugin-tasks/components/__tests__/task-status-indicator.spec.tsx index addae49d98..8311a54f44 100644 --- a/web/app/components/plugins/plugin-page/plugin-tasks/components/__tests__/task-status-indicator.spec.tsx +++ b/web/app/components/plugins/plugin-page/plugin-tasks/components/__tests__/task-status-indicator.spec.tsx @@ -1,9 +1,9 @@ import { fireEvent, render, screen } from '@testing-library/react' import TaskStatusIndicator from '../task-status-indicator' -vi.mock('@/app/components/base/progress-bar/progress-circle', () => ({ - default: ({ percentage }: { percentage: number }) => ( -
+vi.mock('@langgenius/dify-ui/progress', () => ({ + ProgressCircle: ({ value }: { value: number }) => ( +
), })) @@ -68,7 +68,7 @@ describe('TaskStatusIndicator', () => { />, ) const progress = screen.getByTestId('progress-circle') - expect(progress).toHaveAttribute('data-percentage', '40') + expect(progress).toHaveAttribute('data-value', '40') }) it('should show progress circle when isInstallingWithSuccess', () => { @@ -81,7 +81,7 @@ describe('TaskStatusIndicator', () => { />, ) const progress = screen.getByTestId('progress-circle') - expect(progress).toHaveAttribute('data-percentage', '75') + expect(progress).toHaveAttribute('data-value', '75') }) it('should show error progress circle when isInstallingWithError', () => { @@ -106,7 +106,7 @@ describe('TaskStatusIndicator', () => { />, ) const progress = screen.getByTestId('progress-circle') - expect(progress).toHaveAttribute('data-percentage', '0') + expect(progress).toHaveAttribute('data-value', '0') }) }) diff --git a/web/app/components/plugins/plugin-page/plugin-tasks/components/task-status-indicator.tsx b/web/app/components/plugins/plugin-page/plugin-tasks/components/task-status-indicator.tsx index ae75566aae..dfab072746 100644 --- a/web/app/components/plugins/plugin-page/plugin-tasks/components/task-status-indicator.tsx +++ b/web/app/components/plugins/plugin-page/plugin-tasks/components/task-status-indicator.tsx @@ -1,8 +1,8 @@ import type { FC } from 'react' import { Button } from '@langgenius/dify-ui/button' import { cn } from '@langgenius/dify-ui/cn' +import { ProgressCircle } from '@langgenius/dify-ui/progress' import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip' -import ProgressCircle from '@/app/components/base/progress-bar/progress-circle' import DownloadingIcon from '@/app/components/header/plugins-nav/downloading-icon' type TaskStatusIndicatorProps = { @@ -67,16 +67,15 @@ const TaskStatusIndicator: FC = ({
{(isInstalling || isInstallingWithSuccess) && ( 0 ? successPluginsLength / totalPluginsLength : 0) * 100} - circleFillColor="fill-components-progress-brand-bg" + value={(totalPluginsLength > 0 ? successPluginsLength / totalPluginsLength : 0) * 100} + aria-label={tip} /> )} {isInstallingWithError && ( 0 ? runningPluginsLength / totalPluginsLength : 0) * 100} - circleFillColor="fill-components-progress-brand-bg" - sectorFillColor="fill-components-progress-error-border" - circleStrokeColor="stroke-components-progress-error-border" + value={(totalPluginsLength > 0 ? runningPluginsLength / totalPluginsLength : 0) * 100} + color="error" + aria-label={tip} /> )} {showSuccessIcon && !isInstalling && !isInstallingWithSuccess && !isInstallingWithError && ( diff --git a/web/app/components/tools/mcp/detail/content.tsx b/web/app/components/tools/mcp/detail/content.tsx index b83fb847b1..e78d88b783 100644 --- a/web/app/components/tools/mcp/detail/content.tsx +++ b/web/app/components/tools/mcp/detail/content.tsx @@ -12,6 +12,7 @@ import { } from '@langgenius/dify-ui/alert-dialog' import { Button } from '@langgenius/dify-ui/button' import { cn } from '@langgenius/dify-ui/cn' +import { StatusDot } from '@langgenius/dify-ui/status-dot' import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip' import { useBoolean } from 'ahooks' import copy from 'copy-to-clipboard' @@ -19,7 +20,6 @@ import * as React from 'react' import { useCallback, useEffect } from 'react' import { useTranslation } from 'react-i18next' import ActionButton from '@/app/components/base/action-button' -import Indicator from '@/app/components/header/indicator' import Icon from '@/app/components/plugins/card/base/card-icon' import { useAppContext } from '@/context/app-context' import { openOAuthPopup } from '@/hooks/use-oauth' @@ -231,7 +231,7 @@ const MCPDetailContent: FC = ({ onClick={handleAuthorize} disabled={!isCurrentWorkspaceManager} > - + {t('auth.authorized', { ns: 'tools' })} )} diff --git a/web/app/components/tools/mcp/mcp-service-card.tsx b/web/app/components/tools/mcp/mcp-service-card.tsx index 38c757f841..a897d39dbd 100644 --- a/web/app/components/tools/mcp/mcp-service-card.tsx +++ b/web/app/components/tools/mcp/mcp-service-card.tsx @@ -16,6 +16,7 @@ import { import { Button } from '@langgenius/dify-ui/button' import { cn } from '@langgenius/dify-ui/cn' import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover' +import { StatusDot } from '@langgenius/dify-ui/status-dot' import { Switch } from '@langgenius/dify-ui/switch' import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip' import { RiEditLine, RiLoopLeftLine } from '@remixicon/react' @@ -24,7 +25,6 @@ import { useTranslation } from 'react-i18next' import CopyFeedback from '@/app/components/base/copy-feedback' import Divider from '@/app/components/base/divider' import { Mcp } from '@/app/components/base/icons/src/vender/other' -import Indicator from '@/app/components/header/indicator' import MCPServerModal from '@/app/components/tools/mcp/mcp-server-modal' import { collaborationManager } from '@/app/components/workflow/collaboration/core/collaboration-manager' import { useDocLink } from '@/context/i18n' @@ -40,7 +40,7 @@ const StatusIndicator: FC = ({ serverActivated }) => { const { t } = useTranslation() return (
- +
{serverActivated ? t('overview.status.running', { ns: 'appOverview' }) diff --git a/web/app/components/tools/mcp/provider-card.tsx b/web/app/components/tools/mcp/provider-card.tsx index 45e5a24f68..4536d96cad 100644 --- a/web/app/components/tools/mcp/provider-card.tsx +++ b/web/app/components/tools/mcp/provider-card.tsx @@ -9,11 +9,11 @@ import { AlertDialogTitle, } from '@langgenius/dify-ui/alert-dialog' import { cn } from '@langgenius/dify-ui/cn' +import { StatusDot } from '@langgenius/dify-ui/status-dot' import { RiHammerFill } from '@remixicon/react' import { useBoolean } from 'ahooks' import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' -import Indicator from '@/app/components/header/indicator' import Icon from '@/app/components/plugins/card/base/card-icon' import { useAppContext } from '@/context/app-context' import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now' @@ -112,11 +112,11 @@ const MCPCard = ({
/
{`${t('mcp.updateTime', { ns: 'tools' })} ${formatTimeFromNow(data.updated_at! * 1000)}`}
- {data.is_team_authorization && data.tools.length > 0 && } + {data.is_team_authorization && data.tools.length > 0 && } {(!data.is_team_authorization || !data.tools.length) && (
{t('mcp.noConfigured', { ns: 'tools' })} - +
)}
diff --git a/web/app/components/tools/provider/__tests__/detail.spec.tsx b/web/app/components/tools/provider/__tests__/detail.spec.tsx index 5a26589e11..70a35f3ddb 100644 --- a/web/app/components/tools/provider/__tests__/detail.spec.tsx +++ b/web/app/components/tools/provider/__tests__/detail.spec.tsx @@ -83,8 +83,8 @@ vi.mock('@langgenius/dify-ui/toast', () => ({ }, })) -vi.mock('@/app/components/header/indicator', () => ({ - default: () => , +vi.mock('@langgenius/dify-ui/status-dot', () => ({ + StatusDot: () => , })) vi.mock('@/app/components/plugins/card/base/card-icon', () => ({ diff --git a/web/app/components/tools/provider/detail.tsx b/web/app/components/tools/provider/detail.tsx index bf878a6206..ff64e0044c 100644 --- a/web/app/components/tools/provider/detail.tsx +++ b/web/app/components/tools/provider/detail.tsx @@ -20,6 +20,7 @@ import { DrawerPortal, DrawerViewport, } from '@langgenius/dify-ui/drawer' +import { StatusDot } from '@langgenius/dify-ui/status-dot' import { toast } from '@langgenius/dify-ui/toast' import { RiCloseLine, @@ -31,7 +32,6 @@ import ActionButton from '@/app/components/base/action-button' import { LinkExternal02, Settings01 } from '@/app/components/base/icons/src/vender/line/general' import Loading from '@/app/components/base/loading' import { ConfigurationMethodEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' -import Indicator from '@/app/components/header/indicator' import Icon from '@/app/components/plugins/card/base/card-icon' import Description from '@/app/components/plugins/card/base/description' import OrgInfo from '@/app/components/plugins/card/base/org-info' @@ -325,7 +325,7 @@ const ProviderDetail = ({ }} disabled={!isCurrentWorkspaceManager} > - + {t('auth.authorized', { ns: 'tools' })} )} diff --git a/web/app/components/tools/workflow-tool/configure-button.tsx b/web/app/components/tools/workflow-tool/configure-button.tsx index 5b83e37d3a..0adc923e48 100644 --- a/web/app/components/tools/workflow-tool/configure-button.tsx +++ b/web/app/components/tools/workflow-tool/configure-button.tsx @@ -1,9 +1,9 @@ 'use client' import { Button } from '@langgenius/dify-ui/button' import { cn } from '@langgenius/dify-ui/cn' +import { StatusDot } from '@langgenius/dify-ui/status-dot' import { useTranslation } from 'react-i18next' import Loading from '@/app/components/base/loading' -import Indicator from '@/app/components/header/indicator' import { useRouter } from '@/next/navigation' import Divider from '../../base/divider' @@ -90,7 +90,7 @@ const WorkflowToolConfigureButton = ({ disabled={!isCurrentWorkspaceManager || disabled} > {t('common.configure', { ns: 'workflow' })} - {outdated && } + {outdated && }
)} /> @@ -83,7 +83,7 @@ export const ModelBar: FC = (props) => { readonly deprecatedClassName="opacity-50" /> - {showWarn && } + {showWarn && }
) diff --git a/web/app/components/workflow/nodes/agent/components/tool-icon.tsx b/web/app/components/workflow/nodes/agent/components/tool-icon.tsx index f672730523..d898497e42 100644 --- a/web/app/components/workflow/nodes/agent/components/tool-icon.tsx +++ b/web/app/components/workflow/nodes/agent/components/tool-icon.tsx @@ -1,11 +1,11 @@ import type { ReactNode } from 'react' import { cn } from '@langgenius/dify-ui/cn' +import { StatusDot } from '@langgenius/dify-ui/status-dot' import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip' import { memo, useMemo, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' import AppIcon from '@/app/components/base/app-icon' import { Group } from '@/app/components/base/icons/src/vender/other' -import Indicator from '@/app/components/header/indicator' import { useAllBuiltInTools, useAllCustomTools, useAllMCPTools, useAllWorkflowTools } from '@/service/use-tools' import { getIconFromMarketPlace } from '@/utils/get-icon' @@ -50,7 +50,7 @@ export const ToolIcon = memo(({ providerName }: ToolIconProps) => { return 'not-authorized' return undefined }, [currentProvider, isDataReady]) - const indicator = status === 'not-installed' ? 'red' : status === 'not-authorized' ? 'yellow' : undefined + const indicator = status === 'not-installed' ? 'error' : status === 'not-authorized' ? 'warning' : undefined const notSuccess = (['not-installed', 'not-authorized'] as Array).includes(status) const { t } = useTranslation() const tooltip = useMemo(() => { @@ -96,7 +96,7 @@ export const ToolIcon = memo(({ providerName }: ToolIconProps) => {
{iconContent}
- {indicator && } + {indicator && }
) diff --git a/web/app/components/workflow/nodes/human-input/components/delivery-method/method-item.tsx b/web/app/components/workflow/nodes/human-input/components/delivery-method/method-item.tsx index 5a997d0d15..97a086a38b 100644 --- a/web/app/components/workflow/nodes/human-input/components/delivery-method/method-item.tsx +++ b/web/app/components/workflow/nodes/human-input/components/delivery-method/method-item.tsx @@ -6,6 +6,7 @@ import type { } from '@/app/components/workflow/types' import { Button } from '@langgenius/dify-ui/button' import { cn } from '@langgenius/dify-ui/cn' +import { StatusDot } from '@langgenius/dify-ui/status-dot' import { Switch } from '@langgenius/dify-ui/switch' import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip' import { @@ -19,7 +20,6 @@ import { useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import ActionButton, { ActionButtonState } from '@/app/components/base/action-button' import Badge from '@/app/components/base/badge/index' -import Indicator from '@/app/components/header/indicator' import { useSelector as useAppContextWithSelector } from '@/context/app-context' import { DeliveryMethodType } from '../../types' import EmailConfigureModal from './email-configure-modal' @@ -177,7 +177,7 @@ const DeliveryMethodItem: FC = ({ disabled={readonly} > {t(`${i18nPrefix}.deliveryMethod.notConfigured`, { ns: 'workflow' })} - + )}
diff --git a/web/app/components/workflow/run/status.tsx b/web/app/components/workflow/run/status.tsx index a13b52816d..b9b21df9dd 100644 --- a/web/app/components/workflow/run/status.tsx +++ b/web/app/components/workflow/run/status.tsx @@ -1,9 +1,9 @@ 'use client' import type { FC } from 'react' import { cn } from '@langgenius/dify-ui/cn' +import { StatusDot } from '@langgenius/dify-ui/status-dot' import { useMemo } from 'react' import { Trans, useTranslation } from 'react-i18next' -import Indicator from '@/app/components/header/indicator' import StatusContainer from '@/app/components/workflow/run/status-container' import { useDocLink } from '@/context/i18n' import { useWorkflowPausedDetails } from '@/service/use-log' @@ -112,43 +112,43 @@ const StatusPanel: FC = ({ > {status === 'running' && ( <> - + {isListening ? 'Listening' : 'Running'} )} {status === 'succeeded' && ( <> - + SUCCESS )} {status === 'partial-succeeded' && ( <> - + PARTIAL SUCCESS )} {status === 'exception' && ( <> - + EXCEPTION )} {status === 'failed' && ( <> - + FAIL )} {status === 'stopped' && ( <> - + STOP )} {status === 'paused' && ( <> - + PENDING )}