mirror of
https://github.com/langgenius/dify.git
synced 2026-09-24 23:22:26 +08:00
refactor(ui): simplify scroll area API (#39857)
This commit is contained in:
@@ -1,7 +1,13 @@
|
||||
'use client'
|
||||
|
||||
import type { ResourceOpenScope } from '@/models/access-control'
|
||||
import { ScrollArea } from '@langgenius/dify-ui/scroll-area'
|
||||
import {
|
||||
ScrollArea,
|
||||
ScrollAreaContent,
|
||||
ScrollAreaScrollbar,
|
||||
ScrollAreaThumb,
|
||||
ScrollAreaViewport,
|
||||
} from '@langgenius/dify-ui/scroll-area'
|
||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
@@ -89,35 +95,39 @@ const AppAccessConfigContent = ({ appId, maintainerId }: AppAccessConfigContentP
|
||||
)
|
||||
|
||||
return (
|
||||
<ScrollArea
|
||||
className="h-full bg-background-default-subtle"
|
||||
slotClassNames={{ viewport: 'overscroll-contain' }}
|
||||
>
|
||||
<header className="flex min-h-15.5 flex-col justify-center px-6 py-3">
|
||||
<h1 className="system-xl-semibold text-text-primary">
|
||||
{t(($) => $['settings.resourceAccess'], { ns: 'common' })}
|
||||
</h1>
|
||||
<p className="mt-0.5 system-sm-regular text-text-tertiary">
|
||||
{t(($) => $['accessRule.appDescription'], { ns: 'permission' })}
|
||||
</p>
|
||||
</header>
|
||||
<main className="w-full px-6 pt-8 pb-10">
|
||||
<AccessRulesEditor
|
||||
className="w-full max-w-200"
|
||||
rules={appAccessRules}
|
||||
userAccessSettings={appUserAccessSettings}
|
||||
isLoadingRules={isLoadingAppAccessRules}
|
||||
isLoadingUserAccessSettings={isLoadingAppUserAccessSettings}
|
||||
openScope={openScope}
|
||||
isUpdatingOpenScope={isLoadingAppUserAccessSettings || isUpdatingAppOpenScope}
|
||||
updatingAccountId={updatingAccountId}
|
||||
maintainerId={maintainerId}
|
||||
onOpenScopeChange={handleOpenScopeChange}
|
||||
onUserAccessPoliciesChange={handleUserAccessPoliciesChange}
|
||||
onRemoveAccessPolicyMemberBinding={handleRemoveAccessPolicyMemberBinding}
|
||||
onAddAccessSubject={handleUserAccessPoliciesChange}
|
||||
/>
|
||||
</main>
|
||||
<ScrollArea className="h-full bg-background-default-subtle">
|
||||
<ScrollAreaViewport className="overscroll-contain">
|
||||
<ScrollAreaContent>
|
||||
<header className="flex min-h-15.5 flex-col justify-center px-6 py-3">
|
||||
<h1 className="system-xl-semibold text-text-primary">
|
||||
{t(($) => $['settings.resourceAccess'], { ns: 'common' })}
|
||||
</h1>
|
||||
<p className="mt-0.5 system-sm-regular text-text-tertiary">
|
||||
{t(($) => $['accessRule.appDescription'], { ns: 'permission' })}
|
||||
</p>
|
||||
</header>
|
||||
<main className="w-full px-6 pt-8 pb-10">
|
||||
<AccessRulesEditor
|
||||
className="w-full max-w-200"
|
||||
rules={appAccessRules}
|
||||
userAccessSettings={appUserAccessSettings}
|
||||
isLoadingRules={isLoadingAppAccessRules}
|
||||
isLoadingUserAccessSettings={isLoadingAppUserAccessSettings}
|
||||
openScope={openScope}
|
||||
isUpdatingOpenScope={isLoadingAppUserAccessSettings || isUpdatingAppOpenScope}
|
||||
updatingAccountId={updatingAccountId}
|
||||
maintainerId={maintainerId}
|
||||
onOpenScopeChange={handleOpenScopeChange}
|
||||
onUserAccessPoliciesChange={handleUserAccessPoliciesChange}
|
||||
onRemoveAccessPolicyMemberBinding={handleRemoveAccessPolicyMemberBinding}
|
||||
onAddAccessSubject={handleUserAccessPoliciesChange}
|
||||
/>
|
||||
</main>
|
||||
</ScrollAreaContent>
|
||||
</ScrollAreaViewport>
|
||||
<ScrollAreaScrollbar>
|
||||
<ScrollAreaThumb />
|
||||
</ScrollAreaScrollbar>
|
||||
</ScrollArea>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -8,7 +8,13 @@ import { Dialog, DialogCloseButton, DialogContent, DialogTitle } from '@langgeni
|
||||
import { Field, FieldControl, FieldDescription, FieldLabel } from '@langgenius/dify-ui/field'
|
||||
import { Form } from '@langgenius/dify-ui/form'
|
||||
import { Input } from '@langgenius/dify-ui/input'
|
||||
import { ScrollArea } from '@langgenius/dify-ui/scroll-area'
|
||||
import {
|
||||
ScrollArea,
|
||||
ScrollAreaContent,
|
||||
ScrollAreaScrollbar,
|
||||
ScrollAreaThumb,
|
||||
ScrollAreaViewport,
|
||||
} from '@langgenius/dify-ui/scroll-area'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -369,355 +375,367 @@ const SettingsModal: FC<ISettingsModalProps> = ({
|
||||
onFormSubmit={handleFormSubmit}
|
||||
>
|
||||
{/* form body */}
|
||||
<ScrollArea
|
||||
className="relative min-h-0"
|
||||
slotClassNames={{
|
||||
viewport: 'overscroll-contain',
|
||||
content: 'min-w-0 space-y-5 px-6 py-3',
|
||||
}}
|
||||
>
|
||||
{/* name & icon */}
|
||||
<div className="flex gap-4">
|
||||
<Field name="title" className="grow">
|
||||
<FieldLabel>
|
||||
{t(($) => $[`${prefixSettings}.webName`], { ns: 'appOverview' })}
|
||||
</FieldLabel>
|
||||
<FieldControl
|
||||
value={inputInfo.title}
|
||||
onValueChange={(value) => setInputInfo((item) => ({ ...item, title: value }))}
|
||||
placeholder={t(($) => $.appNamePlaceholder, { ns: 'app' }) || ''}
|
||||
/>
|
||||
</Field>
|
||||
<AppIcon
|
||||
size="xxl"
|
||||
onClick={() => {
|
||||
setShowAppIconPicker(true)
|
||||
}}
|
||||
className="mt-2 cursor-pointer"
|
||||
iconType={appIcon.type === 'link' ? 'image' : appIcon.type}
|
||||
icon={appIcon.type === 'image' ? appIcon.fileId : appIcon.icon}
|
||||
background={appIcon.type === 'emoji' ? appIcon.background : undefined}
|
||||
imageUrl={appIcon.type === 'emoji' ? undefined : appIcon.url}
|
||||
/>
|
||||
</div>
|
||||
{/* description */}
|
||||
<Field name="description">
|
||||
<FieldLabel>
|
||||
{t(($) => $[`${prefixSettings}.webDesc`], { ns: 'appOverview' })}
|
||||
</FieldLabel>
|
||||
<Textarea
|
||||
value={inputInfo.desc}
|
||||
onValueChange={onDesChange}
|
||||
placeholder={
|
||||
t(($) => $[`${prefixSettings}.webDescPlaceholder`], {
|
||||
ns: 'appOverview',
|
||||
}) as string
|
||||
}
|
||||
/>
|
||||
<FieldDescription>
|
||||
{t(($) => $[`${prefixSettings}.webDescTip`], { ns: 'appOverview' })}
|
||||
</FieldDescription>
|
||||
</Field>
|
||||
<Divider className="my-0 h-px" />
|
||||
{/* answer icon */}
|
||||
{isChat && (
|
||||
<Field name="use_icon_as_answer_icon" className="w-full">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<FieldLabel>{t(($) => $['answerIcon.title'], { ns: 'app' })}</FieldLabel>
|
||||
<Switch
|
||||
checked={inputInfo.use_icon_as_answer_icon}
|
||||
onCheckedChange={(v) =>
|
||||
setInputInfo({ ...inputInfo, use_icon_as_answer_icon: v })
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<FieldDescription>
|
||||
{t(($) => $['answerIcon.description'], { ns: 'app' })}
|
||||
</FieldDescription>
|
||||
</Field>
|
||||
)}
|
||||
{/* language */}
|
||||
<div className="flex items-center">
|
||||
<div className={cn('grow py-1 system-sm-semibold text-text-secondary')}>
|
||||
{t(($) => $[`${prefixSettings}.language`], { ns: 'appOverview' })}
|
||||
</div>
|
||||
<Select
|
||||
value={selectedLanguage?.value ?? null}
|
||||
onValueChange={handleLanguageChange}
|
||||
>
|
||||
<SelectTrigger
|
||||
aria-label={t(($) => $[`${prefixSettings}.language`], { ns: 'appOverview' })}
|
||||
size="medium"
|
||||
className="w-50"
|
||||
>
|
||||
{selectedLanguage?.name ?? t(($) => $['placeholder.select'], { ns: 'common' })}
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{LANGUAGE_OPTIONS.map((item) => (
|
||||
<SelectItem key={item.value} value={item.value}>
|
||||
<SelectItemText>{item.name}</SelectItemText>
|
||||
<SelectItemIndicator />
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
{/* theme color */}
|
||||
{isChat && (
|
||||
<div className="flex items-center">
|
||||
<div className="grow">
|
||||
<div className={cn('py-1 system-sm-semibold text-text-secondary')}>
|
||||
{t(($) => $[`${prefixSettings}.chatColorTheme`], { ns: 'appOverview' })}
|
||||
</div>
|
||||
<div className="pb-0.5 body-xs-regular text-text-tertiary">
|
||||
{t(($) => $[`${prefixSettings}.chatColorThemeDesc`], { ns: 'appOverview' })}
|
||||
</div>
|
||||
</div>
|
||||
<Field name="chat_color_theme" className="w-50 shrink-0">
|
||||
<FieldControl
|
||||
className="mb-1"
|
||||
value={inputInfo.chatColorTheme ?? ''}
|
||||
onValueChange={(value) =>
|
||||
setInputInfo((item) => ({ ...item, chatColorTheme: value }))
|
||||
}
|
||||
placeholder="E.g #A020F0"
|
||||
/>
|
||||
<div className="flex items-center justify-between gap-2 body-xs-regular text-text-tertiary">
|
||||
<span>
|
||||
{t(($) => $[`${prefixSettings}.chatColorThemeInverted`], {
|
||||
ns: 'appOverview',
|
||||
})}
|
||||
</span>
|
||||
<Switch
|
||||
checked={inputInfo.chatColorThemeInverted}
|
||||
onCheckedChange={(v) =>
|
||||
setInputInfo({ ...inputInfo, chatColorThemeInverted: v })
|
||||
}
|
||||
></Switch>
|
||||
</div>
|
||||
</Field>
|
||||
</div>
|
||||
)}
|
||||
{/* workflow detail */}
|
||||
<Field name="show_workflow_steps" className="w-full">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<FieldLabel>
|
||||
{t(($) => $[`${prefixSettings}.workflow.subTitle`], { ns: 'appOverview' })}
|
||||
</FieldLabel>
|
||||
<Switch
|
||||
disabled={
|
||||
!(
|
||||
appInfo.mode === AppModeEnum.WORKFLOW ||
|
||||
appInfo.mode === AppModeEnum.ADVANCED_CHAT
|
||||
)
|
||||
}
|
||||
checked={inputInfo.show_workflow_steps}
|
||||
onCheckedChange={(v) => setInputInfo({ ...inputInfo, show_workflow_steps: v })}
|
||||
/>
|
||||
</div>
|
||||
<FieldDescription>
|
||||
{t(($) => $[`${prefixSettings}.workflow.showDesc`], { ns: 'appOverview' })}
|
||||
</FieldDescription>
|
||||
</Field>
|
||||
<Divider className="my-0 h-px" />
|
||||
<div className="space-y-5">
|
||||
{INPUT_PLACEHOLDER_SUPPORTED_MODES.includes(appInfo.mode) && (
|
||||
<div className="w-full">
|
||||
<div className="flex items-center">
|
||||
<div className="flex grow items-center">
|
||||
<div
|
||||
id={inputPlaceholderLabelId}
|
||||
className={cn('mr-1 py-1 system-sm-semibold text-text-secondary')}
|
||||
>
|
||||
{t(($) => $[`${prefixSettings}.more.inputPlaceholder`], {
|
||||
ns: 'appOverview',
|
||||
})}
|
||||
</div>
|
||||
{isCloudSandboxPlan && (
|
||||
<div className="h-4.5 select-none">
|
||||
<PremiumBadgeButton size="s" color="blue" onClick={handlePlanClick}>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="i-custom-public-common-sparkles-soft flex h-3.5 w-3.5 items-center py-px pl-0.75 text-components-premium-badge-indigo-text-stop-0"
|
||||
/>
|
||||
<div className="system-xs-medium">
|
||||
<span className="p-1">
|
||||
{t(($) => $['upgradeBtn.encourageShort'], { ns: 'billing' })}
|
||||
</span>
|
||||
</div>
|
||||
</PremiumBadgeButton>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<p
|
||||
id={inputPlaceholderDescriptionId}
|
||||
className="pb-0.5 body-xs-regular text-text-tertiary"
|
||||
>
|
||||
{t(($) => $[`${prefixSettings}.more.inputPlaceholderTip`], {
|
||||
ns: 'appOverview',
|
||||
})}
|
||||
</p>
|
||||
{isCloudSandboxPlan ? (
|
||||
<Tooltip>
|
||||
<TooltipTrigger render={inputPlaceholderField} />
|
||||
<TooltipContent className="w-45">
|
||||
{t(($) => $[`${prefixSettings}.more.inputPlaceholderTooltip`], {
|
||||
ns: 'appOverview',
|
||||
})}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
) : (
|
||||
inputPlaceholderField
|
||||
)}
|
||||
{!isCloudSandboxPlan && (
|
||||
<div className="mt-1 text-right body-xs-regular text-text-tertiary">
|
||||
{`${inputInfo.inputPlaceholder?.length ?? 0} / ${INPUT_PLACEHOLDER_MAX_LENGTH}`}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{/* copyright */}
|
||||
<div className="w-full">
|
||||
<div className="flex items-center">
|
||||
<div className="flex grow items-center">
|
||||
<div className={cn('mr-1 py-1 system-sm-semibold text-text-secondary')}>
|
||||
{t(($) => $[`${prefixSettings}.more.copyright`], { ns: 'appOverview' })}
|
||||
</div>
|
||||
{/* upgrade button */}
|
||||
{isCloudSandboxPlan && (
|
||||
<div className="h-4.5 select-none">
|
||||
<PremiumBadgeButton size="s" color="blue" onClick={handlePlanClick}>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="i-custom-public-common-sparkles-soft flex h-3.5 w-3.5 items-center py-px pl-0.75 text-components-premium-badge-indigo-text-stop-0"
|
||||
/>
|
||||
<div className="system-xs-medium">
|
||||
<span className="p-1">
|
||||
{t(($) => $['upgradeBtn.encourageShort'], { ns: 'billing' })}
|
||||
</span>
|
||||
</div>
|
||||
</PremiumBadgeButton>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{webappCopyrightEnabled ? (
|
||||
<Switch
|
||||
aria-label={t(($) => $[`${prefixSettings}.more.copyright`], {
|
||||
ns: 'appOverview',
|
||||
})}
|
||||
checked={copyrightSwitchValue}
|
||||
onCheckedChange={(v) =>
|
||||
setInputInfo({ ...inputInfo, copyrightSwitchValue: v })
|
||||
<ScrollArea className="relative min-h-0">
|
||||
<ScrollAreaViewport className="overscroll-contain">
|
||||
<ScrollAreaContent style={{ minWidth: 0 }} className="space-y-5 px-6 py-3">
|
||||
{/* name & icon */}
|
||||
<div className="flex gap-4">
|
||||
<Field name="title" className="grow">
|
||||
<FieldLabel>
|
||||
{t(($) => $[`${prefixSettings}.webName`], { ns: 'appOverview' })}
|
||||
</FieldLabel>
|
||||
<FieldControl
|
||||
value={inputInfo.title}
|
||||
onValueChange={(value) =>
|
||||
setInputInfo((item) => ({ ...item, title: value }))
|
||||
}
|
||||
placeholder={t(($) => $.appNamePlaceholder, { ns: 'app' }) || ''}
|
||||
/>
|
||||
) : (
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={
|
||||
<div>
|
||||
<Switch
|
||||
aria-label={t(($) => $[`${prefixSettings}.more.copyright`], {
|
||||
ns: 'appOverview',
|
||||
})}
|
||||
disabled
|
||||
checked={copyrightSwitchValue}
|
||||
onCheckedChange={(v) =>
|
||||
setInputInfo({ ...inputInfo, copyrightSwitchValue: v })
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<TooltipContent className="w-45">
|
||||
{t(($) => $[`${prefixSettings}.more.copyrightTooltip`], {
|
||||
ns: 'appOverview',
|
||||
})}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Field>
|
||||
<AppIcon
|
||||
size="xxl"
|
||||
onClick={() => {
|
||||
setShowAppIconPicker(true)
|
||||
}}
|
||||
className="mt-2 cursor-pointer"
|
||||
iconType={appIcon.type === 'link' ? 'image' : appIcon.type}
|
||||
icon={appIcon.type === 'image' ? appIcon.fileId : appIcon.icon}
|
||||
background={appIcon.type === 'emoji' ? appIcon.background : undefined}
|
||||
imageUrl={appIcon.type === 'emoji' ? undefined : appIcon.url}
|
||||
/>
|
||||
</div>
|
||||
<p className="pb-0.5 body-xs-regular text-text-tertiary">
|
||||
{t(($) => $[`${prefixSettings}.more.copyrightTip`], { ns: 'appOverview' })}
|
||||
</p>
|
||||
{copyrightSwitchValue && (
|
||||
<Input
|
||||
aria-label={t(($) => $[`${prefixSettings}.more.copyright`], {
|
||||
ns: 'appOverview',
|
||||
})}
|
||||
className="mt-2 h-10"
|
||||
value={inputInfo.copyright}
|
||||
onChange={onChange('copyright')}
|
||||
{/* description */}
|
||||
<Field name="description">
|
||||
<FieldLabel>
|
||||
{t(($) => $[`${prefixSettings}.webDesc`], { ns: 'appOverview' })}
|
||||
</FieldLabel>
|
||||
<Textarea
|
||||
value={inputInfo.desc}
|
||||
onValueChange={onDesChange}
|
||||
placeholder={
|
||||
t(($) => $[`${prefixSettings}.more.copyRightPlaceholder`], {
|
||||
t(($) => $[`${prefixSettings}.webDescPlaceholder`], {
|
||||
ns: 'appOverview',
|
||||
}) as string
|
||||
}
|
||||
/>
|
||||
<FieldDescription>
|
||||
{t(($) => $[`${prefixSettings}.webDescTip`], { ns: 'appOverview' })}
|
||||
</FieldDescription>
|
||||
</Field>
|
||||
<Divider className="my-0 h-px" />
|
||||
{/* answer icon */}
|
||||
{isChat && (
|
||||
<Field name="use_icon_as_answer_icon" className="w-full">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<FieldLabel>{t(($) => $['answerIcon.title'], { ns: 'app' })}</FieldLabel>
|
||||
<Switch
|
||||
checked={inputInfo.use_icon_as_answer_icon}
|
||||
onCheckedChange={(v) =>
|
||||
setInputInfo({ ...inputInfo, use_icon_as_answer_icon: v })
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<FieldDescription>
|
||||
{t(($) => $['answerIcon.description'], { ns: 'app' })}
|
||||
</FieldDescription>
|
||||
</Field>
|
||||
)}
|
||||
</div>
|
||||
{/* privacy policy */}
|
||||
<div className="w-full">
|
||||
<div className={cn('py-1 system-sm-semibold text-text-secondary')}>
|
||||
{t(($) => $[`${prefixSettings}.more.privacyPolicy`], { ns: 'appOverview' })}
|
||||
{/* language */}
|
||||
<div className="flex items-center">
|
||||
<div className={cn('grow py-1 system-sm-semibold text-text-secondary')}>
|
||||
{t(($) => $[`${prefixSettings}.language`], { ns: 'appOverview' })}
|
||||
</div>
|
||||
<Select
|
||||
value={selectedLanguage?.value ?? null}
|
||||
onValueChange={handleLanguageChange}
|
||||
>
|
||||
<SelectTrigger
|
||||
aria-label={t(($) => $[`${prefixSettings}.language`], {
|
||||
ns: 'appOverview',
|
||||
})}
|
||||
size="medium"
|
||||
className="w-50"
|
||||
>
|
||||
{selectedLanguage?.name ??
|
||||
t(($) => $['placeholder.select'], { ns: 'common' })}
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{LANGUAGE_OPTIONS.map((item) => (
|
||||
<SelectItem key={item.value} value={item.value}>
|
||||
<SelectItemText>{item.name}</SelectItemText>
|
||||
<SelectItemIndicator />
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<p className={cn('pb-0.5 body-xs-regular text-text-tertiary')}>
|
||||
<Trans
|
||||
i18nKey={($) => $[`${prefixSettings}.more.privacyPolicyTip`]}
|
||||
ns="appOverview"
|
||||
components={{
|
||||
privacyPolicyLink: (
|
||||
<Link
|
||||
href="https://dify.ai/privacy"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-text-accent"
|
||||
{/* theme color */}
|
||||
{isChat && (
|
||||
<div className="flex items-center">
|
||||
<div className="grow">
|
||||
<div className={cn('py-1 system-sm-semibold text-text-secondary')}>
|
||||
{t(($) => $[`${prefixSettings}.chatColorTheme`], { ns: 'appOverview' })}
|
||||
</div>
|
||||
<div className="pb-0.5 body-xs-regular text-text-tertiary">
|
||||
{t(($) => $[`${prefixSettings}.chatColorThemeDesc`], {
|
||||
ns: 'appOverview',
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<Field name="chat_color_theme" className="w-50 shrink-0">
|
||||
<FieldControl
|
||||
className="mb-1"
|
||||
value={inputInfo.chatColorTheme ?? ''}
|
||||
onValueChange={(value) =>
|
||||
setInputInfo((item) => ({ ...item, chatColorTheme: value }))
|
||||
}
|
||||
placeholder="E.g #A020F0"
|
||||
/>
|
||||
<div className="flex items-center justify-between gap-2 body-xs-regular text-text-tertiary">
|
||||
<span>
|
||||
{t(($) => $[`${prefixSettings}.chatColorThemeInverted`], {
|
||||
ns: 'appOverview',
|
||||
})}
|
||||
</span>
|
||||
<Switch
|
||||
checked={inputInfo.chatColorThemeInverted}
|
||||
onCheckedChange={(v) =>
|
||||
setInputInfo({ ...inputInfo, chatColorThemeInverted: v })
|
||||
}
|
||||
></Switch>
|
||||
</div>
|
||||
</Field>
|
||||
</div>
|
||||
)}
|
||||
{/* workflow detail */}
|
||||
<Field name="show_workflow_steps" className="w-full">
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<FieldLabel>
|
||||
{t(($) => $[`${prefixSettings}.workflow.subTitle`], { ns: 'appOverview' })}
|
||||
</FieldLabel>
|
||||
<Switch
|
||||
disabled={
|
||||
!(
|
||||
appInfo.mode === AppModeEnum.WORKFLOW ||
|
||||
appInfo.mode === AppModeEnum.ADVANCED_CHAT
|
||||
)
|
||||
}
|
||||
checked={inputInfo.show_workflow_steps}
|
||||
onCheckedChange={(v) =>
|
||||
setInputInfo({ ...inputInfo, show_workflow_steps: v })
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<FieldDescription>
|
||||
{t(($) => $[`${prefixSettings}.workflow.showDesc`], { ns: 'appOverview' })}
|
||||
</FieldDescription>
|
||||
</Field>
|
||||
<Divider className="my-0 h-px" />
|
||||
<div className="space-y-5">
|
||||
{INPUT_PLACEHOLDER_SUPPORTED_MODES.includes(appInfo.mode) && (
|
||||
<div className="w-full">
|
||||
<div className="flex items-center">
|
||||
<div className="flex grow items-center">
|
||||
<div
|
||||
id={inputPlaceholderLabelId}
|
||||
className={cn('mr-1 py-1 system-sm-semibold text-text-secondary')}
|
||||
>
|
||||
{t(($) => $[`${prefixSettings}.more.inputPlaceholder`], {
|
||||
ns: 'appOverview',
|
||||
})}
|
||||
</div>
|
||||
{isCloudSandboxPlan && (
|
||||
<div className="h-4.5 select-none">
|
||||
<PremiumBadgeButton size="s" color="blue" onClick={handlePlanClick}>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="i-custom-public-common-sparkles-soft flex h-3.5 w-3.5 items-center py-px pl-0.75 text-components-premium-badge-indigo-text-stop-0"
|
||||
/>
|
||||
<div className="system-xs-medium">
|
||||
<span className="p-1">
|
||||
{t(($) => $['upgradeBtn.encourageShort'], { ns: 'billing' })}
|
||||
</span>
|
||||
</div>
|
||||
</PremiumBadgeButton>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<p
|
||||
id={inputPlaceholderDescriptionId}
|
||||
className="pb-0.5 body-xs-regular text-text-tertiary"
|
||||
>
|
||||
{t(($) => $[`${prefixSettings}.more.inputPlaceholderTip`], {
|
||||
ns: 'appOverview',
|
||||
})}
|
||||
</p>
|
||||
{isCloudSandboxPlan ? (
|
||||
<Tooltip>
|
||||
<TooltipTrigger render={inputPlaceholderField} />
|
||||
<TooltipContent className="w-45">
|
||||
{t(($) => $[`${prefixSettings}.more.inputPlaceholderTooltip`], {
|
||||
ns: 'appOverview',
|
||||
})}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
) : (
|
||||
inputPlaceholderField
|
||||
)}
|
||||
{!isCloudSandboxPlan && (
|
||||
<div className="mt-1 text-right body-xs-regular text-text-tertiary">
|
||||
{`${inputInfo.inputPlaceholder?.length ?? 0} / ${INPUT_PLACEHOLDER_MAX_LENGTH}`}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{/* copyright */}
|
||||
<div className="w-full">
|
||||
<div className="flex items-center">
|
||||
<div className="flex grow items-center">
|
||||
<div className={cn('mr-1 py-1 system-sm-semibold text-text-secondary')}>
|
||||
{t(($) => $[`${prefixSettings}.more.copyright`], { ns: 'appOverview' })}
|
||||
</div>
|
||||
{/* upgrade button */}
|
||||
{isCloudSandboxPlan && (
|
||||
<div className="h-4.5 select-none">
|
||||
<PremiumBadgeButton size="s" color="blue" onClick={handlePlanClick}>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="i-custom-public-common-sparkles-soft flex h-3.5 w-3.5 items-center py-px pl-0.75 text-components-premium-badge-indigo-text-stop-0"
|
||||
/>
|
||||
<div className="system-xs-medium">
|
||||
<span className="p-1">
|
||||
{t(($) => $['upgradeBtn.encourageShort'], { ns: 'billing' })}
|
||||
</span>
|
||||
</div>
|
||||
</PremiumBadgeButton>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{webappCopyrightEnabled ? (
|
||||
<Switch
|
||||
aria-label={t(($) => $[`${prefixSettings}.more.copyright`], {
|
||||
ns: 'appOverview',
|
||||
})}
|
||||
checked={copyrightSwitchValue}
|
||||
onCheckedChange={(v) =>
|
||||
setInputInfo({ ...inputInfo, copyrightSwitchValue: v })
|
||||
}
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
<Input
|
||||
aria-label={t(($) => $[`${prefixSettings}.more.privacyPolicy`], {
|
||||
ns: 'appOverview',
|
||||
})}
|
||||
className="mt-1"
|
||||
value={inputInfo.privacyPolicy}
|
||||
onChange={onChange('privacyPolicy')}
|
||||
placeholder={
|
||||
t(($) => $[`${prefixSettings}.more.privacyPolicyPlaceholder`], {
|
||||
ns: 'appOverview',
|
||||
}) as string
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
{/* custom disclaimer */}
|
||||
<div className="w-full">
|
||||
<div className={cn('py-1 system-sm-semibold text-text-secondary')}>
|
||||
{t(($) => $[`${prefixSettings}.more.customDisclaimer`], { ns: 'appOverview' })}
|
||||
) : (
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
render={
|
||||
<div>
|
||||
<Switch
|
||||
aria-label={t(($) => $[`${prefixSettings}.more.copyright`], {
|
||||
ns: 'appOverview',
|
||||
})}
|
||||
disabled
|
||||
checked={copyrightSwitchValue}
|
||||
onCheckedChange={(v) =>
|
||||
setInputInfo({ ...inputInfo, copyrightSwitchValue: v })
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
<TooltipContent className="w-45">
|
||||
{t(($) => $[`${prefixSettings}.more.copyrightTooltip`], {
|
||||
ns: 'appOverview',
|
||||
})}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
)}
|
||||
</div>
|
||||
<p className="pb-0.5 body-xs-regular text-text-tertiary">
|
||||
{t(($) => $[`${prefixSettings}.more.copyrightTip`], { ns: 'appOverview' })}
|
||||
</p>
|
||||
{copyrightSwitchValue && (
|
||||
<Input
|
||||
aria-label={t(($) => $[`${prefixSettings}.more.copyright`], {
|
||||
ns: 'appOverview',
|
||||
})}
|
||||
className="mt-2 h-10"
|
||||
value={inputInfo.copyright}
|
||||
onChange={onChange('copyright')}
|
||||
placeholder={
|
||||
t(($) => $[`${prefixSettings}.more.copyRightPlaceholder`], {
|
||||
ns: 'appOverview',
|
||||
}) as string
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{/* privacy policy */}
|
||||
<div className="w-full">
|
||||
<div className={cn('py-1 system-sm-semibold text-text-secondary')}>
|
||||
{t(($) => $[`${prefixSettings}.more.privacyPolicy`], { ns: 'appOverview' })}
|
||||
</div>
|
||||
<p className={cn('pb-0.5 body-xs-regular text-text-tertiary')}>
|
||||
<Trans
|
||||
i18nKey={($) => $[`${prefixSettings}.more.privacyPolicyTip`]}
|
||||
ns="appOverview"
|
||||
components={{
|
||||
privacyPolicyLink: (
|
||||
<Link
|
||||
href="https://dify.ai/privacy"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-text-accent"
|
||||
/>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
<Input
|
||||
aria-label={t(($) => $[`${prefixSettings}.more.privacyPolicy`], {
|
||||
ns: 'appOverview',
|
||||
})}
|
||||
className="mt-1"
|
||||
value={inputInfo.privacyPolicy}
|
||||
onChange={onChange('privacyPolicy')}
|
||||
placeholder={
|
||||
t(($) => $[`${prefixSettings}.more.privacyPolicyPlaceholder`], {
|
||||
ns: 'appOverview',
|
||||
}) as string
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
{/* custom disclaimer */}
|
||||
<div className="w-full">
|
||||
<div className={cn('py-1 system-sm-semibold text-text-secondary')}>
|
||||
{t(($) => $[`${prefixSettings}.more.customDisclaimer`], {
|
||||
ns: 'appOverview',
|
||||
})}
|
||||
</div>
|
||||
<p className={cn('pb-0.5 body-xs-regular text-text-tertiary')}>
|
||||
{t(($) => $[`${prefixSettings}.more.customDisclaimerTip`], {
|
||||
ns: 'appOverview',
|
||||
})}
|
||||
</p>
|
||||
<Textarea
|
||||
aria-label={t(($) => $[`${prefixSettings}.more.customDisclaimer`], {
|
||||
ns: 'appOverview',
|
||||
})}
|
||||
className="mt-1"
|
||||
value={inputInfo.customDisclaimer}
|
||||
onValueChange={(value) =>
|
||||
setInputInfo((item) => ({ ...item, customDisclaimer: value }))
|
||||
}
|
||||
placeholder={
|
||||
t(($) => $[`${prefixSettings}.more.customDisclaimerPlaceholder`], {
|
||||
ns: 'appOverview',
|
||||
}) as string
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<p className={cn('pb-0.5 body-xs-regular text-text-tertiary')}>
|
||||
{t(($) => $[`${prefixSettings}.more.customDisclaimerTip`], {
|
||||
ns: 'appOverview',
|
||||
})}
|
||||
</p>
|
||||
<Textarea
|
||||
aria-label={t(($) => $[`${prefixSettings}.more.customDisclaimer`], {
|
||||
ns: 'appOverview',
|
||||
})}
|
||||
className="mt-1"
|
||||
value={inputInfo.customDisclaimer}
|
||||
onValueChange={(value) =>
|
||||
setInputInfo((item) => ({ ...item, customDisclaimer: value }))
|
||||
}
|
||||
placeholder={
|
||||
t(($) => $[`${prefixSettings}.more.customDisclaimerPlaceholder`], {
|
||||
ns: 'appOverview',
|
||||
}) as string
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</ScrollAreaContent>
|
||||
</ScrollAreaViewport>
|
||||
<ScrollAreaScrollbar>
|
||||
<ScrollAreaThumb />
|
||||
</ScrollAreaScrollbar>
|
||||
</ScrollArea>
|
||||
{/* footer */}
|
||||
<div className="flex shrink-0 justify-end p-6 pt-5">
|
||||
|
||||
@@ -3,9 +3,9 @@ import type { FC } from 'react'
|
||||
import type { Category } from './types'
|
||||
import { Dialog, DialogContent } from '@langgenius/dify-ui/dialog'
|
||||
import {
|
||||
ScrollArea,
|
||||
ScrollAreaContent,
|
||||
ScrollAreaCorner,
|
||||
ScrollAreaRoot,
|
||||
ScrollAreaScrollbar,
|
||||
ScrollAreaThumb,
|
||||
ScrollAreaViewport,
|
||||
@@ -51,7 +51,7 @@ const Pricing: FC<PricingProps> = ({ onCancel }) => {
|
||||
}}
|
||||
>
|
||||
<DialogContent className="inset-0 size-full max-h-none max-w-none translate-0 overflow-hidden rounded-none border-none bg-saas-background p-0 shadow-none">
|
||||
<ScrollAreaRoot className="relative h-full w-full overflow-hidden">
|
||||
<ScrollArea className="relative h-full w-full overflow-hidden">
|
||||
<ScrollAreaViewport className="overscroll-contain">
|
||||
<ScrollAreaContent className="min-h-full min-w-300">
|
||||
<div className="relative grid min-h-full grid-rows-[1fr_auto_auto_1fr] overflow-hidden">
|
||||
@@ -85,7 +85,7 @@ const Pricing: FC<PricingProps> = ({ onCancel }) => {
|
||||
<ScrollAreaThumb className="rounded-full" />
|
||||
</ScrollAreaScrollbar>
|
||||
<ScrollAreaCorner className="bg-saas-background" />
|
||||
</ScrollAreaRoot>
|
||||
</ScrollArea>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
)
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
'use client'
|
||||
|
||||
import type { ResourceOpenScope } from '@/models/access-control'
|
||||
import { ScrollArea } from '@langgenius/dify-ui/scroll-area'
|
||||
import {
|
||||
ScrollArea,
|
||||
ScrollAreaContent,
|
||||
ScrollAreaScrollbar,
|
||||
ScrollAreaThumb,
|
||||
ScrollAreaViewport,
|
||||
} from '@langgenius/dify-ui/scroll-area'
|
||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
@@ -103,35 +109,39 @@ const DatasetAccessConfigPage = ({ datasetId }: DatasetAccessConfigPageProps) =>
|
||||
if (!canAccessConfig) return <Loading type="app" />
|
||||
|
||||
return (
|
||||
<ScrollArea
|
||||
className="h-full bg-background-default-subtle"
|
||||
slotClassNames={{ viewport: 'overscroll-contain' }}
|
||||
>
|
||||
<header className="flex min-h-15.5 flex-col justify-center px-6 py-3">
|
||||
<h1 className="system-sm-semibold text-text-primary">
|
||||
{t(($) => $['settings.resourceAccess'], { ns: 'common' })}
|
||||
</h1>
|
||||
<p className="mt-0.5 system-xs-regular text-text-tertiary">
|
||||
{t(($) => $['accessRule.datasetDescription'], { ns: 'permission' })}
|
||||
</p>
|
||||
</header>
|
||||
<main className="w-full px-6 pt-8 pb-10">
|
||||
<AccessRulesEditor
|
||||
className="w-full max-w-200"
|
||||
rules={datasetAccessRules}
|
||||
userAccessSettings={datasetUserAccessSettings}
|
||||
isLoadingRules={isLoadingDatasetAccessRules}
|
||||
isLoadingUserAccessSettings={isLoadingDatasetUserAccessSettings}
|
||||
openScope={openScope}
|
||||
isUpdatingOpenScope={isLoadingDatasetUserAccessSettings || isUpdatingDatasetOpenScope}
|
||||
updatingAccountId={updatingAccountId}
|
||||
maintainerId={dataset?.maintainer}
|
||||
onOpenScopeChange={handleOpenScopeChange}
|
||||
onUserAccessPoliciesChange={handleUserAccessPoliciesChange}
|
||||
onRemoveAccessPolicyMemberBinding={handleRemoveAccessPolicyMemberBinding}
|
||||
onAddAccessSubject={handleUserAccessPoliciesChange}
|
||||
/>
|
||||
</main>
|
||||
<ScrollArea className="h-full bg-background-default-subtle">
|
||||
<ScrollAreaViewport className="overscroll-contain">
|
||||
<ScrollAreaContent>
|
||||
<header className="flex min-h-15.5 flex-col justify-center px-6 py-3">
|
||||
<h1 className="system-sm-semibold text-text-primary">
|
||||
{t(($) => $['settings.resourceAccess'], { ns: 'common' })}
|
||||
</h1>
|
||||
<p className="mt-0.5 system-xs-regular text-text-tertiary">
|
||||
{t(($) => $['accessRule.datasetDescription'], { ns: 'permission' })}
|
||||
</p>
|
||||
</header>
|
||||
<main className="w-full px-6 pt-8 pb-10">
|
||||
<AccessRulesEditor
|
||||
className="w-full max-w-200"
|
||||
rules={datasetAccessRules}
|
||||
userAccessSettings={datasetUserAccessSettings}
|
||||
isLoadingRules={isLoadingDatasetAccessRules}
|
||||
isLoadingUserAccessSettings={isLoadingDatasetUserAccessSettings}
|
||||
openScope={openScope}
|
||||
isUpdatingOpenScope={isLoadingDatasetUserAccessSettings || isUpdatingDatasetOpenScope}
|
||||
updatingAccountId={updatingAccountId}
|
||||
maintainerId={dataset?.maintainer}
|
||||
onOpenScopeChange={handleOpenScopeChange}
|
||||
onUserAccessPoliciesChange={handleUserAccessPoliciesChange}
|
||||
onRemoveAccessPolicyMemberBinding={handleRemoveAccessPolicyMemberBinding}
|
||||
onAddAccessSubject={handleUserAccessPoliciesChange}
|
||||
/>
|
||||
</main>
|
||||
</ScrollAreaContent>
|
||||
</ScrollAreaViewport>
|
||||
<ScrollAreaScrollbar>
|
||||
<ScrollAreaThumb />
|
||||
</ScrollAreaScrollbar>
|
||||
</ScrollArea>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -15,8 +15,8 @@ import {
|
||||
} from '@langgenius/dify-ui/alert-dialog'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import {
|
||||
ScrollArea,
|
||||
ScrollAreaContent,
|
||||
ScrollAreaRoot,
|
||||
ScrollAreaScrollbar,
|
||||
ScrollAreaThumb,
|
||||
ScrollAreaViewport,
|
||||
@@ -193,7 +193,7 @@ const SideBar = () => {
|
||||
)}
|
||||
{!isFold ? (
|
||||
<div className="min-h-0 flex-1">
|
||||
<ScrollAreaRoot className="h-full">
|
||||
<ScrollArea className="h-full">
|
||||
<ScrollAreaViewport
|
||||
ref={scrollRef}
|
||||
aria-busy={installedAppsQuery.isFetchingNextPage}
|
||||
@@ -219,7 +219,7 @@ const SideBar = () => {
|
||||
<ScrollAreaScrollbar>
|
||||
<ScrollAreaThumb />
|
||||
</ScrollAreaScrollbar>
|
||||
</ScrollAreaRoot>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
|
||||
@@ -24,8 +24,8 @@ import {
|
||||
} from '@langgenius/dify-ui/dialog'
|
||||
import { Kbd, KbdGroup } from '@langgenius/dify-ui/kbd'
|
||||
import {
|
||||
ScrollArea,
|
||||
ScrollAreaContent,
|
||||
ScrollAreaRoot,
|
||||
ScrollAreaScrollbar,
|
||||
ScrollAreaThumb,
|
||||
ScrollAreaViewport,
|
||||
@@ -443,11 +443,11 @@ function GotoAnythingDialog() {
|
||||
|
||||
<AutocompleteStatus className="sr-only">{autocompleteStatus}</AutocompleteStatus>
|
||||
|
||||
<ScrollAreaRoot
|
||||
aria-busy={isLoading || undefined}
|
||||
className="relative h-60 min-h-0 overflow-hidden"
|
||||
>
|
||||
<ScrollAreaViewport className="scroll-py-1 overscroll-contain">
|
||||
<ScrollArea className="relative h-60 min-h-0 overflow-hidden">
|
||||
<ScrollAreaViewport
|
||||
aria-busy={isLoading || undefined}
|
||||
className="scroll-py-1 overscroll-contain"
|
||||
>
|
||||
<ScrollAreaContent
|
||||
className="min-h-full w-full max-w-full"
|
||||
style={{ minWidth: '100%' }}
|
||||
@@ -576,7 +576,7 @@ function GotoAnythingDialog() {
|
||||
<ScrollAreaScrollbar>
|
||||
<ScrollAreaThumb />
|
||||
</ScrollAreaScrollbar>
|
||||
</ScrollAreaRoot>
|
||||
</ScrollArea>
|
||||
|
||||
<Footer
|
||||
resultCount={autocompleteResultCount}
|
||||
|
||||
@@ -2,7 +2,13 @@
|
||||
import type { AccountSettingTab } from '@/app/components/header/account-setting/constants'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { ScrollArea } from '@langgenius/dify-ui/scroll-area'
|
||||
import {
|
||||
ScrollArea,
|
||||
ScrollAreaContent,
|
||||
ScrollAreaScrollbar,
|
||||
ScrollAreaThumb,
|
||||
ScrollAreaViewport,
|
||||
} from '@langgenius/dify-ui/scroll-area'
|
||||
import { useSuspenseQuery } from '@tanstack/react-query'
|
||||
import { useAtomValue } from 'jotai'
|
||||
import { useRef } from 'react'
|
||||
@@ -225,37 +231,44 @@ export default function AccountSetting({
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative flex min-h-0 w-206 min-w-0">
|
||||
<ScrollArea
|
||||
ref={scrollContainerRef}
|
||||
className="h-full min-h-0 min-w-0 flex-1 bg-components-panel-bg"
|
||||
slotClassNames={{
|
||||
viewport: 'overscroll-contain overflow-x-hidden',
|
||||
content: 'min-h-full min-w-0 w-full max-w-full pb-4',
|
||||
}}
|
||||
>
|
||||
<div className="sticky top-0 z-20 mx-8 flex min-h-15 items-end bg-components-panel-bg pt-8 pb-2">
|
||||
<div className="min-w-0 flex-1 title-2xl-semi-bold text-text-primary">
|
||||
{activeItem?.title ?? activeItem?.name}
|
||||
{activeItem?.description && (
|
||||
<div className="mt-1 system-sm-regular wrap-break-word whitespace-normal text-text-tertiary">
|
||||
{activeItem?.description}
|
||||
<ScrollArea className="h-full min-h-0 min-w-0 flex-1 bg-components-panel-bg">
|
||||
<ScrollAreaViewport
|
||||
ref={scrollContainerRef}
|
||||
style={{ overflowX: 'hidden' }}
|
||||
className="overscroll-contain"
|
||||
>
|
||||
<ScrollAreaContent
|
||||
style={{ minWidth: 0 }}
|
||||
className="min-h-full w-full max-w-full pb-4"
|
||||
>
|
||||
<div className="sticky top-0 z-20 mx-8 flex min-h-15 items-end bg-components-panel-bg pt-8 pb-2">
|
||||
<div className="min-w-0 flex-1 title-2xl-semi-bold text-text-primary">
|
||||
{activeItem?.title ?? activeItem?.name}
|
||||
{activeItem?.description && (
|
||||
<div className="mt-1 system-sm-regular wrap-break-word whitespace-normal text-text-tertiary">
|
||||
{activeItem?.description}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="max-w-full min-w-0 px-4 pt-6 sm:px-8">
|
||||
{activeMenu === ACCOUNT_SETTING_TAB.MEMBERS && <MembersPage />}
|
||||
{activeMenu === ACCOUNT_SETTING_TAB.ROLES_AND_PERMISSIONS && (
|
||||
<PermissionsPage containerRef={scrollContainerRef} />
|
||||
)}
|
||||
{activeMenu === ACCOUNT_SETTING_TAB.PERMISSION_SET && <AccessRulesPage />}
|
||||
{activeMenu === ACCOUNT_SETTING_TAB.BILLING && <BillingPage />}
|
||||
{activeMenu === ACCOUNT_SETTING_TAB.WORKFLOW_LOG_ARCHIVES && (
|
||||
<WorkflowLogArchivesPage />
|
||||
)}
|
||||
{activeMenu === ACCOUNT_SETTING_TAB.CUSTOM && <CustomPage />}
|
||||
{activeMenu === ACCOUNT_SETTING_TAB.PREFERENCES && <PreferencePage />}
|
||||
</div>
|
||||
</div>
|
||||
<div className="max-w-full min-w-0 px-4 pt-6 sm:px-8">
|
||||
{activeMenu === ACCOUNT_SETTING_TAB.MEMBERS && <MembersPage />}
|
||||
{activeMenu === ACCOUNT_SETTING_TAB.ROLES_AND_PERMISSIONS && (
|
||||
<PermissionsPage containerRef={scrollContainerRef} />
|
||||
)}
|
||||
{activeMenu === ACCOUNT_SETTING_TAB.PERMISSION_SET && <AccessRulesPage />}
|
||||
{activeMenu === ACCOUNT_SETTING_TAB.BILLING && <BillingPage />}
|
||||
{activeMenu === ACCOUNT_SETTING_TAB.WORKFLOW_LOG_ARCHIVES && (
|
||||
<WorkflowLogArchivesPage />
|
||||
)}
|
||||
{activeMenu === ACCOUNT_SETTING_TAB.CUSTOM && <CustomPage />}
|
||||
{activeMenu === ACCOUNT_SETTING_TAB.PREFERENCES && <PreferencePage />}
|
||||
</div>
|
||||
</ScrollAreaContent>
|
||||
</ScrollAreaViewport>
|
||||
<ScrollAreaScrollbar>
|
||||
<ScrollAreaThumb />
|
||||
</ScrollAreaScrollbar>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+6
-5
@@ -2,8 +2,8 @@ import type { ReactNode } from 'react'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { ComboboxInput, ComboboxInputGroup } from '@langgenius/dify-ui/combobox'
|
||||
import {
|
||||
ScrollArea,
|
||||
ScrollAreaContent,
|
||||
ScrollAreaRoot,
|
||||
ScrollAreaScrollbar,
|
||||
ScrollAreaThumb,
|
||||
ScrollAreaViewport,
|
||||
@@ -75,18 +75,19 @@ type ModelSelectorScrollBodyProps = {
|
||||
|
||||
export function ModelSelectorScrollBody({ children, label }: ModelSelectorScrollBodyProps) {
|
||||
return (
|
||||
<ScrollAreaRoot className="relative min-h-0 overflow-hidden overscroll-contain">
|
||||
<ScrollArea className="relative min-h-0 overflow-hidden">
|
||||
<ScrollAreaViewport
|
||||
aria-label={label}
|
||||
className="max-h-[calc(min(624px,var(--available-height,624px))-84px)] overflow-x-hidden overscroll-contain"
|
||||
style={{ overflowX: 'hidden' }}
|
||||
className="max-h-[calc(min(624px,var(--available-height,624px))-84px)] overscroll-contain"
|
||||
role="region"
|
||||
>
|
||||
<ScrollAreaContent className="min-w-0 overflow-x-hidden">{children}</ScrollAreaContent>
|
||||
<ScrollAreaContent style={{ minWidth: 0 }}>{children}</ScrollAreaContent>
|
||||
</ScrollAreaViewport>
|
||||
<ScrollAreaScrollbar className="z-2">
|
||||
<ScrollAreaThumb />
|
||||
</ScrollAreaScrollbar>
|
||||
</ScrollAreaRoot>
|
||||
</ScrollArea>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,13 @@
|
||||
import type { PermissionGroup } from '@/models/access-control'
|
||||
import { Checkbox } from '@langgenius/dify-ui/checkbox'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { ScrollArea } from '@langgenius/dify-ui/scroll-area'
|
||||
import {
|
||||
ScrollArea,
|
||||
ScrollAreaContent,
|
||||
ScrollAreaScrollbar,
|
||||
ScrollAreaThumb,
|
||||
ScrollAreaViewport,
|
||||
} from '@langgenius/dify-ui/scroll-area'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
@@ -75,113 +81,114 @@ const PermissionGroupList = ({
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<ScrollArea
|
||||
className="h-full overflow-hidden rounded-xl"
|
||||
slotClassNames={{
|
||||
viewport: 'overscroll-contain',
|
||||
content: groups.length === 0 ? 'h-full' : undefined,
|
||||
}}
|
||||
>
|
||||
{groups.length === 0 ? (
|
||||
<div className="flex h-full items-center justify-center px-3 py-6 text-center system-sm-regular text-text-tertiary">
|
||||
{t(($) => $['permissionList.noPermissionsFound'], { ns: 'permission' })}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col">
|
||||
{groups.map((group) => {
|
||||
const expanded = expandedGroupKeysMerged.has(group.group_key)
|
||||
const selectedCount = group.permissions.reduce(
|
||||
(count, permission) => count + (selectedSet.has(permission.key) ? 1 : 0),
|
||||
0,
|
||||
)
|
||||
const totalCount = group.permissions.length
|
||||
const allSelected = totalCount > 0 && selectedCount === totalCount
|
||||
<ScrollArea className="h-full overflow-hidden rounded-xl">
|
||||
<ScrollAreaViewport className="overscroll-contain">
|
||||
<ScrollAreaContent className={groups.length === 0 ? 'h-full' : undefined}>
|
||||
{groups.length === 0 ? (
|
||||
<div className="flex h-full items-center justify-center px-3 py-6 text-center system-sm-regular text-text-tertiary">
|
||||
{t(($) => $['permissionList.noPermissionsFound'], { ns: 'permission' })}
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-col">
|
||||
{groups.map((group) => {
|
||||
const expanded = expandedGroupKeysMerged.has(group.group_key)
|
||||
const selectedCount = group.permissions.reduce(
|
||||
(count, permission) => count + (selectedSet.has(permission.key) ? 1 : 0),
|
||||
0,
|
||||
)
|
||||
const totalCount = group.permissions.length
|
||||
const allSelected = totalCount > 0 && selectedCount === totalCount
|
||||
|
||||
return (
|
||||
<div key={group.group_key} className="border-b border-b-divider-subtle">
|
||||
<div className="group/permission-category flex h-12 w-full items-center gap-3 px-3 hover:bg-state-base-hover">
|
||||
<button
|
||||
type="button"
|
||||
className="min-w-0 flex-1 truncate text-left system-md-medium text-text-secondary focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-components-input-border-active"
|
||||
aria-expanded={expanded}
|
||||
onClick={() => toggleGroupExpanded(group.group_key)}
|
||||
>
|
||||
{group.group_name}
|
||||
</button>
|
||||
{!readonly && totalCount > 0 && (
|
||||
<button
|
||||
type="button"
|
||||
className="shrink-0 rounded-md px-1.5 py-0.5 system-sm-medium text-text-accent opacity-0 group-hover/permission-category:opacity-100 hover:bg-state-accent-hover focus-visible:opacity-100 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-components-input-border-active"
|
||||
onClick={() => toggleGroupSelection(group, selectedCount)}
|
||||
>
|
||||
{allSelected
|
||||
? t(($) => $['permissionList.clearAll'], { ns: 'permission' })
|
||||
: t(($) => $['permissionList.selectAll'], { ns: 'permission' })}
|
||||
</button>
|
||||
)}
|
||||
{selectedCount > 0 && (
|
||||
<span className="shrink-0 rounded-md bg-util-colors-blue-blue-100 px-2 py-0.5 system-sm-medium text-text-accent">
|
||||
{selectedCount}/{totalCount}
|
||||
</span>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
className="flex h-6 w-6 shrink-0 items-center justify-center focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-components-input-border-active"
|
||||
aria-label={
|
||||
expanded
|
||||
? t(($) => $['permissionList.collapseGroup'], { ns: 'permission' })
|
||||
: t(($) => $['permissionList.expandGroup'], { ns: 'permission' })
|
||||
}
|
||||
onClick={() => toggleGroupExpanded(group.group_key)}
|
||||
>
|
||||
<span
|
||||
aria-hidden
|
||||
className={cn(
|
||||
'i-ri-arrow-right-s-fill h-4 w-4 text-text-tertiary transition-transform',
|
||||
expanded && 'rotate-90',
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
{expanded && (
|
||||
<div className="flex flex-col">
|
||||
{group.permissions.map((permission) => {
|
||||
const checked = selectedSet.has(permission.key)
|
||||
|
||||
return (
|
||||
<div
|
||||
key={permission.key}
|
||||
className={cn(
|
||||
'flex min-h-9 items-center gap-3 px-3 py-1.5',
|
||||
readonly
|
||||
? 'cursor-default'
|
||||
: 'cursor-pointer hover:bg-state-base-hover',
|
||||
checked && 'bg-state-accent-hover',
|
||||
checked && !readonly && 'hover:bg-state-accent-hover',
|
||||
)}
|
||||
onClick={() => togglePermission(permission.key)}
|
||||
return (
|
||||
<div key={group.group_key} className="border-b border-b-divider-subtle">
|
||||
<div className="group/permission-category flex h-12 w-full items-center gap-3 px-3 hover:bg-state-base-hover">
|
||||
<button
|
||||
type="button"
|
||||
className="min-w-0 flex-1 truncate text-left system-md-medium text-text-secondary focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-components-input-border-active"
|
||||
aria-expanded={expanded}
|
||||
onClick={() => toggleGroupExpanded(group.group_key)}
|
||||
>
|
||||
{group.group_name}
|
||||
</button>
|
||||
{!readonly && totalCount > 0 && (
|
||||
<button
|
||||
type="button"
|
||||
className="shrink-0 rounded-md px-1.5 py-0.5 system-sm-medium text-text-accent opacity-0 group-hover/permission-category:opacity-100 hover:bg-state-accent-hover focus-visible:opacity-100 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-components-input-border-active"
|
||||
onClick={() => toggleGroupSelection(group, selectedCount)}
|
||||
>
|
||||
<Checkbox
|
||||
checked={checked}
|
||||
disabled={readonly}
|
||||
className="shrink-0"
|
||||
onCheckedChange={() => {
|
||||
togglePermission(permission.key)
|
||||
}}
|
||||
/>
|
||||
<span className="min-w-0 flex-1 truncate system-md-regular text-text-secondary">
|
||||
{permission.name}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
{allSelected
|
||||
? t(($) => $['permissionList.clearAll'], { ns: 'permission' })
|
||||
: t(($) => $['permissionList.selectAll'], { ns: 'permission' })}
|
||||
</button>
|
||||
)}
|
||||
{selectedCount > 0 && (
|
||||
<span className="shrink-0 rounded-md bg-util-colors-blue-blue-100 px-2 py-0.5 system-sm-medium text-text-accent">
|
||||
{selectedCount}/{totalCount}
|
||||
</span>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
className="flex h-6 w-6 shrink-0 items-center justify-center focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-components-input-border-active"
|
||||
aria-label={
|
||||
expanded
|
||||
? t(($) => $['permissionList.collapseGroup'], { ns: 'permission' })
|
||||
: t(($) => $['permissionList.expandGroup'], { ns: 'permission' })
|
||||
}
|
||||
onClick={() => toggleGroupExpanded(group.group_key)}
|
||||
>
|
||||
<span
|
||||
aria-hidden
|
||||
className={cn(
|
||||
'i-ri-arrow-right-s-fill h-4 w-4 text-text-tertiary transition-transform',
|
||||
expanded && 'rotate-90',
|
||||
)}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
{expanded && (
|
||||
<div className="flex flex-col">
|
||||
{group.permissions.map((permission) => {
|
||||
const checked = selectedSet.has(permission.key)
|
||||
|
||||
return (
|
||||
<div
|
||||
key={permission.key}
|
||||
className={cn(
|
||||
'flex min-h-9 items-center gap-3 px-3 py-1.5',
|
||||
readonly
|
||||
? 'cursor-default'
|
||||
: 'cursor-pointer hover:bg-state-base-hover',
|
||||
checked && 'bg-state-accent-hover',
|
||||
checked && !readonly && 'hover:bg-state-accent-hover',
|
||||
)}
|
||||
onClick={() => togglePermission(permission.key)}
|
||||
>
|
||||
<Checkbox
|
||||
checked={checked}
|
||||
disabled={readonly}
|
||||
className="shrink-0"
|
||||
onCheckedChange={() => {
|
||||
togglePermission(permission.key)
|
||||
}}
|
||||
/>
|
||||
<span className="min-w-0 flex-1 truncate system-md-regular text-text-secondary">
|
||||
{permission.name}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</ScrollAreaContent>
|
||||
</ScrollAreaViewport>
|
||||
<ScrollAreaScrollbar>
|
||||
<ScrollAreaThumb />
|
||||
</ScrollAreaScrollbar>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -5,7 +5,13 @@ import { Checkbox } from '@langgenius/dify-ui/checkbox'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Input } from '@langgenius/dify-ui/input'
|
||||
import { RadioControl, RadioGroup, RadioItem } from '@langgenius/dify-ui/radio'
|
||||
import { ScrollArea } from '@langgenius/dify-ui/scroll-area'
|
||||
import {
|
||||
ScrollArea,
|
||||
ScrollAreaContent,
|
||||
ScrollAreaScrollbar,
|
||||
ScrollAreaThumb,
|
||||
ScrollAreaViewport,
|
||||
} from '@langgenius/dify-ui/scroll-area'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useLocale } from '@/context/i18n'
|
||||
@@ -227,99 +233,102 @@ const WorkspaceRoleCheckboxList = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ScrollArea
|
||||
ref={containerRef}
|
||||
className="min-h-0 flex-1"
|
||||
slotClassNames={{ viewport: 'px-3 overscroll-contain' }}
|
||||
>
|
||||
{rolesLoading ? (
|
||||
<div className="px-3 py-6 text-center system-sm-regular text-text-tertiary">
|
||||
{t(($) => $['role.loading'], { ns: 'permission' })}
|
||||
</div>
|
||||
) : filteredRoles.length === 0 ? (
|
||||
<div className="px-3 py-6 text-center system-sm-regular text-text-tertiary">
|
||||
{t(($) => $['role.noMatchingRoles'], { ns: 'permission' })}
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{allowMultipleRoles ? (
|
||||
<ul className="flex flex-col gap-0.5 pb-2">
|
||||
{filteredRoles.map((role) => {
|
||||
const checked = selectedRoleIdSet.has(role.id)
|
||||
const disabled = disabledRoleIdSet.has(role.id)
|
||||
const handleToggle = () => toggleRole(role)
|
||||
|
||||
return (
|
||||
<li key={role.id}>
|
||||
<div
|
||||
role="checkbox"
|
||||
aria-checked={checked}
|
||||
aria-disabled={disabled}
|
||||
tabIndex={disabled ? -1 : 0}
|
||||
className={cn(
|
||||
'flex cursor-pointer items-start gap-3 rounded-lg px-3 py-2.5 hover:bg-state-base-hover focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-components-input-border-active',
|
||||
checked && 'bg-state-accent-hover hover:bg-state-accent-hover',
|
||||
disabled && 'cursor-not-allowed opacity-50 hover:bg-transparent',
|
||||
)}
|
||||
onClick={handleToggle}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === ' ' || e.key === 'Enter') {
|
||||
e.preventDefault()
|
||||
handleToggle()
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Checkbox
|
||||
checked={checked}
|
||||
disabled={disabled}
|
||||
className="pointer-events-none mt-0.5"
|
||||
/>
|
||||
{renderRoleText(role)}
|
||||
</div>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
<ScrollArea className="min-h-0 flex-1">
|
||||
<ScrollAreaViewport ref={containerRef} className="overscroll-contain px-3">
|
||||
<ScrollAreaContent>
|
||||
{rolesLoading ? (
|
||||
<div className="px-3 py-6 text-center system-sm-regular text-text-tertiary">
|
||||
{t(($) => $['role.loading'], { ns: 'permission' })}
|
||||
</div>
|
||||
) : filteredRoles.length === 0 ? (
|
||||
<div className="px-3 py-6 text-center system-sm-regular text-text-tertiary">
|
||||
{t(($) => $['role.noMatchingRoles'], { ns: 'permission' })}
|
||||
</div>
|
||||
) : (
|
||||
<RadioGroup
|
||||
aria-label={t(($) => $['role.workspaceRoles.title'], { ns: 'permission' })}
|
||||
value={selectedRoleIds[0] ?? ''}
|
||||
onValueChange={handleRadioValueChange}
|
||||
className="flex-col items-stretch gap-0.5 pb-2"
|
||||
render={<ul />}
|
||||
>
|
||||
{filteredRoles.map((role) => {
|
||||
const checked = selectedRoleIdSet.has(role.id)
|
||||
const disabled = disabledRoleIdSet.has(role.id)
|
||||
<>
|
||||
{allowMultipleRoles ? (
|
||||
<ul className="flex flex-col gap-0.5 pb-2">
|
||||
{filteredRoles.map((role) => {
|
||||
const checked = selectedRoleIdSet.has(role.id)
|
||||
const disabled = disabledRoleIdSet.has(role.id)
|
||||
const handleToggle = () => toggleRole(role)
|
||||
|
||||
return (
|
||||
<li key={role.id}>
|
||||
<RadioItem
|
||||
value={role.id}
|
||||
disabled={disabled}
|
||||
nativeButton
|
||||
render={
|
||||
<button
|
||||
type="button"
|
||||
return (
|
||||
<li key={role.id}>
|
||||
<div
|
||||
role="checkbox"
|
||||
aria-checked={checked}
|
||||
aria-disabled={disabled}
|
||||
tabIndex={disabled ? -1 : 0}
|
||||
className={cn(
|
||||
'flex w-full cursor-pointer items-start gap-3 rounded-lg border-0 bg-transparent px-3 py-2.5 text-left hover:bg-state-base-hover focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-components-input-border-active',
|
||||
'flex cursor-pointer items-start gap-3 rounded-lg px-3 py-2.5 hover:bg-state-base-hover focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-components-input-border-active',
|
||||
checked && 'bg-state-accent-hover hover:bg-state-accent-hover',
|
||||
disabled && 'cursor-not-allowed opacity-50 hover:bg-transparent',
|
||||
)}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<RadioControl className="pointer-events-none mt-0.5" />
|
||||
{renderRoleText(role)}
|
||||
</RadioItem>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</RadioGroup>
|
||||
onClick={handleToggle}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === ' ' || e.key === 'Enter') {
|
||||
e.preventDefault()
|
||||
handleToggle()
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Checkbox
|
||||
checked={checked}
|
||||
disabled={disabled}
|
||||
className="pointer-events-none mt-0.5"
|
||||
/>
|
||||
{renderRoleText(role)}
|
||||
</div>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
) : (
|
||||
<RadioGroup
|
||||
aria-label={t(($) => $['role.workspaceRoles.title'], { ns: 'permission' })}
|
||||
value={selectedRoleIds[0] ?? ''}
|
||||
onValueChange={handleRadioValueChange}
|
||||
className="flex-col items-stretch gap-0.5 pb-2"
|
||||
render={<ul />}
|
||||
>
|
||||
{filteredRoles.map((role) => {
|
||||
const checked = selectedRoleIdSet.has(role.id)
|
||||
const disabled = disabledRoleIdSet.has(role.id)
|
||||
|
||||
return (
|
||||
<li key={role.id}>
|
||||
<RadioItem
|
||||
value={role.id}
|
||||
disabled={disabled}
|
||||
nativeButton
|
||||
render={
|
||||
<button
|
||||
type="button"
|
||||
className={cn(
|
||||
'flex w-full cursor-pointer items-start gap-3 rounded-lg border-0 bg-transparent px-3 py-2.5 text-left hover:bg-state-base-hover focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-components-input-border-active',
|
||||
checked && 'bg-state-accent-hover hover:bg-state-accent-hover',
|
||||
disabled && 'cursor-not-allowed opacity-50 hover:bg-transparent',
|
||||
)}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<RadioControl className="pointer-events-none mt-0.5" />
|
||||
{renderRoleText(role)}
|
||||
</RadioItem>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</RadioGroup>
|
||||
)}
|
||||
<div ref={anchorRef} className="h-0" />
|
||||
</>
|
||||
)}
|
||||
<div ref={anchorRef} className="h-0" />
|
||||
</>
|
||||
)}
|
||||
</ScrollAreaContent>
|
||||
</ScrollAreaViewport>
|
||||
<ScrollAreaScrollbar>
|
||||
<ScrollAreaThumb />
|
||||
</ScrollAreaScrollbar>
|
||||
</ScrollArea>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -5,7 +5,13 @@ import type { IntegrationSection } from '@/app/components/integrations/routes'
|
||||
import type { DocPathWithoutLang } from '@/types/doc-paths'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { Collapsible, CollapsiblePanel, CollapsibleTrigger } from '@langgenius/dify-ui/collapsible'
|
||||
import { ScrollArea } from '@langgenius/dify-ui/scroll-area'
|
||||
import {
|
||||
ScrollArea,
|
||||
ScrollAreaContent,
|
||||
ScrollAreaScrollbar,
|
||||
ScrollAreaThumb,
|
||||
ScrollAreaViewport,
|
||||
} from '@langgenius/dify-ui/scroll-area'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import UpdateSettingDialog from '@/app/components/header/account-setting/update-setting-dialog'
|
||||
@@ -339,28 +345,32 @@ export default function IntegrationsPage({
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<ScrollArea
|
||||
className="min-h-0 flex-1 overflow-hidden"
|
||||
label={scrollAreaLabel}
|
||||
slotClassNames={{
|
||||
viewport: 'overscroll-contain',
|
||||
content: 'min-h-full',
|
||||
}}
|
||||
>
|
||||
<IntegrationSectionRenderer
|
||||
key={section}
|
||||
section={section}
|
||||
title={integrationHeader?.title ?? activeItem?.label}
|
||||
description={headerDescriptionWithLink}
|
||||
providerSearchText={providerSearchText}
|
||||
onProviderSearchTextChange={setProviderSearchText}
|
||||
onSwitchToMarketplace={handleSwitchToMarketplace}
|
||||
canInstallPlugin={canInstallPlugin}
|
||||
canDeletePlugin={canDeletePlugin}
|
||||
isInstallPermissionLoading={isReferenceSettingLoading}
|
||||
canUpdatePlugin={canUpdatePlugin}
|
||||
pluginCategoryToolbarAction={pluginSettingAction}
|
||||
/>
|
||||
<ScrollArea className="min-h-0 flex-1 overflow-hidden">
|
||||
<ScrollAreaViewport
|
||||
aria-label={scrollAreaLabel}
|
||||
className="overscroll-contain"
|
||||
role={scrollAreaLabel ? 'region' : undefined}
|
||||
>
|
||||
<ScrollAreaContent className="min-h-full">
|
||||
<IntegrationSectionRenderer
|
||||
key={section}
|
||||
section={section}
|
||||
title={integrationHeader?.title ?? activeItem?.label}
|
||||
description={headerDescriptionWithLink}
|
||||
providerSearchText={providerSearchText}
|
||||
onProviderSearchTextChange={setProviderSearchText}
|
||||
onSwitchToMarketplace={handleSwitchToMarketplace}
|
||||
canInstallPlugin={canInstallPlugin}
|
||||
canDeletePlugin={canDeletePlugin}
|
||||
isInstallPermissionLoading={isReferenceSettingLoading}
|
||||
canUpdatePlugin={canUpdatePlugin}
|
||||
pluginCategoryToolbarAction={pluginSettingAction}
|
||||
/>
|
||||
</ScrollAreaContent>
|
||||
</ScrollAreaViewport>
|
||||
<ScrollAreaScrollbar>
|
||||
<ScrollAreaThumb />
|
||||
</ScrollAreaScrollbar>
|
||||
</ScrollArea>
|
||||
)}
|
||||
</section>
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
'use client'
|
||||
|
||||
import type { ReactNode } from 'react'
|
||||
import { ScrollArea } from '@langgenius/dify-ui/scroll-area'
|
||||
import {
|
||||
ScrollArea,
|
||||
ScrollAreaContent,
|
||||
ScrollAreaScrollbar,
|
||||
ScrollAreaThumb,
|
||||
ScrollAreaViewport,
|
||||
} from '@langgenius/dify-ui/scroll-area'
|
||||
|
||||
type IntegrationSectionLayoutProps = {
|
||||
bodyClassName?: string
|
||||
@@ -15,15 +21,19 @@ export function IntegrationSectionLayout({
|
||||
label,
|
||||
}: IntegrationSectionLayoutProps) {
|
||||
return (
|
||||
<ScrollArea
|
||||
className="min-h-0 flex-1 overflow-hidden"
|
||||
label={label}
|
||||
slotClassNames={{
|
||||
viewport: 'overscroll-contain',
|
||||
content: 'min-h-full',
|
||||
}}
|
||||
>
|
||||
<div className={bodyClassName}>{children}</div>
|
||||
<ScrollArea className="min-h-0 flex-1 overflow-hidden">
|
||||
<ScrollAreaViewport
|
||||
aria-label={label}
|
||||
className="overscroll-contain"
|
||||
role={label ? 'region' : undefined}
|
||||
>
|
||||
<ScrollAreaContent className="min-h-full">
|
||||
<div className={bodyClassName}>{children}</div>
|
||||
</ScrollAreaContent>
|
||||
</ScrollAreaViewport>
|
||||
<ScrollAreaScrollbar>
|
||||
<ScrollAreaThumb />
|
||||
</ScrollAreaScrollbar>
|
||||
</ScrollArea>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@ import type { ToolsContentInset } from '@/app/components/tools/content-inset'
|
||||
import type { Collection } from '@/app/components/tools/types'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import {
|
||||
ScrollArea,
|
||||
ScrollAreaContent,
|
||||
ScrollAreaRoot,
|
||||
ScrollAreaScrollbar,
|
||||
ScrollAreaThumb,
|
||||
ScrollAreaViewport,
|
||||
@@ -203,7 +203,7 @@ const ProviderList = ({ category, contentInset = 'default', layout }: ProviderLi
|
||||
const body = (
|
||||
<>
|
||||
<div className="relative flex h-0 shrink-0 grow flex-col overflow-hidden bg-components-panel-bg">
|
||||
<ScrollAreaRoot className="relative min-h-0 grow overflow-hidden bg-components-panel-bg">
|
||||
<ScrollArea className="relative min-h-0 grow overflow-hidden bg-components-panel-bg">
|
||||
<ScrollAreaViewport
|
||||
ref={containerRef}
|
||||
aria-label={t(($) => $['menus.tools'], { ns: 'common' })}
|
||||
@@ -261,7 +261,7 @@ const ProviderList = ({ category, contentInset = 'default', layout }: ProviderLi
|
||||
<ScrollAreaScrollbar>
|
||||
<ScrollAreaThumb />
|
||||
</ScrollAreaScrollbar>
|
||||
</ScrollAreaRoot>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
{currentProvider && !currentProvider.plugin_id && (
|
||||
<ProviderDetail
|
||||
|
||||
@@ -16,8 +16,8 @@ import {
|
||||
} from '@langgenius/dify-ui/alert-dialog'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import {
|
||||
ScrollArea,
|
||||
ScrollAreaContent,
|
||||
ScrollAreaRoot,
|
||||
ScrollAreaScrollbar,
|
||||
ScrollAreaThumb,
|
||||
ScrollAreaViewport,
|
||||
@@ -213,15 +213,16 @@ const WebAppsSectionContent = () => {
|
||||
</div>
|
||||
)}
|
||||
{appsExpanded && (
|
||||
<ScrollAreaRoot className="relative min-h-0 flex-1 overflow-hidden overscroll-contain">
|
||||
<ScrollArea className="relative min-h-0 flex-1 overflow-hidden">
|
||||
<ScrollAreaViewport
|
||||
ref={scrollRef}
|
||||
aria-busy={installedAppsQuery.isPending || installedAppsQuery.isFetchingNextPage}
|
||||
aria-label={t(($) => $['sidebar.webApps'], { ns: 'explore' })}
|
||||
className="overflow-x-hidden"
|
||||
style={{ overflowX: 'hidden' }}
|
||||
className="overscroll-contain"
|
||||
role="region"
|
||||
>
|
||||
<ScrollAreaContent className="w-full max-w-full min-w-0! px-2">
|
||||
<ScrollAreaContent style={{ minWidth: 0 }} className="w-full max-w-full px-2">
|
||||
{installedAppsQuery.isPending && <WebAppsSkeleton />}
|
||||
{!installedAppsQuery.isPending && installedAppsQuery.isError && (
|
||||
<div
|
||||
@@ -277,7 +278,7 @@ const WebAppsSectionContent = () => {
|
||||
<ScrollAreaScrollbar>
|
||||
<ScrollAreaThumb />
|
||||
</ScrollAreaScrollbar>
|
||||
</ScrollAreaRoot>
|
||||
</ScrollArea>
|
||||
)}
|
||||
<AlertDialog
|
||||
open={uninstallDialogAppId !== null}
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import type { PluginStatus } from '@/app/components/plugins/types'
|
||||
import type { Locale } from '@/i18n-config'
|
||||
import { ScrollArea } from '@langgenius/dify-ui/scroll-area'
|
||||
import {
|
||||
ScrollArea,
|
||||
ScrollAreaContent,
|
||||
ScrollAreaScrollbar,
|
||||
ScrollAreaThumb,
|
||||
ScrollAreaViewport,
|
||||
} from '@langgenius/dify-ui/scroll-area'
|
||||
import PluginItem from './plugin-item'
|
||||
|
||||
type PluginSectionProps = {
|
||||
@@ -36,36 +42,36 @@ function PluginSection({
|
||||
return (
|
||||
<>
|
||||
<div className="sticky top-0 flex items-start justify-between pt-1 pr-1 pl-2 system-sm-semibold-uppercase text-text-secondary">
|
||||
<span className="flex h-6 items-center">
|
||||
<h3 className="flex h-6 items-center">
|
||||
{title} ({count})
|
||||
</span>
|
||||
</h3>
|
||||
{headerAction}
|
||||
</div>
|
||||
<ScrollArea
|
||||
className="overflow-hidden"
|
||||
label={title}
|
||||
slotClassNames={{
|
||||
viewport: 'overscroll-contain',
|
||||
content: 'min-w-0',
|
||||
}}
|
||||
>
|
||||
{plugins.map((plugin) => (
|
||||
<PluginItem
|
||||
key={plugin.plugin_unique_identifier}
|
||||
plugin={plugin}
|
||||
getIconUrl={getIconUrl}
|
||||
language={language}
|
||||
statusIcon={statusIcon}
|
||||
statusText={plugin.message || defaultStatusText}
|
||||
statusClassName={statusClassName}
|
||||
action={renderItemAction?.(plugin)}
|
||||
onClear={
|
||||
onClearSingle
|
||||
? () => onClearSingle(plugin.taskId, plugin.plugin_unique_identifier)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
))}
|
||||
<ScrollArea className="overflow-hidden">
|
||||
<ScrollAreaViewport className="overscroll-contain">
|
||||
<ScrollAreaContent style={{ minWidth: 0 }}>
|
||||
{plugins.map((plugin) => (
|
||||
<PluginItem
|
||||
key={plugin.plugin_unique_identifier}
|
||||
plugin={plugin}
|
||||
getIconUrl={getIconUrl}
|
||||
language={language}
|
||||
statusIcon={statusIcon}
|
||||
statusText={plugin.message || defaultStatusText}
|
||||
statusClassName={statusClassName}
|
||||
action={renderItemAction?.(plugin)}
|
||||
onClear={
|
||||
onClearSingle
|
||||
? () => onClearSingle(plugin.taskId, plugin.plugin_unique_identifier)
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</ScrollAreaContent>
|
||||
</ScrollAreaViewport>
|
||||
<ScrollAreaScrollbar>
|
||||
<ScrollAreaThumb />
|
||||
</ScrollAreaScrollbar>
|
||||
</ScrollArea>
|
||||
</>
|
||||
)
|
||||
|
||||
+88
-77
@@ -1,6 +1,12 @@
|
||||
import type { PluginStatus } from '@/app/components/plugins/types'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { ScrollArea } from '@langgenius/dify-ui/scroll-area'
|
||||
import {
|
||||
ScrollArea,
|
||||
ScrollAreaContent,
|
||||
ScrollAreaScrollbar,
|
||||
ScrollAreaThumb,
|
||||
ScrollAreaViewport,
|
||||
} from '@langgenius/dify-ui/scroll-area'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useGetLanguage } from '@/context/i18n'
|
||||
import ErrorPluginItem from './error-plugin-item'
|
||||
@@ -36,84 +42,89 @@ function PluginTaskList({
|
||||
className="w-90 max-w-[calc(100vw-32px)] overflow-hidden rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg"
|
||||
data-testid="plugin-task-list"
|
||||
>
|
||||
<ScrollArea
|
||||
className="max-h-105 overflow-hidden"
|
||||
label={t(($) => $['task.installing'], { ns: 'plugin' })}
|
||||
slotClassNames={{
|
||||
viewport: 'max-h-[420px] overscroll-contain',
|
||||
content: 'w-full! max-w-full! min-w-0! overflow-x-hidden! pr-3',
|
||||
}}
|
||||
>
|
||||
{runningPlugins.length > 0 && (
|
||||
<PluginSection
|
||||
title={runningSectionTitle}
|
||||
count={runningPlugins.length}
|
||||
plugins={runningPlugins}
|
||||
getIconUrl={getIconUrl}
|
||||
language={language}
|
||||
statusIcon={
|
||||
<span className="i-ri-loader-2-line size-3.5 animate-spin text-text-accent" />
|
||||
}
|
||||
defaultStatusText={t(($) => $['task.installingHint'], { ns: 'plugin' })}
|
||||
/>
|
||||
)}
|
||||
<ScrollArea className="max-h-105 overflow-hidden">
|
||||
<ScrollAreaViewport
|
||||
aria-label={t(($) => $['task.installing'], { ns: 'plugin' })}
|
||||
style={{ overflowX: 'hidden' }}
|
||||
className="max-h-[420px] overscroll-contain"
|
||||
role="region"
|
||||
>
|
||||
<ScrollAreaContent style={{ minWidth: 0 }} className="w-full max-w-full pr-3">
|
||||
{runningPlugins.length > 0 && (
|
||||
<PluginSection
|
||||
title={runningSectionTitle}
|
||||
count={runningPlugins.length}
|
||||
plugins={runningPlugins}
|
||||
getIconUrl={getIconUrl}
|
||||
language={language}
|
||||
statusIcon={
|
||||
<span className="i-ri-loader-2-line size-3.5 animate-spin text-text-accent" />
|
||||
}
|
||||
defaultStatusText={t(($) => $['task.installingHint'], { ns: 'plugin' })}
|
||||
/>
|
||||
)}
|
||||
|
||||
{successPlugins.length > 0 && (
|
||||
<PluginSection
|
||||
title={successSectionTitle}
|
||||
count={successPlugins.length}
|
||||
plugins={successPlugins}
|
||||
getIconUrl={getIconUrl}
|
||||
language={language}
|
||||
statusIcon={<span className="i-ri-checkbox-circle-fill size-3.5 text-text-success" />}
|
||||
defaultStatusText={t(($) => $['task.installed'], { ns: 'plugin' })}
|
||||
statusClassName="text-text-success"
|
||||
headerAction={
|
||||
<Button
|
||||
aria-label={`${successSectionTitle} ${t(($) => $['task.clearAll'], { ns: 'plugin' })}`}
|
||||
className="shrink-0"
|
||||
size="small"
|
||||
variant="ghost"
|
||||
onClick={onClearAll}
|
||||
>
|
||||
{t(($) => $['task.clearAll'], { ns: 'plugin' })}
|
||||
</Button>
|
||||
}
|
||||
onClearSingle={onClearSingle}
|
||||
/>
|
||||
)}
|
||||
{successPlugins.length > 0 && (
|
||||
<PluginSection
|
||||
title={successSectionTitle}
|
||||
count={successPlugins.length}
|
||||
plugins={successPlugins}
|
||||
getIconUrl={getIconUrl}
|
||||
language={language}
|
||||
statusIcon={
|
||||
<span className="i-ri-checkbox-circle-fill size-3.5 text-text-success" />
|
||||
}
|
||||
defaultStatusText={t(($) => $['task.installed'], { ns: 'plugin' })}
|
||||
statusClassName="text-text-success"
|
||||
headerAction={
|
||||
<Button
|
||||
aria-label={`${successSectionTitle} ${t(($) => $['task.clearAll'], { ns: 'plugin' })}`}
|
||||
className="shrink-0"
|
||||
size="small"
|
||||
variant="ghost"
|
||||
onClick={onClearAll}
|
||||
>
|
||||
{t(($) => $['task.clearAll'], { ns: 'plugin' })}
|
||||
</Button>
|
||||
}
|
||||
onClearSingle={onClearSingle}
|
||||
/>
|
||||
)}
|
||||
|
||||
{errorPlugins.length > 0 && (
|
||||
<>
|
||||
<div className="sticky top-0 flex h-7 items-center justify-between px-2 pt-1 system-sm-semibold-uppercase text-text-secondary">
|
||||
{errorSectionTitle} ({errorPlugins.length})
|
||||
<Button
|
||||
aria-label={`${errorSectionTitle} ${t(($) => $['task.clearAll'], { ns: 'plugin' })}`}
|
||||
className="shrink-0"
|
||||
size="small"
|
||||
variant="ghost"
|
||||
onClick={onClearErrors}
|
||||
>
|
||||
{t(($) => $['task.clearAll'], { ns: 'plugin' })}
|
||||
</Button>
|
||||
</div>
|
||||
<div
|
||||
aria-label={errorSectionTitle}
|
||||
className="w-full max-w-full min-w-0 overflow-hidden"
|
||||
role="region"
|
||||
>
|
||||
{errorPlugins.map((plugin) => (
|
||||
<ErrorPluginItem
|
||||
key={plugin.plugin_unique_identifier}
|
||||
plugin={plugin}
|
||||
getIconUrl={getIconUrl}
|
||||
language={language}
|
||||
onClear={() => onClearSingle(plugin.taskId, plugin.plugin_unique_identifier)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{errorPlugins.length > 0 && (
|
||||
<>
|
||||
<div className="sticky top-0 flex h-7 items-center justify-between px-2 pt-1 system-sm-semibold-uppercase text-text-secondary">
|
||||
<h3>
|
||||
{errorSectionTitle} ({errorPlugins.length})
|
||||
</h3>
|
||||
<Button
|
||||
aria-label={`${errorSectionTitle} ${t(($) => $['task.clearAll'], { ns: 'plugin' })}`}
|
||||
className="shrink-0"
|
||||
size="small"
|
||||
variant="ghost"
|
||||
onClick={onClearErrors}
|
||||
>
|
||||
{t(($) => $['task.clearAll'], { ns: 'plugin' })}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="w-full max-w-full min-w-0 overflow-hidden">
|
||||
{errorPlugins.map((plugin) => (
|
||||
<ErrorPluginItem
|
||||
key={plugin.plugin_unique_identifier}
|
||||
plugin={plugin}
|
||||
getIconUrl={getIconUrl}
|
||||
language={language}
|
||||
onClear={() => onClearSingle(plugin.taskId, plugin.plugin_unique_identifier)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</ScrollAreaContent>
|
||||
</ScrollAreaViewport>
|
||||
<ScrollAreaScrollbar>
|
||||
<ScrollAreaThumb />
|
||||
</ScrollAreaScrollbar>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -5,8 +5,8 @@ import type { Collection } from '@/app/components/tools/types'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import {
|
||||
ScrollArea,
|
||||
ScrollAreaContent,
|
||||
ScrollAreaRoot,
|
||||
ScrollAreaScrollbar,
|
||||
ScrollAreaThumb,
|
||||
ScrollAreaViewport,
|
||||
@@ -103,7 +103,7 @@ const PluginsPanelResults = ({
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<ScrollAreaRoot
|
||||
<ScrollArea
|
||||
className={cn(
|
||||
'min-h-0 grow self-stretch overflow-hidden bg-components-panel-bg',
|
||||
contentFrameClassName,
|
||||
@@ -169,7 +169,7 @@ const PluginsPanelResults = ({
|
||||
<ScrollAreaScrollbar>
|
||||
<ScrollAreaThumb />
|
||||
</ScrollAreaScrollbar>
|
||||
</ScrollAreaRoot>
|
||||
</ScrollArea>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,13 @@ import { Field, FieldItem, FieldLabel } from '@langgenius/dify-ui/field'
|
||||
import { Fieldset, FieldsetLegend } from '@langgenius/dify-ui/fieldset'
|
||||
import { Input } from '@langgenius/dify-ui/input'
|
||||
import { Radio, RadioGroup } from '@langgenius/dify-ui/radio'
|
||||
import { ScrollArea } from '@langgenius/dify-ui/scroll-area'
|
||||
import {
|
||||
ScrollArea,
|
||||
ScrollAreaContent,
|
||||
ScrollAreaScrollbar,
|
||||
ScrollAreaThumb,
|
||||
ScrollAreaViewport,
|
||||
} from '@langgenius/dify-ui/scroll-area'
|
||||
import { useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Infotip } from '@/app/components/base/infotip'
|
||||
@@ -112,185 +118,203 @@ export default function ConfigCredential({ positionCenter, credential, onChange,
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<ScrollArea
|
||||
className="min-h-0 flex-1 overflow-hidden"
|
||||
slotClassNames={{
|
||||
viewport: 'overscroll-contain',
|
||||
content: 'space-y-4 pt-2 pr-8 pl-6',
|
||||
}}
|
||||
>
|
||||
<Field name="auth_type" className="contents">
|
||||
<Fieldset
|
||||
render={
|
||||
<RadioGroup<AuthType>
|
||||
className="grid grid-cols-[repeat(auto-fit,minmax(8.5rem,1fr))] gap-2"
|
||||
value={tempCredential.auth_type}
|
||||
onValueChange={handleAuthTypeChange}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<FieldsetLegend className="col-span-full py-2 system-sm-medium text-text-primary">
|
||||
{t(($) => $['createTool.authMethod.type'], { ns: 'tools' })}
|
||||
</FieldsetLegend>
|
||||
<SelectItem<AuthType>
|
||||
text={t(($) => $['createTool.authMethod.types.none'], { ns: 'tools' })}
|
||||
value={AuthType.none}
|
||||
isChecked={tempCredential.auth_type === AuthType.none}
|
||||
/>
|
||||
<SelectItem<AuthType>
|
||||
text={t(($) => $['createTool.authMethod.types.api_key_header'], {
|
||||
ns: 'tools',
|
||||
})}
|
||||
value={AuthType.apiKeyHeader}
|
||||
isChecked={tempCredential.auth_type === AuthType.apiKeyHeader}
|
||||
/>
|
||||
<SelectItem<AuthType>
|
||||
text={t(($) => $['createTool.authMethod.types.api_key_query'], {
|
||||
ns: 'tools',
|
||||
})}
|
||||
value={AuthType.apiKeyQuery}
|
||||
isChecked={tempCredential.auth_type === AuthType.apiKeyQuery}
|
||||
/>
|
||||
</Fieldset>
|
||||
</Field>
|
||||
{tempCredential.auth_type === AuthType.apiKeyHeader && (
|
||||
<>
|
||||
<Field name="api_key_header_prefix" className="contents">
|
||||
<ScrollArea className="min-h-0 flex-1 overflow-hidden">
|
||||
<ScrollAreaViewport className="overscroll-contain">
|
||||
<ScrollAreaContent className="space-y-4 pt-2 pr-8 pl-6">
|
||||
<Field name="auth_type" className="contents">
|
||||
<Fieldset
|
||||
render={
|
||||
<RadioGroup<AuthHeaderPrefix>
|
||||
<RadioGroup<AuthType>
|
||||
className="grid grid-cols-[repeat(auto-fit,minmax(8.5rem,1fr))] gap-2"
|
||||
value={tempCredential.api_key_header_prefix}
|
||||
onValueChange={(value) =>
|
||||
setTempCredential({ ...tempCredential, api_key_header_prefix: value })
|
||||
}
|
||||
value={tempCredential.auth_type}
|
||||
onValueChange={handleAuthTypeChange}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<FieldsetLegend className="col-span-full py-2 system-sm-medium text-text-primary">
|
||||
{t(($) => $['createTool.authHeaderPrefix.title'], { ns: 'tools' })}
|
||||
{t(($) => $['createTool.authMethod.type'], { ns: 'tools' })}
|
||||
</FieldsetLegend>
|
||||
<SelectItem<AuthHeaderPrefix>
|
||||
text={t(($) => $['createTool.authHeaderPrefix.types.basic'], {
|
||||
ns: 'tools',
|
||||
})}
|
||||
value={AuthHeaderPrefix.basic}
|
||||
isChecked={
|
||||
tempCredential.api_key_header_prefix === AuthHeaderPrefix.basic
|
||||
}
|
||||
<SelectItem<AuthType>
|
||||
text={t(($) => $['createTool.authMethod.types.none'], { ns: 'tools' })}
|
||||
value={AuthType.none}
|
||||
isChecked={tempCredential.auth_type === AuthType.none}
|
||||
/>
|
||||
<SelectItem<AuthHeaderPrefix>
|
||||
text={t(($) => $['createTool.authHeaderPrefix.types.bearer'], {
|
||||
<SelectItem<AuthType>
|
||||
text={t(($) => $['createTool.authMethod.types.api_key_header'], {
|
||||
ns: 'tools',
|
||||
})}
|
||||
value={AuthHeaderPrefix.bearer}
|
||||
isChecked={
|
||||
tempCredential.api_key_header_prefix === AuthHeaderPrefix.bearer
|
||||
}
|
||||
value={AuthType.apiKeyHeader}
|
||||
isChecked={tempCredential.auth_type === AuthType.apiKeyHeader}
|
||||
/>
|
||||
<SelectItem<AuthHeaderPrefix>
|
||||
text={t(($) => $['createTool.authHeaderPrefix.types.custom'], {
|
||||
<SelectItem<AuthType>
|
||||
text={t(($) => $['createTool.authMethod.types.api_key_query'], {
|
||||
ns: 'tools',
|
||||
})}
|
||||
value={AuthHeaderPrefix.custom}
|
||||
isChecked={
|
||||
tempCredential.api_key_header_prefix === AuthHeaderPrefix.custom
|
||||
}
|
||||
value={AuthType.apiKeyQuery}
|
||||
isChecked={tempCredential.auth_type === AuthType.apiKeyQuery}
|
||||
/>
|
||||
</Fieldset>
|
||||
</Field>
|
||||
<div>
|
||||
<div className="flex items-center py-2 system-sm-medium text-text-primary">
|
||||
{t(($) => $['createTool.authMethod.key'], { ns: 'tools' })}
|
||||
<Infotip
|
||||
aria-label={t(($) => $['createTool.authMethod.keyTooltip'], {
|
||||
ns: 'tools',
|
||||
})}
|
||||
className="ml-0.5 size-4"
|
||||
popupClassName="w-[261px] text-text-tertiary"
|
||||
>
|
||||
{t(($) => $['createTool.authMethod.keyTooltip'], { ns: 'tools' })}
|
||||
</Infotip>
|
||||
</div>
|
||||
<Input
|
||||
value={tempCredential.api_key_header}
|
||||
onChange={(e) =>
|
||||
setTempCredential({ ...tempCredential, api_key_header: e.target.value })
|
||||
}
|
||||
placeholder={t(($) => $['createTool.authMethod.types.apiKeyPlaceholder'], {
|
||||
ns: 'tools',
|
||||
})!}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div className="py-2 system-sm-medium text-text-primary">
|
||||
{t(($) => $['createTool.authMethod.value'], { ns: 'tools' })}
|
||||
</div>
|
||||
<Input
|
||||
value={tempCredential.api_key_value}
|
||||
onChange={(e) =>
|
||||
setTempCredential({ ...tempCredential, api_key_value: e.target.value })
|
||||
}
|
||||
placeholder={t(
|
||||
($) => $['createTool.authMethod.types.apiValuePlaceholder'],
|
||||
{
|
||||
ns: 'tools',
|
||||
},
|
||||
)!}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{tempCredential.auth_type === AuthType.apiKeyQuery && (
|
||||
<>
|
||||
<div>
|
||||
<div className="flex items-center py-2 system-sm-medium text-text-primary">
|
||||
{t(($) => $['createTool.authMethod.queryParam'], { ns: 'tools' })}
|
||||
<Infotip
|
||||
aria-label={t(($) => $['createTool.authMethod.queryParamTooltip'], {
|
||||
ns: 'tools',
|
||||
})}
|
||||
className="ml-0.5 size-4"
|
||||
popupClassName="w-[261px] text-text-tertiary"
|
||||
>
|
||||
{t(($) => $['createTool.authMethod.queryParamTooltip'], { ns: 'tools' })}
|
||||
</Infotip>
|
||||
</div>
|
||||
<Input
|
||||
value={tempCredential.api_key_query_param}
|
||||
onChange={(e) =>
|
||||
setTempCredential({
|
||||
...tempCredential,
|
||||
api_key_query_param: e.target.value,
|
||||
})
|
||||
}
|
||||
placeholder={t(
|
||||
($) => $['createTool.authMethod.types.queryParamPlaceholder'],
|
||||
{
|
||||
ns: 'tools',
|
||||
},
|
||||
)!}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div className="py-2 system-sm-medium text-text-primary">
|
||||
{t(($) => $['createTool.authMethod.value'], { ns: 'tools' })}
|
||||
</div>
|
||||
<Input
|
||||
value={tempCredential.api_key_value}
|
||||
onChange={(e) =>
|
||||
setTempCredential({ ...tempCredential, api_key_value: e.target.value })
|
||||
}
|
||||
placeholder={t(
|
||||
($) => $['createTool.authMethod.types.apiValuePlaceholder'],
|
||||
{
|
||||
ns: 'tools',
|
||||
},
|
||||
)!}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{tempCredential.auth_type === AuthType.apiKeyHeader && (
|
||||
<>
|
||||
<Field name="api_key_header_prefix" className="contents">
|
||||
<Fieldset
|
||||
render={
|
||||
<RadioGroup<AuthHeaderPrefix>
|
||||
className="grid grid-cols-[repeat(auto-fit,minmax(8.5rem,1fr))] gap-2"
|
||||
value={tempCredential.api_key_header_prefix}
|
||||
onValueChange={(value) =>
|
||||
setTempCredential({
|
||||
...tempCredential,
|
||||
api_key_header_prefix: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<FieldsetLegend className="col-span-full py-2 system-sm-medium text-text-primary">
|
||||
{t(($) => $['createTool.authHeaderPrefix.title'], { ns: 'tools' })}
|
||||
</FieldsetLegend>
|
||||
<SelectItem<AuthHeaderPrefix>
|
||||
text={t(($) => $['createTool.authHeaderPrefix.types.basic'], {
|
||||
ns: 'tools',
|
||||
})}
|
||||
value={AuthHeaderPrefix.basic}
|
||||
isChecked={
|
||||
tempCredential.api_key_header_prefix === AuthHeaderPrefix.basic
|
||||
}
|
||||
/>
|
||||
<SelectItem<AuthHeaderPrefix>
|
||||
text={t(($) => $['createTool.authHeaderPrefix.types.bearer'], {
|
||||
ns: 'tools',
|
||||
})}
|
||||
value={AuthHeaderPrefix.bearer}
|
||||
isChecked={
|
||||
tempCredential.api_key_header_prefix === AuthHeaderPrefix.bearer
|
||||
}
|
||||
/>
|
||||
<SelectItem<AuthHeaderPrefix>
|
||||
text={t(($) => $['createTool.authHeaderPrefix.types.custom'], {
|
||||
ns: 'tools',
|
||||
})}
|
||||
value={AuthHeaderPrefix.custom}
|
||||
isChecked={
|
||||
tempCredential.api_key_header_prefix === AuthHeaderPrefix.custom
|
||||
}
|
||||
/>
|
||||
</Fieldset>
|
||||
</Field>
|
||||
<div>
|
||||
<div className="flex items-center py-2 system-sm-medium text-text-primary">
|
||||
{t(($) => $['createTool.authMethod.key'], { ns: 'tools' })}
|
||||
<Infotip
|
||||
aria-label={t(($) => $['createTool.authMethod.keyTooltip'], {
|
||||
ns: 'tools',
|
||||
})}
|
||||
className="ml-0.5 size-4"
|
||||
popupClassName="w-[261px] text-text-tertiary"
|
||||
>
|
||||
{t(($) => $['createTool.authMethod.keyTooltip'], { ns: 'tools' })}
|
||||
</Infotip>
|
||||
</div>
|
||||
<Input
|
||||
value={tempCredential.api_key_header}
|
||||
onChange={(e) =>
|
||||
setTempCredential({
|
||||
...tempCredential,
|
||||
api_key_header: e.target.value,
|
||||
})
|
||||
}
|
||||
placeholder={t(
|
||||
($) => $['createTool.authMethod.types.apiKeyPlaceholder'],
|
||||
{
|
||||
ns: 'tools',
|
||||
},
|
||||
)!}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div className="py-2 system-sm-medium text-text-primary">
|
||||
{t(($) => $['createTool.authMethod.value'], { ns: 'tools' })}
|
||||
</div>
|
||||
<Input
|
||||
value={tempCredential.api_key_value}
|
||||
onChange={(e) =>
|
||||
setTempCredential({
|
||||
...tempCredential,
|
||||
api_key_value: e.target.value,
|
||||
})
|
||||
}
|
||||
placeholder={t(
|
||||
($) => $['createTool.authMethod.types.apiValuePlaceholder'],
|
||||
{
|
||||
ns: 'tools',
|
||||
},
|
||||
)!}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{tempCredential.auth_type === AuthType.apiKeyQuery && (
|
||||
<>
|
||||
<div>
|
||||
<div className="flex items-center py-2 system-sm-medium text-text-primary">
|
||||
{t(($) => $['createTool.authMethod.queryParam'], { ns: 'tools' })}
|
||||
<Infotip
|
||||
aria-label={t(($) => $['createTool.authMethod.queryParamTooltip'], {
|
||||
ns: 'tools',
|
||||
})}
|
||||
className="ml-0.5 size-4"
|
||||
popupClassName="w-[261px] text-text-tertiary"
|
||||
>
|
||||
{t(($) => $['createTool.authMethod.queryParamTooltip'], {
|
||||
ns: 'tools',
|
||||
})}
|
||||
</Infotip>
|
||||
</div>
|
||||
<Input
|
||||
value={tempCredential.api_key_query_param}
|
||||
onChange={(e) =>
|
||||
setTempCredential({
|
||||
...tempCredential,
|
||||
api_key_query_param: e.target.value,
|
||||
})
|
||||
}
|
||||
placeholder={t(
|
||||
($) => $['createTool.authMethod.types.queryParamPlaceholder'],
|
||||
{
|
||||
ns: 'tools',
|
||||
},
|
||||
)!}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div className="py-2 system-sm-medium text-text-primary">
|
||||
{t(($) => $['createTool.authMethod.value'], { ns: 'tools' })}
|
||||
</div>
|
||||
<Input
|
||||
value={tempCredential.api_key_value}
|
||||
onChange={(e) =>
|
||||
setTempCredential({
|
||||
...tempCredential,
|
||||
api_key_value: e.target.value,
|
||||
})
|
||||
}
|
||||
placeholder={t(
|
||||
($) => $['createTool.authMethod.types.apiValuePlaceholder'],
|
||||
{
|
||||
ns: 'tools',
|
||||
},
|
||||
)!}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</ScrollAreaContent>
|
||||
</ScrollAreaViewport>
|
||||
<ScrollAreaScrollbar>
|
||||
<ScrollAreaThumb />
|
||||
</ScrollAreaScrollbar>
|
||||
</ScrollArea>
|
||||
<div className="mt-4 flex shrink-0 justify-end space-x-2 px-6 py-4">
|
||||
<Button onClick={onHide}>
|
||||
|
||||
@@ -8,8 +8,8 @@ import {
|
||||
PreviewCardTrigger,
|
||||
} from '@langgenius/dify-ui/preview-card'
|
||||
import {
|
||||
ScrollArea,
|
||||
ScrollAreaContent,
|
||||
ScrollAreaRoot,
|
||||
ScrollAreaScrollbar,
|
||||
ScrollAreaThumb,
|
||||
ScrollAreaViewport,
|
||||
@@ -154,7 +154,7 @@ const Snippets = ({ searchText, onSearchTextChange, insertPayload, onInserted }:
|
||||
) : !snippets.length ? (
|
||||
<SnippetEmptyState />
|
||||
) : (
|
||||
<ScrollAreaRoot className="relative max-h-120 max-w-125 overflow-hidden">
|
||||
<ScrollArea className="relative max-h-120 max-w-125 overflow-hidden">
|
||||
<ScrollAreaViewport ref={viewportRef}>
|
||||
<ScrollAreaContent className="p-1">
|
||||
{snippets.map((item) => {
|
||||
@@ -183,7 +183,7 @@ const Snippets = ({ searchText, onSearchTextChange, insertPayload, onInserted }:
|
||||
<ScrollAreaScrollbar orientation="vertical">
|
||||
<ScrollAreaThumb />
|
||||
</ScrollAreaScrollbar>
|
||||
</ScrollAreaRoot>
|
||||
</ScrollArea>
|
||||
)
|
||||
|
||||
return (
|
||||
|
||||
@@ -7,8 +7,8 @@ import type {
|
||||
} from '@/app/components/workflow/block-selector/types'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import {
|
||||
ScrollArea,
|
||||
ScrollAreaContent,
|
||||
ScrollAreaRoot,
|
||||
ScrollAreaScrollbar,
|
||||
ScrollAreaThumb,
|
||||
ScrollAreaViewport,
|
||||
@@ -171,7 +171,7 @@ const TriggerPluginItem: FC<Props> = ({
|
||||
</BlockSelectorRow>
|
||||
|
||||
{!notShowProvider && hasAction && !isFold && (
|
||||
<ScrollAreaRoot className="relative max-h-60 overflow-hidden overscroll-contain">
|
||||
<ScrollArea className="relative max-h-60 overflow-hidden">
|
||||
<ScrollAreaViewport
|
||||
aria-label={t(($) => $['tabs.allTriggers'], { ns: 'workflow' })}
|
||||
className="max-h-60 overscroll-contain"
|
||||
@@ -194,7 +194,7 @@ const TriggerPluginItem: FC<Props> = ({
|
||||
<ScrollAreaScrollbar>
|
||||
<ScrollAreaThumb />
|
||||
</ScrollAreaScrollbar>
|
||||
</ScrollAreaRoot>
|
||||
</ScrollArea>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
'use client'
|
||||
|
||||
import { ScrollArea } from '@langgenius/dify-ui/scroll-area'
|
||||
import {
|
||||
ScrollArea,
|
||||
ScrollAreaContent,
|
||||
ScrollAreaScrollbar,
|
||||
ScrollAreaThumb,
|
||||
ScrollAreaViewport,
|
||||
} from '@langgenius/dify-ui/scroll-area'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useDocLink } from '@/context/i18n'
|
||||
@@ -49,35 +55,40 @@ export function AgentAccessPage({ agentId }: AgentAccessPageProps) {
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<ScrollArea
|
||||
className="min-h-0 flex-1 overflow-hidden"
|
||||
slotClassNames={{
|
||||
content: 'px-6 pt-2 pb-8',
|
||||
}}
|
||||
>
|
||||
<div className="w-full min-w-0 space-y-6">
|
||||
<div className="grid w-full grid-cols-1 gap-3 xl:grid-cols-2">
|
||||
<WebAppAccessCard
|
||||
agent={agentQuery.data}
|
||||
agentId={agentId}
|
||||
isLoading={agentQuery.isPending}
|
||||
/>
|
||||
<ServiceApiAccessCard agentId={agentId} />
|
||||
</div>
|
||||
<ScrollArea className="min-h-0 flex-1 overflow-hidden">
|
||||
<ScrollAreaViewport>
|
||||
<ScrollAreaContent className="px-6 pt-2 pb-8">
|
||||
<div className="w-full min-w-0 space-y-6">
|
||||
<div className="grid w-full grid-cols-1 gap-3 xl:grid-cols-2">
|
||||
<WebAppAccessCard
|
||||
agent={agentQuery.data}
|
||||
agentId={agentId}
|
||||
isLoading={agentQuery.isPending}
|
||||
/>
|
||||
<ServiceApiAccessCard agentId={agentId} />
|
||||
</div>
|
||||
|
||||
<section aria-labelledby="agent-workflow-access-title">
|
||||
<div className="mb-3">
|
||||
<h3 id="agent-workflow-access-title" className="system-md-semibold text-text-primary">
|
||||
{t(($) => $['agentDetail.access.workflow.title'])}
|
||||
</h3>
|
||||
<p className="mt-0.5 system-xs-regular text-text-tertiary">
|
||||
{t(($) => $['agentDetail.access.workflow.description'])}
|
||||
</p>
|
||||
<section aria-labelledby="agent-workflow-access-title">
|
||||
<div className="mb-3">
|
||||
<h3
|
||||
id="agent-workflow-access-title"
|
||||
className="system-md-semibold text-text-primary"
|
||||
>
|
||||
{t(($) => $['agentDetail.access.workflow.title'])}
|
||||
</h3>
|
||||
<p className="mt-0.5 system-xs-regular text-text-tertiary">
|
||||
{t(($) => $['agentDetail.access.workflow.description'])}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<WorkflowReferencesTable agentId={agentId} enabled={agentQuery.isSuccess} />
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<WorkflowReferencesTable agentId={agentId} enabled={agentQuery.isSuccess} />
|
||||
</section>
|
||||
</div>
|
||||
</ScrollAreaContent>
|
||||
</ScrollAreaViewport>
|
||||
<ScrollAreaScrollbar>
|
||||
<ScrollAreaThumb />
|
||||
</ScrollAreaScrollbar>
|
||||
</ScrollArea>
|
||||
</AgentDetailSectionSurface>
|
||||
)
|
||||
|
||||
@@ -687,7 +687,7 @@ describe('AgentConfigurePage', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('should expose a single configure workspace region after loading', () => {
|
||||
it('should leave the configure landmark to the orchestrate panel after loading', () => {
|
||||
const queryClient = new QueryClient()
|
||||
mocks.queryState.composer = {
|
||||
data: {},
|
||||
@@ -705,11 +705,8 @@ describe('AgentConfigurePage', () => {
|
||||
)
|
||||
|
||||
expect(
|
||||
screen.getAllByRole('region', { name: 'agentV2.agentDetail.sections.configure' }),
|
||||
).toHaveLength(1)
|
||||
expect(
|
||||
screen.getByRole('region', { name: 'agentV2.agentDetail.sections.configure' }),
|
||||
).toBeVisible()
|
||||
screen.queryByRole('region', { name: 'agentV2.agentDetail.sections.configure' }),
|
||||
).not.toBeInTheDocument()
|
||||
expect(screen.getByRole('region', { name: 'orchestrate-panel' })).toBeInTheDocument()
|
||||
})
|
||||
|
||||
|
||||
+36
-31
@@ -13,7 +13,13 @@ import {
|
||||
FileTreeLabel,
|
||||
FileTreeList,
|
||||
} from '@langgenius/dify-ui/file-tree'
|
||||
import { ScrollArea } from '@langgenius/dify-ui/scroll-area'
|
||||
import {
|
||||
ScrollArea,
|
||||
ScrollAreaContent,
|
||||
ScrollAreaScrollbar,
|
||||
ScrollAreaThumb,
|
||||
ScrollAreaViewport,
|
||||
} from '@langgenius/dify-ui/scroll-area'
|
||||
import { Fragment } from 'react'
|
||||
|
||||
type AgentFileTreeFolderOpenStrategy = (context: { file: AgentFileNode; depth: number }) => boolean
|
||||
@@ -133,8 +139,7 @@ export function AgentFileTree({
|
||||
selectedFileId,
|
||||
id,
|
||||
treeLabel,
|
||||
label,
|
||||
labelledBy,
|
||||
treeLabelledBy,
|
||||
header,
|
||||
className,
|
||||
scrollAreaClassName,
|
||||
@@ -153,8 +158,7 @@ export function AgentFileTree({
|
||||
selectedFileId?: string
|
||||
id?: string
|
||||
treeLabel?: string
|
||||
label?: string
|
||||
labelledBy?: string
|
||||
treeLabelledBy?: string
|
||||
header?: ReactNode
|
||||
className?: string
|
||||
scrollAreaClassName?: string
|
||||
@@ -174,35 +178,36 @@ export function AgentFileTree({
|
||||
{header}
|
||||
<ScrollArea
|
||||
className={cn('min-h-0 w-full max-w-full flex-1 overflow-hidden', scrollAreaClassName)}
|
||||
label={label}
|
||||
labelledBy={labelledBy}
|
||||
slotClassNames={{
|
||||
viewport: 'max-h-[inherit]',
|
||||
content: 'w-full max-w-full min-w-0!',
|
||||
scrollbar: 'hidden',
|
||||
}}
|
||||
>
|
||||
<FileTree
|
||||
id={id}
|
||||
<ScrollAreaViewport
|
||||
aria-label={treeLabel}
|
||||
className={cn('w-full max-w-full min-w-0 p-0', rootClassName)}
|
||||
aria-labelledby={treeLabelledBy}
|
||||
className="max-h-[inherit]"
|
||||
role={treeLabel || treeLabelledBy ? 'region' : undefined}
|
||||
>
|
||||
<FileTreeList className={cn('w-full max-w-full min-w-0', listClassName)}>
|
||||
<AgentFileTreeRows
|
||||
files={files}
|
||||
selectedFileId={selectedFileId}
|
||||
depth={1}
|
||||
folderOpenStrategy={folderOpenStrategy}
|
||||
folderOpenState={folderOpenState}
|
||||
onFolderOpenChange={onFolderOpenChange}
|
||||
onFolderDoubleClick={onFolderDoubleClick}
|
||||
onFolderOpen={onFolderOpen}
|
||||
renderFile={renderFile}
|
||||
renderFolderSuffix={renderFolderSuffix}
|
||||
renderFolderPanel={renderFolderPanel}
|
||||
/>
|
||||
</FileTreeList>
|
||||
</FileTree>
|
||||
<ScrollAreaContent style={{ minWidth: 0 }} className="w-full max-w-full">
|
||||
<FileTree id={id} className={cn('w-full max-w-full min-w-0 p-0', rootClassName)}>
|
||||
<FileTreeList className={cn('w-full max-w-full min-w-0', listClassName)}>
|
||||
<AgentFileTreeRows
|
||||
files={files}
|
||||
selectedFileId={selectedFileId}
|
||||
depth={1}
|
||||
folderOpenStrategy={folderOpenStrategy}
|
||||
folderOpenState={folderOpenState}
|
||||
onFolderOpenChange={onFolderOpenChange}
|
||||
onFolderDoubleClick={onFolderDoubleClick}
|
||||
onFolderOpen={onFolderOpen}
|
||||
renderFile={renderFile}
|
||||
renderFolderSuffix={renderFolderSuffix}
|
||||
renderFolderPanel={renderFolderPanel}
|
||||
/>
|
||||
</FileTreeList>
|
||||
</FileTree>
|
||||
</ScrollAreaContent>
|
||||
</ScrollAreaViewport>
|
||||
<ScrollAreaScrollbar className="hidden">
|
||||
<ScrollAreaThumb />
|
||||
</ScrollAreaScrollbar>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -9,7 +9,13 @@ import type { AgentBuildDraftChangedKey } from './build-draft-changes-context'
|
||||
import type { Model } from '@/app/components/header/account-setting/model-provider-page/declarations'
|
||||
import type { AgentComposerModel } from '@/features/agent-v2/agent-composer/form-state'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { ScrollArea } from '@langgenius/dify-ui/scroll-area'
|
||||
import {
|
||||
ScrollArea,
|
||||
ScrollAreaContent,
|
||||
ScrollAreaScrollbar,
|
||||
ScrollAreaThumb,
|
||||
ScrollAreaViewport,
|
||||
} from '@langgenius/dify-ui/scroll-area'
|
||||
import { useMemo } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { AgentOrchestrateAddActionsProvider } from './add-actions'
|
||||
@@ -134,36 +140,40 @@ export function AgentOrchestratePanel({
|
||||
|
||||
<AgentOrchestrateReadOnlyContext value={readOnly}>
|
||||
<div aria-readonly={readOnly} className="flex min-h-0 flex-1 flex-col">
|
||||
<ScrollArea
|
||||
className="min-h-0 flex-1 overflow-hidden"
|
||||
label={showHeader ? undefined : orchestrateLabel}
|
||||
slotClassNames={{
|
||||
viewport: 'overscroll-contain',
|
||||
content: cn('min-h-full px-4 py-3', hasBottomAction && 'pb-20'),
|
||||
scrollbar: hasBottomAction ? 'z-20' : undefined,
|
||||
}}
|
||||
>
|
||||
<AgentConfigApiContextProvider value={configApiContext}>
|
||||
<AgentOrchestrateAddActionsProvider>
|
||||
<AgentBuildDraftChangedKeysProvider
|
||||
changedKeys={
|
||||
isBuildDraftActive ? buildDraftChangedKeys : EMPTY_BUILD_DRAFT_CHANGED_KEYS
|
||||
}
|
||||
>
|
||||
<AgentModelField
|
||||
currentModel={currentModel}
|
||||
textGenerationModelList={textGenerationModelList}
|
||||
onSelect={onSelectModel}
|
||||
/>
|
||||
<AgentPromptEditor />
|
||||
<AgentSkills />
|
||||
<AgentFiles />
|
||||
<AgentTools />
|
||||
<AgentKnowledgeRetrieval />
|
||||
<AgentAdvancedSettings />
|
||||
</AgentBuildDraftChangedKeysProvider>
|
||||
</AgentOrchestrateAddActionsProvider>
|
||||
</AgentConfigApiContextProvider>
|
||||
<ScrollArea className="min-h-0 flex-1 overflow-hidden">
|
||||
<ScrollAreaViewport
|
||||
aria-label={showHeader ? undefined : orchestrateLabel}
|
||||
aria-labelledby={showHeader ? orchestrateHeadingId : undefined}
|
||||
className="overscroll-contain"
|
||||
role="region"
|
||||
>
|
||||
<ScrollAreaContent className={cn('min-h-full px-4 py-3', hasBottomAction && 'pb-20')}>
|
||||
<AgentConfigApiContextProvider value={configApiContext}>
|
||||
<AgentOrchestrateAddActionsProvider>
|
||||
<AgentBuildDraftChangedKeysProvider
|
||||
changedKeys={
|
||||
isBuildDraftActive ? buildDraftChangedKeys : EMPTY_BUILD_DRAFT_CHANGED_KEYS
|
||||
}
|
||||
>
|
||||
<AgentModelField
|
||||
currentModel={currentModel}
|
||||
textGenerationModelList={textGenerationModelList}
|
||||
onSelect={onSelectModel}
|
||||
/>
|
||||
<AgentPromptEditor />
|
||||
<AgentSkills />
|
||||
<AgentFiles />
|
||||
<AgentTools />
|
||||
<AgentKnowledgeRetrieval />
|
||||
<AgentAdvancedSettings />
|
||||
</AgentBuildDraftChangedKeysProvider>
|
||||
</AgentOrchestrateAddActionsProvider>
|
||||
</AgentConfigApiContextProvider>
|
||||
</ScrollAreaContent>
|
||||
</ScrollAreaViewport>
|
||||
<ScrollAreaScrollbar className={hasBottomAction ? 'z-20' : undefined}>
|
||||
<ScrollAreaThumb />
|
||||
</ScrollAreaScrollbar>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
</AgentOrchestrateReadOnlyContext>
|
||||
|
||||
+49
-34
@@ -10,8 +10,14 @@ import {
|
||||
DialogTitle,
|
||||
} from '@langgenius/dify-ui/dialog'
|
||||
import { FileTreeFile } from '@langgenius/dify-ui/file-tree'
|
||||
import { ScrollArea } from '@langgenius/dify-ui/scroll-area'
|
||||
import { useCallback } from 'react'
|
||||
import {
|
||||
ScrollArea,
|
||||
ScrollAreaContent,
|
||||
ScrollAreaScrollbar,
|
||||
ScrollAreaThumb,
|
||||
ScrollAreaViewport,
|
||||
} from '@langgenius/dify-ui/scroll-area'
|
||||
import { useCallback, useId } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import { AgentFileTree } from '../files/tree'
|
||||
@@ -113,7 +119,7 @@ function AgentSkillFileList({
|
||||
<AgentFileTree
|
||||
files={files}
|
||||
selectedFileId={selectedFileId}
|
||||
labelledBy="agent-skill-detail-files-heading"
|
||||
treeLabelledBy="agent-skill-detail-files-heading"
|
||||
className={cn('h-full bg-background-section p-1', fileListTreeClassName)}
|
||||
listClassName={fileListTreeListClassName}
|
||||
scrollAreaClassName="flex-1"
|
||||
@@ -337,8 +343,8 @@ export function AgentSkillDetailDialog({
|
||||
skillName: string
|
||||
detail: AgentSkillDetail
|
||||
}) {
|
||||
const { t } = useTranslation('agentV2')
|
||||
const { t: tCommon } = useTranslation('common')
|
||||
const dialogTitleId = useId()
|
||||
const previewTitle = detail.filePreview?.fileName
|
||||
const isHeaderDownloadLoading = detail.filePreview?.downloadActionLoadingTarget === 'header'
|
||||
|
||||
@@ -355,7 +361,9 @@ export function AgentSkillDetailDialog({
|
||||
)}
|
||||
>
|
||||
<DialogDescription className="sr-only">{detail.description}</DialogDescription>
|
||||
<DialogTitle className="sr-only">{previewTitle || skillName}</DialogTitle>
|
||||
<DialogTitle id={dialogTitleId} className="sr-only">
|
||||
{previewTitle || skillName}
|
||||
</DialogTitle>
|
||||
<div className="min-h-0 w-full">
|
||||
<AgentSkillFileList
|
||||
fileListHeader={detail.fileListHeader}
|
||||
@@ -408,35 +416,42 @@ export function AgentSkillDetailDialog({
|
||||
<DialogCloseButton className="static size-7 shrink-0 rounded-md" />
|
||||
</div>
|
||||
</div>
|
||||
<ScrollArea
|
||||
className="relative min-h-0 flex-1 overflow-hidden has-[>_:first-child:focus-visible]:outline-2 has-[>_:first-child:focus-visible]:outline-offset-0 has-[>_:first-child:focus-visible]:outline-state-accent-solid"
|
||||
label={t(($) => $['agentDetail.configure.skills.detail.contentRegion'])}
|
||||
slotClassNames={{
|
||||
viewport: 'overscroll-contain outline-none focus-visible:outline-none',
|
||||
content: 'flex min-h-full w-full max-w-full min-w-0 flex-col gap-2',
|
||||
}}
|
||||
>
|
||||
{detail.filePreview && (
|
||||
<AgentFilePreviewContent
|
||||
binary={detail.filePreview.binary}
|
||||
content={detail.filePreview.content}
|
||||
downloadActionLoadingTarget={detail.filePreview.downloadActionLoadingTarget}
|
||||
downloadUrl={detail.filePreview.downloadUrl}
|
||||
fileName={detail.filePreview.fileName}
|
||||
imageData={detail.filePreview.imageData}
|
||||
isDownloadError={detail.filePreview.isDownloadError}
|
||||
isDownloadLoading={detail.filePreview.isDownloadLoading}
|
||||
isError={detail.filePreview.isError}
|
||||
isImage={detail.filePreview.isImage}
|
||||
isLoading={detail.filePreview.isLoading}
|
||||
onDownloadFile={detail.onDownloadFile}
|
||||
/>
|
||||
)}
|
||||
{detail.sections.map((section) => (
|
||||
<div key={section.id} className="px-4">
|
||||
<AgentSkillDetailSectionBlock section={section} />
|
||||
</div>
|
||||
))}
|
||||
<ScrollArea className="relative min-h-0 flex-1 overflow-hidden has-[>_:first-child:focus-visible]:outline-2 has-[>_:first-child:focus-visible]:outline-offset-0 has-[>_:first-child:focus-visible]:outline-state-accent-solid">
|
||||
<ScrollAreaViewport
|
||||
aria-labelledby={dialogTitleId}
|
||||
className="overscroll-contain outline-none focus-visible:outline-none"
|
||||
role="region"
|
||||
>
|
||||
<ScrollAreaContent
|
||||
style={{ minWidth: 0 }}
|
||||
className="flex min-h-full w-full max-w-full flex-col gap-2"
|
||||
>
|
||||
{detail.filePreview && (
|
||||
<AgentFilePreviewContent
|
||||
binary={detail.filePreview.binary}
|
||||
content={detail.filePreview.content}
|
||||
downloadActionLoadingTarget={detail.filePreview.downloadActionLoadingTarget}
|
||||
downloadUrl={detail.filePreview.downloadUrl}
|
||||
fileName={detail.filePreview.fileName}
|
||||
imageData={detail.filePreview.imageData}
|
||||
isDownloadError={detail.filePreview.isDownloadError}
|
||||
isDownloadLoading={detail.filePreview.isDownloadLoading}
|
||||
isError={detail.filePreview.isError}
|
||||
isImage={detail.filePreview.isImage}
|
||||
isLoading={detail.filePreview.isLoading}
|
||||
onDownloadFile={detail.onDownloadFile}
|
||||
/>
|
||||
)}
|
||||
{detail.sections.map((section) => (
|
||||
<div key={section.id} className="px-4">
|
||||
<AgentSkillDetailSectionBlock section={section} />
|
||||
</div>
|
||||
))}
|
||||
</ScrollAreaContent>
|
||||
</ScrollAreaViewport>
|
||||
<ScrollAreaScrollbar>
|
||||
<ScrollAreaThumb />
|
||||
</ScrollAreaScrollbar>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
</DialogContent>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import type { ReactNode } from 'react'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
type AgentConfigureWorkspaceProps = {
|
||||
'aria-busy'?: boolean
|
||||
@@ -19,11 +18,8 @@ export function AgentConfigureWorkspace({
|
||||
rightPanel,
|
||||
sidePanels,
|
||||
}: AgentConfigureWorkspaceProps) {
|
||||
const { t } = useTranslation('agentV2')
|
||||
|
||||
return (
|
||||
<section
|
||||
aria-label={t(($) => $['agentDetail.sections.configure'])}
|
||||
aria-busy={ariaBusy}
|
||||
className={cn(
|
||||
'flex h-full min-w-0 flex-1 gap-1 overflow-hidden bg-background-body p-1',
|
||||
|
||||
@@ -3,8 +3,8 @@ import type { ReactNode, TdHTMLAttributes, ThHTMLAttributes } from 'react'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import {
|
||||
ScrollArea,
|
||||
ScrollAreaContent,
|
||||
ScrollAreaRoot,
|
||||
ScrollAreaScrollbar,
|
||||
ScrollAreaThumb,
|
||||
ScrollAreaViewport,
|
||||
@@ -52,7 +52,7 @@ export function AgentLogsTable({
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<ScrollAreaRoot className="relative min-h-0 flex-1 overflow-hidden">
|
||||
<ScrollArea className="relative min-h-0 flex-1 overflow-hidden">
|
||||
<ScrollAreaViewport
|
||||
aria-label={t(($) => $['agentDetail.logs.title'])}
|
||||
role="region"
|
||||
@@ -78,7 +78,7 @@ export function AgentLogsTable({
|
||||
<ScrollAreaScrollbar>
|
||||
<ScrollAreaThumb />
|
||||
</ScrollAreaScrollbar>
|
||||
</ScrollAreaRoot>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,13 @@
|
||||
import type { AgentLogSourceResponse } from '@dify/contracts/api/console/agent/types.gen'
|
||||
import type { ReactNode } from 'react'
|
||||
import { Button } from '@langgenius/dify-ui/button'
|
||||
import { ScrollArea } from '@langgenius/dify-ui/scroll-area'
|
||||
import {
|
||||
ScrollArea,
|
||||
ScrollAreaContent,
|
||||
ScrollAreaScrollbar,
|
||||
ScrollAreaThumb,
|
||||
ScrollAreaViewport,
|
||||
} from '@langgenius/dify-ui/scroll-area'
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -129,46 +135,48 @@ export function AgentMonitoringPage({ agentId }: AgentMonitoringPageProps) {
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<ScrollArea
|
||||
className="min-h-0 flex-1 overflow-hidden"
|
||||
slotClassNames={{
|
||||
content: 'px-6 pt-2 pb-3',
|
||||
}}
|
||||
>
|
||||
{shouldShowInitialSkeleton && <AgentMonitoringSkeletonGrid />}
|
||||
{shouldShowError && (
|
||||
<AgentMonitoringState>
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<span>{t(($) => $['agentDetail.monitoring.loadFailed'])}</span>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
void statisticsQuery.refetch()
|
||||
}}
|
||||
>
|
||||
{tCommon(($) => $['operation.retry'])}
|
||||
</Button>
|
||||
</div>
|
||||
</AgentMonitoringState>
|
||||
)}
|
||||
{!shouldShowInitialSkeleton && !shouldShowError && (
|
||||
<div className="grid w-full grid-cols-1 gap-3 xl:grid-cols-2">
|
||||
{metrics.map((metric) => (
|
||||
<AgentMonitoringChart
|
||||
key={metric.id}
|
||||
titleKey={metric.titleKey}
|
||||
explanationKey={metric.explanationKey}
|
||||
summaryValue={metric.summaryValue}
|
||||
rows={metric.rows}
|
||||
chartType={metric.chartType}
|
||||
valueKey={metric.valueKey}
|
||||
unitKey={metric.unitKey}
|
||||
yMaxWhenEmpty={metric.yMaxWhenEmpty}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<ScrollArea className="min-h-0 flex-1 overflow-hidden">
|
||||
<ScrollAreaViewport>
|
||||
<ScrollAreaContent className="px-6 pt-2 pb-3">
|
||||
{shouldShowInitialSkeleton && <AgentMonitoringSkeletonGrid />}
|
||||
{shouldShowError && (
|
||||
<AgentMonitoringState>
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<span>{t(($) => $['agentDetail.monitoring.loadFailed'])}</span>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
void statisticsQuery.refetch()
|
||||
}}
|
||||
>
|
||||
{tCommon(($) => $['operation.retry'])}
|
||||
</Button>
|
||||
</div>
|
||||
</AgentMonitoringState>
|
||||
)}
|
||||
{!shouldShowInitialSkeleton && !shouldShowError && (
|
||||
<div className="grid w-full grid-cols-1 gap-3 xl:grid-cols-2">
|
||||
{metrics.map((metric) => (
|
||||
<AgentMonitoringChart
|
||||
key={metric.id}
|
||||
titleKey={metric.titleKey}
|
||||
explanationKey={metric.explanationKey}
|
||||
summaryValue={metric.summaryValue}
|
||||
rows={metric.rows}
|
||||
chartType={metric.chartType}
|
||||
valueKey={metric.valueKey}
|
||||
unitKey={metric.unitKey}
|
||||
yMaxWhenEmpty={metric.yMaxWhenEmpty}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</ScrollAreaContent>
|
||||
</ScrollAreaViewport>
|
||||
<ScrollAreaScrollbar>
|
||||
<ScrollAreaThumb />
|
||||
</ScrollAreaScrollbar>
|
||||
</ScrollArea>
|
||||
</AgentDetailSectionSurface>
|
||||
)
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
import type { AgentAppPartial } from '@dify/contracts/api/console/agent/types.gen'
|
||||
import type { RosterFilterValue } from './components/roster-filter'
|
||||
import {
|
||||
ScrollArea,
|
||||
ScrollAreaContent,
|
||||
ScrollAreaRoot,
|
||||
ScrollAreaScrollbar,
|
||||
ScrollAreaThumb,
|
||||
ScrollAreaViewport,
|
||||
@@ -108,7 +108,7 @@ export default function RosterPage() {
|
||||
</div>
|
||||
|
||||
<div className="min-h-0 flex-1">
|
||||
<ScrollAreaRoot className="relative h-full min-h-0 min-w-0 overflow-hidden">
|
||||
<ScrollArea className="relative h-full min-h-0 min-w-0 overflow-hidden">
|
||||
<ScrollAreaViewport tabIndex={-1} className="overscroll-contain">
|
||||
<ScrollAreaContent className="min-h-full px-8 pt-2 pb-8">
|
||||
<AgentRosterList
|
||||
@@ -127,7 +127,7 @@ export default function RosterPage() {
|
||||
<ScrollAreaScrollbar>
|
||||
<ScrollAreaThumb />
|
||||
</ScrollAreaScrollbar>
|
||||
</ScrollAreaRoot>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -3,7 +3,13 @@
|
||||
import type { ReactNode } from 'react'
|
||||
import type { GuideStep } from '@/features/deployments/create-guide/state/types'
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { ScrollArea } from '@langgenius/dify-ui/scroll-area'
|
||||
import {
|
||||
ScrollArea,
|
||||
ScrollAreaContent,
|
||||
ScrollAreaScrollbar,
|
||||
ScrollAreaThumb,
|
||||
ScrollAreaViewport,
|
||||
} from '@langgenius/dify-ui/scroll-area'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { TitleTooltip } from '@/features/deployments/shared/components/title-tooltip'
|
||||
|
||||
@@ -161,14 +167,13 @@ export function GuideCard({
|
||||
return (
|
||||
<div className="flex min-h-0 w-full min-w-0 flex-1 flex-col">
|
||||
{contentScrollable ? (
|
||||
<ScrollArea
|
||||
className="min-h-0 flex-1"
|
||||
slotClassNames={{
|
||||
viewport: 'overscroll-contain',
|
||||
content: 'min-h-full pt-0.5 pb-6',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
<ScrollArea className="min-h-0 flex-1">
|
||||
<ScrollAreaViewport className="overscroll-contain">
|
||||
<ScrollAreaContent className="min-h-full pt-0.5 pb-6">{children}</ScrollAreaContent>
|
||||
</ScrollAreaViewport>
|
||||
<ScrollAreaScrollbar>
|
||||
<ScrollAreaThumb />
|
||||
</ScrollAreaScrollbar>
|
||||
</ScrollArea>
|
||||
) : (
|
||||
<div className="min-h-0 flex-1 overflow-hidden pt-0.5 pb-6">{children}</div>
|
||||
|
||||
@@ -203,7 +203,6 @@
|
||||
"agentDetail.configure.rightPanel.previewTipBody": "يشغل Preview الوكيل المكتمل كما سيراه المستخدمون، مع ردود واضحة وميزات الدردشة.",
|
||||
"agentDetail.configure.rightPanel.previewTipTitle": "عاين وكيلك",
|
||||
"agentDetail.configure.skills.add": "إضافة مهارة",
|
||||
"agentDetail.configure.skills.detail.contentRegion": "محتوى تفاصيل المهارة",
|
||||
"agentDetail.configure.skills.detail.fileCount": "{{count}} ملفات",
|
||||
"agentDetail.configure.skills.detail.files": "الملفات",
|
||||
"agentDetail.configure.skills.empty.description": "تمنح المهارات الوكيل خبرة قابلة لإعادة الاستخدام يمكنه استدعاؤها أثناء العمل",
|
||||
|
||||
@@ -203,7 +203,6 @@
|
||||
"agentDetail.configure.rightPanel.previewTipBody": "Preview führt den fertigen Agenten so aus, wie deine Benutzer ihn sehen, mit klaren Antworten und Chat-Funktionen.",
|
||||
"agentDetail.configure.rightPanel.previewTipTitle": "Agenten vorschauen",
|
||||
"agentDetail.configure.skills.add": "Skill hinzufügen",
|
||||
"agentDetail.configure.skills.detail.contentRegion": "Skill-Detailinhalt",
|
||||
"agentDetail.configure.skills.detail.fileCount": "{{count}} DATEIEN",
|
||||
"agentDetail.configure.skills.detail.files": "Dateien",
|
||||
"agentDetail.configure.skills.empty.description": "Skills geben dem Agenten wiederverwendbare Fachkenntnisse, die er bei der Arbeit nutzen kann",
|
||||
|
||||
@@ -203,7 +203,6 @@
|
||||
"agentDetail.configure.rightPanel.previewTipBody": "Preview runs the finished agent the way your users will see it, with clean replies and chat features.",
|
||||
"agentDetail.configure.rightPanel.previewTipTitle": "Preview your agent",
|
||||
"agentDetail.configure.skills.add": "Add skill",
|
||||
"agentDetail.configure.skills.detail.contentRegion": "Skill detail content",
|
||||
"agentDetail.configure.skills.detail.fileCount": "{{count}} FILES",
|
||||
"agentDetail.configure.skills.detail.files": "Files",
|
||||
"agentDetail.configure.skills.empty.description": "Skills give the agent reusable expertise it can call while working",
|
||||
|
||||
@@ -203,7 +203,6 @@
|
||||
"agentDetail.configure.rightPanel.previewTipBody": "Preview ejecuta el agente terminado como lo verán tus usuarios, con respuestas claras y funciones de chat.",
|
||||
"agentDetail.configure.rightPanel.previewTipTitle": "Previsualiza tu agente",
|
||||
"agentDetail.configure.skills.add": "Agregar habilidad",
|
||||
"agentDetail.configure.skills.detail.contentRegion": "Contenido de los detalles de la habilidad",
|
||||
"agentDetail.configure.skills.detail.fileCount": "{{count}} ARCHIVOS",
|
||||
"agentDetail.configure.skills.detail.files": "Archivos",
|
||||
"agentDetail.configure.skills.empty.description": "Las habilidades le dan al agente experiencia reutilizable que puede invocar mientras trabaja",
|
||||
|
||||
@@ -203,7 +203,6 @@
|
||||
"agentDetail.configure.rightPanel.previewTipBody": "Preview عامل تکمیلشده را همانطور که کاربران میبینند اجرا میکند، با پاسخهای تمیز و قابلیتهای گفتگو.",
|
||||
"agentDetail.configure.rightPanel.previewTipTitle": "پیشنمایش عامل",
|
||||
"agentDetail.configure.skills.add": "افزودن مهارت",
|
||||
"agentDetail.configure.skills.detail.contentRegion": "محتوای جزئیات مهارت",
|
||||
"agentDetail.configure.skills.detail.fileCount": "{{count}} فایل",
|
||||
"agentDetail.configure.skills.detail.files": "فایلها",
|
||||
"agentDetail.configure.skills.empty.description": "مهارتها به عامل تخصص قابل استفاده مجدد میدهند که هنگام کار میتواند فراخوانی کند",
|
||||
|
||||
@@ -203,7 +203,6 @@
|
||||
"agentDetail.configure.rightPanel.previewTipBody": "Preview exécute l’agent final comme vos utilisateurs le verront, avec des réponses claires et les fonctions de chat.",
|
||||
"agentDetail.configure.rightPanel.previewTipTitle": "Prévisualiser votre agent",
|
||||
"agentDetail.configure.skills.add": "Ajouter une compétence",
|
||||
"agentDetail.configure.skills.detail.contentRegion": "Contenu des détails de la compétence",
|
||||
"agentDetail.configure.skills.detail.fileCount": "{{count}} FICHIERS",
|
||||
"agentDetail.configure.skills.detail.files": "Fichiers",
|
||||
"agentDetail.configure.skills.empty.description": "Les compétences offrent à l’agent une expertise réutilisable qu’il peut invoquer pendant son travail",
|
||||
|
||||
@@ -203,7 +203,6 @@
|
||||
"agentDetail.configure.rightPanel.previewTipBody": "Preview तैयार agent को वैसे चलाता है जैसे आपके users उसे देखेंगे, साफ replies और chat features के साथ।",
|
||||
"agentDetail.configure.rightPanel.previewTipTitle": "अपने agent का preview करें",
|
||||
"agentDetail.configure.skills.add": "कौशल जोड़ें",
|
||||
"agentDetail.configure.skills.detail.contentRegion": "कौशल विवरण सामग्री",
|
||||
"agentDetail.configure.skills.detail.fileCount": "{{count}} फ़ाइलें",
|
||||
"agentDetail.configure.skills.detail.files": "फ़ाइलें",
|
||||
"agentDetail.configure.skills.empty.description": "कौशल एजेंट को पुनः उपयोग योग्य विशेषज्ञता देते हैं जिसे वह काम करते समय कॉल कर सकता है",
|
||||
|
||||
@@ -203,7 +203,6 @@
|
||||
"agentDetail.configure.rightPanel.previewTipBody": "Preview menjalankan agent yang sudah selesai seperti yang akan dilihat pengguna, dengan balasan rapi dan fitur chat.",
|
||||
"agentDetail.configure.rightPanel.previewTipTitle": "Pratinjau agen Anda",
|
||||
"agentDetail.configure.skills.add": "Tambahkan keterampilan",
|
||||
"agentDetail.configure.skills.detail.contentRegion": "Konten detail keterampilan",
|
||||
"agentDetail.configure.skills.detail.fileCount": "{{count}} FILE",
|
||||
"agentDetail.configure.skills.detail.files": "File",
|
||||
"agentDetail.configure.skills.empty.description": "Keterampilan memberi agen keahlian yang dapat digunakan kembali yang bisa dipanggil saat bekerja",
|
||||
|
||||
@@ -203,7 +203,6 @@
|
||||
"agentDetail.configure.rightPanel.previewTipBody": "Preview esegue l’agente completato come lo vedranno gli utenti, con risposte pulite e funzionalità di chat.",
|
||||
"agentDetail.configure.rightPanel.previewTipTitle": "Anteprima del tuo agente",
|
||||
"agentDetail.configure.skills.add": "Aggiungi abilità",
|
||||
"agentDetail.configure.skills.detail.contentRegion": "Contenuto dei dettagli dell’abilità",
|
||||
"agentDetail.configure.skills.detail.fileCount": "{{count}} FILE",
|
||||
"agentDetail.configure.skills.detail.files": "File",
|
||||
"agentDetail.configure.skills.empty.description": "Le abilità forniscono all’agente competenze riutilizzabili che può richiamare durante il lavoro",
|
||||
|
||||
@@ -203,7 +203,6 @@
|
||||
"agentDetail.configure.rightPanel.previewTipBody": "Preview は完成したエージェントをユーザーに見える形で実行し、整った返信とチャット機能を確認できます。",
|
||||
"agentDetail.configure.rightPanel.previewTipTitle": "エージェントをプレビュー",
|
||||
"agentDetail.configure.skills.add": "スキルを追加",
|
||||
"agentDetail.configure.skills.detail.contentRegion": "スキル詳細コンテンツ",
|
||||
"agentDetail.configure.skills.detail.fileCount": "{{count}} 個のファイル",
|
||||
"agentDetail.configure.skills.detail.files": "ファイル",
|
||||
"agentDetail.configure.skills.empty.description": "スキルはエージェントが作業中に呼び出せる再利用可能な専門知識を提供します",
|
||||
|
||||
@@ -203,7 +203,6 @@
|
||||
"agentDetail.configure.rightPanel.previewTipBody": "Preview는 사용자가 보게 될 방식으로 완성된 에이전트를 실행하며, 깔끔한 답변과 채팅 기능을 보여줍니다.",
|
||||
"agentDetail.configure.rightPanel.previewTipTitle": "에이전트 미리보기",
|
||||
"agentDetail.configure.skills.add": "스킬 추가",
|
||||
"agentDetail.configure.skills.detail.contentRegion": "스킬 상세 내용",
|
||||
"agentDetail.configure.skills.detail.fileCount": "파일 {{count}}개",
|
||||
"agentDetail.configure.skills.detail.files": "파일",
|
||||
"agentDetail.configure.skills.empty.description": "스킬은 에이전트가 작업하면서 호출할 수 있는 재사용 가능한 전문 능력을 제공합니다",
|
||||
|
||||
@@ -203,7 +203,6 @@
|
||||
"agentDetail.configure.rightPanel.previewTipBody": "Preview voert de voltooide agent uit zoals je gebruikers die zien, met duidelijke antwoorden en chatfuncties.",
|
||||
"agentDetail.configure.rightPanel.previewTipTitle": "Voorbeeld van je agent",
|
||||
"agentDetail.configure.skills.add": "Skill toevoegen",
|
||||
"agentDetail.configure.skills.detail.contentRegion": "Skill-detailinhoud",
|
||||
"agentDetail.configure.skills.detail.fileCount": "{{count}} BESTANDEN",
|
||||
"agentDetail.configure.skills.detail.files": "Bestanden",
|
||||
"agentDetail.configure.skills.empty.description": "Skills geven de agent herbruikbare expertise die hij tijdens het werken kan inzetten",
|
||||
|
||||
@@ -203,7 +203,6 @@
|
||||
"agentDetail.configure.rightPanel.previewTipBody": "Preview uruchamia gotowego agenta tak, jak zobaczą go użytkownicy, z przejrzystymi odpowiedziami i funkcjami czatu.",
|
||||
"agentDetail.configure.rightPanel.previewTipTitle": "Podgląd agenta",
|
||||
"agentDetail.configure.skills.add": "Dodaj umiejętność",
|
||||
"agentDetail.configure.skills.detail.contentRegion": "Zawartość szczegółów umiejętności",
|
||||
"agentDetail.configure.skills.detail.fileCount": "{{count}} PLIKÓW",
|
||||
"agentDetail.configure.skills.detail.files": "Pliki",
|
||||
"agentDetail.configure.skills.empty.description": "Umiejętności dają agentowi reużywalną wiedzę, którą może wywołać podczas pracy",
|
||||
|
||||
@@ -203,7 +203,6 @@
|
||||
"agentDetail.configure.rightPanel.previewTipBody": "Preview executa o agente finalizado como seus usuários o verão, com respostas claras e recursos de chat.",
|
||||
"agentDetail.configure.rightPanel.previewTipTitle": "Pré-visualize seu agente",
|
||||
"agentDetail.configure.skills.add": "Adicionar habilidade",
|
||||
"agentDetail.configure.skills.detail.contentRegion": "Conteúdo dos detalhes da habilidade",
|
||||
"agentDetail.configure.skills.detail.fileCount": "{{count}} ARQUIVOS",
|
||||
"agentDetail.configure.skills.detail.files": "Arquivos",
|
||||
"agentDetail.configure.skills.empty.description": "As habilidades dão ao agente expertise reutilizável que ele pode invocar enquanto trabalha",
|
||||
|
||||
@@ -203,7 +203,6 @@
|
||||
"agentDetail.configure.rightPanel.previewTipBody": "Preview rulează agentul finalizat așa cum îl vor vedea utilizatorii, cu răspunsuri clare și funcții de chat.",
|
||||
"agentDetail.configure.rightPanel.previewTipTitle": "Previzualizează agentul",
|
||||
"agentDetail.configure.skills.add": "Adaugă abilitate",
|
||||
"agentDetail.configure.skills.detail.contentRegion": "Conținutul detaliilor abilității",
|
||||
"agentDetail.configure.skills.detail.fileCount": "{{count}} FIȘIERE",
|
||||
"agentDetail.configure.skills.detail.files": "Fișiere",
|
||||
"agentDetail.configure.skills.empty.description": "Abilitățile oferă agentului expertiză reutilizabilă pe care o poate apela în timpul lucrului",
|
||||
|
||||
@@ -203,7 +203,6 @@
|
||||
"agentDetail.configure.rightPanel.previewTipBody": "Preview запускает готового агента так, как его увидят пользователи, с чистыми ответами и функциями чата.",
|
||||
"agentDetail.configure.rightPanel.previewTipTitle": "Предпросмотр агента",
|
||||
"agentDetail.configure.skills.add": "Добавить навык",
|
||||
"agentDetail.configure.skills.detail.contentRegion": "Содержимое деталей навыка",
|
||||
"agentDetail.configure.skills.detail.fileCount": "{{count}} ФАЙЛОВ",
|
||||
"agentDetail.configure.skills.detail.files": "Файлы",
|
||||
"agentDetail.configure.skills.empty.description": "Навыки дают агенту переиспользуемую экспертизу, которую он может вызывать в работе",
|
||||
|
||||
@@ -203,7 +203,6 @@
|
||||
"agentDetail.configure.rightPanel.previewTipBody": "Preview zažene dokončanega agenta tako, kot ga bodo videli uporabniki, z jasnimi odgovori in funkcijami klepeta.",
|
||||
"agentDetail.configure.rightPanel.previewTipTitle": "Predogled agenta",
|
||||
"agentDetail.configure.skills.add": "Dodaj veščino",
|
||||
"agentDetail.configure.skills.detail.contentRegion": "Vsebina podrobnosti veščine",
|
||||
"agentDetail.configure.skills.detail.fileCount": "{{count}} DATOTEK",
|
||||
"agentDetail.configure.skills.detail.files": "Datoteke",
|
||||
"agentDetail.configure.skills.empty.description": "Veščine agentu dajejo ponovno uporabno strokovnost, ki jo lahko kliče med delom",
|
||||
|
||||
@@ -203,7 +203,6 @@
|
||||
"agentDetail.configure.rightPanel.previewTipBody": "Preview เรียกใช้เอเจนต์ที่เสร็จแล้วในแบบที่ผู้ใช้จะเห็น พร้อมคำตอบที่ชัดเจนและฟีเจอร์แชท",
|
||||
"agentDetail.configure.rightPanel.previewTipTitle": "ดูตัวอย่างเอเจนต์",
|
||||
"agentDetail.configure.skills.add": "เพิ่มทักษะ",
|
||||
"agentDetail.configure.skills.detail.contentRegion": "เนื้อหารายละเอียดทักษะ",
|
||||
"agentDetail.configure.skills.detail.fileCount": "{{count}} ไฟล์",
|
||||
"agentDetail.configure.skills.detail.files": "ไฟล์",
|
||||
"agentDetail.configure.skills.empty.description": "ทักษะให้ตัวแทนมีความเชี่ยวชาญที่นำกลับมาใช้ใหม่ได้ซึ่งสามารถเรียกใช้ได้ขณะทำงาน",
|
||||
|
||||
@@ -203,7 +203,6 @@
|
||||
"agentDetail.configure.rightPanel.previewTipBody": "Preview, tamamlanan ajanı kullanıcılarınızın göreceği şekilde, temiz yanıtlar ve sohbet özellikleriyle çalıştırır.",
|
||||
"agentDetail.configure.rightPanel.previewTipTitle": "Ajanınızı önizleyin",
|
||||
"agentDetail.configure.skills.add": "Beceri ekle",
|
||||
"agentDetail.configure.skills.detail.contentRegion": "Beceri detay içeriği",
|
||||
"agentDetail.configure.skills.detail.fileCount": "{{count}} DOSYA",
|
||||
"agentDetail.configure.skills.detail.files": "Dosyalar",
|
||||
"agentDetail.configure.skills.empty.description": "Beceriler, ajana çalışırken çağırabileceği yeniden kullanılabilir uzmanlık sağlar",
|
||||
|
||||
@@ -203,7 +203,6 @@
|
||||
"agentDetail.configure.rightPanel.previewTipBody": "Preview запускає готового агента так, як його побачать користувачі, з чистими відповідями та функціями чату.",
|
||||
"agentDetail.configure.rightPanel.previewTipTitle": "Попередній перегляд агента",
|
||||
"agentDetail.configure.skills.add": "Додати навичку",
|
||||
"agentDetail.configure.skills.detail.contentRegion": "Вміст деталей навички",
|
||||
"agentDetail.configure.skills.detail.fileCount": "{{count}} ФАЙЛІВ",
|
||||
"agentDetail.configure.skills.detail.files": "Файли",
|
||||
"agentDetail.configure.skills.empty.description": "Навички дають агенту перевикористовну експертизу, яку він може викликати під час роботи",
|
||||
|
||||
@@ -203,7 +203,6 @@
|
||||
"agentDetail.configure.rightPanel.previewTipBody": "Preview chạy agent hoàn chỉnh theo cách người dùng sẽ thấy, với câu trả lời rõ ràng và các tính năng chat.",
|
||||
"agentDetail.configure.rightPanel.previewTipTitle": "Xem trước agent",
|
||||
"agentDetail.configure.skills.add": "Thêm kỹ năng",
|
||||
"agentDetail.configure.skills.detail.contentRegion": "Nội dung chi tiết kỹ năng",
|
||||
"agentDetail.configure.skills.detail.fileCount": "{{count}} TỆP",
|
||||
"agentDetail.configure.skills.detail.files": "Tệp",
|
||||
"agentDetail.configure.skills.empty.description": "Kỹ năng mang đến cho tác nhân chuyên môn có thể tái sử dụng mà nó có thể gọi khi làm việc",
|
||||
|
||||
@@ -203,7 +203,6 @@
|
||||
"agentDetail.configure.rightPanel.previewTipBody": "像用户一样测试它。你的交互不会影响 Agent 后续的行为。",
|
||||
"agentDetail.configure.rightPanel.previewTipTitle": "预览你的 Agent",
|
||||
"agentDetail.configure.skills.add": "添加 Skill",
|
||||
"agentDetail.configure.skills.detail.contentRegion": "Skill 详情内容",
|
||||
"agentDetail.configure.skills.detail.fileCount": "{{count}} 个文件",
|
||||
"agentDetail.configure.skills.detail.files": "文件",
|
||||
"agentDetail.configure.skills.empty.description": "Skill 为 Agent 提供工作时可调用的可复用专业能力",
|
||||
|
||||
@@ -203,7 +203,6 @@
|
||||
"agentDetail.configure.rightPanel.previewTipBody": "像使用者一樣測試它。你的互動不會影響 Agent 後續的行為。",
|
||||
"agentDetail.configure.rightPanel.previewTipTitle": "預覽你的 Agent",
|
||||
"agentDetail.configure.skills.add": "新增 Skill",
|
||||
"agentDetail.configure.skills.detail.contentRegion": "Skill 詳情內容",
|
||||
"agentDetail.configure.skills.detail.fileCount": "{{count}} 個檔案",
|
||||
"agentDetail.configure.skills.detail.files": "檔案",
|
||||
"agentDetail.configure.skills.empty.description": "Skill 為 Agent 提供工作時可呼叫的可複用專業能力",
|
||||
|
||||
Reference in New Issue
Block a user