mirror of
https://github.com/langgenius/dify.git
synced 2026-07-25 21:48:30 +08:00
feat: snippet siderbar update (#38371)
This commit is contained in:
parent
5622e8f7ea
commit
7b7956f3e7
@ -77,8 +77,9 @@ export function AppListHeaderFilters({
|
||||
<div className="ml-auto flex max-w-full min-w-0 flex-wrap items-center justify-end gap-2">
|
||||
<Link
|
||||
href="/snippets"
|
||||
className="flex h-8 items-center rounded-lg px-3 text-sm font-semibold whitespace-nowrap text-text-secondary outline-hidden hover:bg-state-base-hover hover:text-text-primary focus-visible:ring-2 focus-visible:ring-state-accent-solid"
|
||||
className="inline-flex h-8 cursor-pointer items-center justify-center gap-1 rounded-lg border-[0.5px] border-components-button-secondary-border bg-components-button-secondary-bg px-3.5 text-[13px] leading-4 font-medium whitespace-nowrap text-components-button-secondary-text shadow-xs outline-hidden backdrop-blur-[5px] hover:border-components-button-secondary-border-hover hover:bg-components-button-secondary-bg-hover focus-visible:ring-2 focus-visible:ring-state-accent-solid"
|
||||
>
|
||||
<span aria-hidden className="i-ri-braces-line size-4 shrink-0" />
|
||||
{t('studio.viewSnippets', { ns: 'app' })}
|
||||
</Link>
|
||||
{showCreateButton && (
|
||||
|
||||
@ -0,0 +1,11 @@
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import { SnippetCollapsedPreview } from '../snippet-collapsed-preview'
|
||||
|
||||
describe('SnippetCollapsedPreview', () => {
|
||||
it('should render collapsed route navigation and input field count', () => {
|
||||
render(<SnippetCollapsedPreview inputFieldCount={2} snippetId="snippet-1" />)
|
||||
|
||||
expect(screen.getByRole('link', { name: 'snippet.sectionOrchestrate' })).toHaveAttribute('href', '/snippets/snippet-1/orchestrate')
|
||||
expect(screen.getByLabelText('2 snippet.inputVariables')).toHaveTextContent('2')
|
||||
})
|
||||
})
|
||||
@ -152,6 +152,19 @@ describe('SnippetSidebarContent', () => {
|
||||
expect(capturedVarListProps?.readonly).toBe(true)
|
||||
})
|
||||
|
||||
it('should render the orchestrate route menu item', () => {
|
||||
render(
|
||||
<SnippetSidebarContent
|
||||
snippet={snippet}
|
||||
fields={fields}
|
||||
readonly={false}
|
||||
onFieldsChange={vi.fn()}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByRole('link', { name: 'snippet.sectionOrchestrate' })).toHaveAttribute('href', '/snippets/snippet-1/orchestrate')
|
||||
})
|
||||
|
||||
it('should add a new input field from the config variable modal', () => {
|
||||
const onFieldsChange = vi.fn()
|
||||
render(
|
||||
|
||||
@ -1,27 +1,62 @@
|
||||
'use client'
|
||||
|
||||
import { cn } from '@langgenius/dify-ui/cn'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import NavLink from '@/app/components/app-sidebar/nav-link'
|
||||
import { SnippetPlaceholderIcon } from './snippet-placeholder-icon'
|
||||
|
||||
const NodeTreeIcon = ({ className }: { className?: string }) => (
|
||||
<span className={cn('i-ri-node-tree', className)} />
|
||||
)
|
||||
|
||||
export function SnippetCollapsedPreview({
|
||||
inputFieldCount,
|
||||
snippetId,
|
||||
}: {
|
||||
inputFieldCount: number
|
||||
snippetId?: string
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
const sectionLabel = t('sectionOrchestrate', { ns: 'snippet' })
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex min-h-0 grow flex-col items-center px-2 pt-4"
|
||||
className="flex min-h-0 grow flex-col items-center px-2"
|
||||
aria-label="Snippet collapsed preview"
|
||||
>
|
||||
<SnippetPlaceholderIcon />
|
||||
<div className="mb-3.5 flex w-full shrink-0 justify-center px-3.5 pt-0.5 pb-0.75">
|
||||
<div className="my-0 h-px w-6.75 shrink-0 bg-divider-subtle" data-testid="divider"></div>
|
||||
</div>
|
||||
<SnippetPlaceholderIcon className="size-9" />
|
||||
<div className="my-4 h-px w-8 rounded-full bg-divider-subtle" aria-hidden="true" />
|
||||
{snippetId
|
||||
? (
|
||||
<NavLink
|
||||
mode="collapse"
|
||||
name={sectionLabel}
|
||||
href={`/snippets/${snippetId}/orchestrate`}
|
||||
active
|
||||
iconMap={{ selected: NodeTreeIcon, normal: NodeTreeIcon }}
|
||||
/>
|
||||
)
|
||||
: (
|
||||
<div
|
||||
aria-label={sectionLabel}
|
||||
className="flex size-8 items-center justify-center rounded-lg border-t-[0.75px] border-r-[0.25px] border-b-[0.25px] border-l-[0.75px] border-effects-highlight-lightmode-off bg-components-menu-item-bg-active p-1.5 text-text-accent-light-mode-only"
|
||||
>
|
||||
<div className="flex size-5 items-center justify-center">
|
||||
<NodeTreeIcon className="size-4.5 shrink-0" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className="relative flex size-8 items-center justify-center rounded-lg border border-divider-subtle bg-background-default-subtle text-text-accent shadow-xs"
|
||||
aria-label={`${inputFieldCount} input fields`}
|
||||
className={cn(
|
||||
'mt-4 flex min-w-6 items-center justify-center rounded-full bg-background-default-subtle px-2 text-2xs leading-4 font-normal text-text-tertiary',
|
||||
inputFieldCount > 99 ? 'h-5' : 'size-5',
|
||||
)}
|
||||
aria-label={`${inputFieldCount} ${t('inputVariables', { ns: 'snippet' })}`}
|
||||
>
|
||||
<span aria-hidden="true" className="i-custom-vender-solid-development-variable-02 size-5" />
|
||||
<span className="absolute -right-1.5 -bottom-1.5 flex size-4 items-center justify-center rounded-full border-2 border-components-panel-bg bg-state-accent-solid text-2xs leading-none text-text-primary-on-surface shadow-xs">
|
||||
{inputFieldCount}
|
||||
</span>
|
||||
{inputFieldCount}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -19,7 +19,7 @@ export function SnippetDetailSection({ expand }: SnippetDetailSectionProps) {
|
||||
const snippetInputFields = useSnippetDraftStore(state => state.inputFields)
|
||||
|
||||
if (!expand)
|
||||
return <SnippetCollapsedPreview inputFieldCount={snippetInputFields.length} />
|
||||
return <SnippetCollapsedPreview inputFieldCount={snippetInputFields.length} snippetId={snippetNavigation.snippet?.id} />
|
||||
|
||||
if (!snippetNavigation.snippet || !snippetNavigation.onFieldsChange)
|
||||
return null
|
||||
|
||||
@ -14,17 +14,12 @@ export function SnippetPlaceholderIcon({
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
'flex size-10 items-center justify-center rounded-[10px] border border-divider-subtle bg-background-default-subtle text-text-tertiary shadow-xs',
|
||||
'flex size-10 shrink-0 items-center justify-center rounded-[10px] border border-divider-subtle bg-state-accent-hover text-text-accent shadow-xs',
|
||||
className,
|
||||
)}
|
||||
aria-hidden="true"
|
||||
>
|
||||
<span className={cn('relative block size-8', graphicClassName)}>
|
||||
<span className="absolute top-1/2 left-1/2 h-4 w-0.5 -translate-x-1/2 -translate-y-1/2 rounded-full bg-util-colors-blue-blue-500" />
|
||||
<span className="absolute top-0.5 left-0.5 size-2.5 rounded-xs bg-util-colors-blue-blue-300 shadow-xs" />
|
||||
<span className="absolute top-1/2 right-0.5 size-2.5 -translate-y-1/2 rounded-xs bg-util-colors-blue-blue-600 shadow-xs" />
|
||||
<span className="absolute bottom-0.5 left-0.5 size-2.5 rounded-xs bg-util-colors-indigo-indigo-400 shadow-xs" />
|
||||
</span>
|
||||
<span className={cn('i-ri-braces-line size-6', graphicClassName)} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -7,6 +7,7 @@ import { toast } from '@langgenius/dify-ui/toast'
|
||||
import { isEqual } from 'es-toolkit/predicate'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import NavLink from '@/app/components/app-sidebar/nav-link'
|
||||
import SnippetInfoDropdown from '@/app/components/app-sidebar/snippet-info/dropdown'
|
||||
import ConfigVarModal from '@/app/components/app/configuration/config-var/config-modal'
|
||||
import Field from '@/app/components/workflow/nodes/_base/components/field'
|
||||
@ -36,6 +37,10 @@ const toSnippetInputField = (field: InputVar): SnippetInputField => ({
|
||||
type: field.type as unknown as SnippetInputField['type'],
|
||||
})
|
||||
|
||||
const NodeTreeIcon = ({ className }: { className?: string }) => (
|
||||
<span className={cn('i-ri-node-tree', className)} />
|
||||
)
|
||||
|
||||
export const SnippetSidebarContent = ({
|
||||
snippet,
|
||||
fields,
|
||||
@ -109,6 +114,16 @@ export const SnippetSidebarContent = ({
|
||||
)}
|
||||
</div>
|
||||
|
||||
<nav className="shrink-0 px-3 pt-4">
|
||||
<NavLink
|
||||
mode="expand"
|
||||
name={t('sectionOrchestrate', { ns: 'snippet' })}
|
||||
href={`/snippets/${snippet.id}/orchestrate`}
|
||||
active
|
||||
iconMap={{ selected: NodeTreeIcon, normal: NodeTreeIcon }}
|
||||
/>
|
||||
</nav>
|
||||
|
||||
<div className="flex min-h-0 grow flex-col px-3 pt-6">
|
||||
<Field
|
||||
title={t('inputVariables', { ns: 'snippet' })}
|
||||
|
||||
@ -224,7 +224,7 @@
|
||||
"studio.starFailed": "فشل تحديث النجمة",
|
||||
"studio.starred": "المميزة بنجمة",
|
||||
"studio.unstarApp": "إزالة النجمة من التطبيق",
|
||||
"studio.viewSnippets": "عرض المقتطفات",
|
||||
"studio.viewSnippets": "المقتطفات",
|
||||
"switch": "التبديل إلى Workflow Orchestrate",
|
||||
"switchLabel": "نسخة التطبيق التي سيتم إنشاؤها",
|
||||
"switchStart": "بدء التبديل",
|
||||
|
||||
@ -224,7 +224,7 @@
|
||||
"studio.starFailed": "Stern konnte nicht aktualisiert werden",
|
||||
"studio.starred": "Markiert",
|
||||
"studio.unstarApp": "Markierung der App entfernen",
|
||||
"studio.viewSnippets": "Snippets anzeigen",
|
||||
"studio.viewSnippets": "Snippets",
|
||||
"switch": "Zu Workflow-Orchestrierung wechseln",
|
||||
"switchLabel": "Die zu erstellende App-Kopie",
|
||||
"switchStart": "Wechsel starten",
|
||||
|
||||
@ -224,7 +224,7 @@
|
||||
"studio.starFailed": "Failed to update star",
|
||||
"studio.starred": "Starred",
|
||||
"studio.unstarApp": "Unstar app",
|
||||
"studio.viewSnippets": "View Snippets",
|
||||
"studio.viewSnippets": "Snippets",
|
||||
"switch": "Switch to Workflow Orchestrate",
|
||||
"switchLabel": "The app copy to be created",
|
||||
"switchStart": "Start switch",
|
||||
|
||||
@ -224,7 +224,7 @@
|
||||
"studio.starFailed": "No se pudo actualizar la estrella",
|
||||
"studio.starred": "Destacadas",
|
||||
"studio.unstarApp": "Quitar estrella de la aplicación",
|
||||
"studio.viewSnippets": "Ver fragmentos",
|
||||
"studio.viewSnippets": "Fragmentos",
|
||||
"switch": "Cambiar a Orquestación de Flujo de Trabajo",
|
||||
"switchLabel": "La copia de la app a crear",
|
||||
"switchStart": "Iniciar cambio",
|
||||
|
||||
@ -224,7 +224,7 @@
|
||||
"studio.starFailed": "بهروزرسانی ستاره ناموفق بود",
|
||||
"studio.starred": "ستارهدار",
|
||||
"studio.unstarApp": "برداشتن ستاره برنامه",
|
||||
"studio.viewSnippets": "مشاهده قطعهها",
|
||||
"studio.viewSnippets": "قطعهها",
|
||||
"switch": "تغییر به سازماندهی گردش کار",
|
||||
"switchLabel": "نسخه برنامه که ایجاد میشود",
|
||||
"switchStart": "شروع تغییر",
|
||||
|
||||
@ -224,7 +224,7 @@
|
||||
"studio.starFailed": "Échec de la mise à jour du favori",
|
||||
"studio.starred": "Favoris",
|
||||
"studio.unstarApp": "Retirer des favoris",
|
||||
"studio.viewSnippets": "Voir les extraits",
|
||||
"studio.viewSnippets": "Extraits",
|
||||
"switch": "Passer à l'orchestration de flux de travail",
|
||||
"switchLabel": "La copie de l'application à créer",
|
||||
"switchStart": "Commencer la commutation",
|
||||
|
||||
@ -224,7 +224,7 @@
|
||||
"studio.starFailed": "स्टार अपडेट करने में विफल",
|
||||
"studio.starred": "स्टार किए गए",
|
||||
"studio.unstarApp": "ऐप से स्टार हटाएं",
|
||||
"studio.viewSnippets": "स्निपेट देखें",
|
||||
"studio.viewSnippets": "स्निपेट",
|
||||
"switch": "वर्कफ़्लो ऑर्केस्ट्रेट पर स्विच करें",
|
||||
"switchLabel": "बनाई जाने वाली ऐप कॉपी",
|
||||
"switchStart": "स्विच शुरू करें",
|
||||
|
||||
@ -224,7 +224,7 @@
|
||||
"studio.starFailed": "Gagal memperbarui bintang",
|
||||
"studio.starred": "Berbintang",
|
||||
"studio.unstarApp": "Hapus bintang aplikasi",
|
||||
"studio.viewSnippets": "Lihat snippet",
|
||||
"studio.viewSnippets": "Snippet",
|
||||
"switch": "Beralih ke Workflow Orchestrate",
|
||||
"switchLabel": "Salinan aplikasi yang akan dibuat",
|
||||
"switchStart": "Sakelar mulai",
|
||||
|
||||
@ -224,7 +224,7 @@
|
||||
"studio.starFailed": "Impossibile aggiornare la stella",
|
||||
"studio.starred": "Preferite",
|
||||
"studio.unstarApp": "Rimuovi app dai preferiti",
|
||||
"studio.viewSnippets": "Visualizza snippet",
|
||||
"studio.viewSnippets": "Snippet",
|
||||
"switch": "Passa a Orchestrazione del flusso di lavoro",
|
||||
"switchLabel": "La copia dell'app da creare",
|
||||
"switchStart": "Inizia il passaggio",
|
||||
|
||||
@ -224,7 +224,7 @@
|
||||
"studio.starFailed": "スターの更新に失敗しました",
|
||||
"studio.starred": "スター付き",
|
||||
"studio.unstarApp": "アプリのスターを外す",
|
||||
"studio.viewSnippets": "スニペットを表示",
|
||||
"studio.viewSnippets": "スニペット",
|
||||
"switch": "ワークフロー オーケストレートに切り替える",
|
||||
"switchLabel": "作成されるアプリのコピー",
|
||||
"switchStart": "切り替えを開始する",
|
||||
|
||||
@ -224,7 +224,7 @@
|
||||
"studio.starFailed": "별표 업데이트 실패",
|
||||
"studio.starred": "별표 표시됨",
|
||||
"studio.unstarApp": "앱 별표 해제",
|
||||
"studio.viewSnippets": "스니펫 보기",
|
||||
"studio.viewSnippets": "스니펫",
|
||||
"switch": "워크플로우 오케스트레이션으로 전환하기",
|
||||
"switchLabel": "생성될 앱의 복사본",
|
||||
"switchStart": "전환 시작하기",
|
||||
|
||||
@ -224,7 +224,7 @@
|
||||
"studio.starFailed": "Ster kon niet worden bijgewerkt",
|
||||
"studio.starred": "Gemarkeerd",
|
||||
"studio.unstarApp": "Markering van app verwijderen",
|
||||
"studio.viewSnippets": "Snippets bekijken",
|
||||
"studio.viewSnippets": "Snippets",
|
||||
"switch": "Switch to Workflow Orchestrate",
|
||||
"switchLabel": "The app copy to be created",
|
||||
"switchStart": "Start switch",
|
||||
|
||||
@ -224,7 +224,7 @@
|
||||
"studio.starFailed": "Nie udało się zaktualizować gwiazdki",
|
||||
"studio.starred": "Oznaczone gwiazdką",
|
||||
"studio.unstarApp": "Usuń gwiazdkę aplikacji",
|
||||
"studio.viewSnippets": "Wyświetl fragmenty",
|
||||
"studio.viewSnippets": "Fragmenty",
|
||||
"switch": "Przełącz na Orkiestrację Przepływu Pracy",
|
||||
"switchLabel": "Kopia aplikacji do utworzenia",
|
||||
"switchStart": "Rozpocznij przełączanie",
|
||||
|
||||
@ -224,7 +224,7 @@
|
||||
"studio.starFailed": "Falha ao atualizar favorito",
|
||||
"studio.starred": "Favoritos",
|
||||
"studio.unstarApp": "Remover app dos favoritos",
|
||||
"studio.viewSnippets": "Ver snippets",
|
||||
"studio.viewSnippets": "Snippets",
|
||||
"switch": "Mudar para Orquestração de Fluxo de Trabalho",
|
||||
"switchLabel": "A cópia do aplicativo a ser criada",
|
||||
"switchStart": "Iniciar mudança",
|
||||
|
||||
@ -224,7 +224,7 @@
|
||||
"studio.starFailed": "Actualizarea stelei a eșuat",
|
||||
"studio.starred": "Marcate cu stea",
|
||||
"studio.unstarApp": "Elimină steaua aplicației",
|
||||
"studio.viewSnippets": "Vezi fragmentele",
|
||||
"studio.viewSnippets": "Fragmente",
|
||||
"switch": "Comută la Orchestrare Flux de Lucru",
|
||||
"switchLabel": "Copia aplicației care urmează să fie creată",
|
||||
"switchStart": "Începe comutarea",
|
||||
|
||||
@ -224,7 +224,7 @@
|
||||
"studio.starFailed": "Не удалось обновить отметку",
|
||||
"studio.starred": "Избранные",
|
||||
"studio.unstarApp": "Удалить приложение из избранного",
|
||||
"studio.viewSnippets": "Посмотреть фрагменты",
|
||||
"studio.viewSnippets": "Фрагменты",
|
||||
"switch": "Переключиться на Workflow",
|
||||
"switchLabel": "Копия приложения, которая будет создана",
|
||||
"switchStart": "Переключиться",
|
||||
|
||||
@ -224,7 +224,7 @@
|
||||
"studio.starFailed": "Posodobitev zvezdice ni uspela",
|
||||
"studio.starred": "Označeno z zvezdico",
|
||||
"studio.unstarApp": "Odstrani zvezdico aplikacije",
|
||||
"studio.viewSnippets": "Prikaži izrezke",
|
||||
"studio.viewSnippets": "Izrezki",
|
||||
"switch": "Preklopi na Workflow Orchestrate",
|
||||
"switchLabel": "Kopija aplikacije, ki bo ustvarjena",
|
||||
"switchStart": "Začni preklop",
|
||||
|
||||
@ -224,7 +224,7 @@
|
||||
"studio.starFailed": "อัปเดตดาวไม่สำเร็จ",
|
||||
"studio.starred": "ติดดาวแล้ว",
|
||||
"studio.unstarApp": "เลิกติดดาวแอป",
|
||||
"studio.viewSnippets": "ดูสนิปเปต",
|
||||
"studio.viewSnippets": "สนิปเปต",
|
||||
"switch": "เปลี่ยนไปใช้ Workflow Orchestrate",
|
||||
"switchLabel": "สําเนาโปรเจกต์ที่จะสร้าง",
|
||||
"switchStart": "สวิตช์สตาร์ท",
|
||||
|
||||
@ -224,7 +224,7 @@
|
||||
"studio.starFailed": "Yıldız güncellenemedi",
|
||||
"studio.starred": "Yıldızlı",
|
||||
"studio.unstarApp": "Uygulamanın yıldızını kaldır",
|
||||
"studio.viewSnippets": "Snippet'leri görüntüle",
|
||||
"studio.viewSnippets": "Snippet'ler",
|
||||
"switch": "İş Akışı Orkestrasyonuna Geç",
|
||||
"switchLabel": "Oluşturulacak uygulama kopyası",
|
||||
"switchStart": "Geçişi Başlat",
|
||||
|
||||
@ -224,7 +224,7 @@
|
||||
"studio.starFailed": "Не вдалося оновити зірку",
|
||||
"studio.starred": "Позначені зіркою",
|
||||
"studio.unstarApp": "Зняти зірку із застосунку",
|
||||
"studio.viewSnippets": "Переглянути фрагменти",
|
||||
"studio.viewSnippets": "Фрагменти",
|
||||
"switch": "Перейти до оркестрації робочого процесу",
|
||||
"switchLabel": "Копія додатка, яка буде створена",
|
||||
"switchStart": "Почати перемикання",
|
||||
|
||||
@ -224,7 +224,7 @@
|
||||
"studio.starFailed": "Không thể cập nhật sao",
|
||||
"studio.starred": "Đã gắn sao",
|
||||
"studio.unstarApp": "Bỏ gắn sao ứng dụng",
|
||||
"studio.viewSnippets": "Xem snippets",
|
||||
"studio.viewSnippets": "Snippets",
|
||||
"switch": "Chuyển sang quản lý quy trình",
|
||||
"switchLabel": "Bản sao ứng dụng sẽ được tạo",
|
||||
"switchStart": "Bắt đầu chuyển",
|
||||
|
||||
@ -224,7 +224,7 @@
|
||||
"studio.starFailed": "更新星标失败",
|
||||
"studio.starred": "星标",
|
||||
"studio.unstarApp": "取消星标应用",
|
||||
"studio.viewSnippets": "查看 Snippets",
|
||||
"studio.viewSnippets": "Snippets",
|
||||
"switch": "迁移为工作流编排",
|
||||
"switchLabel": "新应用创建为",
|
||||
"switchStart": "开始迁移",
|
||||
|
||||
@ -224,7 +224,7 @@
|
||||
"studio.starFailed": "更新星標失敗",
|
||||
"studio.starred": "星標",
|
||||
"studio.unstarApp": "取消星標應用",
|
||||
"studio.viewSnippets": "查看片段",
|
||||
"studio.viewSnippets": "片段",
|
||||
"switch": "遷移為工作流編排",
|
||||
"switchLabel": "新應用建立為",
|
||||
"switchStart": "開始遷移",
|
||||
|
||||
Loading…
Reference in New Issue
Block a user