(['plugins', 'templates']).withDefault('plugins').withOptions({ history: 'replace' }),
}
diff --git a/web/app/components/plugins/plugin-page/nav-operations.tsx b/web/app/components/plugins/plugin-page/nav-operations.tsx
index 91c05fd65e..a638f8deb8 100644
--- a/web/app/components/plugins/plugin-page/nav-operations.tsx
+++ b/web/app/components/plugins/plugin-page/nav-operations.tsx
@@ -78,10 +78,14 @@ export const SubmitRequestDropdown = () => {
)
}
-export const CreationTypeTabs = () => {
+type CreationTypeTabsProps = {
+ creationType?: string
+}
+
+export const CreationTypeTabs = ({ creationType: creationTypeProp }: CreationTypeTabsProps = {}) => {
const { t } = useTranslation()
const searchParams = useSearchParams()
- const creationType = searchParams.get('creationType') || 'plugins'
+ const creationType = creationTypeProp || searchParams.get('creationType') || 'plugins'
return (
diff --git a/web/i18n/en-US/plugin.json b/web/i18n/en-US/plugin.json
index 9cbef2d774..ad16d92c67 100644
--- a/web/i18n/en-US/plugin.json
+++ b/web/i18n/en-US/plugin.json
@@ -196,13 +196,13 @@
"marketplace.discover": "Discover",
"marketplace.empower": "Empower your AI development",
"marketplace.featured": "Featured",
- "marketplace.heroSubtitle": "Use community-built plugins to power your AI development.",
- "marketplace.heroTitle": "Discover. Extend. Build.",
"marketplace.installs": "installs",
"marketplace.moreFrom": "More from Marketplace",
"marketplace.noPluginFound": "No plugin found",
"marketplace.ourTopPicks": "Our top picks to get you started",
"marketplace.partnerTip": "Verified by a Dify partner",
+ "marketplace.pluginsHeroSubtitle": "Use community-built plugins to power your AI development.",
+ "marketplace.pluginsHeroTitle": "Discover. Extend. Build.",
"marketplace.pluginsResult": "{{num}} results",
"marketplace.searchBreadcrumbMarketplace": "Marketplace",
"marketplace.searchBreadcrumbSearch": "Search",
@@ -221,6 +221,8 @@
"marketplace.sortOption.mostPopular": "Most Popular",
"marketplace.sortOption.newlyReleased": "Newly Released",
"marketplace.sortOption.recentlyUpdated": "Recently Updated",
+ "marketplace.templatesHeroSubtitle": "Community-built workflow templates — ready to use, remix, and deploy.",
+ "marketplace.templatesHeroTitle": "Create. Remix. Deploy.",
"marketplace.verifiedTip": "Verified by Dify",
"marketplace.viewMore": "View more",
"metadata.title": "Plugins",
@@ -229,7 +231,6 @@
"pluginInfoModal.repository": "Repository",
"pluginInfoModal.title": "Plugin info",
"plugins": "Plugins",
- "templates": "Templates",
"privilege.admins": "Admins",
"privilege.everyone": "Everyone",
"privilege.noone": "No one",
@@ -262,6 +263,7 @@
"task.installingWithSuccess": "Installing {{installingLength}} plugins, {{successLength}} success.",
"task.runningPlugins": "Installing Plugins",
"task.successPlugins": "Successfully Installed Plugins",
+ "templates": "Templates",
"upgrade.close": "Close",
"upgrade.description": "About to install the following plugin",
"upgrade.successfulTitle": "Install successful",
diff --git a/web/i18n/zh-Hans/plugin.json b/web/i18n/zh-Hans/plugin.json
index 4722d22c32..935093e09e 100644
--- a/web/i18n/zh-Hans/plugin.json
+++ b/web/i18n/zh-Hans/plugin.json
@@ -196,13 +196,13 @@
"marketplace.discover": "探索",
"marketplace.empower": "助力您的 AI 开发",
"marketplace.featured": "精选",
- "marketplace.heroSubtitle": "使用社区构建的插件为您的 AI 开发提供动力。",
- "marketplace.heroTitle": "探索。扩展。构建。",
"marketplace.installs": "次安装",
"marketplace.moreFrom": "更多来自市场",
"marketplace.noPluginFound": "未找到插件",
"marketplace.ourTopPicks": "我们精选推荐",
"marketplace.partnerTip": "此插件由 Dify 合作伙伴认证",
+ "marketplace.pluginsHeroSubtitle": "使用社区构建的插件为您的 AI 开发提供动力。",
+ "marketplace.pluginsHeroTitle": "探索。扩展。构建。",
"marketplace.pluginsResult": "{{num}} 个插件结果",
"marketplace.searchBreadcrumbMarketplace": "市场",
"marketplace.searchBreadcrumbSearch": "搜索",
@@ -221,6 +221,8 @@
"marketplace.sortOption.mostPopular": "最受欢迎",
"marketplace.sortOption.newlyReleased": "最新发布",
"marketplace.sortOption.recentlyUpdated": "最近更新",
+ "marketplace.templatesHeroSubtitle": "社区构建的工作流模板 —— 随时可使用、复刻和部署。",
+ "marketplace.templatesHeroTitle": "创建。复刻。部署。",
"marketplace.verifiedTip": "此插件由 Dify 认证",
"marketplace.viewMore": "查看更多",
"metadata.title": "插件",
@@ -229,7 +231,6 @@
"pluginInfoModal.repository": "仓库",
"pluginInfoModal.title": "插件信息",
"plugins": "插件",
- "templates": "模板",
"privilege.admins": "管理员",
"privilege.everyone": "所有人",
"privilege.noone": "无人",
@@ -262,6 +263,7 @@
"task.installingWithSuccess": "{{installingLength}} 个插件安装中,{{successLength}} 安装成功",
"task.runningPlugins": "正在安装的插件",
"task.successPlugins": "安装成功的插件",
+ "templates": "模板",
"upgrade.close": "关闭",
"upgrade.description": "即将安装以下插件",
"upgrade.successfulTitle": "安装成功",
diff --git a/web/utils/template.ts b/web/utils/template.ts
new file mode 100644
index 0000000000..f041d6c150
--- /dev/null
+++ b/web/utils/template.ts
@@ -0,0 +1,88 @@
+import type { Viewport } from 'reactflow'
+import type { Edge, Node } from '@/app/components/workflow/types'
+import { load as yamlLoad } from 'js-yaml'
+
+type GraphPayload = {
+ nodes?: Node[]
+ edges?: Edge[]
+ viewport?: Viewport
+}
+
+type DslPayload = {
+ workflow?: {
+ graph?: GraphPayload
+ }
+ graph?: GraphPayload
+} | null
+
+export type ParsedGraph = {
+ nodes: Node[]
+ edges: Edge[]
+ viewport: Viewport
+} | null
+
+export const parseGraphFromDsl = (dslContent: string): ParsedGraph => {
+ if (!dslContent)
+ return null
+
+ try {
+ const data = yamlLoad(dslContent) as DslPayload
+ const graph = data?.workflow?.graph ?? data?.graph
+ if (!graph || !graph.nodes || !graph.edges)
+ return null
+
+ return {
+ nodes: graph.nodes || [],
+ edges: graph.edges || [],
+ viewport: graph.viewport || { x: 0, y: 0, zoom: 0.5 },
+ }
+ }
+ catch {
+ return null
+ }
+}
+
+type UsedCountFormatOptions = {
+ precision?: number
+ rounding?: 'round' | 'floor'
+}
+
+export const formatUsedCount = (count?: number, options: UsedCountFormatOptions = {}) => {
+ if (!count)
+ return null
+ if (count < 1000)
+ return String(count)
+
+ const precision = options.precision ?? 1
+ const rounding = options.rounding ?? 'round'
+ const base = count / 1000
+ const factor = 10 ** precision
+ const rounded = rounding === 'floor'
+ ? Math.floor(base * factor) / factor
+ : Math.round(base * factor) / factor
+
+ const display = precision <= 0
+ ? String(rounded)
+ : (rounded % 1 === 0 ? String(rounded) : rounded.toFixed(precision))
+
+ return `${display}k`
+}
+
+type TranslationFn = (key: string, options?: Record) => string
+
+export const formatRelativeTime = (dateStr: string, t: TranslationFn) => {
+ const date = new Date(dateStr)
+ const now = new Date()
+ const diffMs = now.getTime() - date.getTime()
+ const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24))
+
+ if (diffDays < 1)
+ return t('detail.today')
+ if (diffDays < 7)
+ return t('detail.daysAgo', { count: diffDays })
+ if (diffDays < 30)
+ return t('detail.weeksAgo', { count: Math.floor(diffDays / 7) })
+ if (diffDays < 365)
+ return t('detail.monthsAgo', { count: Math.floor(diffDays / 30) })
+ return t('detail.yearsAgo', { count: Math.floor(diffDays / 365) })
+}