refactor(web): migrate short tooltips to dify-ui (#35715)

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
yyh
2026-04-30 11:29:17 +08:00
committed by GitHub
parent 3b1458c08f
commit fe2f7a8920
55 changed files with 1264 additions and 924 deletions

View File

@@ -119,11 +119,6 @@
"count": 3
}
},
"web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-popup.tsx": {
"no-restricted-imports": {
"count": 1
}
},
"web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/provider-panel.tsx": {
"ts/no-explicit-any": {
"count": 1
@@ -526,11 +521,6 @@
"count": 2
}
},
"web/app/components/app/configuration/debug/debug-with-multiple-model/model-parameter-trigger.tsx": {
"no-restricted-imports": {
"count": 1
}
},
"web/app/components/app/configuration/debug/debug-with-multiple-model/text-generation-item.tsx": {
"ts/no-explicit-any": {
"count": 8
@@ -559,11 +549,6 @@
"count": 1
}
},
"web/app/components/app/configuration/prompt-value-panel/index.tsx": {
"no-restricted-imports": {
"count": 1
}
},
"web/app/components/app/configuration/prompt-value-panel/utils.ts": {
"ts/no-explicit-any": {
"count": 1
@@ -620,9 +605,6 @@
}
},
"web/app/components/app/log/list.tsx": {
"no-restricted-imports": {
"count": 1
},
"react/set-state-in-effect": {
"count": 6
},
@@ -645,7 +627,7 @@
},
"web/app/components/app/overview/embedded/index.tsx": {
"no-restricted-imports": {
"count": 2
"count": 1
},
"react/set-state-in-effect": {
"count": 1
@@ -791,11 +773,6 @@
"count": 3
}
},
"web/app/components/base/audio-btn/index.tsx": {
"no-restricted-imports": {
"count": 1
}
},
"web/app/components/base/audio-gallery/AudioPlayer.tsx": {
"ts/no-explicit-any": {
"count": 2
@@ -871,9 +848,6 @@
}
},
"web/app/components/base/chat/chat-with-history/header/index.tsx": {
"no-restricted-imports": {
"count": 1
},
"ts/no-explicit-any": {
"count": 2
}
@@ -991,11 +965,6 @@
"count": 7
}
},
"web/app/components/base/chat/embedded-chatbot/header/index.tsx": {
"no-restricted-imports": {
"count": 1
}
},
"web/app/components/base/chat/embedded-chatbot/hooks.tsx": {
"react-hooks-extra/no-direct-set-state-in-use-effect": {
"count": 3
@@ -1037,11 +1006,6 @@
"count": 1
}
},
"web/app/components/base/copy-feedback/index.tsx": {
"no-restricted-imports": {
"count": 1
}
},
"web/app/components/base/date-and-time-picker/hooks.ts": {
"react/no-unnecessary-use-prefix": {
"count": 2
@@ -1093,11 +1057,6 @@
"count": 1
}
},
"web/app/components/base/features/new-feature-panel/annotation-reply/annotation-ctrl-button.tsx": {
"no-restricted-imports": {
"count": 1
}
},
"web/app/components/base/features/new-feature-panel/annotation-reply/config-param-modal.tsx": {
"no-restricted-imports": {
"count": 1
@@ -1123,15 +1082,7 @@
"count": 2
}
},
"web/app/components/base/features/new-feature-panel/feature-bar.tsx": {
"no-restricted-imports": {
"count": 1
}
},
"web/app/components/base/features/new-feature-panel/feature-card.tsx": {
"no-restricted-imports": {
"count": 1
},
"ts/no-explicit-any": {
"count": 5
}
@@ -1168,9 +1119,6 @@
}
},
"web/app/components/base/file-uploader/file-list-in-log.tsx": {
"no-restricted-imports": {
"count": 1
},
"react/no-missing-key": {
"count": 1
}
@@ -1193,11 +1141,6 @@
"count": 2
}
},
"web/app/components/base/file-uploader/pdf-preview.tsx": {
"no-restricted-imports": {
"count": 1
}
},
"web/app/components/base/file-uploader/store.tsx": {
"react-refresh/only-export-components": {
"count": 4
@@ -1593,15 +1536,7 @@
"count": 1
}
},
"web/app/components/base/image-uploader/image-list.tsx": {
"no-restricted-imports": {
"count": 1
}
},
"web/app/components/base/image-uploader/image-preview.tsx": {
"no-restricted-imports": {
"count": 1
},
"ts/no-explicit-any": {
"count": 1
}
@@ -1764,9 +1699,6 @@
}
},
"web/app/components/base/new-audio-button/index.tsx": {
"no-restricted-imports": {
"count": 1
},
"ts/no-explicit-any": {
"count": 1
}
@@ -1949,11 +1881,6 @@
"count": 1
}
},
"web/app/components/base/qrcode/index.tsx": {
"no-restricted-imports": {
"count": 1
}
},
"web/app/components/base/radio-card/index.stories.tsx": {
"ts/no-explicit-any": {
"count": 1
@@ -2262,11 +2189,6 @@
"count": 1
}
},
"web/app/components/datasets/create/step-two/components/general-chunking-options.tsx": {
"no-restricted-imports": {
"count": 1
}
},
"web/app/components/datasets/create/step-two/components/index.ts": {
"no-barrel-files/no-barrel-files": {
"count": 5
@@ -2374,11 +2296,6 @@
"count": 1
}
},
"web/app/components/datasets/documents/components/document-list/components/document-table-row.tsx": {
"no-restricted-imports": {
"count": 1
}
},
"web/app/components/datasets/documents/components/document-list/components/index.ts": {
"no-barrel-files/no-barrel-files": {
"count": 2
@@ -2553,11 +2470,6 @@
"count": 1
}
},
"web/app/components/datasets/documents/detail/metadata/components/doc-type-selector.tsx": {
"no-restricted-imports": {
"count": 1
}
},
"web/app/components/datasets/documents/detail/metadata/components/metadata-field-list.tsx": {
"ts/no-non-null-asserted-optional-chain": {
"count": 1
@@ -2619,11 +2531,6 @@
"count": 1
}
},
"web/app/components/datasets/hit-testing/components/query-input/textarea.tsx": {
"no-restricted-imports": {
"count": 1
}
},
"web/app/components/datasets/hit-testing/components/result-item-external.tsx": {
"no-restricted-imports": {
"count": 1
@@ -2639,21 +2546,11 @@
"count": 1
}
},
"web/app/components/datasets/list/dataset-card/components/dataset-card-footer.tsx": {
"no-restricted-imports": {
"count": 1
}
},
"web/app/components/datasets/list/dataset-card/hooks/use-dataset-card-state.ts": {
"react/set-state-in-effect": {
"count": 1
}
},
"web/app/components/datasets/metadata/edit-metadata-batch/edited-beacon.tsx": {
"no-restricted-imports": {
"count": 1
}
},
"web/app/components/datasets/metadata/edit-metadata-batch/input-combined.tsx": {
"ts/no-explicit-any": {
"count": 2
@@ -2722,11 +2619,6 @@
"count": 1
}
},
"web/app/components/datasets/settings/index-method/keyword-number.tsx": {
"no-restricted-imports": {
"count": 1
}
},
"web/app/components/datasets/settings/summary-index-setting.tsx": {
"no-restricted-imports": {
"count": 1
@@ -2790,11 +2682,6 @@
"count": 1
}
},
"web/app/components/explore/try-app/app/chat.tsx": {
"no-restricted-imports": {
"count": 1
}
},
"web/app/components/explore/try-app/index.tsx": {
"no-restricted-imports": {
"count": 1
@@ -3043,15 +2930,7 @@
"count": 2
}
},
"web/app/components/header/account-setting/model-provider-page/model-selector/feature-icon.tsx": {
"no-restricted-imports": {
"count": 1
}
},
"web/app/components/header/account-setting/model-provider-page/provider-added-card/cooldown-timer.tsx": {
"no-restricted-imports": {
"count": 1
},
"react/set-state-in-effect": {
"count": 2
}
@@ -3067,9 +2946,6 @@
}
},
"web/app/components/header/account-setting/model-provider-page/provider-added-card/model-load-balancing-configs.tsx": {
"no-restricted-imports": {
"count": 1
},
"ts/no-explicit-any": {
"count": 5
}
@@ -3085,11 +2961,6 @@
"count": 3
}
},
"web/app/components/header/account-setting/model-provider-page/provider-added-card/priority-use-tip.tsx": {
"no-restricted-imports": {
"count": 1
}
},
"web/app/components/header/account-setting/model-provider-page/utils.ts": {
"no-barrel-files/no-barrel-files": {
"count": 2
@@ -3182,20 +3053,12 @@
"count": 1
}
},
"web/app/components/plugins/plugin-auth/authorize/index.tsx": {
"no-restricted-imports": {
"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/item.tsx": {
"no-restricted-imports": {
"count": 1
},
"ts/no-explicit-any": {
"count": 1
}
@@ -3290,9 +3153,6 @@
}
},
"web/app/components/plugins/plugin-detail-panel/multiple-tool-selector/index.tsx": {
"no-restricted-imports": {
"count": 1
},
"ts/no-explicit-any": {
"count": 1
}
@@ -3325,9 +3185,6 @@
"web/app/components/plugins/plugin-detail-panel/subscription-list/create/index.tsx": {
"no-barrel-files/no-barrel-files": {
"count": 3
},
"no-restricted-imports": {
"count": 1
}
},
"web/app/components/plugins/plugin-detail-panel/subscription-list/create/oauth-client.tsx": {
@@ -3363,11 +3220,6 @@
"count": 2
}
},
"web/app/components/plugins/plugin-detail-panel/subscription-list/list-view.tsx": {
"no-restricted-imports": {
"count": 1
}
},
"web/app/components/plugins/plugin-detail-panel/subscription-list/log-viewer.tsx": {
"erasable-syntax-only/enums": {
"count": 1
@@ -3376,11 +3228,6 @@
"count": 2
}
},
"web/app/components/plugins/plugin-detail-panel/subscription-list/selector-view.tsx": {
"no-restricted-imports": {
"count": 1
}
},
"web/app/components/plugins/plugin-detail-panel/subscription-list/subscription-card.tsx": {
"no-restricted-imports": {
"count": 1
@@ -3403,7 +3250,7 @@
},
"web/app/components/plugins/plugin-detail-panel/tool-selector/components/tool-item.tsx": {
"no-restricted-imports": {
"count": 2
"count": 1
}
},
"web/app/components/plugins/plugin-detail-panel/tool-selector/hooks/index.ts": {
@@ -3444,11 +3291,6 @@
"count": 2
}
},
"web/app/components/plugins/plugin-page/index.tsx": {
"no-restricted-imports": {
"count": 1
}
},
"web/app/components/plugins/plugin-page/install-plugin-dropdown.tsx": {
"react/set-state-in-effect": {
"count": 2
@@ -4090,11 +3932,6 @@
"count": 1
}
},
"web/app/components/workflow/nodes/_base/components/config-vision.tsx": {
"no-restricted-imports": {
"count": 1
}
},
"web/app/components/workflow/nodes/_base/components/editor/code-editor/editor-support-vars.tsx": {
"react/set-state-in-effect": {
"count": 1
@@ -4143,9 +3980,6 @@
}
},
"web/app/components/workflow/nodes/_base/components/input-support-select-var.tsx": {
"no-restricted-imports": {
"count": 1
},
"ts/no-explicit-any": {
"count": 1
}
@@ -4209,9 +4043,6 @@
}
},
"web/app/components/workflow/nodes/_base/components/setting-item.tsx": {
"no-restricted-imports": {
"count": 1
},
"ts/no-explicit-any": {
"count": 1
}
@@ -4226,11 +4057,6 @@
"count": 8
}
},
"web/app/components/workflow/nodes/_base/components/variable/object-child-tree-panel/picker/field.tsx": {
"no-restricted-imports": {
"count": 1
}
},
"web/app/components/workflow/nodes/_base/components/variable/output-var-list.tsx": {
"ts/no-non-null-asserted-optional-chain": {
"count": 1
@@ -4544,11 +4370,6 @@
"count": 1
}
},
"web/app/components/workflow/nodes/human-input/components/delivery-method/index.tsx": {
"no-restricted-imports": {
"count": 1
}
},
"web/app/components/workflow/nodes/human-input/components/delivery-method/method-item.tsx": {
"no-restricted-imports": {
"count": 1
@@ -4664,11 +4485,6 @@
"count": 1
}
},
"web/app/components/workflow/nodes/knowledge-base/components/index-method.tsx": {
"no-restricted-imports": {
"count": 1
}
},
"web/app/components/workflow/nodes/knowledge-base/components/retrieval-setting/hooks.tsx": {
"ts/no-explicit-any": {
"count": 4
@@ -4806,11 +4622,6 @@
"count": 2
}
},
"web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/prompt-editor.tsx": {
"no-restricted-imports": {
"count": 1
}
},
"web/app/components/workflow/nodes/llm/components/json-schema-config-modal/visual-editor/context.tsx": {
"react-refresh/only-export-components": {
"count": 2
@@ -5144,11 +4955,6 @@
"count": 7
}
},
"web/app/components/workflow/nodes/trigger-schedule/components/monthly-days-selector.tsx": {
"no-restricted-imports": {
"count": 1
}
},
"web/app/components/workflow/nodes/trigger-schedule/default.ts": {
"regexp/no-unused-capturing-group": {
"count": 2
@@ -5286,11 +5092,6 @@
"count": 12
}
},
"web/app/components/workflow/panel/debug-and-preview/index.tsx": {
"no-restricted-imports": {
"count": 1
}
},
"web/app/components/workflow/panel/env-panel/variable-modal.tsx": {
"no-restricted-imports": {
"count": 1
@@ -5515,9 +5316,6 @@
}
},
"web/app/components/workflow/variable-inspect/group.tsx": {
"no-restricted-imports": {
"count": 1
},
"ts/no-explicit-any": {
"count": 2
}
@@ -5541,17 +5339,11 @@
}
},
"web/app/components/workflow/variable-inspect/right.tsx": {
"no-restricted-imports": {
"count": 1
},
"ts/no-explicit-any": {
"count": 3
}
},
"web/app/components/workflow/variable-inspect/trigger.tsx": {
"no-restricted-imports": {
"count": 1
},
"ts/no-explicit-any": {
"count": 1
}

View File

@@ -3,12 +3,12 @@ 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 { Switch } from '@langgenius/dify-ui/switch'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { useBoolean } from 'ahooks'
import * as React from 'react'
import { useCallback, useState } from 'react'
import { useTranslation } from 'react-i18next'
import Divider from '@/app/components/base/divider'
import Tooltip from '@/app/components/base/tooltip'
import Indicator from '@/app/components/header/indicator'
import ProviderConfigModal from './provider-config-modal'
import ProviderPanel from './provider-panel'
@@ -338,10 +338,13 @@ const ConfigPopup: FC<PopupProps> = ({
<>
{providerAllNotConfigured
? (
<Tooltip
popupContent={t(`${I18N_PREFIX}.disabledTip`, { ns: 'app' })}
>
{switchContent}
<Tooltip>
<TooltipTrigger
render={switchContent}
/>
<TooltipContent>
{t(`${I18N_PREFIX}.disabledTip`, { ns: 'app' })}
</TooltipContent>
</Tooltip>
)
: switchContent}

View File

@@ -5,6 +5,7 @@ import type {
ModelProvider,
} from '@/app/components/header/account-setting/model-provider-page/declarations'
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { createMockProviderContextValue } from '@/__mocks__/provider-context'
import {
ConfigurationMethodEnum,
@@ -86,12 +87,6 @@ vi.mock('@/app/components/header/account-setting/model-provider-page/model-name'
),
}))
vi.mock('@/app/components/base/tooltip', () => ({
default: ({ children, popupContent }: { children: ReactNode, popupContent: string }) => (
<div data-testid="tooltip" data-content={popupContent}>{children}</div>
),
}))
const createModelAndParameter = (overrides: Partial<ModelAndParameter> = {}): ModelAndParameter => ({
id: 'model-1',
model: 'gpt-3.5-turbo',
@@ -385,14 +380,15 @@ describe('ModelParameterTrigger', () => {
expect(screen.getByText('common.modelProvider.selectModel')).toBeInTheDocument()
})
it('should render configured model id and incompatible tooltip when model is missing from the provider list', () => {
it('should render configured model id and incompatible tooltip when model is missing from the provider list', async () => {
renderComponent()
expect(screen.getByText('gpt-3.5-turbo')).toBeInTheDocument()
expect(screen.getByTestId('tooltip')).toHaveAttribute('data-content', 'common.modelProvider.selector.incompatibleTip')
await userEvent.hover(screen.getByLabelText('common.modelProvider.selector.incompatibleTip'))
expect(await screen.findByText('common.modelProvider.selector.incompatibleTip')).toBeInTheDocument()
})
it('should render configure required tooltip for no-configure status', () => {
it('should render configure required tooltip for no-configure status', async () => {
const { unmount } = renderComponent()
const triggerContent = capturedModalProps?.renderTrigger({
open: false,
@@ -403,10 +399,11 @@ describe('ModelParameterTrigger', () => {
unmount()
render(<>{triggerContent}</>)
expect(screen.getByTestId('tooltip')).toHaveAttribute('data-content', 'common.modelProvider.selector.configureRequired')
await userEvent.hover(screen.getByLabelText('common.modelProvider.selector.configureRequired'))
expect(await screen.findByText('common.modelProvider.selector.configureRequired')).toBeInTheDocument()
})
it('should render disabled tooltip for disabled status', () => {
it('should render disabled tooltip for disabled status', async () => {
const { unmount } = renderComponent()
const triggerContent = capturedModalProps?.renderTrigger({
open: false,
@@ -417,7 +414,8 @@ describe('ModelParameterTrigger', () => {
unmount()
render(<>{triggerContent}</>)
expect(screen.getByTestId('tooltip')).toHaveAttribute('data-content', 'common.modelProvider.selector.disabled')
await userEvent.hover(screen.getByLabelText('common.modelProvider.selector.disabled'))
expect(await screen.findByText('common.modelProvider.selector.disabled')).toBeInTheDocument()
})
it('should apply expanded and warning styles when the trigger is open for a non-active status', () => {

View File

@@ -1,9 +1,9 @@
import type { FC } from 'react'
import type { ModelAndParameter } from '../types'
import type { FormValue } from '@/app/components/header/account-setting/model-provider-page/declarations'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { memo } from 'react'
import { useTranslation } from 'react-i18next'
import Tooltip from '@/app/components/base/tooltip'
import {
DERIVED_MODEL_STATUS_BADGE_I18N,
DERIVED_MODEL_STATUS_TOOLTIP_I18N,
@@ -132,8 +132,18 @@ const ModelParameterTrigger: FC<ModelParameterTriggerProps> = ({
<span className={`i-ri-arrow-down-s-line h-3 w-3 ${isEmpty ? 'text-text-accent' : 'text-text-tertiary'}`} />
{
!isEmpty && !isActive && statusLabelKey && (
<Tooltip popupContent={t((statusTooltipKey || statusLabelKey) as 'modelProvider.selector.incompatible', { ns: 'common' })}>
<span className="i-custom-vender-line-alertsAndFeedback-alert-triangle h-4 w-4 text-[#F79009]" />
<Tooltip>
<TooltipTrigger
render={(
<span
aria-label={t((statusTooltipKey || statusLabelKey) as 'modelProvider.selector.incompatible', { ns: 'common' })}
className="i-custom-vender-line-alertsAndFeedback-alert-triangle h-4 w-4 text-[#F79009]"
/>
)}
/>
<TooltipContent>
{t((statusTooltipKey || statusLabelKey) as 'modelProvider.selector.incompatible', { ns: 'common' })}
</TooltipContent>
</Tooltip>
)
}

View File

@@ -5,6 +5,7 @@ import type { VisionFile, VisionSettings } from '@/types/app'
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { Select, SelectContent, SelectItem, SelectItemIndicator, SelectItemText, SelectTrigger } from '@langgenius/dify-ui/select'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import {
RiArrowDownSLine,
RiArrowRightSLine,
@@ -19,7 +20,6 @@ import FeatureBar from '@/app/components/base/features/new-feature-panel/feature
import TextGenerationImageUploader from '@/app/components/base/image-uploader/text-generation-image-uploader'
import Input from '@/app/components/base/input'
import Textarea from '@/app/components/base/textarea'
import Tooltip from '@/app/components/base/tooltip'
import BoolInput from '@/app/components/workflow/nodes/_base/components/before-run-form/bool-input'
import ConfigContext from '@/context/debug-configuration'
import { AppModeEnum, ModelModeType } from '@/types/app'
@@ -224,16 +224,23 @@ const PromptValuePanel: FC<IPromptValuePanelProps> = ({
<div className="flex justify-between border-t border-divider-subtle p-4 pt-3">
<Button className="w-[72px]" disabled={readonly} onClick={onClear}>{t('operation.clear', { ns: 'common' })}</Button>
{canNotRun && (
<Tooltip popupContent={t('otherError.promptNoBeEmpty', { ns: 'appDebug' })}>
<Button
variant="primary"
disabled={canNotRun || readonly}
onClick={() => onSend?.()}
className="w-[96px]"
>
<RiPlayLargeFill className="mr-0.5 h-4 w-4 shrink-0" aria-hidden="true" />
{t('inputs.run', { ns: 'appDebug' })}
</Button>
<Tooltip>
<TooltipTrigger
render={(
<Button
variant="primary"
disabled={canNotRun || readonly}
onClick={() => onSend?.()}
className="w-[96px]"
>
<RiPlayLargeFill className="mr-0.5 h-4 w-4 shrink-0" aria-hidden="true" />
{t('inputs.run', { ns: 'appDebug' })}
</Button>
)}
/>
<TooltipContent>
{t('otherError.promptNoBeEmpty', { ns: 'appDebug' })}
</TooltipContent>
</Tooltip>
)}
{!canNotRun && (

View File

@@ -10,6 +10,7 @@ import {
} from '@heroicons/react/24/outline'
import { cn } from '@langgenius/dify-ui/cn'
import { toast } from '@langgenius/dify-ui/toast'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { RiCloseLine, RiEditFill } from '@remixicon/react'
import dayjs from 'dayjs'
import timezone from 'dayjs/plugin/timezone'
@@ -30,7 +31,6 @@ import CopyIcon from '@/app/components/base/copy-icon'
import Drawer from '@/app/components/base/drawer'
import Loading from '@/app/components/base/loading'
import MessageLogModal from '@/app/components/base/message-log-modal'
import Tooltip from '@/app/components/base/tooltip'
import { WorkflowContextProvider } from '@/app/components/workflow/context'
import { useAppContext } from '@/context/app-context'
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
@@ -409,10 +409,15 @@ function DetailPanel({ detail, onFeedback }: IDetailPanel) {
<div className="mb-0.5 system-xs-semibold-uppercase text-text-primary">{isChatMode ? t('detail.conversationId', { ns: 'appLog' }) : t('detail.time', { ns: 'appLog' })}</div>
{isChatMode && (
<div className="flex items-center system-2xs-regular-uppercase text-text-secondary">
<Tooltip
popupContent={detail.id}
>
<div className="truncate">{detail.id}</div>
<Tooltip>
<TooltipTrigger
render={(
<div className="truncate">{detail.id}</div>
)}
/>
<TooltipContent>
{detail.id}
</TooltipContent>
</Tooltip>
<CopyIcon content={detail.id} />
</div>
@@ -769,18 +774,20 @@ const ConversationList: FC<IConversationList> = ({ logs, appDetail, onRefresh })
// Annotated data needs to be highlighted
const renderTdValue = (value: string | number | null, isEmptyStyle: boolean, isHighlight = false, annotation?: LogAnnotation) => {
return (
<Tooltip
popupContent={(
<Tooltip>
<TooltipTrigger
render={(
<div className={cn(isEmptyStyle ? 'text-text-quaternary' : 'text-text-secondary', !isHighlight ? '' : 'bg-orange-100', 'overflow-hidden system-sm-regular text-ellipsis whitespace-nowrap')}>
{value || '-'}
</div>
)}
/>
<TooltipContent className={(isHighlight && !isChatMode) ? '' : 'hidden!'}>
<span className="inline-flex items-center text-xs text-text-tertiary">
<RiEditFill className="mr-1 h-3 w-3" />
{`${t('detail.annotationTip', { ns: 'appLog', user: annotation?.account?.name })} ${formatTime(annotation?.created_at || dayjs().unix(), 'MM-DD hh:mm A')}`}
</span>
)}
popupClassName={(isHighlight && !isChatMode) ? '' : 'hidden!'}
>
<div className={cn(isEmptyStyle ? 'text-text-quaternary' : 'text-text-secondary', !isHighlight ? '' : 'bg-orange-100', 'overflow-hidden system-sm-regular text-ellipsis whitespace-nowrap')}>
{value || '-'}
</div>
</TooltipContent>
</Tooltip>
)
}

View File

@@ -1,5 +1,6 @@
import type { SiteInfo } from '@/models/share'
import { cn } from '@langgenius/dify-ui/cn'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import {
RiClipboardFill,
RiClipboardLine,
@@ -11,7 +12,6 @@ import { useTranslation } from 'react-i18next'
import ActionButton from '@/app/components/base/action-button'
import { useThemeContext } from '@/app/components/base/chat/embedded-chatbot/theme/theme-context'
import Modal from '@/app/components/base/modal'
import Tooltip from '@/app/components/base/tooltip'
import { IS_CE_EDITION } from '@/config'
import { useAppContext } from '@/context/app-context'
import { basePath } from '@/utils/var'
@@ -174,21 +174,24 @@ const Embedded = ({ siteInfo, isShow, onClose, appBaseUrl, accessToken, classNam
<div className="shrink-0 grow system-sm-medium text-text-secondary">
{t(`${prefixEmbedded}.${option}`, { ns: 'appOverview' })}
</div>
<Tooltip
popupContent={
(isCopied[option]
<Tooltip>
<TooltipTrigger
render={(
<ActionButton>
<div
onClick={onClickCopy}
>
{isCopied[option] && <RiClipboardFill className="h-4 w-4" />}
{!isCopied[option] && <RiClipboardLine className="h-4 w-4" />}
</div>
</ActionButton>
)}
/>
<TooltipContent>
{(isCopied[option]
? t(`${prefixEmbedded}.copied`, { ns: 'appOverview' })
: t(`${prefixEmbedded}.copy`, { ns: 'appOverview' })) || ''
}
>
<ActionButton>
<div
onClick={onClickCopy}
>
{isCopied[option] && <RiClipboardFill className="h-4 w-4" />}
{!isCopied[option] && <RiClipboardLine className="h-4 w-4" />}
</div>
</ActionButton>
: t(`${prefixEmbedded}.copy`, { ns: 'appOverview' })) || ''}
</TooltipContent>
</Tooltip>
</div>
<div className="flex w-full items-start justify-start gap-2 overflow-x-auto p-3">

View File

@@ -1,9 +1,9 @@
'use client'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { t } from 'i18next'
import { useState } from 'react'
import { AudioPlayerManager } from '@/app/components/base/audio-btn/audio.player.manager'
import Loading from '@/app/components/base/loading'
import Tooltip from '@/app/components/base/tooltip'
import { useParams, usePathname } from '@/next/navigation'
import s from './style.module.css'
@@ -82,27 +82,34 @@ const AudioBtn = ({
return (
<div className={`inline-flex items-center justify-center ${(audioState === 'loading' || audioState === 'playing') ? 'mr-1' : className}`}>
<Tooltip
popupContent={tooltipContent}
>
<button
type="button"
disabled={audioState === 'loading'}
className={`box-border flex h-6 w-6 cursor-pointer items-center justify-center ${isAudition ? 'p-0.5' : 'rounded-md bg-white p-0'}`}
onClick={handleToggle}
>
{audioState === 'loading'
? (
<div className="flex h-full w-full items-center justify-center rounded-md">
<Loading />
</div>
)
: (
<div className="flex h-full w-full items-center justify-center rounded-md hover:bg-gray-50">
<div className={`h-4 w-4 ${(audioState === 'playing') ? s.pauseIcon : s.playIcon}`}></div>
</div>
)}
</button>
<Tooltip>
<TooltipTrigger
render={(
<span className="inline-flex">
<button
type="button"
disabled={audioState === 'loading'}
className={`box-border flex h-6 w-6 cursor-pointer items-center justify-center ${isAudition ? 'p-0.5' : 'rounded-md bg-white p-0'}`}
onClick={handleToggle}
>
{audioState === 'loading'
? (
<div className="flex h-full w-full items-center justify-center rounded-md">
<Loading />
</div>
)
: (
<div className="flex h-full w-full items-center justify-center rounded-md hover:bg-gray-50">
<div className={`h-4 w-4 ${(audioState === 'playing') ? s.pauseIcon : s.playIcon}`}></div>
</div>
)}
</button>
</span>
)}
/>
<TooltipContent>
{tooltipContent}
</TooltipContent>
</Tooltip>
</div>
)

View File

@@ -9,6 +9,7 @@ import {
AlertDialogTitle,
} from '@langgenius/dify-ui/alert-dialog'
import { cn } from '@langgenius/dify-ui/cn'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import {
RiEditBoxLine,
RiLayoutRight2Line,
@@ -20,7 +21,6 @@ import ActionButton, { ActionButtonState } from '@/app/components/base/action-bu
import AppIcon from '@/app/components/base/app-icon'
import ViewFormDropdown from '@/app/components/base/chat/chat-with-history/inputs-form/view-form-dropdown'
import RenameModal from '@/app/components/base/chat/chat-with-history/sidebar/rename-modal'
import Tooltip from '@/app/components/base/tooltip'
import {
useChatWithHistoryContext,
} from '../context'
@@ -117,31 +117,41 @@ const Header = () => {
<div className="h-[14px] w-px bg-divider-regular"></div>
</div>
{isSidebarCollapsed && (
<Tooltip
disabled={!!currentConversationId}
popupContent={t('chat.newChatTip', { ns: 'share' })}
>
<div>
<ActionButton
size="l"
state={(!currentConversationId || isResponding) ? ActionButtonState.Disabled : ActionButtonState.Default}
disabled={!currentConversationId || isResponding}
onClick={handleNewConversation}
>
<RiEditBoxLine className="h-[18px] w-[18px]" />
</ActionButton>
</div>
<Tooltip>
<TooltipTrigger
disabled={!!currentConversationId}
render={(
<div>
<ActionButton
size="l"
state={(!currentConversationId || isResponding) ? ActionButtonState.Disabled : ActionButtonState.Default}
disabled={!currentConversationId || isResponding}
onClick={handleNewConversation}
>
<RiEditBoxLine className="h-[18px] w-[18px]" />
</ActionButton>
</div>
)}
/>
<TooltipContent>
{t('chat.newChatTip', { ns: 'share' })}
</TooltipContent>
</Tooltip>
)}
</div>
<div className="flex items-center gap-1">
{currentConversationId && (
<Tooltip
popupContent={t('chat.resetChat', { ns: 'share' })}
>
<ActionButton size="l" onClick={handleNewConversation}>
<RiResetLeftLine className="h-[18px] w-[18px]" />
</ActionButton>
<Tooltip>
<TooltipTrigger
render={(
<ActionButton size="l" onClick={handleNewConversation}>
<RiResetLeftLine className="h-[18px] w-[18px]" />
</ActionButton>
)}
/>
<TooltipContent>
{t('chat.resetChat', { ns: 'share' })}
</TooltipContent>
</Tooltip>
)}
{currentConversationId && inputsForms.length > 0 && (

View File

@@ -1,6 +1,7 @@
import type { FC } from 'react'
import type { Theme } from '../theme/theme-context'
import { cn } from '@langgenius/dify-ui/cn'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { useSuspenseQuery } from '@tanstack/react-query'
import * as React from 'react'
import { useCallback, useEffect, useState } from 'react'
@@ -9,7 +10,6 @@ import ActionButton from '@/app/components/base/action-button'
import ViewFormDropdown from '@/app/components/base/chat/embedded-chatbot/inputs-form/view-form-dropdown'
import Divider from '@/app/components/base/divider'
import DifyLogo from '@/app/components/base/logo/dify-logo'
import Tooltip from '@/app/components/base/tooltip'
import { systemFeaturesQueryOptions } from '@/service/system-features'
import { isClient } from '@/utils/client'
import {
@@ -111,26 +111,36 @@ const Header: FC<IHeaderProps> = ({
)}
{
showToggleExpandButton && (
<Tooltip
popupContent={expanded ? t('chat.collapse', { ns: 'share' }) : t('chat.expand', { ns: 'share' })}
>
<ActionButton size="l" onClick={handleToggleExpand} data-testid="expand-button">
{
expanded
? <div className="i-ri-collapse-diagonal-2-line h-[18px] w-[18px]" />
: <div className="i-ri-expand-diagonal-2-line h-[18px] w-[18px]" />
}
</ActionButton>
<Tooltip>
<TooltipTrigger
render={(
<ActionButton size="l" onClick={handleToggleExpand} data-testid="expand-button">
{
expanded
? <div className="i-ri-collapse-diagonal-2-line h-[18px] w-[18px]" />
: <div className="i-ri-expand-diagonal-2-line h-[18px] w-[18px]" />
}
</ActionButton>
)}
/>
<TooltipContent>
{expanded ? t('chat.collapse', { ns: 'share' }) : t('chat.expand', { ns: 'share' })}
</TooltipContent>
</Tooltip>
)
}
{currentConversationId && allowResetChat && (
<Tooltip
popupContent={t('chat.resetChat', { ns: 'share' })}
>
<ActionButton size="l" onClick={onCreateNewChat} data-testid="reset-chat-button">
<div className="i-ri-reset-left-line h-[18px] w-[18px]" />
</ActionButton>
<Tooltip>
<TooltipTrigger
render={(
<ActionButton size="l" onClick={onCreateNewChat} data-testid="reset-chat-button">
<div className="i-ri-reset-left-line h-[18px] w-[18px]" />
</ActionButton>
)}
/>
<TooltipContent>
{t('chat.resetChat', { ns: 'share' })}
</TooltipContent>
</Tooltip>
)}
{currentConversationId && inputsForms.length > 0 && !allInputsHidden && (
@@ -158,26 +168,36 @@ const Header: FC<IHeaderProps> = ({
<div className="flex items-center gap-1">
{
showToggleExpandButton && (
<Tooltip
popupContent={expanded ? t('chat.collapse', { ns: 'share' }) : t('chat.expand', { ns: 'share' })}
>
<ActionButton size="l" onClick={handleToggleExpand} data-testid="mobile-expand-button">
{
expanded
? <div className={cn('i-ri-collapse-diagonal-2-line h-[18px] w-[18px]', theme?.colorPathOnHeader)} />
: <div className={cn('i-ri-expand-diagonal-2-line h-[18px] w-[18px]', theme?.colorPathOnHeader)} />
}
</ActionButton>
<Tooltip>
<TooltipTrigger
render={(
<ActionButton size="l" onClick={handleToggleExpand} data-testid="mobile-expand-button">
{
expanded
? <div className={cn('i-ri-collapse-diagonal-2-line h-[18px] w-[18px]', theme?.colorPathOnHeader)} />
: <div className={cn('i-ri-expand-diagonal-2-line h-[18px] w-[18px]', theme?.colorPathOnHeader)} />
}
</ActionButton>
)}
/>
<TooltipContent>
{expanded ? t('chat.collapse', { ns: 'share' }) : t('chat.expand', { ns: 'share' })}
</TooltipContent>
</Tooltip>
)
}
{currentConversationId && allowResetChat && (
<Tooltip
popupContent={t('chat.resetChat', { ns: 'share' })}
>
<ActionButton size="l" onClick={onCreateNewChat} data-testid="mobile-reset-chat-button">
<div className={cn('i-ri-reset-left-line h-[18px] w-[18px]', theme?.colorPathOnHeader)} />
</ActionButton>
<Tooltip>
<TooltipTrigger
render={(
<ActionButton size="l" onClick={onCreateNewChat} data-testid="mobile-reset-chat-button">
<div className={cn('i-ri-reset-left-line h-[18px] w-[18px]', theme?.colorPathOnHeader)} />
</ActionButton>
)}
/>
<TooltipContent>
{t('chat.resetChat', { ns: 'share' })}
</TooltipContent>
</Tooltip>
)}
{currentConversationId && inputsForms.length > 0 && !allInputsHidden && (

View File

@@ -1,4 +1,5 @@
'use client'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import {
RiClipboardFill,
RiClipboardLine,
@@ -6,7 +7,6 @@ import {
import { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import ActionButton from '@/app/components/base/action-button'
import Tooltip from '@/app/components/base/tooltip'
import { useClipboard } from '@/hooks/use-clipboard'
import copyStyle from './style.module.css'
@@ -35,15 +35,20 @@ const CopyFeedback = ({ content }: Props) => {
}, [copy, content])
return (
<Tooltip
popupContent={safeText}
>
<ActionButton>
<div onClick={handleCopy}>
{copied && <RiClipboardFill className="h-4 w-4" />}
{!copied && <RiClipboardLine className="h-4 w-4" />}
</div>
</ActionButton>
<Tooltip>
<TooltipTrigger
render={(
<ActionButton>
<div onClick={handleCopy}>
{copied && <RiClipboardFill className="h-4 w-4" />}
{!copied && <RiClipboardLine className="h-4 w-4" />}
</div>
</ActionButton>
)}
/>
<TooltipContent>
{safeText}
</TooltipContent>
</Tooltip>
)
}
@@ -65,18 +70,23 @@ export const CopyFeedbackNew = ({ content, className }: Pick<Props, 'className'
}, [copy, content])
return (
<Tooltip
popupContent={safeText}
>
<div
className={`h-8 w-8 cursor-pointer rounded-lg hover:bg-components-button-ghost-bg-hover ${className ?? ''}`}
>
<div
onClick={handleCopy}
className={`h-full w-full ${copyStyle.copyIcon} ${copied ? copyStyle.copied : ''}`}
>
</div>
</div>
<Tooltip>
<TooltipTrigger
render={(
<div
className={`h-8 w-8 cursor-pointer rounded-lg hover:bg-components-button-ghost-bg-hover ${className ?? ''}`}
>
<div
onClick={handleCopy}
className={`h-full w-full ${copyStyle.copyIcon} ${copied ? copyStyle.copied : ''}`}
>
</div>
</div>
)}
/>
<TooltipContent>
{safeText}
</TooltipContent>
</Tooltip>
)
}

View File

@@ -1,11 +1,11 @@
'use client'
import type { FC } from 'react'
import { toast } from '@langgenius/dify-ui/toast'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { RiEditLine, RiFileEditLine } from '@remixicon/react'
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import ActionButton from '@/app/components/base/action-button'
import Tooltip from '@/app/components/base/tooltip'
import { useModalContext } from '@/context/modal-context'
import { useProviderContext } from '@/context/provider-context'
import { addAnnotation } from '@/service/annotation'
@@ -40,17 +40,31 @@ const AnnotationCtrlButton: FC<Props> = ({ cached, query, answer, appId, message
return (
<>
{cached && (
<Tooltip popupContent={t('feature.annotation.edit', { ns: 'appDebug' })}>
<ActionButton onClick={onEdit}>
<RiEditLine className="h-4 w-4" />
</ActionButton>
<Tooltip>
<TooltipTrigger
render={(
<ActionButton onClick={onEdit}>
<RiEditLine className="h-4 w-4" />
</ActionButton>
)}
/>
<TooltipContent>
{t('feature.annotation.edit', { ns: 'appDebug' })}
</TooltipContent>
</Tooltip>
)}
{!cached && answer && (
<Tooltip popupContent={t('feature.annotation.add', { ns: 'appDebug' })}>
<ActionButton onClick={handleAdd}>
<RiFileEditLine className="h-4 w-4" />
</ActionButton>
<Tooltip>
<TooltipTrigger
render={(
<ActionButton onClick={handleAdd}>
<RiFileEditLine className="h-4 w-4" />
</ActionButton>
)}
/>
<TooltipContent>
{t('feature.annotation.add', { ns: 'appDebug' })}
</TooltipContent>
</Tooltip>
)}
</>

View File

@@ -1,5 +1,6 @@
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { RiApps2AddLine, RiArrowRightLine, RiSparklingFill } from '@remixicon/react'
import * as React from 'react'
import { useMemo, useState } from 'react'
@@ -7,7 +8,6 @@ import { useTranslation } from 'react-i18next'
import { useFeatures } from '@/app/components/base/features/hooks'
import VoiceSettings from '@/app/components/base/features/new-feature-panel/text-to-speech/voice-settings'
import { Citations, ContentModeration, FolderUpload, LoveMessage, MessageFast, Microphone01, TextToAudio, VirtualAssistant } from '@/app/components/base/icons/src/vender/features'
import Tooltip from '@/app/components/base/tooltip'
type Props = {
isChatMode?: boolean
@@ -51,86 +51,131 @@ const FeatureBar = ({
<div className="flex items-center gap-2">
<div className="flex shrink-0 items-center gap-0.5">
{!!features.moreLikeThis?.enabled && (
<Tooltip
popupContent={t('feature.moreLikeThis.title', { ns: 'appDebug' })}
>
<div className="shrink-0 rounded-lg border-[0.5px] border-divider-subtle bg-util-colors-blue-light-blue-light-500 p-1 shadow-xs">
<RiSparklingFill className="h-3.5 w-3.5 text-text-primary-on-surface" />
</div>
<Tooltip>
<TooltipTrigger
render={(
<div className="shrink-0 rounded-lg border-[0.5px] border-divider-subtle bg-util-colors-blue-light-blue-light-500 p-1 shadow-xs">
<RiSparklingFill className="h-3.5 w-3.5 text-text-primary-on-surface" />
</div>
)}
/>
<TooltipContent>
{t('feature.moreLikeThis.title', { ns: 'appDebug' })}
</TooltipContent>
</Tooltip>
)}
{!!features.opening?.enabled && (
<Tooltip
popupContent={t('feature.conversationOpener.title', { ns: 'appDebug' })}
>
<div className="shrink-0 rounded-lg border-[0.5px] border-divider-subtle bg-util-colors-blue-light-blue-light-500 p-1 shadow-xs">
<LoveMessage className="h-3.5 w-3.5 text-text-primary-on-surface" />
</div>
<Tooltip>
<TooltipTrigger
render={(
<div className="shrink-0 rounded-lg border-[0.5px] border-divider-subtle bg-util-colors-blue-light-blue-light-500 p-1 shadow-xs">
<LoveMessage className="h-3.5 w-3.5 text-text-primary-on-surface" />
</div>
)}
/>
<TooltipContent>
{t('feature.conversationOpener.title', { ns: 'appDebug' })}
</TooltipContent>
</Tooltip>
)}
{!!features.moderation?.enabled && (
<Tooltip
popupContent={t('feature.moderation.title', { ns: 'appDebug' })}
>
<div className="shrink-0 rounded-lg border-[0.5px] border-divider-subtle bg-text-success p-1 shadow-xs">
<ContentModeration className="h-3.5 w-3.5 text-text-primary-on-surface" />
</div>
<Tooltip>
<TooltipTrigger
render={(
<div className="shrink-0 rounded-lg border-[0.5px] border-divider-subtle bg-text-success p-1 shadow-xs">
<ContentModeration className="h-3.5 w-3.5 text-text-primary-on-surface" />
</div>
)}
/>
<TooltipContent>
{t('feature.moderation.title', { ns: 'appDebug' })}
</TooltipContent>
</Tooltip>
)}
{!!features.speech2text?.enabled && (
<Tooltip
popupContent={t('feature.speechToText.title', { ns: 'appDebug' })}
>
<div className="shrink-0 rounded-lg border-[0.5px] border-divider-subtle bg-util-colors-violet-violet-600 p-1 shadow-xs">
<Microphone01 className="h-3.5 w-3.5 text-text-primary-on-surface" />
</div>
<Tooltip>
<TooltipTrigger
render={(
<div className="shrink-0 rounded-lg border-[0.5px] border-divider-subtle bg-util-colors-violet-violet-600 p-1 shadow-xs">
<Microphone01 className="h-3.5 w-3.5 text-text-primary-on-surface" />
</div>
)}
/>
<TooltipContent>
{t('feature.speechToText.title', { ns: 'appDebug' })}
</TooltipContent>
</Tooltip>
)}
{!!features.text2speech?.enabled && (
<VoiceSettings placementLeft={false} open={modalOpen && !disabled} onOpen={setModalOpen}>
<Tooltip
popupContent={t('feature.textToSpeech.title', { ns: 'appDebug' })}
>
<div className={cn('shrink-0 rounded-lg border-[0.5px] border-divider-subtle bg-util-colors-violet-violet-600 p-1 shadow-xs', !disabled && 'cursor-pointer')}>
<TextToAudio className="h-3.5 w-3.5 text-text-primary-on-surface" />
</div>
<Tooltip>
<TooltipTrigger
render={(
<div className={cn('shrink-0 rounded-lg border-[0.5px] border-divider-subtle bg-util-colors-violet-violet-600 p-1 shadow-xs', !disabled && 'cursor-pointer')}>
<TextToAudio className="h-3.5 w-3.5 text-text-primary-on-surface" />
</div>
)}
/>
<TooltipContent>
{t('feature.textToSpeech.title', { ns: 'appDebug' })}
</TooltipContent>
</Tooltip>
</VoiceSettings>
)}
{showFileUpload && !!features.file?.enabled && (
<Tooltip
popupContent={t('feature.fileUpload.title', { ns: 'appDebug' })}
>
<div className="shrink-0 rounded-lg border-[0.5px] border-divider-subtle bg-util-colors-blue-blue-600 p-1 shadow-xs">
<FolderUpload className="h-3.5 w-3.5 text-text-primary-on-surface" />
</div>
<Tooltip>
<TooltipTrigger
render={(
<div className="shrink-0 rounded-lg border-[0.5px] border-divider-subtle bg-util-colors-blue-blue-600 p-1 shadow-xs">
<FolderUpload className="h-3.5 w-3.5 text-text-primary-on-surface" />
</div>
)}
/>
<TooltipContent>
{t('feature.fileUpload.title', { ns: 'appDebug' })}
</TooltipContent>
</Tooltip>
)}
{!!features.suggested?.enabled && (
<Tooltip
popupContent={t('feature.suggestedQuestionsAfterAnswer.title', { ns: 'appDebug' })}
>
<div className="shrink-0 rounded-lg border-[0.5px] border-divider-subtle bg-util-colors-blue-light-blue-light-500 p-1 shadow-xs">
<VirtualAssistant className="h-3.5 w-3.5 text-text-primary-on-surface" />
</div>
<Tooltip>
<TooltipTrigger
render={(
<div className="shrink-0 rounded-lg border-[0.5px] border-divider-subtle bg-util-colors-blue-light-blue-light-500 p-1 shadow-xs">
<VirtualAssistant className="h-3.5 w-3.5 text-text-primary-on-surface" />
</div>
)}
/>
<TooltipContent>
{t('feature.suggestedQuestionsAfterAnswer.title', { ns: 'appDebug' })}
</TooltipContent>
</Tooltip>
)}
{isChatMode && !!features.citation?.enabled && (
<Tooltip
popupContent={t('feature.citation.title', { ns: 'appDebug' })}
>
<div className="shrink-0 rounded-lg border-[0.5px] border-divider-subtle bg-util-colors-warning-warning-500 p-1 shadow-xs">
<Citations className="h-4 w-4 text-text-primary-on-surface" />
</div>
<Tooltip>
<TooltipTrigger
render={(
<div className="shrink-0 rounded-lg border-[0.5px] border-divider-subtle bg-util-colors-warning-warning-500 p-1 shadow-xs">
<Citations className="h-4 w-4 text-text-primary-on-surface" />
</div>
)}
/>
<TooltipContent>
{t('feature.citation.title', { ns: 'appDebug' })}
</TooltipContent>
</Tooltip>
)}
{isChatMode && !!features.annotationReply?.enabled && (
<Tooltip
popupContent={t('feature.annotation.title', { ns: 'appDebug' })}
>
<div className="shrink-0 rounded-lg border-[0.5px] border-divider-subtle bg-util-colors-indigo-indigo-600 p-1 shadow-xs">
<MessageFast className="h-3.5 w-3.5 text-text-primary-on-surface" />
</div>
<Tooltip>
<TooltipTrigger
render={(
<div className="shrink-0 rounded-lg border-[0.5px] border-divider-subtle bg-util-colors-indigo-indigo-600 p-1 shadow-xs">
<MessageFast className="h-3.5 w-3.5 text-text-primary-on-surface" />
</div>
)}
/>
<TooltipContent>
{t('feature.annotation.title', { ns: 'appDebug' })}
</TooltipContent>
</Tooltip>
)}
</div>

View File

@@ -1,9 +1,9 @@
import { Switch } from '@langgenius/dify-ui/switch'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import {
RiQuestionLine,
} from '@remixicon/react'
import * as React from 'react'
import Tooltip from '@/app/components/base/tooltip'
type Props = {
icon: any
@@ -41,10 +41,15 @@ const FeatureCard = ({
<div className="flex grow items-center system-sm-semibold text-text-secondary">
{title}
{tooltip && (
<Tooltip
popupContent={tooltip}
>
<div className="ml-0.5 p-px"><RiQuestionLine className="h-3.5 w-3.5 text-text-quaternary" /></div>
<Tooltip>
<TooltipTrigger
render={(
<div className="ml-0.5 p-px"><RiQuestionLine className="h-3.5 w-3.5 text-text-quaternary" /></div>
)}
/>
<TooltipContent>
{tooltip}
</TooltipContent>
</Tooltip>
)}
</div>

View File

@@ -1,10 +1,10 @@
import type { FileEntity } from './types'
import { cn } from '@langgenius/dify-ui/cn'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { RiArrowRightSLine } from '@remixicon/react'
import * as React from 'react'
import { useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import Tooltip from '@/app/components/base/tooltip'
import { SupportUploadFileTypes } from '@/app/components/workflow/types'
import FileImageRender from './file-image-render'
import FileTypeIcon from './file-type-icon'
@@ -49,27 +49,37 @@ const FileListInLog = ({ fileList, isExpanded = false, noBorder = false, noPaddi
return (
<>
{isImageFile && (
<Tooltip
popupContent={name}
>
<div key={id}>
<FileImageRender
className="h-8 w-8"
imageUrl={base64Url || url || ''}
/>
</div>
<Tooltip>
<TooltipTrigger
render={(
<div key={id}>
<FileImageRender
className="h-8 w-8"
imageUrl={base64Url || url || ''}
/>
</div>
)}
/>
<TooltipContent>
{name}
</TooltipContent>
</Tooltip>
)}
{!isImageFile && (
<Tooltip
popupContent={name}
>
<div key={id} className="rounded-md border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg p-1.5 shadow-xs">
<FileTypeIcon
type={getFileAppearanceType(name, type)}
size="lg"
/>
</div>
<Tooltip>
<TooltipTrigger
render={(
<div key={id} className="rounded-md border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg p-1.5 shadow-xs">
<FileTypeIcon
type={getFileAppearanceType(name, type)}
size="lg"
/>
</div>
)}
/>
<TooltipContent>
{name}
</TooltipContent>
</Tooltip>
)}
</>

View File

@@ -1,4 +1,5 @@
import type { FC } from 'react'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { RiCloseLine, RiZoomInLine, RiZoomOutLine } from '@remixicon/react'
import { noop } from 'es-toolkit/function'
import { t } from 'i18next'
@@ -7,7 +8,6 @@ import { useState } from 'react'
import { createPortal } from 'react-dom'
import { useHotkeys } from 'react-hotkeys-hook'
import Loading from '@/app/components/base/loading'
import Tooltip from '@/app/components/base/tooltip'
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
import { PdfHighlighter, PdfLoader } from './pdf-highlighter-adapter'
@@ -76,29 +76,50 @@ const PdfPreview: FC<PdfPreviewProps> = ({
}}
</PdfLoader>
</div>
<Tooltip popupContent={t('operation.zoomOut', { ns: 'common' })}>
<div
className="absolute top-6 right-24 flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg"
onClick={zoomOut}
>
<RiZoomOutLine className="h-4 w-4 text-gray-500" />
</div>
<Tooltip>
<TooltipTrigger
render={(
<div
className="absolute top-6 right-24 flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg"
onClick={zoomOut}
>
<RiZoomOutLine className="h-4 w-4 text-gray-500" />
</div>
)}
/>
<TooltipContent>
{t('operation.zoomOut', { ns: 'common' })}
</TooltipContent>
</Tooltip>
<Tooltip popupContent={t('operation.zoomIn', { ns: 'common' })}>
<div
className="absolute top-6 right-16 flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg"
onClick={zoomIn}
>
<RiZoomInLine className="h-4 w-4 text-gray-500" />
</div>
<Tooltip>
<TooltipTrigger
render={(
<div
className="absolute top-6 right-16 flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg"
onClick={zoomIn}
>
<RiZoomInLine className="h-4 w-4 text-gray-500" />
</div>
)}
/>
<TooltipContent>
{t('operation.zoomIn', { ns: 'common' })}
</TooltipContent>
</Tooltip>
<Tooltip popupContent={t('operation.cancel', { ns: 'common' })}>
<div
className="absolute top-6 right-6 flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg bg-white/8 backdrop-blur-[2px]"
onClick={onCancel}
>
<RiCloseLine className="h-4 w-4 text-gray-500" />
</div>
<Tooltip>
<TooltipTrigger
render={(
<div
className="absolute top-6 right-6 flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg bg-white/8 backdrop-blur-[2px]"
onClick={onCancel}
>
<RiCloseLine className="h-4 w-4 text-gray-500" />
</div>
)}
/>
<TooltipContent>
{t('operation.cancel', { ns: 'common' })}
</TooltipContent>
</Tooltip>
</div>,
document.body,

View File

@@ -1,11 +1,11 @@
import type { FC } from 'react'
import type { ImageFile } from '@/types/app'
import { cn } from '@langgenius/dify-ui/cn'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback'
import ImagePreview from '@/app/components/base/image-uploader/image-preview'
import Tooltip from '@/app/components/base/tooltip'
import { TransferMethod } from '@/types/app'
type ImageListProps = {
@@ -82,10 +82,15 @@ const ImageList: FC<ImageListProps> = ({
<span className="i-ri-loader-2-line h-5 w-5 animate-spin text-white" data-testid="image-loader" />
)}
{item.progress === -1 && (
<Tooltip
popupContent={t('imageUploader.pasteImageLinkInvalid', { ns: 'common' })}
>
<AlertTriangle className="h-4 w-4 text-[#DC6803]" />
<Tooltip>
<TooltipTrigger
render={(
<AlertTriangle className="h-4 w-4 text-[#DC6803]" />
)}
/>
<TooltipContent>
{t('imageUploader.pasteImageLinkInvalid', { ns: 'common' })}
</TooltipContent>
</Tooltip>
)}
</div>

View File

@@ -1,12 +1,12 @@
import type { FC } from 'react'
import { toast } from '@langgenius/dify-ui/toast'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { noop } from 'es-toolkit/function'
import { t } from 'i18next'
import * as React from 'react'
import { useCallback, useEffect, useRef, useState } from 'react'
import { createPortal } from 'react-dom'
import { useHotkeys } from 'react-hotkeys-hook'
import Tooltip from '@/app/components/base/tooltip'
import { downloadUrl } from '@/utils/download'
type ImagePreviewProps = {
@@ -198,55 +198,97 @@ const ImagePreview: FC<ImagePreviewProps> = ({
}}
data-testid="image-preview-image"
/>
<Tooltip popupContent={t('operation.copyImage', { ns: 'common' })}>
<div
className="absolute top-6 right-48 flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg"
onClick={imageCopy}
>
{isCopied
? <span className="i-ri-file-copy-line h-4 w-4 text-green-500" data-testid="image-preview-copied-icon" />
: <span className="i-ri-file-copy-line h-4 w-4 text-gray-500" data-testid="image-preview-copy-button" />}
</div>
<Tooltip>
<TooltipTrigger
render={(
<div
className="absolute top-6 right-48 flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg"
onClick={imageCopy}
>
{isCopied
? <span className="i-ri-file-copy-line h-4 w-4 text-green-500" data-testid="image-preview-copied-icon" />
: <span className="i-ri-file-copy-line h-4 w-4 text-gray-500" data-testid="image-preview-copy-button" />}
</div>
)}
/>
<TooltipContent>
{t('operation.copyImage', { ns: 'common' })}
</TooltipContent>
</Tooltip>
<Tooltip popupContent={t('operation.zoomOut', { ns: 'common' })}>
<div
className="absolute top-6 right-40 flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg"
onClick={zoomOut}
>
<span className="i-ri-zoom-out-line h-4 w-4 text-gray-500" data-testid="image-preview-zoom-out-button" />
</div>
<Tooltip>
<TooltipTrigger
render={(
<div
className="absolute top-6 right-40 flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg"
onClick={zoomOut}
>
<span className="i-ri-zoom-out-line h-4 w-4 text-gray-500" data-testid="image-preview-zoom-out-button" />
</div>
)}
/>
<TooltipContent>
{t('operation.zoomOut', { ns: 'common' })}
</TooltipContent>
</Tooltip>
<Tooltip popupContent={t('operation.zoomIn', { ns: 'common' })}>
<div
className="absolute top-6 right-32 flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg"
onClick={zoomIn}
>
<span className="i-ri-zoom-in-line h-4 w-4 text-gray-500" data-testid="image-preview-zoom-in-button" />
</div>
<Tooltip>
<TooltipTrigger
render={(
<div
className="absolute top-6 right-32 flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg"
onClick={zoomIn}
>
<span className="i-ri-zoom-in-line h-4 w-4 text-gray-500" data-testid="image-preview-zoom-in-button" />
</div>
)}
/>
<TooltipContent>
{t('operation.zoomIn', { ns: 'common' })}
</TooltipContent>
</Tooltip>
<Tooltip popupContent={t('operation.download', { ns: 'common' })}>
<div
className="absolute top-6 right-24 flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg"
onClick={downloadImage}
>
<span className="i-ri-download-cloud-2-line h-4 w-4 text-gray-500" data-testid="image-preview-download-button" />
</div>
<Tooltip>
<TooltipTrigger
render={(
<div
className="absolute top-6 right-24 flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg"
onClick={downloadImage}
>
<span className="i-ri-download-cloud-2-line h-4 w-4 text-gray-500" data-testid="image-preview-download-button" />
</div>
)}
/>
<TooltipContent>
{t('operation.download', { ns: 'common' })}
</TooltipContent>
</Tooltip>
<Tooltip popupContent={t('operation.openInNewTab', { ns: 'common' })}>
<div
className="absolute top-6 right-16 flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg"
onClick={openInNewTab}
>
<span className="i-ri-add-box-line h-4 w-4 text-gray-500" data-testid="image-preview-open-in-tab-button" />
</div>
<Tooltip>
<TooltipTrigger
render={(
<div
className="absolute top-6 right-16 flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg"
onClick={openInNewTab}
>
<span className="i-ri-add-box-line h-4 w-4 text-gray-500" data-testid="image-preview-open-in-tab-button" />
</div>
)}
/>
<TooltipContent>
{t('operation.openInNewTab', { ns: 'common' })}
</TooltipContent>
</Tooltip>
<Tooltip popupContent={t('operation.cancel', { ns: 'common' })}>
<div
className="absolute top-6 right-6 flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg bg-white/8 backdrop-blur-[2px]"
onClick={onCancel}
>
<span className="i-ri-close-line h-4 w-4 text-gray-500" data-testid="image-preview-close-button" />
</div>
<Tooltip>
<TooltipTrigger
render={(
<div
className="absolute top-6 right-6 flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg bg-white/8 backdrop-blur-[2px]"
onClick={onCancel}
>
<span className="i-ri-close-line h-4 w-4 text-gray-500" data-testid="image-preview-close-button" />
</div>
)}
/>
<TooltipContent>
{t('operation.cancel', { ns: 'common' })}
</TooltipContent>
</Tooltip>
</div>,
document.body,

View File

@@ -1,4 +1,5 @@
'use client'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import {
RiVolumeUpLine,
} from '@remixicon/react'
@@ -6,7 +7,6 @@ import { t } from 'i18next'
import { useState } from 'react'
import ActionButton, { ActionButtonState } from '@/app/components/base/action-button'
import { AudioPlayerManager } from '@/app/components/base/audio-btn/audio.player.manager'
import Tooltip from '@/app/components/base/tooltip'
import { useParams, usePathname } from '@/next/navigation'
type AudioBtnProps = {
@@ -78,20 +78,27 @@ const AudioBtn = ({
}[audioState]
return (
<Tooltip
popupContent={tooltipContent}
>
<ActionButton
state={
audioState === 'loading' || audioState === 'playing'
? ActionButtonState.Active
: ActionButtonState.Default
}
onClick={handleToggle}
disabled={audioState === 'loading'}
>
<RiVolumeUpLine className="h-4 w-4" />
</ActionButton>
<Tooltip>
<TooltipTrigger
render={(
<span className="inline-flex">
<ActionButton
state={
audioState === 'loading' || audioState === 'playing'
? ActionButtonState.Active
: ActionButtonState.Default
}
onClick={handleToggle}
disabled={audioState === 'loading'}
>
<RiVolumeUpLine className="h-4 w-4" />
</ActionButton>
</span>
)}
/>
<TooltipContent>
{tooltipContent}
</TooltipContent>
</Tooltip>
)
}

View File

@@ -1,10 +1,10 @@
'use client'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { QRCodeCanvas as QRCode } from 'qrcode.react'
import * as React from 'react'
import { useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import ActionButton from '@/app/components/base/action-button'
import Tooltip from '@/app/components/base/tooltip'
import { downloadUrl } from '@/utils/download'
type Props = {
@@ -54,28 +54,33 @@ const ShareQRCode = ({ content }: Props) => {
const safeTooltipText = tooltipText || ''
return (
<Tooltip
popupContent={safeTooltipText}
>
<div className="relative h-6 w-6" onClick={toggleQRCode} data-testid="qrcode-container">
<ActionButton>
<span className="i-ri-qr-code-line h-4 w-4" />
</ActionButton>
{isShow && (
<div
ref={qrCodeRef}
className="absolute top-8 -right-8 z-10 flex w-[232px] flex-col items-center rounded-lg bg-components-panel-bg p-4 shadow-xs"
onClick={handlePanelClick}
>
<QRCode size={160} value={content} className="mb-2" />
<div className="flex items-center system-xs-regular">
<div className="text-text-tertiary">{t('overview.appInfo.qrcode.scan', { ns: 'appOverview' })}</div>
<div className="text-text-tertiary">·</div>
<div className="cursor-pointer text-text-accent-secondary" onClick={downloadQR}>{t('overview.appInfo.qrcode.download', { ns: 'appOverview' })}</div>
</div>
<Tooltip>
<TooltipTrigger
render={(
<div className="relative h-6 w-6" onClick={toggleQRCode} data-testid="qrcode-container">
<ActionButton>
<span className="i-ri-qr-code-line h-4 w-4" />
</ActionButton>
{isShow && (
<div
ref={qrCodeRef}
className="absolute top-8 -right-8 z-10 flex w-[232px] flex-col items-center rounded-lg bg-components-panel-bg p-4 shadow-xs"
onClick={handlePanelClick}
>
<QRCode size={160} value={content} className="mb-2" />
<div className="flex items-center system-xs-regular">
<div className="text-text-tertiary">{t('overview.appInfo.qrcode.scan', { ns: 'appOverview' })}</div>
<div className="text-text-tertiary">·</div>
<div className="cursor-pointer text-text-accent-secondary" onClick={downloadQR}>{t('overview.appInfo.qrcode.download', { ns: 'appOverview' })}</div>
</div>
</div>
)}
</div>
)}
</div>
/>
<TooltipContent>
{safeTooltipText}
</TooltipContent>
</Tooltip>
)
}

View File

@@ -3,6 +3,7 @@
import type { FC } from 'react'
import type { PreProcessingRule, SummaryIndexSetting as SummaryIndexSettingType } from '@/models/datasets'
import { Button } from '@langgenius/dify-ui/button'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import {
RiAlertFill,
RiSearchEyeLine,
@@ -10,7 +11,6 @@ import {
import { useTranslation } from 'react-i18next'
import Checkbox from '@/app/components/base/checkbox'
import Divider from '@/app/components/base/divider'
import Tooltip from '@/app/components/base/tooltip'
import SummaryIndexSetting from '@/app/components/datasets/settings/summary-index-setting'
import { IS_CE_EDITION } from '@/config'
import { ChunkingMode } from '@/models/datasets'
@@ -191,7 +191,18 @@ export const GeneralChunkingOptions: FC<GeneralChunkingOptionsProps> = ({
onSelect={onDocLanguageChange}
disabled={currentDocForm !== ChunkingMode.qa}
/>
<Tooltip popupContent={t('stepTwo.QATip', { ns: 'datasetCreation' })} />
<Tooltip>
<TooltipTrigger
render={(
<span className="flex h-3.5 w-3.5 shrink-0 p-px">
<span aria-hidden className="i-ri-question-line h-full w-full text-text-quaternary hover:text-text-tertiary" />
</span>
)}
/>
<TooltipContent>
{t('stepTwo.QATip', { ns: 'datasetCreation' })}
</TooltipContent>
</Tooltip>
</div>
{currentDocForm === ChunkingMode.qa && (
<div

View File

@@ -1,11 +1,11 @@
import type { FC } from 'react'
import type { SimpleDocumentDetail } from '@/models/datasets'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { pick } from 'es-toolkit/object'
import * as React from 'react'
import { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import Checkbox from '@/app/components/base/checkbox'
import Tooltip from '@/app/components/base/tooltip'
import ChunkingModeLabel from '@/app/components/datasets/common/chunking-mode-label'
import Operations from '@/app/components/datasets/documents/components/operations'
import SummaryStatus from '@/app/components/datasets/documents/detail/completed/common/summary-status'
@@ -101,8 +101,15 @@ const DocumentTableRow: FC<DocumentTableRowProps> = React.memo(({
<div className="flex shrink-0 items-center">
<DocumentSourceIcon doc={doc} fileType={fileType} />
</div>
<Tooltip popupContent={doc.name}>
<span className="grow truncate text-sm">{doc.name}</span>
<Tooltip>
<TooltipTrigger
render={(
<span className="grow truncate text-sm">{doc.name}</span>
)}
/>
<TooltipContent>
{doc.name}
</TooltipContent>
</Tooltip>
{doc.summary_index_status && (
<div className="ml-1 hidden shrink-0 group-hover:flex">
@@ -110,13 +117,20 @@ const DocumentTableRow: FC<DocumentTableRowProps> = React.memo(({
</div>
)}
<div className="hidden shrink-0 group-hover:ml-auto group-hover:flex">
<Tooltip popupContent={t('list.table.rename', { ns: 'datasetDocuments' })}>
<div
className="cursor-pointer rounded-md p-1 hover:bg-state-base-hover"
onClick={handleRenameClick}
>
<span className="i-ri-edit-line h-4 w-4 text-text-tertiary" />
</div>
<Tooltip>
<TooltipTrigger
render={(
<div
className="cursor-pointer rounded-md p-1 hover:bg-state-base-hover"
onClick={handleRenameClick}
>
<span className="i-ri-edit-line h-4 w-4 text-text-tertiary" />
</div>
)}
/>
<TooltipContent>
{t('list.table.rename', { ns: 'datasetDocuments' })}
</TooltipContent>
</Tooltip>
</div>
</div>

View File

@@ -3,9 +3,9 @@ import type { FC } from 'react'
import type { DocType } from '@/models/datasets'
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { useTranslation } from 'react-i18next'
import Radio from '@/app/components/base/radio'
import Tooltip from '@/app/components/base/tooltip'
import { useMetadataMap } from '@/hooks/use-metadata'
import { CUSTOMIZABLE_DOC_TYPES } from '@/models/datasets'
import s from '../style.module.css'
@@ -17,13 +17,20 @@ const TypeIcon: FC<{ iconName: string, className?: string }> = ({ iconName, clas
const IconButton: FC<{ type: DocType, isChecked: boolean }> = ({ type, isChecked = false }) => {
const metadataMap = useMetadataMap()
return (
<Tooltip popupContent={metadataMap[type].text}>
<button type="button" className={cn(s.iconWrapper, 'group', isChecked ? s.iconCheck : '')}>
<TypeIcon
iconName={metadataMap[type].iconName || ''}
className={`group-hover:bg-primary-600 ${isChecked ? 'bg-primary-600!' : ''}`}
/>
</button>
<Tooltip>
<TooltipTrigger
render={(
<button type="button" className={cn(s.iconWrapper, 'group', isChecked ? s.iconCheck : '')}>
<TypeIcon
iconName={metadataMap[type].iconName || ''}
className={`group-hover:bg-primary-600 ${isChecked ? 'bg-primary-600!' : ''}`}
/>
</button>
)}
/>
<TooltipContent>
{metadataMap[type].text}
</TooltipContent>
</Tooltip>
)
}

View File

@@ -1,9 +1,9 @@
import type { ChangeEvent } from 'react'
import { cn } from '@langgenius/dify-ui/cn'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import { Corner } from '@/app/components/base/icons/src/vender/solid/shapes'
import Tooltip from '@/app/components/base/tooltip'
type TextareaProps = {
text: string
@@ -36,14 +36,19 @@ const Textarea = ({
/>
{text.length > 200
? (
<Tooltip
popupContent={t('input.countWarning', { ns: 'datasetHitTesting' })}
>
<div
className={cn('bg-util-colors-red-red-100 py-1 pr-2 system-2xs-medium-uppercase text-util-colors-red-red-600')}
>
{`${text.length}/200`}
</div>
<Tooltip>
<TooltipTrigger
render={(
<div
className={cn('bg-util-colors-red-red-100 py-1 pr-2 system-2xs-medium-uppercase text-util-colors-red-red-600')}
>
{`${text.length}/200`}
</div>
)}
/>
<TooltipContent>
{t('input.countWarning', { ns: 'datasetHitTesting' })}
</TooltipContent>
</Tooltip>
)
: (

View File

@@ -1,10 +1,10 @@
import type { DataSet } from '@/models/datasets'
import { cn } from '@langgenius/dify-ui/cn'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { RiFileTextFill, RiRobot2Fill } from '@remixicon/react'
import * as React from 'react'
import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import Tooltip from '@/app/components/base/tooltip'
import { useFormatTimeFromNow } from '@/hooks/use-format-time-from-now'
const EXTERNAL_PROVIDER = 'external'
@@ -39,18 +39,32 @@ const DatasetCardFooter = ({ dataset }: DatasetCardFooterProps) => {
!dataset.embedding_available && 'opacity-30',
)}
>
<Tooltip popupContent={documentCountTooltip}>
<div className="flex items-center gap-x-1">
<RiFileTextFill className="size-3 text-text-quaternary" />
<span className="system-xs-medium">{documentCount}</span>
</div>
<Tooltip>
<TooltipTrigger
render={(
<div className="flex items-center gap-x-1">
<RiFileTextFill className="size-3 text-text-quaternary" />
<span className="system-xs-medium">{documentCount}</span>
</div>
)}
/>
<TooltipContent>
{documentCountTooltip}
</TooltipContent>
</Tooltip>
{!isExternalProvider && (
<Tooltip popupContent={`${dataset.app_count} ${t('appCount', { ns: 'dataset' })}`}>
<div className="flex items-center gap-x-1">
<RiRobot2Fill className="size-3 text-text-quaternary" />
<span className="system-xs-medium">{dataset.app_count}</span>
</div>
<Tooltip>
<TooltipTrigger
render={(
<div className="flex items-center gap-x-1">
<RiRobot2Fill className="size-3 text-text-quaternary" />
<span className="system-xs-medium">{dataset.app_count}</span>
</div>
)}
/>
<TooltipContent>
{`${dataset.app_count} ${t('appCount', { ns: 'dataset' })}`}
</TooltipContent>
</Tooltip>
)}
<span className="system-xs-regular text-divider-deep">/</span>

View File

@@ -1,11 +1,11 @@
'use client'
import type { FC } from 'react'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { RiResetLeftLine } from '@remixicon/react'
import { useHover } from 'ahooks'
import * as React from 'react'
import { useRef } from 'react'
import { useTranslation } from 'react-i18next'
import Tooltip from '@/app/components/base/tooltip'
type Props = {
onReset: () => void
@@ -22,10 +22,17 @@ const EditedBeacon: FC<Props> = ({
<div ref={ref} className="size-4 cursor-pointer">
{isHovering
? (
<Tooltip popupContent={t('operation.reset', { ns: 'common' })}>
<div className="flex size-4 items-center justify-center rounded-full bg-text-accent-secondary" onClick={onReset}>
<RiResetLeftLine className="size-[10px] text-text-primary-on-surface" />
</div>
<Tooltip>
<TooltipTrigger
render={(
<div className="flex size-4 items-center justify-center rounded-full bg-text-accent-secondary" onClick={onReset}>
<RiResetLeftLine className="size-[10px] text-text-primary-on-surface" />
</div>
)}
/>
<TooltipContent>
{t('operation.reset', { ns: 'common' })}
</TooltipContent>
</Tooltip>
)
: (

View File

@@ -7,10 +7,10 @@ import {
NumberFieldInput,
} from '@langgenius/dify-ui/number-field'
import { Slider } from '@langgenius/dify-ui/slider'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import * as React from 'react'
import { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import Tooltip from '@/app/components/base/tooltip'
const MIN_KEYWORD_NUMBER = 0
const MAX_KEYWORD_NUMBER = 50
@@ -36,10 +36,15 @@ const KeyWordNumber = ({
<div className="truncate system-xs-medium text-text-secondary">
{t('form.numberOfKeywords', { ns: 'datasetSettings' })}
</div>
<Tooltip
popupContent={t('form.numberOfKeywords', { ns: 'datasetSettings' })}
>
<span className="i-ri-question-line h-3.5 w-3.5 text-text-quaternary" />
<Tooltip>
<TooltipTrigger
render={(
<span className="i-ri-question-line h-3.5 w-3.5 text-text-quaternary" />
)}
/>
<TooltipContent>
{t('form.numberOfKeywords', { ns: 'datasetSettings' })}
</TooltipContent>
</Tooltip>
</div>
<Slider

View File

@@ -5,6 +5,7 @@ import type {
} from '@/app/components/base/chat/embedded-chatbot/context'
import type { TryAppInfo } from '@/service/try-app'
import { cn } from '@langgenius/dify-ui/cn'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { RiResetLeftLine } from '@remixicon/react'
import { useBoolean } from 'ahooks'
import * as React from 'react'
@@ -21,7 +22,6 @@ import {
useEmbeddedChatbot,
} from '@/app/components/base/chat/embedded-chatbot/hooks'
import ViewFormDropdown from '@/app/components/base/chat/embedded-chatbot/inputs-form/view-form-dropdown'
import Tooltip from '@/app/components/base/tooltip'
import useBreakpoints, { MediaType } from '@/hooks/use-breakpoints'
import { AppSourceType } from '@/service/share'
import { useThemeContext } from '../../../base/chat/embedded-chatbot/theme/theme-context'
@@ -78,12 +78,17 @@ const TryApp: FC<Props> = ({
</div>
<div className="flex items-center gap-1">
{currentConversationId && (
<Tooltip
popupContent={t('chat.resetChat', { ns: 'share' })}
>
<ActionButton size="l" onClick={handleNewConversation}>
<RiResetLeftLine className="h-[18px] w-[18px]" />
</ActionButton>
<Tooltip>
<TooltipTrigger
render={(
<ActionButton size="l" onClick={handleNewConversation}>
<RiResetLeftLine className="h-[18px] w-[18px]" />
</ActionButton>
)}
/>
<TooltipContent>
{t('chat.resetChat', { ns: 'share' })}
</TooltipContent>
</Tooltip>
)}
{currentConversationId && inputsForms.length > 0 && (

View File

@@ -1,4 +1,5 @@
import { fireEvent, render, screen } from '@testing-library/react'
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import {
ModelFeatureEnum,
ModelFeatureTextEnum,
@@ -36,7 +37,7 @@ describe('FeatureIcon', () => {
for (const { feature, text } of cases) {
const { container, unmount } = render(<FeatureIcon feature={feature} />)
fireEvent.mouseEnter(container.firstElementChild as HTMLElement)
await userEvent.hover(container.firstElementChild as HTMLElement)
expect(await screen.findByText(`common.modelProvider.featureSupported:{"feature":"${text}"}`))
.toBeInTheDocument()
unmount()

View File

@@ -1,5 +1,6 @@
import type { FC } from 'react'
import { cn } from '@langgenius/dify-ui/cn'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import {
RiFileTextLine,
RiFilmAiLine,
@@ -7,7 +8,6 @@ import {
RiVoiceAiFill,
} from '@remixicon/react'
import { useTranslation } from 'react-i18next'
import Tooltip from '@/app/components/base/tooltip'
import {
ModelFeatureEnum,
ModelFeatureTextEnum,
@@ -75,19 +75,24 @@ const FeatureIcon: FC<FeatureIconProps> = ({
}
return (
<Tooltip
popupContent={t('modelProvider.featureSupported', { ns: 'common', feature: ModelFeatureTextEnum.vision })}
>
<div className="inline-block cursor-help">
<ModelBadge
className={cn(
'w-[18px] justify-center px-0!',
className,
)}
>
<RiImageCircleAiLine className="size-3" />
</ModelBadge>
</div>
<Tooltip>
<TooltipTrigger
render={(
<div className="inline-block cursor-help">
<ModelBadge
className={cn(
'w-[18px] justify-center px-0!',
className,
)}
>
<RiImageCircleAiLine className="size-3" />
</ModelBadge>
</div>
)}
/>
<TooltipContent>
{t('modelProvider.featureSupported', { ns: 'common', feature: ModelFeatureTextEnum.vision })}
</TooltipContent>
</Tooltip>
)
}
@@ -105,19 +110,24 @@ const FeatureIcon: FC<FeatureIconProps> = ({
}
return (
<Tooltip
popupContent={t('modelProvider.featureSupported', { ns: 'common', feature: ModelFeatureTextEnum.document })}
>
<div className="inline-block cursor-help">
<ModelBadge
className={cn(
'w-[18px] justify-center px-0!',
className,
)}
>
<RiFileTextLine className="size-3" />
</ModelBadge>
</div>
<Tooltip>
<TooltipTrigger
render={(
<div className="inline-block cursor-help">
<ModelBadge
className={cn(
'w-[18px] justify-center px-0!',
className,
)}
>
<RiFileTextLine className="size-3" />
</ModelBadge>
</div>
)}
/>
<TooltipContent>
{t('modelProvider.featureSupported', { ns: 'common', feature: ModelFeatureTextEnum.document })}
</TooltipContent>
</Tooltip>
)
}
@@ -135,19 +145,24 @@ const FeatureIcon: FC<FeatureIconProps> = ({
}
return (
<Tooltip
popupContent={t('modelProvider.featureSupported', { ns: 'common', feature: ModelFeatureTextEnum.audio })}
>
<div className="inline-block cursor-help">
<ModelBadge
className={cn(
'w-[18px] justify-center px-0!',
className,
)}
>
<RiVoiceAiFill className="size-3" />
</ModelBadge>
</div>
<Tooltip>
<TooltipTrigger
render={(
<div className="inline-block cursor-help">
<ModelBadge
className={cn(
'w-[18px] justify-center px-0!',
className,
)}
>
<RiVoiceAiFill className="size-3" />
</ModelBadge>
</div>
)}
/>
<TooltipContent>
{t('modelProvider.featureSupported', { ns: 'common', feature: ModelFeatureTextEnum.audio })}
</TooltipContent>
</Tooltip>
)
}
@@ -165,19 +180,24 @@ const FeatureIcon: FC<FeatureIconProps> = ({
}
return (
<Tooltip
popupContent={t('modelProvider.featureSupported', { ns: 'common', feature: ModelFeatureTextEnum.video })}
>
<div className="inline-block cursor-help">
<ModelBadge
className={cn(
'w-[18px] justify-center px-0!',
className,
)}
>
<RiFilmAiLine className="size-3" />
</ModelBadge>
</div>
<Tooltip>
<TooltipTrigger
render={(
<div className="inline-block cursor-help">
<ModelBadge
className={cn(
'w-[18px] justify-center px-0!',
className,
)}
>
<RiFilmAiLine className="size-3" />
</ModelBadge>
</div>
)}
/>
<TooltipContent>
{t('modelProvider.featureSupported', { ns: 'common', feature: ModelFeatureTextEnum.video })}
</TooltipContent>
</Tooltip>
)
}

View File

@@ -1,8 +1,8 @@
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { useLatest } from 'ahooks'
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import SimplePieChart from '@/app/components/base/simple-pie-chart'
import Tooltip from '@/app/components/base/tooltip'
type CooldownTimerProps = {
secondsRemaining?: number
@@ -54,8 +54,15 @@ const CooldownTimer = ({ secondsRemaining, onFinish }: CooldownTimerProps) => {
return displayTime
? (
<Tooltip popupContent={t('modelProvider.apiKeyRateLimit', { ns: 'common', seconds: displayTime })}>
<SimplePieChart percentage={Math.round(displayTime / 60 * 100)} className="h-3 w-3" />
<Tooltip>
<TooltipTrigger
render={(
<SimplePieChart percentage={Math.round(displayTime / 60 * 100)} className="h-3 w-3" />
)}
/>
<TooltipContent>
{t('modelProvider.apiKeyRateLimit', { ns: 'common', seconds: displayTime })}
</TooltipContent>
</Tooltip>
)
: null

View File

@@ -10,11 +10,11 @@ import type {
} from '../declarations'
import { cn } from '@langgenius/dify-ui/cn'
import { Switch } from '@langgenius/dify-ui/switch'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { useCallback, useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import Badge from '@/app/components/base/badge/index'
import GridMask from '@/app/components/base/grid-mask'
import Tooltip from '@/app/components/base/tooltip'
import UpgradeBtn from '@/app/components/billing/upgrade-btn'
import s from '@/app/components/custom/style.module.css'
import { AddCredentialInLoadBalancing } from '@/app/components/header/account-setting/model-provider-page/model-auth'
@@ -152,11 +152,18 @@ const ModelLoadBalancingConfigs = ({
<div className="grow">
<div className="flex items-center gap-1 text-sm text-text-primary">
{t('modelProvider.loadBalancing', { ns: 'common' })}
<Tooltip
popupContent={t('modelProvider.loadBalancingInfo', { ns: 'common' })}
popupClassName="max-w-[300px]"
triggerClassName="w-3 h-3"
/>
<Tooltip>
<TooltipTrigger
render={(
<span className="flex h-3 w-3 shrink-0 p-px">
<span aria-hidden className="i-ri-question-line h-full w-full text-text-quaternary hover:text-text-tertiary" />
</span>
)}
/>
<TooltipContent className="max-w-[300px]">
{t('modelProvider.loadBalancingInfo', { ns: 'common' })}
</TooltipContent>
</Tooltip>
</div>
<div className="text-xs text-text-tertiary">{t('modelProvider.loadBalancingDescription', { ns: 'common' })}</div>
</div>
@@ -187,8 +194,15 @@ const ModelLoadBalancingConfigs = ({
<CooldownTimer secondsRemaining={config.ttl} onFinish={() => clearCountdown(index)} />
)
: (
<Tooltip popupContent={t('modelProvider.apiKeyStatusNormal', { ns: 'common' })}>
<Indicator color={credential?.not_allowed_to_use ? 'gray' : 'green'} />
<Tooltip>
<TooltipTrigger
render={(
<Indicator color={credential?.not_allowed_to_use ? 'gray' : 'green'} />
)}
/>
<TooltipContent>
{t('modelProvider.apiKeyStatusNormal', { ns: 'common' })}
</TooltipContent>
</Tooltip>
)}
</div>
@@ -208,14 +222,21 @@ const ModelLoadBalancingConfigs = ({
{!isProviderManaged && (
<>
<div className="flex items-center gap-1 opacity-0 transition-opacity group-hover:opacity-100">
<Tooltip popupContent={t('operation.remove', { ns: 'common' })}>
<span
className="flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg bg-components-button-secondary-bg text-text-tertiary transition-colors hover:bg-components-button-secondary-bg-hover"
onClick={() => updateConfigEntry(index, () => undefined)}
data-testid={`load-balancing-remove-${config.id || index}`}
>
<div className="i-ri-indeterminate-circle-line h-4 w-4" />
</span>
<Tooltip>
<TooltipTrigger
render={(
<span
className="flex h-8 w-8 cursor-pointer items-center justify-center rounded-lg bg-components-button-secondary-bg text-text-tertiary transition-colors hover:bg-components-button-secondary-bg-hover"
onClick={() => updateConfigEntry(index, () => undefined)}
data-testid={`load-balancing-remove-${config.id || index}`}
>
<div className="i-ri-indeterminate-circle-line h-4 w-4" />
</span>
)}
/>
<TooltipContent>
{t('operation.remove', { ns: 'common' })}
</TooltipContent>
</Tooltip>
</div>
</>

View File

@@ -1,17 +1,22 @@
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { useTranslation } from 'react-i18next'
import { ChevronDownDouble } from '@/app/components/base/icons/src/vender/line/arrows'
import Tooltip from '@/app/components/base/tooltip'
const PriorityUseTip = () => {
const { t } = useTranslation()
return (
<Tooltip
popupContent={t('modelProvider.priorityUsing', { ns: 'common' }) || ''}
>
<div className="absolute -top-[5px] -right-[5px] cursor-pointer rounded-[5px] border-[0.5px] border-components-panel-border-subtle bg-util-colors-indigo-indigo-50 shadow-xs">
<ChevronDownDouble className="h-3 w-3 rotate-180 text-util-colors-indigo-indigo-600" />
</div>
<Tooltip>
<TooltipTrigger
render={(
<div className="absolute -top-[5px] -right-[5px] cursor-pointer rounded-[5px] border-[0.5px] border-components-panel-border-subtle bg-util-colors-indigo-indigo-50 shadow-xs">
<ChevronDownDouble className="h-3 w-3 rotate-180 text-util-colors-indigo-indigo-600" />
</div>
)}
/>
<TooltipContent>
{t('modelProvider.priorityUsing', { ns: 'common' }) || ''}
</TooltipContent>
</Tooltip>
)
}

View File

@@ -2,12 +2,12 @@ import type { PluginPayload } from '../types'
import type { AddApiKeyButtonProps } from './add-api-key-button'
import type { AddOAuthButtonProps } from './add-oauth-button'
import { cn } from '@langgenius/dify-ui/cn'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import {
memo,
useMemo,
} from 'react'
import { useTranslation } from 'react-i18next'
import Tooltip from '@/app/components/base/tooltip'
import AddApiKeyButton from './add-api-key-button'
import AddOAuthButton from './add-oauth-button'
@@ -79,8 +79,11 @@ const Authorize = ({
if (notAllowCustomCredential) {
return (
<Tooltip popupContent={t('auth.credentialUnavailable', { ns: 'plugin' })}>
{Item}
<Tooltip>
<TooltipTrigger render={Item} />
<TooltipContent>
{t('auth.credentialUnavailable', { ns: 'plugin' })}
</TooltipContent>
</Tooltip>
)
}
@@ -100,8 +103,11 @@ const Authorize = ({
if (notAllowCustomCredential) {
return (
<Tooltip popupContent={t('auth.credentialUnavailable', { ns: 'plugin' })}>
{Item}
<Tooltip>
<TooltipTrigger render={Item} />
<TooltipContent>
{t('auth.credentialUnavailable', { ns: 'plugin' })}
</TooltipContent>
</Tooltip>
)
}

View File

@@ -110,7 +110,7 @@ describe('Item Component', () => {
const { container } = render(<Item credential={credential} />)
expect(container.querySelector('[data-state]')).toBeInTheDocument()
expect(container.firstElementChild).toHaveClass('cursor-not-allowed', 'opacity-50')
})
it('should not call onItemClick when disabled is true', () => {

View File

@@ -1,6 +1,7 @@
import type { Credential } from '../types'
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import {
RiCheckLine,
RiDeleteBinLine,
@@ -16,7 +17,6 @@ import { useTranslation } from 'react-i18next'
import ActionButton from '@/app/components/base/action-button'
import Badge from '@/app/components/base/badge'
import Input from '@/app/components/base/input'
import Tooltip from '@/app/components/base/tooltip'
import Indicator from '@/app/components/header/indicator'
import { CredentialTypeEnum } from '../types'
@@ -172,55 +172,76 @@ const Item = ({
}
{
!disableRename && !credential.from_enterprise && !credential.not_allowed_to_use && (
<Tooltip popupContent={t('operation.rename', { ns: 'common' })}>
<ActionButton
disabled={disabled}
onClick={(e) => {
e.stopPropagation()
setRenaming(true)
setRenameValue(credential.name)
}}
>
<RiEditLine className="h-4 w-4 text-text-tertiary" />
</ActionButton>
<Tooltip>
<TooltipTrigger
render={(
<ActionButton
disabled={disabled}
onClick={(e) => {
e.stopPropagation()
setRenaming(true)
setRenameValue(credential.name)
}}
>
<RiEditLine className="h-4 w-4 text-text-tertiary" />
</ActionButton>
)}
/>
<TooltipContent>
{t('operation.rename', { ns: 'common' })}
</TooltipContent>
</Tooltip>
)
}
{
!isOAuth && !disableEdit && !credential.from_enterprise && !credential.not_allowed_to_use && (
<Tooltip popupContent={t('operation.edit', { ns: 'common' })}>
<ActionButton
disabled={disabled}
onClick={(e) => {
e.stopPropagation()
onEdit?.(
credential.id,
{
...credential.credentials,
__name__: credential.name,
__credential_id__: credential.id,
},
)
}}
>
<RiEqualizer2Line className="h-4 w-4 text-text-tertiary" />
</ActionButton>
<Tooltip>
<TooltipTrigger
render={(
<ActionButton
disabled={disabled}
onClick={(e) => {
e.stopPropagation()
onEdit?.(
credential.id,
{
...credential.credentials,
__name__: credential.name,
__credential_id__: credential.id,
},
)
}}
>
<RiEqualizer2Line className="h-4 w-4 text-text-tertiary" />
</ActionButton>
)}
/>
<TooltipContent>
{t('operation.edit', { ns: 'common' })}
</TooltipContent>
</Tooltip>
)
}
{
!disableDelete && !credential.from_enterprise && (
<Tooltip popupContent={t('operation.delete', { ns: 'common' })}>
<ActionButton
className="hover:bg-transparent"
disabled={disabled}
onClick={(e) => {
e.stopPropagation()
onDelete?.(credential.id)
}}
>
<RiDeleteBinLine className="h-4 w-4 text-text-tertiary hover:text-text-destructive" />
</ActionButton>
<Tooltip>
<TooltipTrigger
render={(
<ActionButton
className="hover:bg-transparent"
disabled={disabled}
onClick={(e) => {
e.stopPropagation()
onDelete?.(credential.id)
}}
>
<RiDeleteBinLine className="h-4 w-4 text-text-tertiary hover:text-text-destructive" />
</ActionButton>
)}
/>
<TooltipContent>
{t('operation.delete', { ns: 'common' })}
</TooltipContent>
</Tooltip>
)
}
@@ -232,8 +253,11 @@ const Item = ({
if (credential.not_allowed_to_use) {
return (
<Tooltip popupContent={t('auth.customCredentialUnavailable', { ns: 'plugin' })}>
{CredentialItem}
<Tooltip>
<TooltipTrigger render={CredentialItem} />
<TooltipContent>
{t('auth.customCredentialUnavailable', { ns: 'plugin' })}
</TooltipContent>
</Tooltip>
)
}

View File

@@ -2,6 +2,7 @@ import type { Node } from 'reactflow'
import type { ToolValue } from '@/app/components/workflow/block-selector/types'
import type { NodeOutPutVar } from '@/app/components/workflow/types'
import { cn } from '@langgenius/dify-ui/cn'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import {
RiAddLine,
RiQuestionLine,
@@ -11,7 +12,6 @@ import { useTranslation } from 'react-i18next'
import ActionButton from '@/app/components/base/action-button'
import Divider from '@/app/components/base/divider'
import { ArrowDownRoundFill } from '@/app/components/base/icons/src/vender/solid/general'
import Tooltip from '@/app/components/base/tooltip'
import ToolSelector from '@/app/components/plugins/plugin-detail-panel/tool-selector'
import { useMCPToolAvailability } from '@/app/components/workflow/nodes/_base/components/mcp-tool-availability'
import { useAllMCPTools } from '@/service/use-tools'
@@ -112,10 +112,15 @@ const MultipleToolSelector = ({
<div className="flex h-6 items-center system-sm-semibold-uppercase text-text-secondary">{label}</div>
{required && <div className="text-red-500">*</div>}
{tooltip && (
<Tooltip
popupContent={tooltip}
>
<div><RiQuestionLine className="h-3.5 w-3.5 text-text-quaternary hover:text-text-tertiary" /></div>
<Tooltip>
<TooltipTrigger
render={(
<div><RiQuestionLine className="h-3.5 w-3.5 text-text-quaternary hover:text-text-tertiary" /></div>
)}
/>
<TooltipContent>
{tooltip}
</TooltipContent>
</Tooltip>
)}
{supportCollapse && (

View File

@@ -3,13 +3,13 @@ import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { Select, SelectContent, SelectItem, SelectItemIndicator, SelectTrigger } from '@langgenius/dify-ui/select'
import { toast } from '@langgenius/dify-ui/toast'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { RiAddLine, RiEqualizer2Line } from '@remixicon/react'
import { useBoolean } from 'ahooks'
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'
import Tooltip from '@/app/components/base/tooltip'
import { openOAuthPopup } from '@/hooks/use-oauth'
import { useInitiateTriggerOAuth, useTriggerOAuthConfig, useTriggerProviderInfo } from '@/service/use-triggers'
import { SupportedCreationMethods } from '../../../types'
@@ -86,10 +86,17 @@ export const CreateSubscriptionButton = ({ buttonType = CreateButtonType.FULL_BU
</Badge>
),
extra: (
<Tooltip popupContent={t('subscription.addType.options.oauth.clientSettings', { ns: 'pluginTrigger' })}>
<ActionButton onClick={onClickClientSettings}>
<RiEqualizer2Line className="h-4 w-4 text-text-tertiary" />
</ActionButton>
<Tooltip>
<TooltipTrigger
render={(
<ActionButton onClick={onClickClientSettings}>
<RiEqualizer2Line className="h-4 w-4 text-text-tertiary" />
</ActionButton>
)}
/>
<TooltipContent>
{t('subscription.addType.options.oauth.clientSettings', { ns: 'pluginTrigger' })}
</TooltipContent>
</Tooltip>
),
show: supportedMethods.includes(SupportedCreationMethods.OAUTH),
@@ -102,7 +109,20 @@ export const CreateSubscriptionButton = ({ buttonType = CreateButtonType.FULL_BU
{
value: SupportedCreationMethods.MANUAL,
label: t('subscription.addType.options.manual.description', { ns: 'pluginTrigger' }),
extra: <Tooltip popupContent={t('subscription.addType.options.manual.tip', { ns: 'pluginTrigger' })} />,
extra: (
<Tooltip>
<TooltipTrigger
render={(
<span className="flex h-3.5 w-3.5 shrink-0 p-px">
<span aria-hidden className="i-ri-question-line h-full w-full text-text-quaternary hover:text-text-tertiary" />
</span>
)}
/>
<TooltipContent>
{t('subscription.addType.options.manual.tip', { ns: 'pluginTrigger' })}
</TooltipContent>
</Tooltip>
),
show: supportedMethods.includes(SupportedCreationMethods.MANUAL),
},
]
@@ -196,30 +216,42 @@ export const CreateSubscriptionButton = ({ buttonType = CreateButtonType.FULL_BU
&& (
<div className="ml-auto flex items-center">
<div className="h-4 w-px bg-text-primary-on-surface opacity-15" />
<Tooltip popupContent={t('subscription.addType.options.oauth.clientSettings', { ns: 'pluginTrigger' })}>
<div onClick={onClickClientSettings} className="p-2">
<RiEqualizer2Line className="size-4 text-components-button-primary-text" />
</div>
<Tooltip>
<TooltipTrigger
render={(
<div onClick={onClickClientSettings} className="p-2">
<RiEqualizer2Line className="size-4 text-components-button-primary-text" />
</div>
)}
/>
<TooltipContent>
{t('subscription.addType.options.oauth.clientSettings', { ns: 'pluginTrigger' })}
</TooltipContent>
</Tooltip>
</div>
)}
</Button>
)
: (
<Tooltip
popupContent={subscriptionCount >= MAX_COUNT ? t('subscription.maxCount', { ns: 'pluginTrigger', num: MAX_COUNT }) : t(`subscription.addType.options.${methodType!.toLowerCase() as Lowercase<SupportedCreationMethods>}.description`, { ns: 'pluginTrigger' })}
disabled={!(supportedMethods?.length === 1 || subscriptionCount >= MAX_COUNT)}
>
<ActionButton
onClick={onClickCreate}
className={cn(
'float-right',
shape === 'circle' && 'rounded-full! border-[0.5px] border-components-button-secondary-border-hover bg-components-button-secondary-bg-hover text-components-button-secondary-accent-text shadow-xs hover:border-components-button-secondary-border-disabled hover:bg-components-button-secondary-bg-disabled hover:text-components-button-secondary-accent-text-disabled',
<Tooltip>
<TooltipTrigger
disabled={!(supportedMethods?.length === 1 || subscriptionCount >= MAX_COUNT)}
render={(
<ActionButton
onClick={onClickCreate}
className={cn(
'float-right',
shape === 'circle' && 'rounded-full! border-[0.5px] border-components-button-secondary-border-hover bg-components-button-secondary-bg-hover text-components-button-secondary-accent-text shadow-xs hover:border-components-button-secondary-border-disabled hover:bg-components-button-secondary-bg-disabled hover:text-components-button-secondary-accent-text-disabled',
)}
state={subscriptionCount >= MAX_COUNT ? ActionButtonState.Disabled : ActionButtonState.Default}
>
<RiAddLine className="size-4" />
</ActionButton>
)}
state={subscriptionCount >= MAX_COUNT ? ActionButtonState.Disabled : ActionButtonState.Default}
>
<RiAddLine className="size-4" />
</ActionButton>
/>
<TooltipContent>
{subscriptionCount >= MAX_COUNT ? t('subscription.maxCount', { ns: 'pluginTrigger', num: MAX_COUNT }) : t(`subscription.addType.options.${methodType!.toLowerCase() as Lowercase<SupportedCreationMethods>}.description`, { ns: 'pluginTrigger' })}
</TooltipContent>
</Tooltip>
)}
</SelectTrigger>

View File

@@ -1,9 +1,9 @@
'use client'
import type { PluginDetail } from '@/app/components/plugins/types'
import { cn } from '@langgenius/dify-ui/cn'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import Tooltip from '@/app/components/base/tooltip'
import { CreateButtonType, CreateSubscriptionButton } from './create'
import SubscriptionCard from './subscription-card'
import { useSubscriptionList } from './use-subscription-list'
@@ -30,7 +30,18 @@ export const SubscriptionListView: React.FC<SubscriptionListViewProps> = ({
<span className="system-sm-semibold-uppercase text-text-secondary">
{t('subscription.listNum', { ns: 'pluginTrigger', num: subscriptionCount })}
</span>
<Tooltip popupContent={t('subscription.list.tip', { ns: 'pluginTrigger' })} />
<Tooltip>
<TooltipTrigger
render={(
<span className="flex h-3.5 w-3.5 shrink-0 p-px">
<span aria-hidden className="i-ri-question-line h-full w-full text-text-quaternary hover:text-text-tertiary" />
</span>
)}
/>
<TooltipContent>
{t('subscription.list.tip', { ns: 'pluginTrigger' })}
</TooltipContent>
</Tooltip>
</div>
)}
<CreateSubscriptionButton

View File

@@ -1,12 +1,12 @@
'use client'
import type { TriggerSubscription } from '@/app/components/workflow/block-selector/types'
import { cn } from '@langgenius/dify-ui/cn'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { RiCheckLine, RiDeleteBinLine, RiWebhookLine } from '@remixicon/react'
import * as React from 'react'
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import ActionButton from '@/app/components/base/action-button'
import Tooltip from '@/app/components/base/tooltip'
import { CreateButtonType, CreateSubscriptionButton } from './create'
import { DeleteConfirm } from './delete-confirm'
import { useSubscriptionList } from './use-subscription-list'
@@ -33,7 +33,18 @@ export const SubscriptionSelectorView: React.FC<SubscriptionSelectorProps> = ({
<span className="system-sm-semibold-uppercase text-text-secondary">
{t('subscription.listNum', { ns: 'pluginTrigger', num: subscriptionCount })}
</span>
<Tooltip popupContent={t('subscription.list.tip', { ns: 'pluginTrigger' })} />
<Tooltip>
<TooltipTrigger
render={(
<span className="flex h-3.5 w-3.5 shrink-0 p-px">
<span aria-hidden className="i-ri-question-line h-full w-full text-text-quaternary hover:text-text-tertiary" />
</span>
)}
/>
<TooltipContent>
{t('subscription.list.tip', { ns: 'pluginTrigger' })}
</TooltipContent>
</Tooltip>
</div>
<CreateSubscriptionButton
buttonType={CreateButtonType.ICON_BUTTON}

View File

@@ -1,4 +1,5 @@
import { fireEvent, render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import ToolItem from '../tool-item'
@@ -26,21 +27,6 @@ vi.mock('@/app/components/workflow/nodes/_base/components/switch-plugin-version'
),
}))
vi.mock('@/app/components/base/tooltip', () => ({
default: ({
children,
popupContent,
}: {
children: React.ReactNode
popupContent: React.ReactNode
}) => (
<div>
{children}
<div>{popupContent}</div>
</div>
),
}))
describe('ToolItem', () => {
beforeEach(() => {
vi.clearAllMocks()
@@ -102,7 +88,7 @@ describe('ToolItem', () => {
expect(onInstall).toHaveBeenCalledTimes(2)
})
it('blocks unsupported MCP tools and still exposes error state', () => {
it('blocks unsupported MCP tools and still exposes error state', async () => {
mcpAllowed = false
const { rerender } = render(
<ToolItem
@@ -125,6 +111,7 @@ describe('ToolItem', () => {
/>,
)
expect(screen.getByText('tool failed')).toBeInTheDocument()
await userEvent.hover(screen.getByLabelText('tool failed'))
expect(await screen.findByText('tool failed')).toBeInTheDocument()
})
})

View File

@@ -2,6 +2,7 @@
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { Switch } from '@langgenius/dify-ui/switch'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import {
RiDeleteBinLine,
RiEqualizer2Line,
@@ -13,7 +14,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 Tooltip from '@/app/components/base/tooltip'
import { ToolTipContent } from '@/app/components/base/tooltip/content'
import Indicator from '@/app/components/header/indicator'
import { InstallPluginButton } from '@/app/components/workflow/nodes/_base/components/install-plugin-button'
@@ -167,12 +167,17 @@ const ToolItem = ({
/>
)}
{isError && (
<Tooltip
popupContent={errorTip}
>
<div>
<RiErrorWarningFill className="h-4 w-4 text-text-destructive" />
</div>
<Tooltip>
<TooltipTrigger
render={(
<div aria-label={typeof errorTip === 'string' ? errorTip : undefined}>
<RiErrorWarningFill className="h-4 w-4 text-text-destructive" />
</div>
)}
/>
<TooltipContent>
{errorTip}
</TooltipContent>
</Tooltip>
)}
</div>

View File

@@ -4,6 +4,7 @@ import type { Dependency, PluginDeclaration, PluginManifestInMarket } from '../t
import type { PluginPageTab } from './context'
import { Button } from '@langgenius/dify-ui/button'
import { cn } from '@langgenius/dify-ui/cn'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import {
RiBookOpenLine,
RiDragDropLine,
@@ -15,7 +16,6 @@ import { noop } from 'es-toolkit/function'
import { useEffect, useMemo, useState } from 'react'
import { useTranslation } from 'react-i18next'
import TabSlider from '@/app/components/base/tab-slider'
import Tooltip from '@/app/components/base/tooltip'
import ReferenceSettingModal from '@/app/components/plugins/reference-setting-modal'
import { MARKETPLACE_API_PREFIX, SUPPORT_INSTALL_LOCAL_FILE_EXTENSIONS } from '@/config'
import { useDocLink } from '@/context/i18n'
@@ -218,16 +218,21 @@ const PluginPage = ({
}
{
canSetPermissions && (
<Tooltip
popupContent={t('privilege.title', { ns: 'plugin' })}
>
<Button
data-testid="plugin-settings-button"
className="group h-full w-full p-2 text-components-button-secondary-text"
onClick={setShowPluginSettingModal}
>
<RiEqualizer2Line className="h-4 w-4" />
</Button>
<Tooltip>
<TooltipTrigger
render={(
<Button
data-testid="plugin-settings-button"
className="group h-full w-full p-2 text-components-button-secondary-text"
onClick={setShowPluginSettingModal}
>
<RiEqualizer2Line className="h-4 w-4" />
</Button>
)}
/>
<TooltipContent>
{t('privilege.title', { ns: 'plugin' })}
</TooltipContent>
</Tooltip>
)
}

View File

@@ -2,11 +2,11 @@
import type { FC } from 'react'
import type { ValueSelector, Var, VisionSetting } from '@/app/components/workflow/types'
import { Switch } from '@langgenius/dify-ui/switch'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { produce } from 'immer'
import * as React from 'react'
import { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import Tooltip from '@/app/components/base/tooltip'
import Field from '@/app/components/workflow/nodes/_base/components/field'
import ResolutionPicker from '@/app/components/workflow/nodes/llm/components/resolution-picker'
import { VarType } from '@/app/components/workflow/types'
@@ -61,11 +61,16 @@ const ConfigVision: FC<Props> = ({
title={t(`${i18nPrefix}.vision`, { ns: 'workflow' })}
tooltip={t('vision.description', { ns: 'appDebug' })!}
operations={(
<Tooltip
popupContent={t('vision.onlySupportVisionModelTip', { ns: 'appDebug' })!}
disabled={isVisionModel}
>
<Switch disabled={readOnly || !isVisionModel} size="md" checked={!isVisionModel ? false : enabled} onCheckedChange={onEnabledChange} />
<Tooltip>
<TooltipTrigger
disabled={isVisionModel}
render={(
<Switch disabled={readOnly || !isVisionModel} size="md" checked={!isVisionModel ? false : enabled} onCheckedChange={onEnabledChange} />
)}
/>
<TooltipContent>
{t('vision.onlySupportVisionModelTip', { ns: 'appDebug' })!}
</TooltipContent>
</Tooltip>
)}
>

View File

@@ -5,6 +5,7 @@ import type {
NodeOutPutVar,
} from '@/app/components/workflow/types'
import { cn } from '@langgenius/dify-ui/cn'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { useBoolean } from 'ahooks'
import { noop } from 'es-toolkit/function'
import * as React from 'react'
@@ -12,7 +13,6 @@ import { useEffect } from 'react'
import { useTranslation } from 'react-i18next'
import { Variable02 } from '@/app/components/base/icons/src/vender/solid/development'
import PromptEditor from '@/app/components/base/prompt-editor'
import Tooltip from '@/app/components/base/tooltip'
import { useStore } from '@/app/components/workflow/store'
import { BlockEnum } from '@/app/components/workflow/types'
@@ -119,12 +119,17 @@ const Editor: FC<Props> = ({
{readOnly && <div className="absolute inset-0 z-10"></div>}
{isFocus && (
<div className={cn('absolute z-10', insertVarTipToLeft ? 'top-1.5 left-[-12px]' : 'top-[-9px] right-1')}>
<Tooltip
popupContent={`${t('common.insertVarTip', { ns: 'workflow' })}`}
>
<div className="cursor-pointer rounded-[5px] border-[0.5px] border-divider-regular bg-components-badge-white-to-dark p-0.5 shadow-lg">
<Variable02 className="h-3.5 w-3.5 text-components-button-secondary-accent-text" />
</div>
<Tooltip>
<TooltipTrigger
render={(
<div className="cursor-pointer rounded-[5px] border-[0.5px] border-divider-regular bg-components-badge-white-to-dark p-0.5 shadow-lg">
<Variable02 className="h-3.5 w-3.5 text-components-button-secondary-accent-text" />
</div>
)}
/>
<TooltipContent>
{`${t('common.insertVarTip', { ns: 'workflow' })}`}
</TooltipContent>
</Tooltip>
</div>
)}

View File

@@ -1,7 +1,7 @@
import type { ComponentProps, PropsWithChildren, ReactNode } from 'react'
import { cn } from '@langgenius/dify-ui/cn'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { memo } from 'react'
import Tooltip from '@/app/components/base/tooltip'
import Indicator from '@/app/components/header/indicator'
type SettingItemProps = PropsWithChildren<{
@@ -18,10 +18,18 @@ export const SettingItem = memo(({ label, children, status, tooltip }: SettingIt
<div className={cn('max-w-full shrink-0 truncate system-xs-medium-uppercase text-text-tertiary', !!children && 'max-w-[100px]')}>
{label}
</div>
<Tooltip popupContent={tooltip} disabled={!needTooltip}>
<div className="truncate text-right system-xs-medium text-text-secondary">
{children}
</div>
<Tooltip>
<TooltipTrigger
disabled={!needTooltip}
render={(
<div className="truncate text-right system-xs-medium text-text-secondary">
{children}
</div>
)}
/>
<TooltipContent>
{tooltip}
</TooltipContent>
</Tooltip>
{indicator && <Indicator color={indicator} className="absolute -top-0.5 -right-0.5" />}
</div>

View File

@@ -3,10 +3,10 @@ import type { FC } from 'react'
import type { Field as FieldType } from '../../../../../llm/types'
import type { ValueSelector } from '@/app/components/workflow/types'
import { cn } from '@langgenius/dify-ui/cn'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { RiMoreFill } from '@remixicon/react'
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import Tooltip from '@/app/components/base/tooltip'
import { Type } from '../../../../../llm/types'
import { getFieldType } from '../../../../../llm/utils'
import TreeIndentLine from '../tree-indent-line'
@@ -38,24 +38,32 @@ const Field: FC<Props> = ({
return null
return (
<div>
<Tooltip popupContent={t('structOutput.moreFillTip', { ns: 'app' })} disabled={depth !== MAX_DEPTH + 1}>
<div
className={cn('flex items-center justify-between rounded-md pr-2', !readonly && 'hover:bg-state-base-hover', depth !== MAX_DEPTH + 1 && 'cursor-pointer')}
onMouseDown={() => !readonly && onSelect?.([...valueSelector, name])}
>
<div className="flex grow items-stretch">
<TreeIndentLine depth={depth} />
{depth === MAX_DEPTH + 1
? (
<RiMoreFill className="h-3 w-3 text-text-tertiary" />
)
: (<div className={cn('h-6 w-0 grow truncate system-sm-medium leading-6 text-text-secondary', isHighlight && 'text-text-accent')}>{name}</div>)}
<Tooltip>
<TooltipTrigger
disabled={depth !== MAX_DEPTH + 1}
render={(
<div
className={cn('flex items-center justify-between rounded-md pr-2', !readonly && 'hover:bg-state-base-hover', depth !== MAX_DEPTH + 1 && 'cursor-pointer')}
onMouseDown={() => !readonly && onSelect?.([...valueSelector, name])}
>
<div className="flex grow items-stretch">
<TreeIndentLine depth={depth} />
{depth === MAX_DEPTH + 1
? (
<RiMoreFill className="h-3 w-3 text-text-tertiary" />
)
: (<div className={cn('h-6 w-0 grow truncate system-sm-medium leading-6 text-text-secondary', isHighlight && 'text-text-accent')}>{name}</div>)}
</div>
{depth < MAX_DEPTH + 1 && (
<div className="ml-2 shrink-0 system-xs-regular text-text-tertiary">{getFieldType(payload)}</div>
</div>
{depth < MAX_DEPTH + 1 && (
<div className="ml-2 shrink-0 system-xs-regular text-text-tertiary">{getFieldType(payload)}</div>
)}
</div>
)}
</div>
/>
<TooltipContent>
{t('structOutput.moreFillTip', { ns: 'app' })}
</TooltipContent>
</Tooltip>
{depth <= MAX_DEPTH && payload.type === Type.object && payload.properties && (

View File

@@ -3,10 +3,10 @@ import type {
Node,
NodeOutPutVar,
} from '@/app/components/workflow/types'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { produce } from 'immer'
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import Tooltip from '@/app/components/base/tooltip'
import { useNodesSyncDraft } from '@/app/components/workflow/hooks'
import MethodItem from './method-item'
import MethodSelector from './method-selector'
@@ -71,9 +71,18 @@ const DeliveryMethodForm: React.FC<Props> = ({
<div className="mb-1 flex items-center justify-between">
<div className="flex items-center gap-0.5">
<div className="system-sm-semibold-uppercase text-text-secondary">{t(`${i18nPrefix}.deliveryMethod.title`, { ns: 'workflow' })}</div>
<Tooltip
popupContent={t(`${i18nPrefix}.deliveryMethod.tooltip`, { ns: 'workflow' })}
/>
<Tooltip>
<TooltipTrigger
render={(
<span className="flex h-3.5 w-3.5 shrink-0 p-px">
<span aria-hidden className="i-ri-question-line h-full w-full text-text-quaternary hover:text-text-tertiary" />
</span>
)}
/>
<TooltipContent>
{t(`${i18nPrefix}.deliveryMethod.tooltip`, { ns: 'workflow' })}
</TooltipContent>
</Tooltip>
</div>
{!readonly && (
<div className="flex items-center px-1">

View File

@@ -1,5 +1,6 @@
import { cn } from '@langgenius/dify-ui/cn'
import { Slider } from '@langgenius/dify-ui/slider'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { RiQuestionLine } from '@remixicon/react'
import {
memo,
@@ -11,7 +12,6 @@ import {
HighQuality,
} from '@/app/components/base/icons/src/vender/knowledge'
import Input from '@/app/components/base/input'
import Tooltip from '@/app/components/base/tooltip'
import { Field } from '@/app/components/workflow/nodes/_base/components/layout'
import {
ChunkStructureEnum,
@@ -97,10 +97,13 @@ const IndexMethod = ({
<div className="truncate system-xs-medium text-text-secondary">
{t('form.numberOfKeywords', { ns: 'datasetSettings' })}
</div>
<Tooltip
popupContent="number of keywords"
>
<RiQuestionLine className="ml-0.5 h-3.5 w-3.5 text-text-quaternary" />
<Tooltip>
<TooltipTrigger
render={<RiQuestionLine className="ml-0.5 h-3.5 w-3.5 text-text-quaternary" />}
/>
<TooltipContent>
number of keywords
</TooltipContent>
</Tooltip>
</div>
<Slider

View File

@@ -2,12 +2,12 @@ import type { FC } from 'react'
import type { FormValue } from '@/app/components/header/account-setting/model-provider-page/declarations'
import type { Model } from '@/types/app'
import { Button } from '@langgenius/dify-ui/button'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { RiCloseLine, RiSparklingFill } from '@remixicon/react'
import * as React from 'react'
import { useCallback } from 'react'
import { useTranslation } from 'react-i18next'
import Textarea from '@/app/components/base/textarea'
import Tooltip from '@/app/components/base/tooltip'
import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal'
type ModelInfo = {
@@ -75,7 +75,18 @@ const PromptEditor: FC<PromptEditorProps> = ({
<div className="flex flex-col gap-y-1 px-4 py-2">
<div className="flex h-6 items-center system-sm-semibold-uppercase text-text-secondary">
<span>{t('nodes.llm.jsonSchema.instruction', { ns: 'workflow' })}</span>
<Tooltip popupContent={t('nodes.llm.jsonSchema.promptTooltip', { ns: 'workflow' })} />
<Tooltip>
<TooltipTrigger
render={(
<span className="flex h-3.5 w-3.5 shrink-0 p-px">
<span aria-hidden className="i-ri-question-line h-full w-full text-text-quaternary hover:text-text-tertiary" />
</span>
)}
/>
<TooltipContent>
{t('nodes.llm.jsonSchema.promptTooltip', { ns: 'workflow' })}
</TooltipContent>
</Tooltip>
</div>
<div className="flex items-center">
<Textarea

View File

@@ -1,7 +1,7 @@
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { RiQuestionLine } from '@remixicon/react'
import * as React from 'react'
import { useTranslation } from 'react-i18next'
import Tooltip from '@/app/components/base/tooltip'
type MonthlyDaysSelectorProps = {
selectedDays: (number | 'last')[]
@@ -57,10 +57,15 @@ const MonthlyDaysSelector = ({ selectedDays, onChange }: MonthlyDaysSelectorProp
? (
<div className="flex items-center justify-center gap-1">
<span>{t('nodes.triggerSchedule.lastDay', { ns: 'workflow' })}</span>
<Tooltip
popupContent={t('nodes.triggerSchedule.lastDayTooltip', { ns: 'workflow' })}
>
<RiQuestionLine className="h-3 w-3 text-text-quaternary" />
<Tooltip>
<TooltipTrigger
render={(
<RiQuestionLine className="h-3 w-3 text-text-quaternary" />
)}
/>
<TooltipContent>
{t('nodes.triggerSchedule.lastDayTooltip', { ns: 'workflow' })}
</TooltipContent>
</Tooltip>
</div>
)

View File

@@ -1,6 +1,7 @@
import type { StartNodeType } from '../../nodes/start/types'
import { cn } from '@langgenius/dify-ui/cn'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { RiCloseLine, RiEqualizer2Line } from '@remixicon/react'
import { debounce } from 'es-toolkit/compat'
import { noop } from 'es-toolkit/function'
@@ -15,7 +16,6 @@ import { useTranslation } from 'react-i18next'
import { useNodes } from 'reactflow'
import ActionButton, { ActionButtonState } from '@/app/components/base/action-button'
import { RefreshCcw01 } from '@/app/components/base/icons/src/vender/line/arrows'
import Tooltip from '@/app/components/base/tooltip'
import { useEdgesInteractionsWithoutSync } from '@/app/components/workflow/hooks/use-edges-interactions-without-sync'
import { useNodesInteractionsWithoutSync } from '@/app/components/workflow/hooks/use-nodes-interactions-without-sync'
import { useStore } from '@/app/components/workflow/store'
@@ -99,21 +99,31 @@ const DebugAndPreview = () => {
<div className="flex shrink-0 items-center justify-between px-4 pt-3 pb-2 system-xl-semibold text-text-primary">
<div className="h-8">{t('common.debugAndPreview', { ns: 'workflow' }).toLocaleUpperCase()}</div>
<div className="flex items-center gap-1">
<Tooltip
popupContent={t('operation.refresh', { ns: 'common' })}
>
<ActionButton onClick={() => handleRestartChat()}>
<RefreshCcw01 className="h-4 w-4" />
</ActionButton>
<Tooltip>
<TooltipTrigger
render={(
<ActionButton onClick={() => handleRestartChat()}>
<RefreshCcw01 className="h-4 w-4" />
</ActionButton>
)}
/>
<TooltipContent>
{t('operation.refresh', { ns: 'common' })}
</TooltipContent>
</Tooltip>
{visibleVariables.length > 0 && (
<div className="relative">
<Tooltip
popupContent={t('panel.userInputField', { ns: 'workflow' })}
>
<ActionButton state={expanded ? ActionButtonState.Active : undefined} onClick={() => setExpanded(!expanded)}>
<RiEqualizer2Line className="h-4 w-4" />
</ActionButton>
<Tooltip>
<TooltipTrigger
render={(
<ActionButton state={expanded ? ActionButtonState.Active : undefined} onClick={() => setExpanded(!expanded)}>
<RiEqualizer2Line className="h-4 w-4" />
</ActionButton>
)}
/>
<TooltipContent>
{t('panel.userInputField', { ns: 'workflow' })}
</TooltipContent>
</Tooltip>
{expanded && <div className="absolute right-[5px] bottom-[-17px] z-10 h-3 w-3 rotate-45 border-t-[0.5px] border-l-[0.5px] border-components-panel-border-subtle bg-components-panel-on-panel-item-bg" />}
</div>

View File

@@ -1,6 +1,7 @@
import type { currentVarType } from './panel'
import type { NodeWithVar, VarInInspect } from '@/types/workflow'
import { cn } from '@langgenius/dify-ui/cn'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import {
RiArrowRightSLine,
RiDeleteBinLine,
@@ -12,7 +13,6 @@ import { useState } from 'react'
import { useTranslation } from 'react-i18next'
// import { Button } from '@langgenius/dify-ui/button'
import ActionButton from '@/app/components/base/action-button'
import Tooltip from '@/app/components/base/tooltip'
import BlockIcon from '@/app/components/workflow/block-icon'
import { VariableIconWithColor } from '@/app/components/workflow/nodes/_base/components/variable/variable-label'
import { VarInInspectType } from '@/types/workflow'
@@ -130,15 +130,29 @@ const Group = ({
</div>
{nodeData && !nodeData.isSingRunRunning && (
<div className="hidden shrink-0 items-center group-hover:flex">
<Tooltip popupContent={t('debug.variableInspect.view', { ns: 'workflow' })}>
<ActionButton onClick={handleView}>
<RiFileList3Line className="h-4 w-4" />
</ActionButton>
<Tooltip>
<TooltipTrigger
render={(
<ActionButton onClick={handleView}>
<RiFileList3Line className="h-4 w-4" />
</ActionButton>
)}
/>
<TooltipContent>
{t('debug.variableInspect.view', { ns: 'workflow' })}
</TooltipContent>
</Tooltip>
<Tooltip popupContent={t('debug.variableInspect.clearNode', { ns: 'workflow' })}>
<ActionButton onClick={handleClear}>
<RiDeleteBinLine className="h-4 w-4" />
</ActionButton>
<Tooltip>
<TooltipTrigger
render={(
<ActionButton onClick={handleClear}>
<RiDeleteBinLine className="h-4 w-4" />
</ActionButton>
)}
/>
<TooltipContent>
{t('debug.variableInspect.clearNode', { ns: 'workflow' })}
</TooltipContent>
</Tooltip>
</div>
)}

View File

@@ -1,6 +1,7 @@
import type { currentVarType } from './panel'
import type { GenRes } from '@/service/debug'
import { cn } from '@langgenius/dify-ui/cn'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import {
RiArrowGoBackLine,
RiCloseLine,
@@ -17,7 +18,6 @@ import ActionButton from '@/app/components/base/action-button'
import Badge from '@/app/components/base/badge'
import CopyFeedback from '@/app/components/base/copy-feedback'
import Loading from '@/app/components/base/loading'
import Tooltip from '@/app/components/base/tooltip'
import BlockIcon from '@/app/components/workflow/block-icon'
import { VariableIconWithColor } from '@/app/components/workflow/nodes/_base/components/variable/variable-label'
import { useEventEmitterContextContext } from '@/context/event-emitter'
@@ -217,25 +217,39 @@ const Right = ({
{currentNodeVar && (
<>
{canShowPromptGenerator && (
<Tooltip popupContent={t('generate.optimizePromptTooltip', { ns: 'appDebug' })}>
<div
className="cursor-pointer rounded-md p-1 hover:bg-state-accent-active"
onClick={handleShowPromptGenerator}
>
<RiSparklingFill className="size-4 text-components-input-border-active-prompt-1" />
</div>
<Tooltip>
<TooltipTrigger
render={(
<div
className="cursor-pointer rounded-md p-1 hover:bg-state-accent-active"
onClick={handleShowPromptGenerator}
>
<RiSparklingFill className="size-4 text-components-input-border-active-prompt-1" />
</div>
)}
/>
<TooltipContent>
{t('generate.optimizePromptTooltip', { ns: 'appDebug' })}
</TooltipContent>
</Tooltip>
)}
{isTruncated && (
<Tooltip popupContent={t('debug.variableInspect.exportToolTip', { ns: 'workflow' })}>
<ActionButton>
<a
href={fullContent?.download_url}
target="_blank"
>
<RiFileDownloadFill className="size-4" />
</a>
</ActionButton>
<Tooltip>
<TooltipTrigger
render={(
<ActionButton>
<a
href={fullContent?.download_url}
target="_blank"
>
<RiFileDownloadFill className="size-4" />
</a>
</ActionButton>
)}
/>
<TooltipContent>
{t('debug.variableInspect.exportToolTip', { ns: 'workflow' })}
</TooltipContent>
</Tooltip>
)}
{!isTruncated && currentNodeVar.var.edited && (
@@ -245,17 +259,31 @@ const Right = ({
</Badge>
)}
{!isTruncated && currentNodeVar.var.edited && currentNodeVar.var.type !== VarInInspectType.conversation && (
<Tooltip popupContent={t('debug.variableInspect.reset', { ns: 'workflow' })}>
<ActionButton onClick={resetValue}>
<RiArrowGoBackLine className="h-4 w-4" />
</ActionButton>
<Tooltip>
<TooltipTrigger
render={(
<ActionButton onClick={resetValue}>
<RiArrowGoBackLine className="h-4 w-4" />
</ActionButton>
)}
/>
<TooltipContent>
{t('debug.variableInspect.reset', { ns: 'workflow' })}
</TooltipContent>
</Tooltip>
)}
{!isTruncated && currentNodeVar.var.edited && currentNodeVar.var.type === VarInInspectType.conversation && (
<Tooltip popupContent={t('debug.variableInspect.resetConversationVar', { ns: 'workflow' })}>
<ActionButton onClick={handleClear}>
<RiArrowGoBackLine className="h-4 w-4" />
</ActionButton>
<Tooltip>
<TooltipTrigger
render={(
<ActionButton onClick={handleClear}>
<RiArrowGoBackLine className="h-4 w-4" />
</ActionButton>
)}
/>
<TooltipContent>
{t('debug.variableInspect.resetConversationVar', { ns: 'workflow' })}
</TooltipContent>
</Tooltip>
)}
{currentNodeVar.var.value_type !== 'secret' && (

View File

@@ -1,11 +1,11 @@
import type { FC } from 'react'
import type { CommonNodeType } from '@/app/components/workflow/types'
import { cn } from '@langgenius/dify-ui/cn'
import { Tooltip, TooltipContent, TooltipTrigger } from '@langgenius/dify-ui/tooltip'
import { RiLoader2Line, RiStopCircleFill } from '@remixicon/react'
import { useMemo } from 'react'
import { useTranslation } from 'react-i18next'
import { useNodes } from 'reactflow'
import Tooltip from '@/app/components/base/tooltip'
import { NodeRunningStatus, WorkflowRunningStatus } from '@/app/components/workflow/types'
import { EVENT_WORKFLOW_STOP } from '@/app/components/workflow/variable-inspect/types'
import { useEventEmitterContextContext } from '@/context/event-emitter'
@@ -108,15 +108,20 @@ const VariableInspectTrigger: FC = () => {
<span className="text-text-accent">{t('debug.variableInspect.trigger.running', { ns: 'workflow' })}</span>
</div>
{isPreviewRunning && (
<Tooltip
popupContent={t('debug.variableInspect.trigger.stop', { ns: 'workflow' })}
>
<div
className="flex h-6 cursor-pointer items-center rounded-md border-[0.5px] border-effects-highlight bg-components-actionbar-bg px-1 shadow-lg backdrop-blur-xs hover:bg-components-actionbar-bg-accent"
onClick={handleStop}
>
<RiStopCircleFill className="h-4 w-4 text-text-accent" />
</div>
<Tooltip>
<TooltipTrigger
render={(
<div
className="flex h-6 cursor-pointer items-center rounded-md border-[0.5px] border-effects-highlight bg-components-actionbar-bg px-1 shadow-lg backdrop-blur-xs hover:bg-components-actionbar-bg-accent"
onClick={handleStop}
>
<RiStopCircleFill className="h-4 w-4 text-text-accent" />
</div>
)}
/>
<TooltipContent>
{t('debug.variableInspect.trigger.stop', { ns: 'workflow' })}
</TooltipContent>
</Tooltip>
)}
</>