diff --git a/ai/src/main/java/com/alibaba/nacos/ai/pipeline/PublishPipelineManager.java b/ai/src/main/java/com/alibaba/nacos/ai/pipeline/PublishPipelineManager.java index ddd823e37f2..1a52e96dab4 100644 --- a/ai/src/main/java/com/alibaba/nacos/ai/pipeline/PublishPipelineManager.java +++ b/ai/src/main/java/com/alibaba/nacos/ai/pipeline/PublishPipelineManager.java @@ -32,7 +32,6 @@ import java.util.Map; import java.util.Properties; import java.util.ServiceLoader; -import java.util.Set; import java.util.stream.Collectors; /** @@ -105,7 +104,8 @@ void initWithBuilders(Iterable builders, Pipeline * Get pipeline services matching the given resource type and node configuration list. * *

Filters services that support the specified resource type and whose pipelineId is present - * in the nodes list. Results are sorted by {@link PublishPipelineService#getPreferOrder()} ascending.

+ * in the nodes list. Results are sorted by configured node order first, then + * {@link PublishPipelineService#getPreferOrder()} ascending.

* * @param resourceType the resource type to filter by * @param nodes the configured pipeline nodes to match against @@ -114,14 +114,17 @@ void initWithBuilders(Iterable builders, Pipeline public List getPipelineServices( PublishPipelineResourceType resourceType, List nodes) { - Set pipelineIds = nodes.stream() - .map(PipelineNodeConfig::getPipelineId) - .collect(Collectors.toSet()); + Map nodeConfigMap = new HashMap<>(); + for (PipelineNodeConfig node : nodes) { + if (node.getPipelineId() != null) { + nodeConfigMap.put(node.getPipelineId(), node); + } + } return serviceMap.values().stream() - .filter(service -> pipelineIds.contains(service.pipelineId())) + .filter(service -> nodeConfigMap.containsKey(service.pipelineId())) .filter(service -> supportsResourceType(service, resourceType)) - .sorted(Comparator.comparingInt(PublishPipelineService::getPreferOrder)) + .sorted(Comparator.comparingInt(service -> getEffectiveOrder(service, nodeConfigMap))) .collect(Collectors.toList()); } @@ -142,4 +145,13 @@ private boolean supportsResourceType(PublishPipelineService service, } return Arrays.asList(types).contains(resourceType); } + + private int getEffectiveOrder(PublishPipelineService service, + Map nodeConfigMap) { + PipelineNodeConfig nodeConfig = nodeConfigMap.get(service.pipelineId()); + if (nodeConfig != null && nodeConfig.getOrder() != null) { + return nodeConfig.getOrder(); + } + return service.getPreferOrder(); + } } diff --git a/ai/src/main/java/com/alibaba/nacos/ai/pipeline/config/FilePipelineConfigProvider.java b/ai/src/main/java/com/alibaba/nacos/ai/pipeline/config/FilePipelineConfigProvider.java index 9646bb24c7f..777f2b3a6ec 100644 --- a/ai/src/main/java/com/alibaba/nacos/ai/pipeline/config/FilePipelineConfigProvider.java +++ b/ai/src/main/java/com/alibaba/nacos/ai/pipeline/config/FilePipelineConfigProvider.java @@ -23,14 +23,12 @@ import com.alibaba.nacos.sys.env.EnvUtil; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; -import java.util.Comparator; import java.util.LinkedHashSet; import java.util.List; -import java.util.Map; import java.util.Properties; import java.util.Set; -import java.util.TreeMap; /** * File-based (application.properties) implementation of {@link PipelineConfigProvider}. @@ -39,11 +37,12 @@ *
    *
  • {@code nacos.plugin.ai-pipeline.enabled} - optional global switch for ai-pipeline plugin
  • *
  • {@code nacos.plugin.ai-pipeline.type} - enabled implementation type(s), for example - * {@code skill-scanner}
  • + * {@code skill-scanner}; default is {@code skill-spector,skill-scanner} + *
  • {@code nacos.plugin.ai-pipeline.{type}.order} - optional execution order override; + * lower values execute first
  • *
  • {@code nacos.plugin.ai-pipeline.{type}.{key}} - implementation properties passed to builder
  • *
- *

For example: {@code nacos.plugin.ai-pipeline.type=skill-scanner} and - * {@code nacos.plugin.ai-pipeline.skill-scanner.enabled=true}. + *

For example: {@code nacos.plugin.ai-pipeline.type=skill-scanner}. * *

Follows the singleton pattern like PushConfig. * @@ -60,6 +59,11 @@ public class FilePipelineConfigProvider extends AbstractDynamicConfig private static final String KEY_ENABLED = KEY_PLUGIN_PREFIX + ".enabled"; private static final String KEY_TYPE = KEY_PLUGIN_PREFIX + ".type"; + + private static final String KEY_ORDER = "order"; + + private static final List DEFAULT_TYPES = Collections.unmodifiableList( + Arrays.asList("skill-spector", "skill-scanner")); private static final FilePipelineConfigProvider INSTANCE = new FilePipelineConfigProvider(); @@ -116,19 +120,15 @@ private List readEnabledPluginNodes() { return Collections.emptyList(); } - Map pluginPropertyMap = new TreeMap<>(); + List nodes = new ArrayList<>(configuredTypes.size()); for (String typeName : configuredTypes) { - pluginPropertyMap.put(typeName, readNodeProperties(typeName, allProperties)); + Properties nodeProperties = readNodeProperties(typeName, allProperties); + PipelineNodeConfig nodeConfig = new PipelineNodeConfig(); + nodeConfig.setPipelineId(typeName); + nodeConfig.setProperties(nodeProperties); + nodeConfig.setOrder(parseOrder(nodeProperties.getProperty(KEY_ORDER))); + nodes.add(nodeConfig); } - List nodes = new ArrayList<>(pluginPropertyMap.size()); - pluginPropertyMap.entrySet().stream() - .sorted(Map.Entry.comparingByKey(Comparator.naturalOrder())) - .forEach(entry -> { - PipelineNodeConfig nodeConfig = new PipelineNodeConfig(); - nodeConfig.setPipelineId(entry.getKey()); - nodeConfig.setProperties(entry.getValue()); - nodes.add(nodeConfig); - }); return nodes; } @@ -143,7 +143,7 @@ private boolean isPluginEnabled(Properties allProperties) { private List readConfiguredTypes(Properties allProperties) { String typeValue = allProperties.getProperty(KEY_TYPE); if (StringUtils.isBlank(typeValue)) { - return Collections.emptyList(); + return DEFAULT_TYPES; } Set types = new LinkedHashSet<>(); for (String each : typeValue.split(",")) { @@ -169,6 +169,17 @@ private Properties readNodeProperties(String typeName, Properties allProperties) } return properties; } + + private Integer parseOrder(String value) { + if (StringUtils.isBlank(value)) { + return null; + } + try { + return Integer.parseInt(value.trim()); + } catch (NumberFormatException e) { + return null; + } + } @Override protected String printConfig() { diff --git a/ai/src/main/java/com/alibaba/nacos/ai/pipeline/model/PipelineNodeConfig.java b/ai/src/main/java/com/alibaba/nacos/ai/pipeline/model/PipelineNodeConfig.java index e132178eaa0..6b4821d10d6 100644 --- a/ai/src/main/java/com/alibaba/nacos/ai/pipeline/model/PipelineNodeConfig.java +++ b/ai/src/main/java/com/alibaba/nacos/ai/pipeline/model/PipelineNodeConfig.java @@ -35,6 +35,11 @@ public class PipelineNodeConfig { * Custom configuration properties for this node (e.g. endpoint, timeout). */ private Properties properties; + + /** + * Optional configured execution order. Lower values execute first. + */ + private Integer order; public PipelineNodeConfig() { } @@ -54,4 +59,12 @@ public Properties getProperties() { public void setProperties(Properties properties) { this.properties = properties; } + + public Integer getOrder() { + return order; + } + + public void setOrder(Integer order) { + this.order = order; + } } diff --git a/ai/src/test/java/com/alibaba/nacos/ai/pipeline/PublishPipelineManagerTest.java b/ai/src/test/java/com/alibaba/nacos/ai/pipeline/PublishPipelineManagerTest.java index dd12e6e82fd..a8054a5fc71 100644 --- a/ai/src/test/java/com/alibaba/nacos/ai/pipeline/PublishPipelineManagerTest.java +++ b/ai/src/test/java/com/alibaba/nacos/ai/pipeline/PublishPipelineManagerTest.java @@ -33,6 +33,7 @@ import java.util.Set; import java.util.stream.Collectors; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -205,6 +206,32 @@ void getPipelineServicesFilteringAndSorting() { } } } + + @Test + void configuredNodeOrderShouldOverrideServicePreferOrder() { + PublishPipelineManager manager = new PublishPipelineManager(); + List builders = new ArrayList<>(); + builders.add(createServiceBuilder(new ServiceDescriptor("first-by-default", 10, + PublishPipelineResourceType.values()))); + builders.add(createServiceBuilder(new ServiceDescriptor("first-by-config", 20, + PublishPipelineResourceType.values()))); + + PipelineConfig config = new PipelineConfig(); + config.setEnabled(true); + config.setNodes(new ArrayList<>()); + manager.initWithBuilders(builders, config); + + List nodes = new ArrayList<>(); + nodes.add(createNode("first-by-default", 30)); + nodes.add(createNode("first-by-config", 5)); + + List result = + manager.getPipelineServices(PublishPipelineResourceType.SKILL, nodes); + + assertEquals(2, result.size()); + assertEquals("first-by-config", result.get(0).pipelineId()); + assertEquals("first-by-default", result.get(1).pipelineId()); + } private PublishPipelineServiceBuilder createMockBuilder(BuilderDescriptor desc) { return new PublishPipelineServiceBuilder() { @@ -245,6 +272,14 @@ public PublishPipelineResourceType[] pipelineResourceTypes() { } }; } + + private PipelineNodeConfig createNode(String pipelineId, Integer order) { + PipelineNodeConfig nodeConfig = new PipelineNodeConfig(); + nodeConfig.setPipelineId(pipelineId); + nodeConfig.setProperties(new Properties()); + nodeConfig.setOrder(order); + return nodeConfig; + } static class BuilderDescriptor { diff --git a/ai/src/test/java/com/alibaba/nacos/ai/pipeline/config/FilePipelineConfigProviderTest.java b/ai/src/test/java/com/alibaba/nacos/ai/pipeline/config/FilePipelineConfigProviderTest.java index 44350fb01ce..c6420728e57 100644 --- a/ai/src/test/java/com/alibaba/nacos/ai/pipeline/config/FilePipelineConfigProviderTest.java +++ b/ai/src/test/java/com/alibaba/nacos/ai/pipeline/config/FilePipelineConfigProviderTest.java @@ -28,7 +28,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.Comparator; import java.util.List; import java.util.Properties; import java.util.stream.Collectors; @@ -36,6 +35,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; /** @@ -50,7 +50,6 @@ class FilePipelineConfigProviderTest { private static List> sampleValidNodeConfigs() { List> lists = new ArrayList<>(); - lists.add(Collections.emptyList()); Properties p1 = new Properties(); p1.setProperty("k1", "v1"); lists.add(Collections.singletonList(new NodeTestData("scanner", "t", p1))); @@ -62,6 +61,42 @@ private static List> sampleValidNodeConfigs() { new NodeTestData("beta", "y", new Properties()))); return lists; } + + @Test + void defaultTypesShouldBeEnabledWhenTypeUnset() { + Properties envProperties = new Properties(); + envProperties.setProperty("nacos.plugin.ai-pipeline.enabled", "true"); + + try (MockedStatic envMock = Mockito.mockStatic(EnvUtil.class); + MockedStatic notifyMock = Mockito.mockStatic(NotifyCenter.class)) { + envMock.when(EnvUtil::getProperties).thenReturn(envProperties); + + FilePipelineConfigProvider provider = createFreshInstance(); + PipelineConfig config = provider.getConfig(); + + assertTrue(config.isEnabled()); + assertEquals(2, config.getNodes().size()); + assertNotNull(findNode(config, "skill-spector")); + assertNotNull(findNode(config, "skill-scanner")); + } + } + + @Test + void explicitDisableShouldTurnOffDefaultTypes() { + Properties envProperties = new Properties(); + envProperties.setProperty("nacos.plugin.ai-pipeline.enabled", "false"); + + try (MockedStatic envMock = Mockito.mockStatic(EnvUtil.class); + MockedStatic notifyMock = Mockito.mockStatic(NotifyCenter.class)) { + envMock.when(EnvUtil::getProperties).thenReturn(envProperties); + + FilePipelineConfigProvider provider = createFreshInstance(); + PipelineConfig config = provider.getConfig(); + + assertFalse(config.isEnabled()); + assertTrue(config.getNodes().isEmpty()); + } + } private static List sampleErrorScenarios() { return Arrays.asList( @@ -101,8 +136,6 @@ void configParsingCorrectness() { FilePipelineConfigProvider provider = createFreshInstance(); PipelineConfig config = provider.getConfig(); - List expectedNodes = new ArrayList<>(nodeConfigs); - expectedNodes.sort(Comparator.comparing(node -> node.pipelineId)); assertNotNull(config, "Parsed config should not be null"); assertEquals(enabled && !nodeConfigs.isEmpty(), config.isEnabled(), @@ -112,7 +145,7 @@ void configParsingCorrectness() { "Number of parsed nodes should match input"); for (int i = 0; i < config.getNodes().size(); i++) { - NodeTestData expected = expectedNodes.get(i); + NodeTestData expected = nodeConfigs.get(i); PipelineNodeConfig actual = config.getNodes().get(i); assertEquals(expected.pipelineId, actual.getPipelineId(), "Node pipelineId at index " + i + " should match"); @@ -163,11 +196,10 @@ void configErrorTolerance() { } @Test - void subPropertyEnabledShouldNotBeTreatedAsNodeSwitch() { + void subPropertyCommandShouldBePreserved() { Properties envProperties = new Properties(); envProperties.setProperty("nacos.plugin.ai-pipeline.enabled", "true"); envProperties.setProperty("nacos.plugin.ai-pipeline.type", "skill-scanner"); - envProperties.setProperty("nacos.plugin.ai-pipeline.skill-scanner.enabled", "true"); envProperties.setProperty("nacos.plugin.ai-pipeline.skill-scanner.command", "/tmp/skill-scanner"); try (MockedStatic envMock = Mockito.mockStatic(EnvUtil.class); @@ -183,12 +215,34 @@ void subPropertyEnabledShouldNotBeTreatedAsNodeSwitch() { PipelineNodeConfig node = config.getNodes().get(0); assertEquals("skill-scanner", node.getPipelineId(), "Node id should match configured type"); - assertEquals("true", node.getProperties().getProperty("enabled"), - "Type sub-property enabled should be preserved as node property"); assertEquals("/tmp/skill-scanner", node.getProperties().getProperty("command"), "Type sub-property command should be preserved"); } } + + @Test + void nodeOrderShouldBeParsedFromSubProperty() { + Properties envProperties = new Properties(); + envProperties.setProperty("nacos.plugin.ai-pipeline.enabled", "true"); + envProperties.setProperty("nacos.plugin.ai-pipeline.type", + "skill-spector,skill-scanner,broken"); + envProperties.setProperty("nacos.plugin.ai-pipeline.skill-spector.order", "20"); + envProperties.setProperty("nacos.plugin.ai-pipeline.skill-scanner.order", "-10"); + envProperties.setProperty("nacos.plugin.ai-pipeline.broken.order", "invalid"); + try (MockedStatic envMock = Mockito.mockStatic(EnvUtil.class); + MockedStatic notifyMock = Mockito.mockStatic(NotifyCenter.class)) { + envMock.when(EnvUtil::getProperties).thenReturn(envProperties); + + FilePipelineConfigProvider provider = createFreshInstance(); + PipelineConfig config = provider.getConfig(); + + assertEquals(20, findNode(config, "skill-spector").getOrder()); + assertEquals(-10, findNode(config, "skill-scanner").getOrder()); + assertNull(findNode(config, "broken").getOrder()); + assertEquals("20", findNode(config, "skill-spector").getProperties() + .getProperty("order")); + } + } private FilePipelineConfigProvider createFreshInstance() { try { @@ -201,6 +255,13 @@ private FilePipelineConfigProvider createFreshInstance() { "Failed to create FilePipelineConfigProvider instance via reflection", e); } } + + private PipelineNodeConfig findNode(PipelineConfig config, String pipelineId) { + return config.getNodes().stream() + .filter(node -> pipelineId.equals(node.getPipelineId())) + .findFirst() + .orElseThrow(() -> new AssertionError("Missing node: " + pipelineId)); + } static class ErrorScenario { diff --git a/console-ui-next/src/locales/en-US.json b/console-ui-next/src/locales/en-US.json index d484804a424..7c96bbe3ca7 100644 --- a/console-ui-next/src/locales/en-US.json +++ b/console-ui-next/src/locales/en-US.json @@ -1082,6 +1082,10 @@ "pipelineNodePassed": "Passed", "pipelineNodeFailed": "Failed", "pipelineNoMessage": "No output message", + "pipelineCopy": "Copy result", + "pipelineCopied": "Copied", + "pipelineCopySuccess": "Scan result copied", + "pipelineCopyFailed": "Failed to copy", "labelsUpdateSuccess": "Labels updated", "bizTagsUpdateSuccess": "Tags updated", "bizTagPlaceholder": "Add a tag", @@ -1336,6 +1340,10 @@ "pipelineNodePassed": "Passed", "pipelineNodeFailed": "Failed", "pipelineNoMessage": "No output message", + "pipelineCopy": "Copy result", + "pipelineCopied": "Copied", + "pipelineCopySuccess": "Scan result copied", + "pipelineCopyFailed": "Failed to copy", "publishDisabledPipeline": "Publish requires pipeline approval", "forcePublish": "Force Publish", "forcePublishSuccess": "Force-published successfully", diff --git a/console-ui-next/src/locales/zh-CN.json b/console-ui-next/src/locales/zh-CN.json index a35020381a0..1075b81a2fd 100644 --- a/console-ui-next/src/locales/zh-CN.json +++ b/console-ui-next/src/locales/zh-CN.json @@ -1085,6 +1085,10 @@ "pipelineNodePassed": "通过", "pipelineNodeFailed": "失败", "pipelineNoMessage": "暂无输出信息", + "pipelineCopy": "复制结果", + "pipelineCopied": "已复制", + "pipelineCopySuccess": "扫描结果已复制", + "pipelineCopyFailed": "复制失败", "labelsUpdateSuccess": "标签更新成功", "bizTagsUpdateSuccess": "业务标签更新成功", "bizTagPlaceholder": "添加标签", @@ -1336,6 +1340,10 @@ "pipelineNodePassed": "通过", "pipelineNodeFailed": "失败", "pipelineNoMessage": "暂无输出信息", + "pipelineCopy": "复制结果", + "pipelineCopied": "已复制", + "pipelineCopySuccess": "扫描结果已复制", + "pipelineCopyFailed": "复制失败", "publishDisabledPipeline": "需等待审核通过后才能发布", "forcePublish": "强制发布", "forcePublishSuccess": "强制发布成功", diff --git a/console-ui-next/src/pages/skillManagement/components/PipelineStatusDisplay.tsx b/console-ui-next/src/pages/skillManagement/components/PipelineStatusDisplay.tsx index 7e3318766ac..80c565d88a6 100644 --- a/console-ui-next/src/pages/skillManagement/components/PipelineStatusDisplay.tsx +++ b/console-ui-next/src/pages/skillManagement/components/PipelineStatusDisplay.tsx @@ -1,6 +1,6 @@ import { useState, useEffect, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; -import { Loader2, CheckCircle2, XCircle, Clock, RefreshCw, Eye, Minus, ShieldCheck, ShieldX } from 'lucide-react'; +import { Loader2, CheckCircle2, XCircle, Clock, RefreshCw, Eye, Minus, ShieldCheck, ShieldX, Copy, Check } from 'lucide-react'; import { Badge } from '@/components/ui/badge'; import { Button } from '@/components/ui/button'; import { @@ -15,6 +15,7 @@ import { cn } from '@/lib/utils'; import dayjs from 'dayjs'; import Markdown from 'react-markdown'; import remarkGfm from 'remark-gfm'; +import { toast } from 'sonner'; interface PipelineCheckpointInfo { title: string; @@ -75,6 +76,29 @@ function formatDuration(ms: number) { return ms >= 1000 ? `${(ms / 1000).toFixed(1)}s` : `${ms}ms`; } +async function copyTextToClipboard(text: string) { + if (navigator.clipboard && window.isSecureContext) { + try { + await navigator.clipboard.writeText(text); + return; + } catch { + // Fall through to textarea fallback for insecure or restricted contexts. + } + } + + const textarea = document.createElement('textarea'); + textarea.value = text; + textarea.style.position = 'absolute'; + textarea.style.left = '-999999px'; + document.body.appendChild(textarea); + textarea.select(); + try { + document.execCommand('copy'); + } finally { + document.body.removeChild(textarea); + } +} + /** Render node message content based on messageType. */ function NodeMessageContent({ message, messageType }: { message: string; messageType?: string }) { const formattedJson = useMemo(() => { @@ -142,6 +166,7 @@ function PipelineDetailDialog({ // Auto-select first failed node, or first node const firstFailedIdx = nodes.findIndex((n) => !n.passed); const [selectedIdx, setSelectedIdx] = useState(firstFailedIdx >= 0 ? firstFailedIdx : 0); + const [copiedNodeId, setCopiedNodeId] = useState(null); // Reset selection when dialog opens or pipeline data changes useEffect(() => { @@ -155,6 +180,20 @@ function PipelineDetailDialog({ const StatusIcon = config.icon; const selectedNode = nodes[selectedIdx]; + const handleCopyMessage = async (node: PipelineNodeInfo) => { + if (!node.message) return; + try { + await copyTextToClipboard(node.message); + setCopiedNodeId(node.nodeId); + toast.success(t(`${translationPrefix}.pipelineCopySuccess`)); + window.setTimeout(() => { + setCopiedNodeId((current) => current === node.nodeId ? null : current); + }, 2000); + } catch { + toast.error(t(`${translationPrefix}.pipelineCopyFailed`)); + } + }; + return (

e.preventDefault()}> @@ -263,12 +302,31 @@ function PipelineDetailDialog({ {formatDuration(selectedNode.durationMs)} )} - {selectedNode.executedAt && ( - - - {dayjs(selectedNode.executedAt).format('YYYY-MM-DD HH:mm:ss')} - - )} +
+ {selectedNode.executedAt && ( + + + {dayjs(selectedNode.executedAt).format('YYYY-MM-DD HH:mm:ss')} + + )} + {selectedNode.message && ( + + )} +
{/* Message area */} diff --git a/console/src/main/resources/static/next/js/CliCommandCard.js b/console/src/main/resources/static/next/js/CliCommandCard.js index 46b0a193ca9..27689093878 100644 --- a/console/src/main/resources/static/next/js/CliCommandCard.js +++ b/console/src/main/resources/static/next/js/CliCommandCard.js @@ -1 +1 @@ -import{o as e}from"./rolldown-runtime.js";import{At as t,C as n,Dt as r,Ft as i,Gt as a,Lt as o,Nt as s,P as c,Rt as l,Tt as u,Z as d,en as f,h as p,k as m,n as h,q as g,x as _,z as v}from"./vendor-icons.js";import{v as y}from"./vendor-react.js";import{i as b}from"./client.js";import{a as x,i as S,r as C}from"./vendor-markdown.js";import{n as w,t as T}from"./button.js";import{i as E,n as D,r as O,t as k}from"./tooltip.js";import{t as A}from"./badge.js";import{a as j,i as M,n as N,o as P,r as F,t as I}from"./dialog.js";import{t as L}from"./input.js";import{n as R,t as z}from"./card.js";import{t as B}from"./checkbox.js";import{t as V}from"./dayjs.min.js";var H=e(f(),1),U=e(V(),1),W=x(),G={IN_PROGRESS:{icon:d,labelSuffix:`pipelineInProgress`,badgeClass:`bg-blue-50 text-blue-700 dark:bg-blue-950/40 dark:text-blue-300`,iconClass:`animate-spin text-blue-500`,dotClass:`bg-blue-400`},APPROVED:{icon:l,labelSuffix:`pipelineApproved`,badgeClass:`bg-emerald-50 text-emerald-700 dark:bg-emerald-950/40 dark:text-emerald-300`,iconClass:`text-emerald-500`,dotClass:`bg-emerald-400`},REJECTED:{icon:o,labelSuffix:`pipelineRejected`,badgeClass:`bg-red-50 text-red-700 dark:bg-red-950/40 dark:text-red-300`,iconClass:`text-red-500`,dotClass:`bg-red-400`}};function K(e){return e>=1e3?`${(e/1e3).toFixed(1)}s`:`${e}ms`}function q({message:e,messageType:t}){let n=(0,H.useMemo)(()=>{if(t!==`json`)return null;try{return JSON.stringify(JSON.parse(e),null,2)}catch{return e}},[e,t]),r=t||`text`;return r===`markdown`?(0,W.jsx)(`div`,{className:`app-markdown prose prose-sm dark:prose-invert max-w-none text-xs`,children:(0,W.jsx)(S,{remarkPlugins:[C],children:e})}):r===`html`?(0,W.jsx)(`div`,{className:`text-xs text-muted-foreground prose prose-sm dark:prose-invert max-w-none`,dangerouslySetInnerHTML:{__html:e}}):r===`json`?(0,W.jsx)(`pre`,{className:`text-xs text-muted-foreground whitespace-pre-wrap break-words font-mono leading-relaxed bg-muted/30 rounded-md p-3 border`,children:n}):(0,W.jsx)(`pre`,{className:`text-xs text-muted-foreground whitespace-pre-wrap break-words font-mono leading-relaxed`,children:e})}function J({open:e,onOpenChange:t,pipelineInfo:r,translationPrefix:a=`skill`,onRefresh:s,refreshing:u}){let{t:d}=y(),f=r.pipeline||[],p=f.findIndex(e=>!e.passed),[m,h]=(0,H.useState)(p>=0?p:0);(0,H.useEffect)(()=>{if(e){let e=f.findIndex(e=>!e.passed);h(e>=0?e:0)}},[e,f.length]);let v=G[r.status],b=v.icon,x=f[m];return(0,W.jsx)(I,{open:e,onOpenChange:t,children:(0,W.jsxs)(N,{className:`max-w-3xl max-h-[85vh] flex flex-col gap-0 p-0`,onOpenAutoFocus:e=>e.preventDefault(),children:[(0,W.jsxs)(j,{className:`px-6 pt-5 pb-4 border-b shrink-0`,children:[(0,W.jsxs)(`div`,{className:`flex items-center gap-3`,children:[(0,W.jsx)(P,{className:`text-base`,children:d(`${a}.pipelineStatus`)}),(0,W.jsxs)(A,{className:w(`text-[11px] px-2 py-0.5 h-5 font-medium border-0 gap-1`,v.badgeClass),children:[(0,W.jsx)(b,{className:w(`h-3 w-3`,v.iconClass)}),d(`${a}.${v.labelSuffix}`)]}),r.status===`IN_PROGRESS`&&s&&(0,W.jsx)(T,{variant:`ghost`,size:`icon`,className:`h-7 w-7 ml-auto`,onClick:s,disabled:u,children:(0,W.jsx)(c,{className:w(`h-3.5 w-3.5`,u&&`animate-spin`)})})]}),(0,W.jsx)(F,{className:`sr-only`,children:d(`${a}.pipelineStatus`)})]}),f.length>0&&(0,W.jsx)(`div`,{className:`px-6 py-4 border-b shrink-0 overflow-x-auto`,children:(0,W.jsx)(`div`,{className:`flex items-center min-w-max`,children:f.map((e,t)=>{let n=t===m,r=t===f.length-1;return(0,W.jsxs)(`div`,{className:`flex items-center`,children:[(0,W.jsx)(O,{delayDuration:200,children:(0,W.jsxs)(k,{children:[(0,W.jsx)(E,{asChild:!0,children:(0,W.jsxs)(`button`,{type:`button`,className:`flex flex-col items-center gap-1.5 group`,onClick:()=>h(t),children:[(0,W.jsx)(`div`,{className:w(`relative flex items-center justify-center rounded-full transition-all`,`w-8 h-8 border-2`,e.passed?`border-emerald-400 bg-emerald-50 dark:bg-emerald-950/30`:`border-red-400 bg-red-50 dark:bg-red-950/30`,n&&`ring-2 ring-offset-2 ring-primary`),children:e.passed?(0,W.jsx)(l,{className:`h-4 w-4 text-emerald-500`}):(0,W.jsx)(o,{className:`h-4 w-4 text-red-500`})}),(0,W.jsx)(`span`,{className:w(`text-[10px] font-mono max-w-[72px] truncate`,n?`font-semibold text-foreground`:`text-muted-foreground`),children:e.nodeId})]})}),(0,W.jsxs)(D,{side:`bottom`,className:`text-xs`,children:[(0,W.jsx)(`p`,{children:e.nodeId}),e.durationMs!=null&&(0,W.jsx)(`p`,{children:K(e.durationMs)})]})]})}),!r&&(0,W.jsx)(`div`,{className:`flex items-center px-1 -mt-5`,children:(0,W.jsx)(g,{className:`h-3 w-6 text-border`})})]},e.nodeId)})})}),x&&(0,W.jsxs)(`div`,{className:`flex-1 min-h-0 flex flex-col overflow-hidden`,children:[(0,W.jsxs)(`div`,{className:`px-6 py-3 border-b bg-muted/20 flex items-center gap-3 flex-wrap shrink-0`,children:[x.passed?(0,W.jsx)(l,{className:`h-4 w-4 shrink-0 text-emerald-500`}):(0,W.jsx)(o,{className:`h-4 w-4 shrink-0 text-red-500`}),(0,W.jsx)(`span`,{className:`text-sm font-medium font-mono`,children:x.nodeId}),x.durationMs!=null&&(0,W.jsx)(A,{variant:`secondary`,className:`text-[10px] h-5`,children:K(x.durationMs)}),x.executedAt&&(0,W.jsxs)(`span`,{className:`inline-flex items-center gap-1 text-[11px] text-muted-foreground ml-auto`,children:[(0,W.jsx)(i,{className:`h-3 w-3`}),(0,U.default)(x.executedAt).format(`YYYY-MM-DD HH:mm:ss`)]})]}),(0,W.jsx)(`div`,{className:`flex-1 min-h-0 overflow-y-auto`,children:(0,W.jsxs)(`div`,{className:`px-6 py-4 space-y-4`,children:[x.checkpoints&&x.checkpoints.length>0&&(0,W.jsxs)(`div`,{className:`space-y-1.5`,children:[(0,W.jsx)(`h4`,{className:`text-[11px] font-semibold text-muted-foreground uppercase tracking-wider`,children:`Checkpoints`}),(0,W.jsx)(`div`,{className:`rounded-lg border divide-y`,children:x.checkpoints.map((e,t)=>(0,W.jsxs)(`div`,{className:`flex items-center gap-2.5 px-3 py-2`,children:[e.passed?(0,W.jsx)(n,{className:`h-3.5 w-3.5 shrink-0 text-emerald-500`}):(0,W.jsx)(_,{className:`h-3.5 w-3.5 shrink-0 text-red-500`}),(0,W.jsx)(`span`,{className:w(`text-xs`,e.passed?`text-muted-foreground`:`text-red-600 dark:text-red-400 font-medium`),children:e.title})]},t))})]}),x.message?(0,W.jsx)(q,{message:x.message,messageType:x.messageType}):(0,W.jsx)(`p`,{className:`text-xs text-muted-foreground/60 italic`,children:d(`${a}.pipelineNoMessage`)})]})})]}),f.length===0&&(0,W.jsx)(`div`,{className:`px-6 py-12 text-center text-sm text-muted-foreground`,children:d(`${a}.pipelineNone`)})]})})}function Y({pipelineInfo:e,compact:t=!1,translationPrefix:n=`skill`,onRefresh:r,refreshing:i}){let{t:a}=y(),[o,s]=(0,H.useState)(!1);if(!e)return t?null:(0,W.jsx)(`p`,{className:`text-xs text-muted-foreground py-2`,children:a(`${n}.pipelineNone`)});let l=G[e.status],d=l.icon;if(t)return(0,W.jsxs)(A,{className:w(`text-xs px-2.5 h-7 font-medium border-0 gap-1.5 rounded-md inline-flex items-center`,l.badgeClass),children:[(0,W.jsx)(d,{className:w(`h-3.5 w-3.5`,l.iconClass)}),a(`${n}.${l.labelSuffix}`)]});let f=e.pipeline||[];return(0,W.jsxs)(W.Fragment,{children:[(0,W.jsxs)(`div`,{className:`space-y-3`,children:[(0,W.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,W.jsx)(d,{className:w(`h-4 w-4`,l.iconClass)}),(0,W.jsx)(`span`,{className:`text-sm font-medium`,children:a(`${n}.${l.labelSuffix}`)}),(0,W.jsx)(`div`,{className:`flex items-center gap-1 ml-auto`,children:e.status===`IN_PROGRESS`&&r&&(0,W.jsx)(T,{variant:`ghost`,size:`icon`,className:`h-6 w-6`,onClick:r,disabled:i,children:(0,W.jsx)(c,{className:w(`h-3.5 w-3.5`,i&&`animate-spin`)})})})]}),f.length>0&&(0,W.jsxs)(`button`,{type:`button`,className:`group w-full flex items-center gap-3 rounded-lg border bg-muted/10 px-3 py-2.5 transition-colors hover:bg-muted/30 cursor-pointer`,onClick:()=>s(!0),children:[(0,W.jsx)(`div`,{className:`flex items-center gap-2 flex-wrap flex-1 min-w-0`,children:f.map((e,t)=>(0,W.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,W.jsxs)(`div`,{className:`flex items-center gap-1.5 min-w-0`,children:[(0,W.jsx)(`div`,{className:w(`w-2.5 h-2.5 rounded-full shrink-0`,e.passed?`bg-emerald-400 dark:bg-emerald-500`:`bg-red-400 dark:bg-red-500`)}),(0,W.jsx)(`span`,{className:w(`text-[11px] font-mono truncate max-w-[120px]`,e.passed?`text-muted-foreground`:`text-red-600 dark:text-red-400 font-medium`),children:e.nodeId})]}),t{if(!e)return;let t=new Set;for(let[e,i]of Object.entries(r))i===n&&t.add(e);h(t),_([]),l(``),d(``)},[e,n,r]);let b=e=>{t(e)},x=(0,H.useMemo)(()=>{let e=new Set(Object.keys(r));for(let t of g)Z(t)||e.add(t);return Array.from(e).sort()},[r,g]),S=(0,H.useMemo)(()=>{let e=c.trim().toLowerCase();return e?x.filter(t=>t.toLowerCase().includes(e)):x},[x,c]),C=(0,H.useMemo)(()=>S.filter(e=>!Z(e)),[S]),w=x.some(e=>e.toLowerCase()===c.trim().toLowerCase()),E=Z(c.trim()),D=c.trim()&&!E&&!w&&Q(c.trim(),[]),O=(e,t)=>{if(Z(e))return;let n=new Set(f);t?n.add(e):n.delete(e),h(n)},k=()=>{let e=c.trim();if(e){if(!Q(e,x)){x.includes(e)?d(a(`common.versionLabels.keyDuplicate`)):d(a(`common.versionLabels.keyInvalid`));return}_(t=>[...t,e]),h(t=>new Set([...t,e])),l(``),d(``)}};return(0,W.jsx)(I,{open:e,onOpenChange:b,children:(0,W.jsxs)(N,{className:`sm:max-w-md`,children:[(0,W.jsxs)(j,{children:[(0,W.jsxs)(P,{className:`flex items-center gap-2`,children:[(0,W.jsx)(p,{className:`h-4 w-4`}),a(`common.versionLabels.labelManagement`)]}),(0,W.jsx)(F,{children:a(`common.versionLabels.labelBindDesc`,{version:n})})]}),(0,W.jsxs)(`div`,{className:`space-y-4`,children:[(0,W.jsxs)(`div`,{className:`relative`,children:[(0,W.jsx)(m,{className:`absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground`}),(0,W.jsx)(L,{value:c,onChange:e=>{l(e.target.value),d(``)},onKeyDown:e=>{e.key===`Enter`&&D&&(e.preventDefault(),k())},placeholder:a(`common.versionLabels.searchOrCreateLabel`),className:`pl-8 text-sm`})]}),u&&(0,W.jsx)(`p`,{className:`text-xs text-destructive`,children:u}),E&&(0,W.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:a(`common.versionLabels.reservedLatest`)}),D&&(0,W.jsxs)(`button`,{type:`button`,className:`flex items-center gap-2 w-full px-3 py-2 text-sm rounded-md hover:bg-muted/60 transition-colors text-primary`,onClick:k,children:[(0,W.jsx)(v,{className:`h-3.5 w-3.5`}),a(`common.versionLabels.createLabel`,{name:c.trim()})]}),S.length>0&&(0,W.jsxs)(`div`,{className:`space-y-1`,children:[C.length>0&&(0,W.jsxs)(`div`,{className:`flex items-center gap-2 px-1 pb-1 text-xs text-muted-foreground`,children:[(0,W.jsx)(`button`,{type:`button`,className:`hover:text-foreground transition-colors`,onClick:()=>{let e=new Set(f);for(let t of C)e.add(t);h(e)},children:a(`common.versionLabels.selectAll`,{count:C.length})}),(0,W.jsx)(`span`,{children:`·`}),(0,W.jsx)(`button`,{type:`button`,className:`hover:text-foreground transition-colors`,onClick:()=>{let e=new Set(f);for(let t of C)e.delete(t);h(e)},children:a(`common.versionLabels.clearAll`)})]}),(0,W.jsx)(`div`,{className:`max-h-[240px] overflow-y-auto space-y-0.5`,children:S.map(e=>{let t=f.has(e),i=r[e],o=i&&i!==n,s=Z(e);return(0,W.jsxs)(`label`,{className:`flex items-center gap-3 px-3 py-2 rounded-md hover:bg-muted/60 transition-colors ${s?`cursor-not-allowed opacity-70`:`cursor-pointer`}`,title:s?a(`common.versionLabels.reservedLatest`):void 0,children:[(0,W.jsx)(B,{checked:t,disabled:s,onCheckedChange:t=>O(e,t===!0)}),(0,W.jsx)(`span`,{className:`flex-1 text-sm font-mono`,children:e}),t?(0,W.jsxs)(A,{variant:`secondary`,className:`text-[10px] px-1.5 py-0 h-4 font-mono`,children:[`→ `,n]}):o?(0,W.jsxs)(`span`,{className:`text-[10px] text-muted-foreground font-mono`,children:[`→ `,i]}):null]},e)})})]}),S.length===0&&!D&&(0,W.jsx)(`p`,{className:`text-sm text-muted-foreground text-center py-4`,children:a(`common.versionLabels.noLabels`)})]}),(0,W.jsx)(M,{children:(0,W.jsx)(T,{onClick:async()=>{s(!0);try{let e={};for(let[t,i]of Object.entries(r))Z(t)||(i===n?f.has(t)&&(e[t]=n):f.has(t)?e[t]=n:e[t]=i);for(let t of g)!Z(t)&&f.has(t)&&!(t in e)&&(e[t]=n);await i(e),t(!1)}finally{s(!1)}},disabled:o,className:`w-full`,children:a(o?`common.loading`:`common.versionLabels.save`)})})]})})}function $({label:e,onRemove:t,className:n}){return(0,W.jsxs)(`span`,{className:w(`inline-flex h-6 items-center gap-1 rounded-lg border border-border/60 bg-muted/70 px-2.5 text-xs font-medium leading-none text-foreground shadow-sm shadow-black/[0.03]`,n),children:[(0,W.jsx)(`span`,{children:e}),t&&(0,W.jsx)(`button`,{type:`button`,className:`inline-flex h-4 w-4 items-center justify-center rounded-sm text-muted-foreground transition-colors hover:bg-background/80 hover:text-foreground`,onClick:t,children:(0,W.jsx)(h,{className:`h-3 w-3`})})]})}function te({open:e,onOpenChange:t,tags:n,placeholder:r,emptyText:i,onSave:a}){let{t:o}=y(),[s,c]=(0,H.useState)([]),[l,u]=(0,H.useState)(``),[d,f]=(0,H.useState)(!1),m=(0,H.useMemo)(()=>Array.from(new Set(s.map(e=>e.trim()).filter(Boolean))),[s]);(0,H.useEffect)(()=>{e&&(c(n),u(``))},[e,n]);let h=e=>{t(e)},g=()=>{let e=l.trim();if(!e||m.includes(e)){u(``);return}c(t=>[...t,e]),u(``)},_=e=>{c(t=>t.filter(t=>t!==e))};return(0,W.jsx)(I,{open:e,onOpenChange:h,children:(0,W.jsxs)(N,{className:`sm:max-w-md`,children:[(0,W.jsxs)(j,{children:[(0,W.jsxs)(P,{className:`flex items-center gap-2`,children:[(0,W.jsx)(p,{className:`h-4 w-4`}),o(`common.bizTagEditor.title`)]}),(0,W.jsx)(F,{children:o(`common.bizTagEditor.description`)})]}),(0,W.jsxs)(`div`,{className:`space-y-4`,children:[(0,W.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,W.jsx)(L,{value:l,onChange:e=>u(e.target.value),onKeyDown:e=>{e.key===`Enter`&&(e.preventDefault(),g())},placeholder:r,className:`h-9`}),(0,W.jsx)(T,{variant:`outline`,size:`icon`,className:`h-9 w-9 shrink-0`,onClick:g,children:(0,W.jsx)(v,{className:`h-3.5 w-3.5`})})]}),m.length>0?(0,W.jsx)(`div`,{className:`flex flex-wrap gap-1.5`,children:m.map(e=>(0,W.jsx)($,{label:e,onRemove:()=>_(e)},e))}):(0,W.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:i})]}),(0,W.jsxs)(M,{children:[(0,W.jsx)(T,{variant:`outline`,onClick:()=>t(!1),disabled:d,children:o(`common.cancel`)}),(0,W.jsx)(T,{onClick:async()=>{f(!0);try{await a(m),t(!1)}finally{f(!1)}},disabled:d,children:o(`common.save`)})]})]})})}function ne({commands:e,className:n,onDownload:i,downloadFileName:a,downloadDisabled:o}){let{t:s}=y(),c=a||s(`common.cliUsage.downloadZip`);return e.length===0&&!i?null:(0,W.jsxs)(z,{className:w(`overflow-hidden py-0 gap-0`,n),children:[(0,W.jsx)(`div`,{className:`px-4 py-3 border-b bg-muted/30`,children:(0,W.jsxs)(`h2`,{className:`text-sm font-semibold flex items-center gap-2`,children:[(0,W.jsx)(t,{className:`h-4 w-4 text-muted-foreground`}),s(`common.cliUsage.title`)]})}),(0,W.jsxs)(R,{className:`p-3.5 space-y-3`,children:[i&&(0,W.jsxs)(W.Fragment,{children:[(0,W.jsx)(`p`,{className:`text-xs font-medium text-foreground`,children:s(`common.cliUsage.manualDownload`)}),(0,W.jsxs)(T,{variant:`outline`,size:`sm`,className:`w-full h-8 min-w-0 overflow-hidden text-xs gap-1.5`,disabled:o,onClick:i,children:[(0,W.jsx)(t,{className:`h-3.5 w-3.5`}),(0,W.jsx)(`span`,{className:`min-w-0 truncate`,title:c,children:c})]})]}),e.length>0&&(0,W.jsxs)(W.Fragment,{children:[i&&(0,W.jsx)(`div`,{className:`border-t`}),(0,W.jsxs)(`p`,{className:`text-xs font-medium text-foreground flex items-center gap-1.5`,children:[s(`common.cliUsage.cliInstall`),(0,W.jsxs)(`a`,{href:`https://github.com/nacos-group/nacos-cli`,target:`_blank`,rel:`noopener noreferrer`,className:`inline-flex items-center gap-0.5 text-[11px] text-muted-foreground hover:text-foreground transition-colors`,children:[s(`common.cliUsage.cliDoc`),(0,W.jsx)(r,{className:`h-3 w-3`})]})]}),e.map((e,t)=>(0,W.jsx)(re,{command:e.command},t))]})]})]})}function re({command:e}){let{t}=y(),[n,r]=(0,H.useState)(!1),i=(0,H.useCallback)(async()=>{try{await navigator.clipboard.writeText(e),r(!0),b.success(t(`common.cliUsage.copied`)),setTimeout(()=>r(!1),2e3)}catch{let n=document.createElement(`textarea`);n.value=e,document.body.appendChild(n),n.select(),document.execCommand(`copy`),document.body.removeChild(n),r(!0),b.success(t(`common.cliUsage.copied`)),setTimeout(()=>r(!1),2e3)}},[e,t]);return(0,W.jsx)(`div`,{children:(0,W.jsxs)(`div`,{className:`group relative rounded-md bg-zinc-950 dark:bg-zinc-900 border border-zinc-800 overflow-hidden`,children:[(0,W.jsxs)(`pre`,{className:`px-3 py-2.5 pr-10 text-[11px] leading-relaxed text-zinc-300 font-mono overflow-x-auto whitespace-pre-wrap break-all`,children:[(0,W.jsx)(`span`,{className:`text-emerald-400 select-none`,children:`$ `}),e]}),(0,W.jsx)(T,{variant:`ghost`,size:`icon`,className:`absolute top-1.5 right-1.5 h-6 w-6 text-zinc-500 hover:text-zinc-200 hover:bg-zinc-800 opacity-0 group-hover:opacity-100 transition-opacity`,onClick:i,children:n?(0,W.jsx)(a,{className:`h-3 w-3 text-emerald-400`}):(0,W.jsx)(s,{className:`h-3 w-3`})})]})})}export{Y as a,ee as i,te as n,$ as r,ne as t}; \ No newline at end of file +import{o as e}from"./rolldown-runtime.js";import{At as t,C as n,Dt as r,Ft as i,Gt as a,Lt as o,Nt as s,P as c,Rt as l,Tt as u,Z as d,en as f,h as p,k as m,n as h,q as g,x as _,z as v}from"./vendor-icons.js";import{v as y}from"./vendor-react.js";import{i as b}from"./client.js";import{a as x,i as S,r as C}from"./vendor-markdown.js";import{n as w,t as T}from"./button.js";import{i as E,n as D,r as O,t as k}from"./tooltip.js";import{t as A}from"./badge.js";import{a as j,i as M,n as N,o as P,r as F,t as I}from"./dialog.js";import{t as L}from"./input.js";import{n as R,t as z}from"./card.js";import{t as B}from"./checkbox.js";import{t as ee}from"./dayjs.min.js";var V=e(f(),1),H=e(ee(),1),U=x(),W={IN_PROGRESS:{icon:d,labelSuffix:`pipelineInProgress`,badgeClass:`bg-blue-50 text-blue-700 dark:bg-blue-950/40 dark:text-blue-300`,iconClass:`animate-spin text-blue-500`,dotClass:`bg-blue-400`},APPROVED:{icon:l,labelSuffix:`pipelineApproved`,badgeClass:`bg-emerald-50 text-emerald-700 dark:bg-emerald-950/40 dark:text-emerald-300`,iconClass:`text-emerald-500`,dotClass:`bg-emerald-400`},REJECTED:{icon:o,labelSuffix:`pipelineRejected`,badgeClass:`bg-red-50 text-red-700 dark:bg-red-950/40 dark:text-red-300`,iconClass:`text-red-500`,dotClass:`bg-red-400`}};function G(e){return e>=1e3?`${(e/1e3).toFixed(1)}s`:`${e}ms`}async function K(e){if(navigator.clipboard&&window.isSecureContext)try{await navigator.clipboard.writeText(e);return}catch{}let t=document.createElement(`textarea`);t.value=e,t.style.position=`absolute`,t.style.left=`-999999px`,document.body.appendChild(t),t.select();try{document.execCommand(`copy`)}finally{document.body.removeChild(t)}}function q({message:e,messageType:t}){let n=(0,V.useMemo)(()=>{if(t!==`json`)return null;try{return JSON.stringify(JSON.parse(e),null,2)}catch{return e}},[e,t]),r=t||`text`;return r===`markdown`?(0,U.jsx)(`div`,{className:`app-markdown prose prose-sm dark:prose-invert max-w-none text-xs`,children:(0,U.jsx)(S,{remarkPlugins:[C],children:e})}):r===`html`?(0,U.jsx)(`div`,{className:`text-xs text-muted-foreground prose prose-sm dark:prose-invert max-w-none`,dangerouslySetInnerHTML:{__html:e}}):r===`json`?(0,U.jsx)(`pre`,{className:`text-xs text-muted-foreground whitespace-pre-wrap break-words font-mono leading-relaxed bg-muted/30 rounded-md p-3 border`,children:n}):(0,U.jsx)(`pre`,{className:`text-xs text-muted-foreground whitespace-pre-wrap break-words font-mono leading-relaxed`,children:e})}function J({open:e,onOpenChange:t,pipelineInfo:r,translationPrefix:u=`skill`,onRefresh:d,refreshing:f}){let{t:p}=y(),m=r.pipeline||[],h=m.findIndex(e=>!e.passed),[v,x]=(0,V.useState)(h>=0?h:0),[S,C]=(0,V.useState)(null);(0,V.useEffect)(()=>{if(e){let e=m.findIndex(e=>!e.passed);x(e>=0?e:0)}},[e,m.length]);let M=W[r.status],L=M.icon,R=m[v],z=async e=>{if(e.message)try{await K(e.message),C(e.nodeId),b.success(p(`${u}.pipelineCopySuccess`)),window.setTimeout(()=>{C(t=>t===e.nodeId?null:t)},2e3)}catch{b.error(p(`${u}.pipelineCopyFailed`))}};return(0,U.jsx)(I,{open:e,onOpenChange:t,children:(0,U.jsxs)(N,{className:`max-w-3xl max-h-[85vh] flex flex-col gap-0 p-0`,onOpenAutoFocus:e=>e.preventDefault(),children:[(0,U.jsxs)(j,{className:`px-6 pt-5 pb-4 border-b shrink-0`,children:[(0,U.jsxs)(`div`,{className:`flex items-center gap-3`,children:[(0,U.jsx)(P,{className:`text-base`,children:p(`${u}.pipelineStatus`)}),(0,U.jsxs)(A,{className:w(`text-[11px] px-2 py-0.5 h-5 font-medium border-0 gap-1`,M.badgeClass),children:[(0,U.jsx)(L,{className:w(`h-3 w-3`,M.iconClass)}),p(`${u}.${M.labelSuffix}`)]}),r.status===`IN_PROGRESS`&&d&&(0,U.jsx)(T,{variant:`ghost`,size:`icon`,className:`h-7 w-7 ml-auto`,onClick:d,disabled:f,children:(0,U.jsx)(c,{className:w(`h-3.5 w-3.5`,f&&`animate-spin`)})})]}),(0,U.jsx)(F,{className:`sr-only`,children:p(`${u}.pipelineStatus`)})]}),m.length>0&&(0,U.jsx)(`div`,{className:`px-6 py-4 border-b shrink-0 overflow-x-auto`,children:(0,U.jsx)(`div`,{className:`flex items-center min-w-max`,children:m.map((e,t)=>{let n=t===v,r=t===m.length-1;return(0,U.jsxs)(`div`,{className:`flex items-center`,children:[(0,U.jsx)(O,{delayDuration:200,children:(0,U.jsxs)(k,{children:[(0,U.jsx)(E,{asChild:!0,children:(0,U.jsxs)(`button`,{type:`button`,className:`flex flex-col items-center gap-1.5 group`,onClick:()=>x(t),children:[(0,U.jsx)(`div`,{className:w(`relative flex items-center justify-center rounded-full transition-all`,`w-8 h-8 border-2`,e.passed?`border-emerald-400 bg-emerald-50 dark:bg-emerald-950/30`:`border-red-400 bg-red-50 dark:bg-red-950/30`,n&&`ring-2 ring-offset-2 ring-primary`),children:e.passed?(0,U.jsx)(l,{className:`h-4 w-4 text-emerald-500`}):(0,U.jsx)(o,{className:`h-4 w-4 text-red-500`})}),(0,U.jsx)(`span`,{className:w(`text-[10px] font-mono max-w-[72px] truncate`,n?`font-semibold text-foreground`:`text-muted-foreground`),children:e.nodeId})]})}),(0,U.jsxs)(D,{side:`bottom`,className:`text-xs`,children:[(0,U.jsx)(`p`,{children:e.nodeId}),e.durationMs!=null&&(0,U.jsx)(`p`,{children:G(e.durationMs)})]})]})}),!r&&(0,U.jsx)(`div`,{className:`flex items-center px-1 -mt-5`,children:(0,U.jsx)(g,{className:`h-3 w-6 text-border`})})]},e.nodeId)})})}),R&&(0,U.jsxs)(`div`,{className:`flex-1 min-h-0 flex flex-col overflow-hidden`,children:[(0,U.jsxs)(`div`,{className:`px-6 py-3 border-b bg-muted/20 flex items-center gap-3 flex-wrap shrink-0`,children:[R.passed?(0,U.jsx)(l,{className:`h-4 w-4 shrink-0 text-emerald-500`}):(0,U.jsx)(o,{className:`h-4 w-4 shrink-0 text-red-500`}),(0,U.jsx)(`span`,{className:`text-sm font-medium font-mono`,children:R.nodeId}),R.durationMs!=null&&(0,U.jsx)(A,{variant:`secondary`,className:`text-[10px] h-5`,children:G(R.durationMs)}),(0,U.jsxs)(`div`,{className:`ml-auto flex items-center gap-2`,children:[R.executedAt&&(0,U.jsxs)(`span`,{className:`inline-flex items-center gap-1 whitespace-nowrap text-[11px] text-muted-foreground`,children:[(0,U.jsx)(i,{className:`h-3 w-3`}),(0,H.default)(R.executedAt).format(`YYYY-MM-DD HH:mm:ss`)]}),R.message&&(0,U.jsxs)(T,{variant:`ghost`,size:`sm`,className:`h-7 gap-1.5 px-2.5 text-xs text-muted-foreground hover:text-foreground`,onClick:()=>z(R),children:[S===R.nodeId?(0,U.jsx)(a,{className:`h-3.5 w-3.5 text-emerald-500`}):(0,U.jsx)(s,{className:`h-3.5 w-3.5`}),S===R.nodeId?p(`${u}.pipelineCopied`):p(`${u}.pipelineCopy`)]})]})]}),(0,U.jsx)(`div`,{className:`flex-1 min-h-0 overflow-y-auto`,children:(0,U.jsxs)(`div`,{className:`px-6 py-4 space-y-4`,children:[R.checkpoints&&R.checkpoints.length>0&&(0,U.jsxs)(`div`,{className:`space-y-1.5`,children:[(0,U.jsx)(`h4`,{className:`text-[11px] font-semibold text-muted-foreground uppercase tracking-wider`,children:`Checkpoints`}),(0,U.jsx)(`div`,{className:`rounded-lg border divide-y`,children:R.checkpoints.map((e,t)=>(0,U.jsxs)(`div`,{className:`flex items-center gap-2.5 px-3 py-2`,children:[e.passed?(0,U.jsx)(n,{className:`h-3.5 w-3.5 shrink-0 text-emerald-500`}):(0,U.jsx)(_,{className:`h-3.5 w-3.5 shrink-0 text-red-500`}),(0,U.jsx)(`span`,{className:w(`text-xs`,e.passed?`text-muted-foreground`:`text-red-600 dark:text-red-400 font-medium`),children:e.title})]},t))})]}),R.message?(0,U.jsx)(q,{message:R.message,messageType:R.messageType}):(0,U.jsx)(`p`,{className:`text-xs text-muted-foreground/60 italic`,children:p(`${u}.pipelineNoMessage`)})]})})]}),m.length===0&&(0,U.jsx)(`div`,{className:`px-6 py-12 text-center text-sm text-muted-foreground`,children:p(`${u}.pipelineNone`)})]})})}function Y({pipelineInfo:e,compact:t=!1,translationPrefix:n=`skill`,onRefresh:r,refreshing:i}){let{t:a}=y(),[o,s]=(0,V.useState)(!1);if(!e)return t?null:(0,U.jsx)(`p`,{className:`text-xs text-muted-foreground py-2`,children:a(`${n}.pipelineNone`)});let l=W[e.status],d=l.icon;if(t)return(0,U.jsxs)(A,{className:w(`text-xs px-2.5 h-7 font-medium border-0 gap-1.5 rounded-md inline-flex items-center`,l.badgeClass),children:[(0,U.jsx)(d,{className:w(`h-3.5 w-3.5`,l.iconClass)}),a(`${n}.${l.labelSuffix}`)]});let f=e.pipeline||[];return(0,U.jsxs)(U.Fragment,{children:[(0,U.jsxs)(`div`,{className:`space-y-3`,children:[(0,U.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,U.jsx)(d,{className:w(`h-4 w-4`,l.iconClass)}),(0,U.jsx)(`span`,{className:`text-sm font-medium`,children:a(`${n}.${l.labelSuffix}`)}),(0,U.jsx)(`div`,{className:`flex items-center gap-1 ml-auto`,children:e.status===`IN_PROGRESS`&&r&&(0,U.jsx)(T,{variant:`ghost`,size:`icon`,className:`h-6 w-6`,onClick:r,disabled:i,children:(0,U.jsx)(c,{className:w(`h-3.5 w-3.5`,i&&`animate-spin`)})})})]}),f.length>0&&(0,U.jsxs)(`button`,{type:`button`,className:`group w-full flex items-center gap-3 rounded-lg border bg-muted/10 px-3 py-2.5 transition-colors hover:bg-muted/30 cursor-pointer`,onClick:()=>s(!0),children:[(0,U.jsx)(`div`,{className:`flex items-center gap-2 flex-wrap flex-1 min-w-0`,children:f.map((e,t)=>(0,U.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,U.jsxs)(`div`,{className:`flex items-center gap-1.5 min-w-0`,children:[(0,U.jsx)(`div`,{className:w(`w-2.5 h-2.5 rounded-full shrink-0`,e.passed?`bg-emerald-400 dark:bg-emerald-500`:`bg-red-400 dark:bg-red-500`)}),(0,U.jsx)(`span`,{className:w(`text-[11px] font-mono truncate max-w-[120px]`,e.passed?`text-muted-foreground`:`text-red-600 dark:text-red-400 font-medium`),children:e.nodeId})]}),t{if(!e)return;let t=new Set;for(let[e,i]of Object.entries(r))i===n&&t.add(e);h(t),_([]),l(``),d(``)},[e,n,r]);let b=e=>{t(e)},x=(0,V.useMemo)(()=>{let e=new Set(Object.keys(r));for(let t of g)Z(t)||e.add(t);return Array.from(e).sort()},[r,g]),S=(0,V.useMemo)(()=>{let e=c.trim().toLowerCase();return e?x.filter(t=>t.toLowerCase().includes(e)):x},[x,c]),C=(0,V.useMemo)(()=>S.filter(e=>!Z(e)),[S]),w=x.some(e=>e.toLowerCase()===c.trim().toLowerCase()),E=Z(c.trim()),D=c.trim()&&!E&&!w&&Q(c.trim(),[]),O=(e,t)=>{if(Z(e))return;let n=new Set(f);t?n.add(e):n.delete(e),h(n)},k=()=>{let e=c.trim();if(e){if(!Q(e,x)){x.includes(e)?d(a(`common.versionLabels.keyDuplicate`)):d(a(`common.versionLabels.keyInvalid`));return}_(t=>[...t,e]),h(t=>new Set([...t,e])),l(``),d(``)}};return(0,U.jsx)(I,{open:e,onOpenChange:b,children:(0,U.jsxs)(N,{className:`sm:max-w-md`,children:[(0,U.jsxs)(j,{children:[(0,U.jsxs)(P,{className:`flex items-center gap-2`,children:[(0,U.jsx)(p,{className:`h-4 w-4`}),a(`common.versionLabels.labelManagement`)]}),(0,U.jsx)(F,{children:a(`common.versionLabels.labelBindDesc`,{version:n})})]}),(0,U.jsxs)(`div`,{className:`space-y-4`,children:[(0,U.jsxs)(`div`,{className:`relative`,children:[(0,U.jsx)(m,{className:`absolute left-2.5 top-1/2 -translate-y-1/2 h-3.5 w-3.5 text-muted-foreground`}),(0,U.jsx)(L,{value:c,onChange:e=>{l(e.target.value),d(``)},onKeyDown:e=>{e.key===`Enter`&&D&&(e.preventDefault(),k())},placeholder:a(`common.versionLabels.searchOrCreateLabel`),className:`pl-8 text-sm`})]}),u&&(0,U.jsx)(`p`,{className:`text-xs text-destructive`,children:u}),E&&(0,U.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:a(`common.versionLabels.reservedLatest`)}),D&&(0,U.jsxs)(`button`,{type:`button`,className:`flex items-center gap-2 w-full px-3 py-2 text-sm rounded-md hover:bg-muted/60 transition-colors text-primary`,onClick:k,children:[(0,U.jsx)(v,{className:`h-3.5 w-3.5`}),a(`common.versionLabels.createLabel`,{name:c.trim()})]}),S.length>0&&(0,U.jsxs)(`div`,{className:`space-y-1`,children:[C.length>0&&(0,U.jsxs)(`div`,{className:`flex items-center gap-2 px-1 pb-1 text-xs text-muted-foreground`,children:[(0,U.jsx)(`button`,{type:`button`,className:`hover:text-foreground transition-colors`,onClick:()=>{let e=new Set(f);for(let t of C)e.add(t);h(e)},children:a(`common.versionLabels.selectAll`,{count:C.length})}),(0,U.jsx)(`span`,{children:`·`}),(0,U.jsx)(`button`,{type:`button`,className:`hover:text-foreground transition-colors`,onClick:()=>{let e=new Set(f);for(let t of C)e.delete(t);h(e)},children:a(`common.versionLabels.clearAll`)})]}),(0,U.jsx)(`div`,{className:`max-h-[240px] overflow-y-auto space-y-0.5`,children:S.map(e=>{let t=f.has(e),i=r[e],o=i&&i!==n,s=Z(e);return(0,U.jsxs)(`label`,{className:`flex items-center gap-3 px-3 py-2 rounded-md hover:bg-muted/60 transition-colors ${s?`cursor-not-allowed opacity-70`:`cursor-pointer`}`,title:s?a(`common.versionLabels.reservedLatest`):void 0,children:[(0,U.jsx)(B,{checked:t,disabled:s,onCheckedChange:t=>O(e,t===!0)}),(0,U.jsx)(`span`,{className:`flex-1 text-sm font-mono`,children:e}),t?(0,U.jsxs)(A,{variant:`secondary`,className:`text-[10px] px-1.5 py-0 h-4 font-mono`,children:[`→ `,n]}):o?(0,U.jsxs)(`span`,{className:`text-[10px] text-muted-foreground font-mono`,children:[`→ `,i]}):null]},e)})})]}),S.length===0&&!D&&(0,U.jsx)(`p`,{className:`text-sm text-muted-foreground text-center py-4`,children:a(`common.versionLabels.noLabels`)})]}),(0,U.jsx)(M,{children:(0,U.jsx)(T,{onClick:async()=>{s(!0);try{let e={};for(let[t,i]of Object.entries(r))Z(t)||(i===n?f.has(t)&&(e[t]=n):f.has(t)?e[t]=n:e[t]=i);for(let t of g)!Z(t)&&f.has(t)&&!(t in e)&&(e[t]=n);await i(e),t(!1)}finally{s(!1)}},disabled:o,className:`w-full`,children:a(o?`common.loading`:`common.versionLabels.save`)})})]})})}function $({label:e,onRemove:t,className:n}){return(0,U.jsxs)(`span`,{className:w(`inline-flex h-6 items-center gap-1 rounded-lg border border-border/60 bg-muted/70 px-2.5 text-xs font-medium leading-none text-foreground shadow-sm shadow-black/[0.03]`,n),children:[(0,U.jsx)(`span`,{children:e}),t&&(0,U.jsx)(`button`,{type:`button`,className:`inline-flex h-4 w-4 items-center justify-center rounded-sm text-muted-foreground transition-colors hover:bg-background/80 hover:text-foreground`,onClick:t,children:(0,U.jsx)(h,{className:`h-3 w-3`})})]})}function ne({open:e,onOpenChange:t,tags:n,placeholder:r,emptyText:i,onSave:a}){let{t:o}=y(),[s,c]=(0,V.useState)([]),[l,u]=(0,V.useState)(``),[d,f]=(0,V.useState)(!1),m=(0,V.useMemo)(()=>Array.from(new Set(s.map(e=>e.trim()).filter(Boolean))),[s]);(0,V.useEffect)(()=>{e&&(c(n),u(``))},[e,n]);let h=e=>{t(e)},g=()=>{let e=l.trim();if(!e||m.includes(e)){u(``);return}c(t=>[...t,e]),u(``)},_=e=>{c(t=>t.filter(t=>t!==e))};return(0,U.jsx)(I,{open:e,onOpenChange:h,children:(0,U.jsxs)(N,{className:`sm:max-w-md`,children:[(0,U.jsxs)(j,{children:[(0,U.jsxs)(P,{className:`flex items-center gap-2`,children:[(0,U.jsx)(p,{className:`h-4 w-4`}),o(`common.bizTagEditor.title`)]}),(0,U.jsx)(F,{children:o(`common.bizTagEditor.description`)})]}),(0,U.jsxs)(`div`,{className:`space-y-4`,children:[(0,U.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,U.jsx)(L,{value:l,onChange:e=>u(e.target.value),onKeyDown:e=>{e.key===`Enter`&&(e.preventDefault(),g())},placeholder:r,className:`h-9`}),(0,U.jsx)(T,{variant:`outline`,size:`icon`,className:`h-9 w-9 shrink-0`,onClick:g,children:(0,U.jsx)(v,{className:`h-3.5 w-3.5`})})]}),m.length>0?(0,U.jsx)(`div`,{className:`flex flex-wrap gap-1.5`,children:m.map(e=>(0,U.jsx)($,{label:e,onRemove:()=>_(e)},e))}):(0,U.jsx)(`p`,{className:`text-xs text-muted-foreground`,children:i})]}),(0,U.jsxs)(M,{children:[(0,U.jsx)(T,{variant:`outline`,onClick:()=>t(!1),disabled:d,children:o(`common.cancel`)}),(0,U.jsx)(T,{onClick:async()=>{f(!0);try{await a(m),t(!1)}finally{f(!1)}},disabled:d,children:o(`common.save`)})]})]})})}function re({commands:e,className:n,onDownload:i,downloadFileName:a,downloadDisabled:o}){let{t:s}=y(),c=a||s(`common.cliUsage.downloadZip`);return e.length===0&&!i?null:(0,U.jsxs)(z,{className:w(`overflow-hidden py-0 gap-0`,n),children:[(0,U.jsx)(`div`,{className:`px-4 py-3 border-b bg-muted/30`,children:(0,U.jsxs)(`h2`,{className:`text-sm font-semibold flex items-center gap-2`,children:[(0,U.jsx)(t,{className:`h-4 w-4 text-muted-foreground`}),s(`common.cliUsage.title`)]})}),(0,U.jsxs)(R,{className:`p-3.5 space-y-3`,children:[i&&(0,U.jsxs)(U.Fragment,{children:[(0,U.jsx)(`p`,{className:`text-xs font-medium text-foreground`,children:s(`common.cliUsage.manualDownload`)}),(0,U.jsxs)(T,{variant:`outline`,size:`sm`,className:`w-full h-8 min-w-0 overflow-hidden text-xs gap-1.5`,disabled:o,onClick:i,children:[(0,U.jsx)(t,{className:`h-3.5 w-3.5`}),(0,U.jsx)(`span`,{className:`min-w-0 truncate`,title:c,children:c})]})]}),e.length>0&&(0,U.jsxs)(U.Fragment,{children:[i&&(0,U.jsx)(`div`,{className:`border-t`}),(0,U.jsxs)(`p`,{className:`text-xs font-medium text-foreground flex items-center gap-1.5`,children:[s(`common.cliUsage.cliInstall`),(0,U.jsxs)(`a`,{href:`https://github.com/nacos-group/nacos-cli`,target:`_blank`,rel:`noopener noreferrer`,className:`inline-flex items-center gap-0.5 text-[11px] text-muted-foreground hover:text-foreground transition-colors`,children:[s(`common.cliUsage.cliDoc`),(0,U.jsx)(r,{className:`h-3 w-3`})]})]}),e.map((e,t)=>(0,U.jsx)(ie,{command:e.command},t))]})]})]})}function ie({command:e}){let{t}=y(),[n,r]=(0,V.useState)(!1),i=(0,V.useCallback)(async()=>{try{await navigator.clipboard.writeText(e),r(!0),b.success(t(`common.cliUsage.copied`)),setTimeout(()=>r(!1),2e3)}catch{let n=document.createElement(`textarea`);n.value=e,document.body.appendChild(n),n.select(),document.execCommand(`copy`),document.body.removeChild(n),r(!0),b.success(t(`common.cliUsage.copied`)),setTimeout(()=>r(!1),2e3)}},[e,t]);return(0,U.jsx)(`div`,{children:(0,U.jsxs)(`div`,{className:`group relative rounded-md bg-zinc-950 dark:bg-zinc-900 border border-zinc-800 overflow-hidden`,children:[(0,U.jsxs)(`pre`,{className:`px-3 py-2.5 pr-10 text-[11px] leading-relaxed text-zinc-300 font-mono overflow-x-auto whitespace-pre-wrap break-all`,children:[(0,U.jsx)(`span`,{className:`text-emerald-400 select-none`,children:`$ `}),e]}),(0,U.jsx)(T,{variant:`ghost`,size:`icon`,className:`absolute top-1.5 right-1.5 h-6 w-6 text-zinc-500 hover:text-zinc-200 hover:bg-zinc-800 opacity-0 group-hover:opacity-100 transition-opacity`,onClick:i,children:n?(0,U.jsx)(a,{className:`h-3 w-3 text-emerald-400`}):(0,U.jsx)(s,{className:`h-3 w-3`})})]})})}export{Y as a,te as i,ne as n,$ as r,re as t}; \ No newline at end of file diff --git a/console/src/main/resources/static/next/js/client.js b/console/src/main/resources/static/next/js/client.js index 6d3337fd3f4..8bdda0996f4 100644 --- a/console/src/main/resources/static/next/js/client.js +++ b/console/src/main/resources/static/next/js/client.js @@ -1,4 +1,4 @@ -import{o as e,r as t,t as n}from"./rolldown-runtime.js";import{en as r}from"./vendor-icons.js";import{C as i,b as a,x as o}from"./vendor-react.js";var s=e(r(),1),c=e(i(),1),l=`docsite_language`,u=`nacos_theme`,d=`nacos_sidebar_collapsed`,f=`token`;function p(){let e=localStorage.getItem(l);return e===`zh-CN`||e===`en-US`?e:(navigator.language||navigator.userLanguage)?.startsWith(`zh`)?`zh-CN`:`en-US`}function m(e){localStorage.setItem(l,e)}function h(){let e=localStorage.getItem(u);return e===`light`||e===`dark`?e:`light`}function g(e){localStorage.setItem(u,e)}function _(){return localStorage.getItem(d)===`true`}function v(e){localStorage.setItem(d,String(e))}function y(){try{let e=localStorage.getItem(f);if(e)return JSON.parse(e)}catch{}return null}var b={"zh-CN":{translation:{header:{home:`首页`,docs:`文档`,blog:`博客`,community:`社区`,enterprise:`Nacos企业版`,mcp:`MCP市场`,languageSwitch:`En`,logout:`登出`,changePassword:`修改密码`},login:{login:`登录`,initPassword:`初始化密码`,internalSysTip1:`内部系统,不可暴露到公网`,internalSysTip3:"初始化管理员用户`nacos`的密码",internalSysTip4:`建议使用高强度密码并妥善保管`,submit:`提交`,pleaseInputUsername:`请输入用户名`,pleaseInputPassword:`请输入密码`,pleaseInputPasswordTips:`请输入密码(若密码为空,将生成随机密码)`,invalidUsernameOrPassword:`用户名或密码错误`,passwordRequired:`密码不能为空`,usernameRequired:`用户名不能为空`,productDesc:`一个易于构建 AI Agent 应用的动态服务发现、配置管理和 AI 智能体管理平台`,consoleClosed:`控制台已关闭`,consoleClosedDesc:`Nacos 控制台已关闭。`,signInWithSSO:`使用 SSO 登录`,ssoLoginTip:`您的管理员已启用单点登录。点击下方按钮跳转到身份提供商完成登录。`,oidcRedirectFailed:`跳转到 SSO 登录页失败,请联系管理员。`,oidcAuthFailed:`SSO 认证失败`},menu:{configManagement:`配置管理`,configList:`配置列表`,historyRollback:`历史版本`,listeningQuery:`监听查询`,serviceManagement:`服务管理`,serviceList:`服务列表`,subscriberList:`订阅者列表`,aiRegistry:`AI 注册中心`,mcpRegistry:`MCP 管理`,agentRegistry:`Agent 管理`,skillRegistry:`Skill 管理`,promptRegistry:`Prompt 管理`,agentSpecRegistry:`AgentSpec 管理`,platformManagement:`平台管理`,namespace:`命名空间`,clusterManagement:`集群管理`,clusterNodeList:`节点列表`,pluginManagement:`插件管理`,authorityControl:`权限控制`,userList:`用户列表`,roleManagement:`角色管理`,privilegeManagement:`权限管理`,settingCenter:`设置中心`},common:{confirm:`确认`,cancel:`取消`,retry:`重试`,delete:`删除`,edit:`编辑`,create:`新建`,search:`搜索`,reset:`重置`,loading:`加载中...`,noData:`暂无数据`,operation:`操作`,detail:`详情`,save:`保存`,bizTags:`业务标签`,from:`来源`,add:`添加`,optional:`可选`,back:`返回`,success:`操作成功`,failed:`操作失败`,legacyConsole:`旧版控制台`,switchToLegacy:`切换到旧版控制台`,collapse:`收起侧栏`,expand:`展开侧栏`,mode:`模式`,version:`版本`,pageSize:`每页条数`,selectNamespace:`命名空间`,sessionExpired:`会话已过期,请重新登录`,noPermission:`无权限执行此操作`,requestFailed:`请求失败`,versionLabels:{title:`版本标签`,noLabels:`暂无版本标签`,keyPlaceholder:`标签名`,valuePlaceholder:`版本号`,keyRequired:`标签键不能为空`,keyDuplicate:`标签键已存在`,keyInvalid:`标签键格式不合法(仅支持字母、数字、. _ -)`,reservedLatest:`latest 由服务端维护,不支持手动设置或取消`,save:`保存版本标签`,updateSuccess:`版本标签更新成功`,editLabels:`编辑版本标签`,labelManagement:`版本标签`,labelBindDesc:`选择要绑定到版本 {{version}} 的标签,或创建新标签。`,searchOrCreateLabel:`搜索或创建标签...`,createLabel:`创建标签「{{name}}」`,selectAll:`全选 {{count}} 个`,clearAll:`清除`},bizTagEditor:{title:`业务标签`,description:`编辑业务标签列表。`},cliUsage:{title:`安装`,cliInstall:`CLI 安装`,cliDoc:`CLI 使用文档`,downloadFile:`下载文件`,manualDownload:`手动下载`,downloadZip:`下载 .zip 文件`,description:`通过 Nacos CLI 在本地获取当前资源,需先安装 Node.js。`,copied:`命令已复制`,latest:`获取最新版本`,byVersion:`指定版本`,byLabel:`指定标签`}},config:{title:`配置列表`,newConfig:`新建配置`,editConfig:`编辑配置`,configDetail:`配置详情`,dataId:`Data ID`,group:`Group`,content:`配置内容`,type:`配置格式`,tags:`标签`,appName:`归属应用`,description:`描述`,md5:`MD5`,createTime:`创建时间`,modifyTime:`最后更新时间`,fuzzySearch:`模糊搜索`,exactSearch:`精确搜索`,contentSearch:`内容搜索`,advancedSearch:`高级搜索`,publishSuccess:`配置发布成功`,publishFailed:`配置发布失败`,publishConfirmTitle:`确认发布`,publishConfirmDescription:`发布前请确认本次配置内容变更。`,currentPublishedContent:`当前已发布内容`,pendingPublishContent:`待发布内容`,notFoundInNamespace:`当前命名空间下不存在该配置,已返回配置列表。`,unsavedNamespaceSwitchConfirm:`当前配置有未发布的修改,切换命名空间将丢弃这些修改,是否继续?`,deleteConfirm:`确定要删除该配置吗?`,deleteSuccess:`配置删除成功`,dataIdRequired:`Data ID 不能为空`,groupRequired:`Group 不能为空`,contentRequired:`配置内容不能为空`,maxTags:`最多只能添加 5 个标签`,noSpecialChars:`不允许输入特殊字符`,configExists:`该配置已存在`,totalConfigs:`共 {{total}} 条配置`,publish:`发布`,history:`历史版本`,listeners:`监听查询`,rollback:`回滚`,clone:`克隆`,export:`导出`,import:`导入`,backToList:`返回列表`,sampleCode:`示例代码`,formatContent:`格式化`,fullscreen:`全屏编辑`,batchDelete:`批量删除`,batchDeleteConfirm:`确定要删除选中的 {{count}} 个配置吗?`,exportAll:`导出全部`,exportSelected:`导出选中`,importTitle:`导入配置`,importSuccess:`导入成功`,importFailed:`导入失败`,cloneTitle:`克隆配置`,sourceNamespace:`源命名空间`,targetNamespace:`目标命名空间`,conflictPolicy:`冲突处理策略`,policyAbort:`中止导入`,policySkip:`跳过已有`,policyOverwrite:`覆盖已有`,selectedCount:`已选择 {{count}} 项`,selectFile:`选择文件`,noFileSelected:`请选择 ZIP 文件`,noTargetNamespace:`请选择目标命名空间`,noSelection:`请先选择配置`,startClone:`开始克隆`,cloneSuccess:`克隆成功`,configCount:`配置数量`,uploadZip:`上传 ZIP 文件`,batchDeleteSuccess:`批量删除成功`,importResult:`成功 {{success}} 条,跳过 {{skip}} 条`,betaPublish:`Beta 发布`,production:`正式`,beta:`Beta`,betaIps:`Beta IP 列表`,betaIpsPlaceholder:`输入 IP 地址,多个用逗号分隔`,stopBeta:`停止 Beta`,stopBetaConfirm:`确定要停止 Beta 发布吗?`,betaPublishSuccess:`Beta 发布成功`,betaStopSuccess:`Beta 已停止`,noBeta:`当前无 Beta 配置`},listener:{title:`监听查询`,queryByConfig:`按配置查询`,queryByIp:`按 IP 查询`,ip:`IP 地址`,md5:`MD5`,noListeners:`暂无监听者`,totalListeners:`共 {{total}} 个监听者`},history:{title:`历史版本`,detailTitle:`历史详情`,rollbackTitle:`配置回滚`,publishType:`发布类型`,formal:`正式`,gray:`灰度`,grayRule:`灰度规则`,operator:`操作人`,sourceIp:`来源IP`,opType:`操作类型`,opInsert:`插入`,opUpdate:`更新`,opDelete:`删除`,compare:`对比`,rollback:`回滚`,rollbackConfirm:`确定要回滚到此版本吗?`,rollbackInsertWarn:`回滚将删除此配置`,rollbackUpdateWarn:`回滚将恢复到此版本内容`,rollbackDeleteWarn:`回滚将重新发布此配置`,rollbackSuccess:`回滚成功`,rollbackFailed:`回滚失败`,selectedVersion:`选中版本`,currentVersion:`当前版本`,totalHistory:`共 {{total}} 条历史记录`},service:{title:`服务列表`,createService:`创建服务`,editService:`编辑服务`,serviceDetail:`服务详情`,serviceName:`服务名`,groupName:`分组名`,clusterCount:`集群数`,ipCount:`IP 数`,healthyInstanceCount:`健康实例数`,triggerFlag:`触发保护阈值`,ignoreEmptyService:`隐藏空服务`,protectThreshold:`保护阈值`,metadata:`元数据`,selectorType:`选择器类型`,selectorExpression:`选择器表达式`,ephemeral:`临时服务`,deleteConfirm:`确定要删除服务 {{name}} 吗?`,deleteSuccess:`服务删除成功`,deleteHasInstances:`服务下存在实例,无法删除。请先注销所有实例后再试。`,createSuccess:`服务创建成功`,updateSuccess:`服务更新成功`,namespaceSwitchConfirm:`当前有未完成的服务操作,切换命名空间将关闭该操作,是否继续?`,totalServices:`共 {{total}} 个服务`,clusterName:`集群名称`,editCluster:`编辑集群`,checkType:`健康检查类型`,checkPort:`检查端口`,checkPath:`检查路径`,checkHeaders:`检查请求头`,useInstancePort:`使用实例端口`,clusterUpdateSuccess:`集群更新成功`,ip:`IP`,port:`端口`,weight:`权重`,healthy:`健康`,enabled:`启用`,instanceEphemeral:`临时`,editInstance:`编辑实例`,deleteInstance:`删除实例`,instanceUpdateSuccess:`实例更新成功`,instanceDeleteSuccess:`实例删除成功`,instanceDeleteConfirm:`确定要删除实例 {{ip}}:{{port}} 吗?`,online:`上线`,offline:`下线`,noInstances:`该集群暂无实例`,subscriberTitle:`订阅者列表`,subscriberName:`订阅者`,subscribeCount:`订阅数`,clusters:`集群`,totalSubscribers:`共 {{total}} 个订阅者`,metadataInvalid:`元数据格式不正确,请输入有效的 JSON`,serviceNameRequired:`服务名不能为空`},mcp:{title:`MCP Server 管理`,createServer:`创建 MCP Server`,editServer:`编辑 MCP Server`,serverDetail:`MCP Server 详情`,serverName:`服务名称`,protocol:`协议类型`,frontProtocol:`前端协议`,description:`描述`,repository:`代码仓库`,websiteUrl:`官网链接`,version:`版本`,createTime:`创建时间`,author:`作者`,status:`状态`,enabled:`已启用`,disabled:`已禁用`,enable:`启用`,disable:`禁用`,capabilities:`能力列表`,tools:`工具列表`,toolName:`工具名`,toolDescription:`工具描述`,parameters:`参数`,paramName:`参数名`,paramType:`类型`,paramRequired:`必填`,paramDescription:`描述`,packages:`包信息`,endpoints:`端点信息`,frontendEndpoints:`前端端点`,backendEndpoints:`后端端点`,serverSpecification:`服务规格`,toolSpecification:`工具规格`,endpointSpecification:`端点规格`,localServerConfig:`本地服务配置`,remoteServerConfig:`远程服务配置`,serviceRef:`服务引用`,exportPath:`导出路径`,securitySchemes:`安全方案`,addSecurityScheme:`添加安全方案`,overrideExisting:`覆盖已有版本`,setAsLatest:`设为最新版本`,copyConfig:`复制配置`,copySuccess:`配置已复制到剪贴板`,newVersion:`新建版本`,importFromRegistry:`从 MCP 市场导入`,importSource:`导入来源`,importUrl:`MCP 服务地址`,importType:`导入类型`,importTypeJson:`JSON`,importTypeUrl:`URL`,importTypeFile:`文件`,importSearchPlaceholder:`搜索 MCP Server 名称`,importValidate:`验证`,importExecute:`执行导入`,importSuccess:`导入成功`,importFailed:`导入失败`,importResult:`导入结果:成功 {{success}} 个,失败 {{failed}} 个,跳过 {{skipped}} 个`,importSkipInvalid:`跳过无效项`,importOverride:`覆盖已有`,importConflictPolicy:`冲突策略`,importConflictSkip:`跳过`,importConflictOverwrite:`覆盖`,importAll:`导入全部有效项`,importSelectedCount:`已选择 {{count}} 项`,importValidatedCount:`已校验有效 {{count}} 项`,importSelectAll:`全选`,importClearSelection:`清空选择`,importLoadMore:`加载更多`,noImportSource:`暂无可用导入来源`,importStatusValid:`有效`,importStatusWarning:`警告`,importStatusConflict:`冲突`,importStatusInvalid:`无效`,importUnnamed:`未命名`,importMetadata:{protocol:`协议`,status:`状态`},selectedServers:`已选服务`,importTools:`导入工具`,importToolsFromUrl:`从端点导入工具`,searchPlaceholder:`搜索 MCP Server 名称`,deleteConfirm:`确定要删除 MCP Server「{{name}}」吗?`,batchDeleteConfirm:`确定要删除选中的 {{count}} 个 MCP Server 吗?`,deleteSuccess:`删除成功`,batchDelete:`批量删除`,batchDeleteSuccess:`批量删除成功`,enableSuccess:`已启用`,disableSuccess:`已禁用`,createSuccess:`MCP Server 创建成功`,updateSuccess:`MCP Server 更新成功`,totalServers:`共 {{total}} 个 MCP Server`,noTools:`暂无工具`,noPackages:`暂无包信息`,noEndpoints:`暂无端点信息`,serverNameRequired:`服务名称不能为空`,serverSpecRequired:`服务规格不能为空`,invalidJson:`JSON 格式不正确`,formatJson:`格式化 JSON`,advancedConfig:`高级配置`,statusActive:`正常`,statusDeprecated:`已废弃`,statusDeleted:`已删除`,protocolStdio:`Stdio`,protocolSse:`MCP-SSE`,protocolStreamable:`MCP-Streamable`,protocolHttp:`HTTP`,protocolDubbo:`Dubbo`,useExistService:`使用已有服务`,directConnect:`直连新服务`,httpToMcp:`HTTP 转 MCP`,mcpServerEndpoint:`MCP Server 端点 URL`,address:`地址`,port:`端口`,transportProtocol:`传输协议`,selectService:`选择服务`,selectVersion:`选择版本`,inputSchema:`输入参数`,outputSchema:`输出参数`,noDescription:`暂无描述`,allVersions:`所有版本`,latestVersion:`最新`,versionHistory:`版本历史`,totalVersions:`共 {{count}} 个版本`,currentVersion:`当前版本`,toolCount:`{{count}} 个工具`,expandAll:`展开全部`,collapseAll:`收起全部`,backToList:`返回列表`,basicInfo:`基本信息`,noSecuritySchemes:`暂无安全方案`,noCapabilities:`暂无能力`,securityType:`类型`,securitySchemeField:`Scheme`,securityIn:`参数位置`,securityDefaultCredential:`默认凭证`,packageIdentifier:`包标识`,packageRuntime:`运行时`,packageVersion:`包版本`,runtimeArguments:`运行时参数`,packageArguments:`包参数`,environmentVariables:`环境变量`,noEnvironmentVariables:`无环境变量`,envVarName:`变量名`,envVarDefault:`默认值`,envVarRequired:`必需`,envVarSecret:`敏感`,endpointProtocol:`协议`,endpointAddress:`地址`,endpointPort:`端口`,endpointPath:`路径`,endpointHeaders:`请求头`,noHeaders:`无请求头`,serviceName:`服务名`,groupName:`分组名`,loadFailed:`数据加载失败`,retry:`重试`,restToMcpSwitch:`HTTP 转 MCP`,restToMcpSwitchTip:`开启后可将已有 HTTP 服务转为 MCP 协议`,useExistServiceOption:`使用已有服务`,directConnectOption:`直连新服务`,mcpEndpointUrl:`MCP Server 端点 URL`,mcpEndpointUrlPlaceholder:`例如: http://127.0.0.1:8080/sse`,addressPlaceholder:`例如: 127.0.0.1`,portPlaceholder:`例如: 8080`,localServerConfigTip:`输入 MCP 本地服务的 JSON 配置`,versionPlaceholder:`例如: 1.0.0`,descriptionPlaceholder:`描述该 MCP Server 的功能`,securitySchemeId:`方案 ID`,securitySchemeType:`认证类型`,schemeBasic:`Basic`,schemeBearer:`Bearer`,inHeader:`Header`,inQuery:`Query`,defaultDownstreamSecurity:`默认下游安全认证`,defaultDownstreamSecurityDesc:`适用于客户端到网关的请求(如 tools/list),当没有工具级别覆盖时生效。`,defaultUpstreamSecurity:`默认上游安全认证`,defaultUpstreamSecurityDesc:`适用于网关到后端服务的调用(如默认的 requestTemplate.security)。`,passthroughAuth:`透传认证`,credentialOverride:`凭证覆盖`,addRow:`添加`,removeRow:`移除`,publishAndCreate:`创建并发布`,publishAndUpdate:`更新并发布`,saving:`保存中...`,protocolRequired:`请选择协议类型`,versionRequired:`版本号不能为空`,addressRequired:`地址不能为空`,portRequired:`端口不能为空`,mcpEndpointRequired:`MCP 端点 URL 不能为空`,localConfigRequired:`本地服务配置不能为空`,toolManagement:`工具管理`,noToolsYet:`暂无工具,请添加或导入`,newTool:`新建工具`,editTool:`编辑工具`,deleteTool:`删除工具`,deleteToolConfirm:`确定要删除工具「{{name}}」吗?`,toolNamePlaceholder:`例如: get_user_info`,toolNameRequired:`工具名称不能为空`,toolNameExists:`工具名称已存在`,toolEnabled:`启用状态`,searchTools:`搜索工具...`,importFromMcpInstance:`从 MCP 实例导入`,importFromOpenApi:`从 OpenAPI 导入`,importToolsFromMCPTitle:`从 MCP 实例导入工具`,importToolsFromOpenAPITitle:`从 OpenAPI 文件导入工具`,dragOrClickToUpload:`拖拽文件到此处或点击选择`,supportedFormats:`支持 .json, .yaml, .yml 格式`,fileReadFailed:`文件读取失败`,fileInvalidFormat:`文件格式无效`,pleaseSelectFile:`请选择文件`,noHealthyInstance:`未找到健康实例`,importingTools:`导入中...`,importToolsSuccess:`工具导入成功,共 {{count}} 个`,importToolsFailed:`导入工具失败`,authToken:`认证令牌`,authTokenPlaceholder:`可选,用于访问需要认证的 MCP 服务`,serverAddress:`服务地址`,toolBasicInfo:`基本信息`,toolInputSchema:`入参配置`,toolOutputSchema:`出参配置`,toolMeta:`Meta`,toolAnnotations:`Annotations`,toolAdvancedConfig:`高级配置`,requestTemplate:`请求模板`,responseTemplate:`响应模板`,addParameter:`添加参数`,addProperty:`添加属性`,deleteParam:`删除参数`,deleteParamConfirm:`确定要删除此参数及其子属性吗?`,typeString:`字符串`,typeNumber:`数字`,typeInteger:`整数`,typeBoolean:`布尔值`,typeArray:`数组`,typeObject:`对象`,annotationsTitle:`标题`,readOnlyHint:`只读提示`,readOnlyHintDesc:`此工具是否只执行读取操作`,destructiveHint:`破坏性提示`,destructiveHintDesc:`此工具是否可能执行破坏性操作`,idempotentHint:`幂等提示`,idempotentHintDesc:`此工具是否是幂等的`,openWorldHint:`开放世界提示`,openWorldHintDesc:`此工具是否可能与外部实体交互`,foundOperations:`发现 {{count}} 个 API 操作`,foundSecuritySchemes:`发现 {{count}} 个安全方案`,createTool:`创建工具`,previewTool:`预览工具`,toolParams:`{{count}} 个参数`,noParameters:`暂无参数`,selectToolToView:`请选择一个工具查看详情`},agent:{title:`Agent 管理`,createAgent:`创建 Agent`,editAgent:`编辑 Agent`,agentDetail:`Agent 详情`,agentName:`Agent 名称`,version:`版本号`,protocolVersion:`协议版本`,serviceUrl:`服务地址`,transport:`传输协议`,selectTransport:`请选择传输协议`,description:`描述`,descriptionPlaceholder:`请输入 Agent 的功能描述...`,noDescription:`暂无描述`,provider:`提供商`,providerName:`提供商名称`,providerNamePlaceholder:`请输入提供商名称`,providerUrl:`提供商 URL`,iconUrl:`图标 URL`,documentationUrl:`文档 URL`,documentation:`文档`,capabilities:`能力配置`,streaming:`流式传输`,streamingDesc:`是否支持流式数据传输`,pushNotifications:`推送通知`,pushNotificationsDesc:`是否支持推送通知功能`,stateHistory:`状态历史`,stateHistoryDesc:`是否支持记录状态转换历史`,skills:`技能列表`,skillsHelp:`Agent 具备的技能清单,JSON 数组格式`,skillCount:`{{count}} 个技能`,noSkills:`暂无技能配置`,inputModes:`输入模式`,outputModes:`输出模式`,ioModes:`输入/输出模式`,security:`安全配置`,securitySchemes:`安全方案`,supportedInterfaces:`支持接口`,additionalInterfaces:`附加接口`,interfaceItem:`接口 {{index}}`,preferredInterface:`首选项`,tenant:`租户`,extendedCardSupport:`扩展卡片支持`,extendedCardSupportDesc:`是否支持认证扩展卡片功能`,advancedConfig:`高级配置`,showAdvanced:`展开高级配置`,hideAdvanced:`收起高级配置`,setAsLatest:`设为最新版本`,setAsLatestDesc:`开启后,此版本将成为发布版本`,basicInfo:`基本信息`,versionHistory:`版本历史`,totalVersions:`共 {{count}} 个版本`,currentVersion:`当前版本`,isLatest:`是否最新`,selectVersion:`选择版本`,searchPlaceholder:`搜索 Agent 名称`,totalAgents:`共 {{total}} 个 Agent`,deleteConfirm:`确定要删除 Agent「{{name}}」吗?`,batchDeleteConfirm:`确定要删除选中的 {{count}} 个 Agent 吗?`,deleteSuccess:`删除成功`,batchDelete:`批量删除`,batchDeleteSuccess:`批量删除成功`,createSuccess:`Agent 创建成功`,updateSuccess:`Agent 更新成功`,create:`创建`,update:`更新`,publish:`发布`,confirmPublish:`确认发布`,publishStrategy:`发布策略`,publishStrategyDesc:`选择如何处理此次修改。版本号:`,strategyNewVersion:`发布新版本`,strategyNewVersionDesc:`以新版本号发布,但不设为默认版本`,strategySetLatest:`设为最新版本`,strategySetLatestDesc:`设为默认版本,客户端未指定版本时自动使用`,strategyEditCurrent:`更新当前版本`,strategyEditCurrentDesc:`覆盖当前版本配置,版本号和最新标记不变`,strategyReasonVersionExists:`版本已存在`,strategyReasonVersionNotExists:`版本不存在`,strategyReasonAlreadyLatest:`已是最新版本`,loadFailed:`数据加载失败`,backToList:`返回列表`,nameRequired:`Agent 名称不能为空`,versionRequired:`版本号不能为空`,urlRequired:`服务地址不能为空`,protocolVersionRequired:`协议版本不能为空`,transportRequired:`请选择传输协议`,jsonFormatError:`格式错误`},prompt:{title:`Prompt 管理`,createPrompt:`创建 Prompt`,editPrompt:`编辑 Prompt`,editMetadata:`编辑描述与标签`,promptDetail:`Prompt 详情`,promptKey:`Prompt Key`,version:`版本号`,latestVersion:`最新版本`,template:`模板内容`,versionDiff:`版本 Diff`,diffBeforeVersion:`基准版本`,diffAfterVersion:`目标版本`,diffSelectVersionPlaceholder:`请选择版本`,diffSwapVersions:`交换对比版本`,diffModifiedContent:`修改内容`,diffTemplateFile:`模板内容`,diffSelectVersions:`请选择基准版本和目标版本进行对比`,diffNoChanges:`两个版本没有模板内容差异`,diffSameVersion:`请选择两个不同版本进行对比`,variables:`变量列表`,variableName:`变量名`,variableDefault:`默认值`,variableDescription:`描述`,description:`描述`,commitMsg:`提交信息`,bizTags:`业务标签`,labels:`标签`,labelName:`标签名`,bindLabel:`绑定标签`,unbindLabel:`解绑标签`,versionHistory:`版本历史`,downloadMarkdown:`下载 Markdown`,downloadMarkdownTip:`将当前选中的版本导出为 Markdown 文档`,downloadMarkdownSuccess:`Markdown 文档下载成功`,downloadMarkdownFailed:`Markdown 文档下载失败`,downloadCount:`{{count}} 次下载`,downloads:`下载量`,versionDownloads:`版本下载量`,publishVersion:`发布新版本`,debug:`调试`,startDebug:`开始调试`,clearResult:`清除结果`,aiOptimize:`AI 优化`,optimizeGoal:`优化目标`,optimizeGoalPlaceholder:`描述优化目标,如:让表达更清晰、增加示例等(可选)`,startOptimize:`开始优化`,optimizing:`优化中...`,optimizedResult:`优化结果`,originalTemplate:`原始模板`,applyOptimize:`应用优化结果`,noDescription:`暂无描述`,noVariables:`暂无变量`,noLabels:`暂无标签`,noVersions:`暂无版本历史`,totalVersions:`共 {{count}} 个版本`,currentVersion:`当前版本`,searchPlaceholder:`搜索 Prompt Key`,totalPrompts:`共 {{total}} 个 Prompt`,deleteConfirm:`确定要删除 Prompt「{{name}}」吗?`,batchDeleteConfirm:`确定要删除选中的 {{count}} 个 Prompt 吗?`,deleteSuccess:`删除成功`,batchDelete:`批量删除`,batchDeleteSuccess:`批量删除成功`,createSuccess:`Prompt 创建成功`,updateSuccess:`Prompt 更新成功`,publishSuccess:`版本发布成功`,bindLabelSuccess:`标签绑定成功`,unbindLabelSuccess:`标签解绑成功`,metadataUpdateSuccess:`信息更新成功`,loadFailed:`数据加载失败`,backToList:`返回列表`,keyRequired:`Prompt Key 不能为空`,keyInvalid:`Prompt Key 仅支持字母、数字、下划线和横杠`,versionRequired:`版本号不能为空`,versionInvalid:`版本号格式不正确,格式为 x.y.z`,versionMustGreater:`新版本号必须大于当前版本 {{current}}`,templateRequired:`模板内容不能为空`,labelInvalid:`标签仅支持字母、数字、点号、下划线和横杠`,selectVersion:`选择版本`,newVersion:`新版本号`,newVersionPlaceholder:`如 0.0.2 或 v3(留空自动递增)`,templatePlaceholder:`输入 Prompt 模板,使用 {{variable}} 语法定义变量...`,commitMsgPlaceholder:`描述此次变更的内容...`,descriptionPlaceholder:`描述该 Prompt 的用途...`,tagPlaceholder:`输入标签后按回车添加`,keyPlaceholder:`请输入 Prompt Key`,versionPlaceholder:`例如: 1.0.0`,thinking:`思考过程`,modelOutput:`模型输出`,streaming:`输出中...`,userInput:`用户输入`,userInputPlaceholder:`输入要发送给模型的用户消息`,renderedPrompt:`渲染后的 Prompt`,send:`发送`,basicInfo:`基本信息`,templateEditor:`模板编辑器`,debugPanel:`调试面板`,labelManagement:`标签管理`,addLabel:`添加标签`,manageLabels:`管理标签`,labelBoundTo:`绑定到`,publishTime:`发布时间`,publisher:`发布者`,retry:`重试`,"versionStatus.draft":`草稿`,"versionStatus.reviewing":`审核中`,"versionStatus.reviewed":`待发布`,"versionStatus.online":`已上线`,"versionStatus.offline":`已下线`,"versionStatus.pendingPublish":`待发布`,"versionStatus.rejected":`审核未通过`,submit:`提交审核`,publish:`发布`,forcePublish:`强制发布`,online:`上线`,offline:`下线`,createDraft:`创建草稿`,createDraftFrom:`基于此版本创建草稿`,deleteDraft:`删除草稿`,saveDraft:`保存草稿`,draftExistsTip:`已存在编辑中或审核中的版本`,publishDisabledPipeline:`Pipeline 未通过审核`,forcePublishConfirmTitle:`确认强制发布`,forcePublishConfirmDesc:`确定要强制发布版本 {{version}} 吗?此操作将跳过 Pipeline 审核。`,forcePublishConfirm:`确认强制发布`,onlineCount:`在线: {{count}}`,hasDraft:`有草稿`,hasReviewing:`审核中`,noOnlineVersion:`无在线版本`,submitSuccess:`提交审核成功`,draftSaveSuccess:`草稿保存成功`,draftDeleteSuccess:`草稿删除成功`,onlineSuccess:`上线成功`,offlineSuccess:`下线成功`,forcePublishSuccess:`强制发布成功`,redraft:`重新编辑`,redraftSuccess:`版本已退回草稿,可重新编辑`,labelsUpdateSuccess:`标签更新成功`,descriptionUpdateSuccess:`描述更新成功`,bizTagsUpdateSuccess:`业务标签更新成功`,governanceInfo:`治理信息`,versionTimeline:`版本时间线`,pipelineStatus:`审核流水线`,pipelineInProgress:`审核中`,editDraft:`编辑草稿`,createPromptDesc:`创建新的 Prompt 草稿,包含模板和变量定义。`,createDraftFromDesc:`基于版本 {{version}} 创建新草稿`,status:`状态`},namespace:{title:`命名空间`,create:`新建命名空间`,edit:`编辑命名空间`,detail:`命名空间详情`,name:`命名空间名称`,id:`命名空间 ID`,description:`描述`,configCount:`配置数`,quota:`配额`,namePlaceholder:`请输入命名空间名称`,idPlaceholder:`不填则自动生成`,descriptionPlaceholder:`请输入命名空间描述`,nameRequired:`命名空间名称不能为空`,deleteConfirm:`确定要删除命名空间「{{name}}」吗?该操作不可撤销。`,deleteSuccess:`命名空间删除成功`,createSuccess:`命名空间创建成功`,updateSuccess:`命名空间更新成功`,publicNamespace:`public`,publicNamespaceDesc:`公共命名空间,不可编辑和删除`,total:`共 {{total}} 个命名空间`},cluster:{title:`集群节点`,nodeAddress:`节点地址`,nodeIp:`IP`,nodePort:`端口`,nodeState:`状态`,searchPlaceholder:`搜索节点 IP 或地址`,leave:`下线`,leaveConfirm:`确定要将节点「{{address}}」从集群中移除吗?`,leaveSuccess:`节点下线成功`,total:`共 {{total}} 个节点`,stateUp:`运行中`,stateDown:`已下线`,stateSuspicious:`可疑`,refresh:`刷新`},plugin:{title:`插件管理`,pluginName:`插件名称`,pluginType:`插件类型`,enabled:`已启用`,disabled:`已禁用`,critical:`关键`,status:`状态`,allTypes:`全部类型`,enable:`启用`,disable:`禁用`,enableSuccess:`插件已启用`,disableSuccess:`插件已禁用`,detail:`插件详情`,version:`版本`,description:`描述`,configurable:`可配置`,clusterAvailability:`集群可用性`,noPlugins:`暂无插件`,total:`共 {{total}} 个插件`,typeAuth:`认证`,typeDatasource:`数据源`,typeConfigChange:`配置变更`,typeEncryption:`加密`,typeTrace:`追踪`,typeEnvironment:`环境`,typeControl:`控制`,typeAiPipeline:`AI 发布流水线`,typeAiStorage:`AI 资源存储`,typeVisibility:`可见性`,availableNodes:`可用节点`,yes:`是`,no:`否`,pluginCount:`个插件`},authority:{title:`权限控制`,userManagement:`用户管理`,roleManagement:`角色管理`,permissionManagement:`权限管理`,username:`用户名`,password:`密码`,newPassword:`新密码`,confirmPassword:`确认密码`,role:`角色`,resource:`资源`,action:`动作`,createUser:`创建用户`,deleteUser:`删除用户`,resetPassword:`重置密码`,bindRole:`绑定角色`,addPermission:`添加权限`,usernamePlaceholder:`请输入用户名`,passwordPlaceholder:`请输入密码`,confirmPasswordPlaceholder:`请再次输入密码`,newPasswordPlaceholder:`请输入新密码`,rolePlaceholder:`请输入角色名`,selectRolePlaceholder:`请选择角色`,selectUserPlaceholder:`请选择用户`,resourcePlaceholder:`请选择资源`,searchUserPlaceholder:`搜索用户名`,searchRolePlaceholder:`搜索角色名`,usernameRequired:`用户名不能为空`,passwordRequired:`密码不能为空`,passwordMismatch:`两次输入的密码不一致`,roleRequired:`角色名不能为空`,resourceRequired:`资源不能为空`,actionRequired:`请选择动作`,deleteUserConfirm:`确定要删除用户「{{name}}」吗?`,deleteRoleConfirm:`确定要删除角色「{{role}}」与用户「{{username}}」的绑定吗?`,deletePermissionConfirm:`确定要删除该权限配置吗?`,createUserSuccess:`用户创建成功`,deleteUserSuccess:`用户删除成功`,resetPasswordSuccess:`密码重置成功`,createRoleSuccess:`角色绑定成功`,deleteRoleSuccess:`角色绑定删除成功`,createPermissionSuccess:`权限添加成功`,deletePermissionSuccess:`权限删除成功`,totalUsers:`共 {{total}} 个用户`,totalRoles:`共 {{total}} 条角色绑定`,totalPermissions:`共 {{total}} 条权限配置`,actionRead:`只读`,actionWrite:`只写`,actionReadWrite:`读写`,fuzzySearch:`模糊搜索`,exactSearch:`精确搜索`,adminCannotDelete:`管理员角色不能删除`},settings:{title:`设置`,description:`管理 Nacos 控制台的偏好与集成配置`,copilotConfig:`Copilot 配置`,copilotConfigDesc:`配置 AI 助手的模型与密钥,用于智能化配置管理`,apiKey:`API 密钥`,apiKeyHint:`建议通过环境变量 COPILOT_API_KEY 设置,优先级高于此处配置`,apiKeyPlaceholder:`请输入 API Key`,model:`模型`,modelPlaceholder:`请选择模型`,saveSuccess:`保存成功`},agentSpec:{title:`AgentSpec 管理`,totalAgentSpecs:`共 {{total}} 个 AgentSpec`,upload:`上传`,createAgentSpec:`创建 AgentSpec`,createAgentSpecDesc:`填写基础信息后将创建第一个草稿版本`,createSuccess:`AgentSpec 创建成功`,createFailed:`创建 AgentSpec 失败`,editAgentSpec:`编辑 AgentSpec`,newFile:`新建文件`,newFolder:`新建文件夹`,basicInfo:`基础信息`,editor:`编辑器`,editorLoading:`编辑器加载中...`,editBasicInfo:`编辑基础信息`,agentSpecName:`AgentSpec 名称`,namespace:`命名空间`,files:`文件概览`,fileTreeTitle:`文件`,fileTree:`文件树`,activeFile:`当前文件`,workspaceSummary:`工作区摘要`,unnamedAgentSpec:`未命名 AgentSpec`,noDescriptionPreview:`manifest 中的描述将显示在这里,便于在编辑时快速确认 AgentSpec 的定位。`,modeNew:`新建`,modeEdit:`草稿`,modeVersion:`新版本`,modePreview:`预览`,modified:`已修改`,readOnly:`只读`,resizeFileTreePanel:`调整文件树面板大小`,renameNode:`重命名 {{name}}`,deleteNode:`删除 {{name}}`,createFileIn:`在 {{name}} 中新建文件`,createFolderIn:`在 {{name}} 中新建文件夹`,resourceRenameFile:`重命名文件`,resourceCopyFile:`复制文件内容`,resourceDownloadFile:`下载文件`,resourceDeleteFile:`删除文件`,resourceCopySuccess:`文件内容已复制`,resourceCopyFailed:`复制文件内容失败`,createFile:`创建文件`,createFolder:`创建文件夹`,createFileDesc:`输入文件路径并选择资源类型。`,createFolderDesc:`输入文件夹路径并选择资源类型。`,resourceType:`资源类型`,resourceTypeRequired:`资源类型不能为空`,customFolder:`自定义`,filePath:`文件路径`,folderPath:`文件夹路径`,filePathPlaceholderCompact:`例如: tools/helper.py`,folderPathPlaceholderCompact:`例如: tools`,filePathPlaceholder:`例如: skill/tools/helper.py`,folderPathPlaceholder:`例如: skill/tools`,pathRequired:`路径不能为空`,invalidFileName:`文件名无效`,fileExists:`文件已存在`,folderExists:`文件夹已存在`,namePlaceholder:`请输入 AgentSpec 名称`,descriptionPlaceholder:`请输入 AgentSpec 描述`,searchPlaceholder:`搜索 AgentSpec 名称`,sortDefault:`默认排序`,sortByDownloads:`按下载量排序`,filterByOwner:`按创建者过滤`,filterOwnerPlaceholder:`输入创建者名称`,filterOnlyMine:`只看我的`,filterScopeAll:`所有范围`,filterScopePublic:`仅公开`,filterScopePrivate:`仅私有`,batchDelete:`批量删除`,deleteConfirm:`确定要删除 AgentSpec「{{name}}」吗?`,batchDeleteConfirm:`确定要删除选中的 {{count}} 个 AgentSpec 吗?`,deleteSuccess:`删除成功`,batchDeleteSuccess:`批量删除成功`,loadListError:`获取 AgentSpec 列表失败`,loadError:`加载 AgentSpec 详情失败`,nameRequired:`AgentSpec 名称不能为空`,descriptionRequired:`描述不能为空`,saveSuccess:`保存成功`,unsaved:`未保存`,enabled:`已启用`,disabled:`已禁用`,version:`版本`,createTime:`创建时间`,status:`状态`,versionStatus:{draft:`草稿`,reviewing:`审核中`,reviewed:`待发布`,pendingPublish:`待发布`,rejected:`审核未通过`,online:`已上线`,offline:`已下线`},updateTime:`更新时间`,onlineCountLabel:`在线情况`,description:`描述`,noDescription:`暂无描述`,editing:`编辑中`,hasDraft:`有草稿`,draftVersion:`编辑中`,reviewing:`审核中`,reviewingVersion:`审核中`,submit:`提交审核`,resubmit:`重新提交审核`,saveAndPublish:`保存并发布`,publish:`发布`,online:`版本上线`,offline:`版本下线`,onlineCount:`已上线版本数:{{count}}`,noOnlineVersion:`无上线版本`,downloadCount:`{{count}} 次下载`,downloads:`下载量`,versionDownloads:`版本下载量`,author:`作者`,backToList:`返回列表`,selectVersion:`选择版本`,latestVersion:`最新`,totalVersions:`共 {{count}} 个版本`,versionHistory:`版本历史`,resources:`资源文件`,agentsFile:`AGENTS.md`,noAgentsFile:`暂无 AGENTS.md 内容`,labels:`标签`,newVersion:`新建版本`,createDraft:`创建草稿`,createDraftFrom:`基于此版本创建草稿`,draftExistsTip:`已存在草稿/未发布版本`,createDraftSuccess:`草稿创建成功`,deleteDraft:`删除草稿`,editDraft:`编辑草稿`,cancelEdit:`取消`,saveDraft:`保存草稿`,draftSaveSuccess:`草稿已保存`,deleteDraftSuccess:`草稿已删除`,submitSuccess:`提交审核成功`,publishSuccess:`发布成功`,redraft:`重新编辑`,redraftSuccess:`版本已退回草稿,可重新编辑`,onlineSuccess:`上线成功`,offlineSuccess:`下线成功`,enableSuccess:`已启用 AgentSpec`,disableSuccess:`已禁用 AgentSpec`,scopePublic:`公开`,scopePrivate:`私有`,scopeUpdateSuccess:`可见范围已更新`,pipelineStatus:`审核流水线`,pipelineInProgress:`审核中`,pipelineApproved:`已通过`,pipelineRejected:`审核未通过`,pipelineNone:`暂无审核流水线`,pipelineNodePassed:`通过`,pipelineNodeFailed:`失败`,pipelineNoMessage:`暂无输出信息`,labelsUpdateSuccess:`标签更新成功`,bizTagsUpdateSuccess:`业务标签更新成功`,bizTagPlaceholder:`添加标签`,noBizTags:`暂无业务标签`,noLabels:`暂无标签`,noVersions:`暂无版本`,labelKeyRequired:`标签 key 不能为空`,labelKeyDuplicate:`标签 key 已存在`,labelKeyInvalid:`标签 key 包含非法字符`,labelKey:`Key`,labelValue:`Value`,saveLabels:`保存标签`,uploadZip:`上传 AgentSpec ZIP`,uploadZipDesc:`选择一个 .zip 文件导入 AgentSpec 包。`,invalidZipFile:`请选择有效的 .zip 文件`,uploadSuccess:`上传成功`,uploadFailed:`上传失败`,dragOrClick:`点击选择 .zip 文件`},skill:JSON.parse(`{"title":"Skill 管理","totalSkills":"共 {{total}} 个 Skill","upload":"上传","searchPlaceholder":"搜索 Skill 名称","skillName":"Skill 名称","description":"描述","noDescription":"暂无描述","enabled":"已启用","disabled":"已禁用","editing":"编辑中","hasDraft":"有草稿","reviewing":"审核中","online":"版本上线","offline":"版本下线","versionStatus":{"draft":"草稿","reviewing":"审核中","reviewed":"待发布","pendingPublish":"待发布","rejected":"审核未通过","online":"已上线","offline":"已下线"},"onlineCount":"已上线版本数:{{count}}","noOnlineVersion":"无上线版本","downloadCount":"{{count}} 次下载","downloads":"下载量","versionDownloads":"版本下载量","basicInfo":"基础信息","instruction":"SKILL.md","skillMd":"SKILL.md","resources":"资源文件","noResources":"暂无资源","resourceRenameFile":"重命名文件","resourceCopyFile":"复制文件内容","resourceDownloadFile":"下载文件","resourceDeleteFile":"删除文件","resourceCopySuccess":"文件内容已复制","resourceCopyFailed":"复制文件内容失败","versionDiff":"版本 Diff","diffBeforeVersion":"基准版本","diffAfterVersion":"目标版本","diffSelectVersionPlaceholder":"请选择版本","diffSwapVersions":"交换对比版本","diffAddedFiles":"新增文件","diffRemovedFiles":"删除文件","diffModifiedFiles":"修改文件","diffChangedFiles":"变更文件","diffSelectVersions":"请选择基准版本和目标版本进行对比","diffNoChanges":"两个版本没有文件差异","diffSameVersion":"请选择两个不同版本进行对比","diffTextUnsupported":"该文件不支持文本 Diff","diffTextUnsupportedDesc":"图片、PPT、压缩包等文件无法以文本方式对比,当前仅展示版本间的变更状态。","diffStatusAdded":"新增","diffStatusRemoved":"删除","diffStatusModified":"修改","resourceName":"资源名称","resourceType":"资源类型","resourceContent":"内容","labels":"标签","noLabels":"暂无标签","version":"版本","status":"状态","createTime":"创建时间","updateTime":"更新时间","author":"作者","versionHistory":"版本历史","totalVersions":"共 {{count}} 个版本","noVersions":"暂无版本","selectVersion":"选择版本","latestVersion":"最新","newVersion":"新建版本","createDraft":"创建草稿","createDraftSuccess":"草稿创建成功","submit":"提交审核","resubmit":"重新提交审核","submitSuccess":"提交审核成功","publish":"发布","publishSuccess":"发布成功","onlineSuccess":"上线成功","offlineSuccess":"下线成功","enableSuccess":"已启用 Skill","disableSuccess":"已禁用 Skill","scopePublic":"公开","scopePrivate":"私有","toggleScope":"切换可见范围","scopeUpdateSuccess":"可见范围已更新","masterSwitch":"Skill 主控开关","masterSwitchDesc":"禁用后所有版本将对 Agent 不可见","skillDisabledWarning":"Skill 已禁用,版本状态变更不会生效","versionActions":"当前版本操作","labelsUpdateSuccess":"标签已更新","labelManagement":"版本标签","labelBindDesc":"选择要绑定到版本 {{version}} 的标签,或创建新标签。","searchOrCreateLabel":"搜索或创建标签...","createLabel":"创建标签「{{name}}」","selectAll":"全选 {{count}} 个","clearAll":"清除","deleteConfirm":"确定要删除 Skill「{{name}}」吗?","batchDeleteConfirm":"确定要删除选中的 {{count}} 个 Skill 吗?","deleteSuccess":"删除成功","batchDelete":"批量删除","batchDeleteSuccess":"批量删除成功","backToList":"返回列表","loadFailed":"加载数据失败","uploadZip":"上传 Skill ZIP","uploadZipDesc":"选择一个 .zip 文件导入 Skill 包。","invalidZipFile":"请选择有效的 .zip 文件","uploadSuccess":"上传成功","uploadSuccessWithName":"Skill {{name}} 上传成功","uploadFailed":"上传失败","precheckUpload":"解析并校验","confirmUpload":"确认上传","confirmForceOverwriteUpload":"强制覆盖并上传","confirmBatchUpload":"确认上传","uploadChecking":"校验中","uploadPrecheckBlocked":"当前 Skill 包不能上传","precheckNewSkill":"当前 Skill 不存在,上传后会创建草稿版本 {{version}}。","precheckExistingSkillCreateDraft":"当前 Skill 已存在,上传后会创建草稿版本 {{version}}。","precheckVersionExists":"解析版本 {{version}} 已存在。","precheckDraftOverwriteOnly":"当前已有草稿 {{draftVersion}};继续上传会覆盖该草稿,上传后草稿版本为 {{version}}。是否覆盖?","precheckVersionConverted":"用户上传版本是 {{parsedVersion}},已转化为合法版本 {{version}}。","precheckVersionNormalizedAndAdjusted":"用户上传版本 {{parsedVersion}} 已规范化为 {{normalizedVersion}};因版本 {{normalizedVersion}} 已存在,本次会创建草稿版本 {{version}}。","precheckCreateVersionAdjusted":"解析版本 {{parsedVersion}} 已被占用,本次会创建草稿版本 {{version}}。","precheckReviewingBlocked":"当前已有审核中版本 {{version}},审核结束前不能上传。","precheckNoPermission":"你没有这个 Skill 的编辑权限,不能上传。","parsedVersion":"解析版本","uploadedVersion":"上传版本","resolvedVersion":"目标版本","resultVersion":"目标版本:{{version}}","dragOrClick":"拖拽 .zip 文件到此处,或点击选择","dragDropHint":"支持拖拽 .zip 文件上传","dropFileHere":"释放 .zip 文件以上传","batchUploadResult":"批量上传:{{succeeded}} 个成功,{{failed}} 个失败","batchUploadAllSuccess":"全部 {{count}} 个 Skill 上传成功","batchUploadSuccessWithSkipped":"{{succeeded}} 个 Skill 上传成功,已跳过 {{skipped}} 个","batchUploadSkipped":"已跳过 {{count}} 个 Skill","batchUploadPartialFail":"{{succeeded}} 个成功,{{failed}} 个失败:{{reasons}}","batchUploadAllFailed":"全部 {{count}} 个 Skill 上传失败","batchUploadAborted":"检测到 {{existing}} 个已有 Skill、{{blocked}} 个阻断项,已终止上传。","batchUploadNothingToUpload":"没有可上传的 Skill。","targetNamespace":"目标命名空间","batchSkillCount":"Skill 数量","batchUploadOnlyEntryCount":"无效/非 Skill","batchPrecheckSummary":"检查完成:共 {{total}} 个,新增 {{fresh}} 个,已有 {{existing}} 个,阻断 {{blocked}} 个。","batchPrecheckBlockedTip":"无权限或审核中的 Skill 会按原批量上传结果返回失败。","batchPrecheckUploadOnlyTip":"另有 {{count}} 个目录不是有效 Skill,不参与 Skill 冲突检查,上传后仍按批量结果展示。","sameSkillPolicy":"相同 Skill:","batchPolicyAbort":"终止上传","batchPolicySkip":"跳过","batchPolicyOverwrite":"覆盖","batchPolicySkipDrafts":"跳过已有草稿","batchPolicyOverwriteDrafts":"覆盖已有草稿","batchPolicySkipDraftsDesc":"已有草稿的 Skill 不上传;其他 Skill 按正常规则上传,失败仍在批量结果中展示。","batchPolicyOverwriteDraftsDesc":"已有草稿的 Skill 会被当前 ZIP 内容覆盖;无权限或审核中的 Skill 仍会失败。","batchItemNew":"新增","batchItemExisting":"已有","batchItemDraft":"已有草稿","batchItemBlocked":"阻断","batchItemInvalidSkill":"解析失败","batchItemInvalidSkillDesc":"SKILL.md 不是有效 Skill 描述文件","batchItemNonSkillFolder":"非 Skill","batchItemNonSkillFolderDesc":"目录内没有 SKILL.md,不作为 Skill 预检","batchItemVersionConverted":"上传版本 {{parsedVersion}} -> {{version}}","batchItemVersionNormalizedAndAdjusted":"上传版本 {{parsedVersion}} -> {{normalizedVersion}} -> {{version}}","resultSucceeded":"成功 ({{count}})","resultFailed":"失败 ({{count}})","importFromRegistry":"从 Skill 市场导入","importSource":"导入来源","importSearchPlaceholder":"搜索 Skill 名称","importValidate":"验证","importExecute":"执行导入","importSuccess":"导入成功","importFailed":"导入失败","importResult":"导入结果:成功 {{success}} 个,失败 {{failed}} 个,跳过 {{skipped}} 个","importSkipInvalid":"跳过无效项","importOverride":"覆盖已有","importConflictPolicy":"冲突策略","importConflictSkip":"跳过","importConflictOverwrite":"覆盖","importAll":"导入全部有效项","importSelectedCount":"已选择 {{count}} 项","importValidatedCount":"已校验有效 {{count}} 项","importSelectAll":"全选","importClearSelection":"清空选择","importLoadMore":"加载更多","noImportSource":"暂无可用导入来源","importStatusValid":"有效","importStatusWarning":"警告","importStatusConflict":"冲突","importStatusInvalid":"无效","importUnnamed":"未命名","importMetadata":{"fileCount":"文件数","source":"来源"},"download":"下载","downloadVersion":"下载版本","createSkill":"创建 Skill","createSkillDesc":"创建一个新的 Skill 草稿版本,后续可以继续编辑内容。","namePlaceholder":"请输入 Skill 名称","descPlaceholder":"描述该 Skill 的功能","skillMdPlaceholder":"输入完整的 SKILL.md 内容...","skillMdHint":"当 Agent 加载该 Skill 时,将注入完整的 SKILL.md 内容(包含 frontmatter 与正文)作为执行上下文。","nameRequired":"Skill 名称不能为空","nameTooLong":"Skill 名称长度须在 1–64 个字符之间","nameInvalidFormat":"Skill 名称只能包含小写字母、数字和连字符,且不能以连字符开头或结尾","nameNoConsecutiveHyphens":"Skill 名称不能包含连续的连字符(--)","descriptionRequired":"描述不能为空","instructionRequired":"SKILL.md 内容不能为空","skillMdRequired":"SKILL.md 内容不能为空","submitRequiresFields":"提交前请填写描述和 SKILL.md 内容","createSuccess":"Skill 创建成功","createFailed":"创建 Skill 失败","retry":"重试","editDraft":"编辑草稿","saveDraft":"保存草稿","cancelEdit":"取消","draftSaveSuccess":"草稿已保存","commitMsg":"版本提交说明","commitMsgPlaceholder":"可选:说明本草稿版本的变更内容","commitMsgHint":"仅保存在该草稿版本上(版本历史中展示),与上方的 Skill 描述无关。","toggleEnable":"启用/禁用 Skill","pipelineStatus":"审核流水线","pipelineInProgress":"审核中","pipelineApproved":"已通过","pipelineRejected":"审核未通过","pipelineNone":"暂无审核流水线","pipelineNodePassed":"通过","pipelineNodeFailed":"失败","pipelineNoMessage":"暂无输出信息","publishDisabledPipeline":"需等待审核通过后才能发布","forcePublish":"强制发布","forcePublishSuccess":"强制发布成功","redraft":"重新编辑","redraftSuccess":"版本已退回草稿,可重新编辑","forcePublishConfirmTitle":"强制发布(跳过审核流水线)","forcePublishConfirmDesc":"您将强制发布版本 \\"{{version}}\\",跳过审核流水线校验,立即上线。此操作不可撤销,请确认。","forcePublishConfirm":"确认强制发布","createDraftFrom":"基于此版本创建草稿","newVersionTitle":"创建新草稿版本","newVersionDesc":"基于版本 {{current}} 创建新草稿,请输入新版本号","newVersionPlaceholder":"例如: 1.0.1 或 v2","newVersionRequired":"新版本号不能为空","versionInvalid":"版本号格式不正确,格式为 x.y.z 或 vN","versionMustGreater":"新版本号必须大于当前版本 {{current}}","draftExistsTip":"已存在草稿/未发布版本","bizTagsUpdateSuccess":"业务标签更新成功","bizTagPlaceholder":"添加标签","noBizTags":"暂无业务标签","sortDefault":"默认排序","sortByDownloads":"按下载量排序","filterByOwner":"按创建者过滤","filterOwnerPlaceholder":"输入创建者名称","filterOnlyMine":"只看我的","filterScopeAll":"所有范围","filterScopePublic":"仅公开","filterScopePrivate":"仅私有","filterByBizTag":"按业务标签过滤","filterBizTagPlaceholder":"输入业务标签","labelKeyRequired":"标签键不能为空","labelKeyInvalid":"标签键格式不合法","labelKeyDuplicate":"标签键已存在","saveLabels":"保存标签","labelKey":"键","labelValue":"版本","deleteDraft":"删除草稿","deleteDraftSuccess":"草稿已删除","manualCreate":"手动创建","aiGenerate":"AI 生成","aiOptimize":"AI 优化","backgroundInfo":"背景信息","backgroundInfoPlaceholder":"描述你想要创建的 Skill 的功能、用途、目标用户等...","backgroundInfoRequired":"请输入背景信息","generateSkill":"生成 Skill","generating":"生成中...","generateSuccess":"Skill 生成成功","generateFailed":"Skill 生成失败","regenerate":"重新生成","applyGenerated":"应用生成结果","optimizationGoal":"优化目标","optimizationGoalPlaceholder":"描述你希望如何优化这个 Skill(可选)...","selectTargetFile":"选择优化目标文件","startOptimize":"开始优化","optimizing":"优化中...","optimizeSuccess":"Skill 优化成功","optimizeFailed":"Skill 优化失败","applyOptimize":"应用优化","originalContent":"原始内容","optimizedContent":"优化后内容","mcpToolsOptional":"MCP 工具 (可选)","selectMcpServer":"选择 MCP Server","availableTools":"可用工具","searchTools":"搜索工具...","selectedToolsCount":"已选 {{count}} 个","noToolsAvailable":"暂无可用工具","loadingTools":"加载工具中...","conversationHistory":"对话历史 (可选)","conversationHistoryPlaceholder":"JSON 格式的对话历史,用于多轮生成...","conversationHistoryInvalid":"对话历史 JSON 格式无效","thinking":"思考过程","generatedContent":"生成内容","streamContent":"流式输出","selectResource":"选择一个文件查看内容","editMode":"编辑模式","binaryFileHint":"暂不支持预览该文件内容","imagePreviewUnavailable":"无法预览该图片文件"}`)}},"en-US":{translation:{header:{home:`HOME`,docs:`DOCS`,blog:`BLOG`,community:`COMMUNITY`,enterprise:`ENTERPRISE EDITION`,mcp:`MCP Marketplace`,languageSwitch:`中`,logout:`Logout`,changePassword:`Change Password`},login:{login:`Login`,initPassword:`Initialize Password`,internalSysTip1:`Internal system. Do not expose to the public network.`,internalSysTip3:"Init admin user `nacos` password",internalSysTip4:`It is recommended to use a strong password and keep it secure.`,submit:`Submit`,pleaseInputUsername:`Please enter username`,pleaseInputPassword:`Please enter password`,pleaseInputPasswordTips:`Please enter a password (a random password will be generated if left empty).`,invalidUsernameOrPassword:`Invalid username or password`,passwordRequired:`Password is required`,usernameRequired:`Username is required`,productDesc:`An easy-to-use dynamic service discovery, configuration and AI agent management platform for building AI agent applications`,consoleClosed:`Console Closed`,consoleClosedDesc:`The Nacos console has been closed.`,signInWithSSO:`Sign in with SSO`,ssoLoginTip:`Single sign-on is enabled by your administrator. Click the button below to continue to the identity provider.`,oidcRedirectFailed:`Failed to redirect to SSO login page. Please contact your administrator.`,oidcAuthFailed:`SSO authentication failed`},menu:{configManagement:`Configuration`,configList:`Configurations`,historyRollback:`Historical Versions`,listeningQuery:`Listener Query`,serviceManagement:`Services`,serviceList:`Service List`,subscriberList:`Subscribers`,aiRegistry:`AI Registry`,mcpRegistry:`MCP Registry`,agentRegistry:`Agent Registry`,skillRegistry:`Skill Registry`,promptRegistry:`Prompt Registry`,agentSpecRegistry:`AgentSpec Registry`,platformManagement:`Platform`,namespace:`Namespace`,clusterManagement:`Cluster`,clusterNodeList:`Cluster Nodes`,pluginManagement:`Plugins`,authorityControl:`Authority Control`,userList:`User List`,roleManagement:`Role Management`,privilegeManagement:`Privileges`,settingCenter:`Settings`},common:{confirm:`Confirm`,cancel:`Cancel`,retry:`Retry`,delete:`Delete`,edit:`Edit`,create:`Create`,search:`Search`,reset:`Reset`,loading:`Loading...`,noData:`No Data`,operation:`Operation`,detail:`Detail`,save:`Save`,bizTags:`Business Tags`,from:`Source`,add:`Add`,optional:`Optional`,back:`Back`,success:`Success`,failed:`Failed`,legacyConsole:`Legacy Console`,switchToLegacy:`Switch to legacy console`,collapse:`Collapse`,expand:`Expand`,mode:`Mode`,version:`Version`,pageSize:`Page Size`,selectNamespace:`Namespace`,sessionExpired:`Session expired, please login again`,noPermission:`No permission to perform this operation`,requestFailed:`Request failed`,versionLabels:{title:`Version Labels`,noLabels:`No version labels`,keyPlaceholder:`Label name`,valuePlaceholder:`Version`,keyRequired:`Label key is required`,keyDuplicate:`Label key already exists`,keyInvalid:`Invalid label key format (only letters, digits, . _ -)`,reservedLatest:`latest is managed by the server and cannot be set or removed manually`,save:`Save Version Labels`,updateSuccess:`Version labels updated`,editLabels:`Edit Version Labels`,labelManagement:`Version Labels`,labelBindDesc:`Select labels to bind to version {{version}}, or create new ones.`,searchOrCreateLabel:`Search or create label...`,createLabel:`Create label "{{name}}"`,selectAll:`Select all {{count}}`,clearAll:`Clear`},bizTagEditor:{title:`Business Tags`,description:`Edit the business tag list.`},cliUsage:{title:`Install`,cliInstall:`CLI Install`,cliDoc:`CLI Documentation`,downloadFile:`Download File`,manualDownload:`Manual Download`,downloadZip:`Download .zip`,description:`Get this resource locally via Nacos CLI. Requires Node.js.`,copied:`Command copied`,latest:`Get latest`,byVersion:`By version`,byLabel:`By label`}},config:{title:`Configuration List`,newConfig:`New Config`,editConfig:`Edit Config`,configDetail:`Config Detail`,dataId:`Data ID`,group:`Group`,content:`Content`,type:`Format`,tags:`Tags`,appName:`Application`,description:`Description`,md5:`MD5`,createTime:`Created`,modifyTime:`Last Modified`,fuzzySearch:`Fuzzy Search`,exactSearch:`Exact Search`,contentSearch:`Content Search`,advancedSearch:`Advanced Search`,publishSuccess:`Configuration published successfully`,publishFailed:`Configuration publish failed`,publishConfirmTitle:`Confirm Publish`,publishConfirmDescription:`Review the content changes before publishing this configuration.`,currentPublishedContent:`Current published content`,pendingPublishContent:`Pending publish content`,notFoundInNamespace:`This configuration does not exist in the selected namespace. Returning to the configuration list.`,unsavedNamespaceSwitchConfirm:`You have unpublished changes. Switch namespace and discard them?`,deleteConfirm:`Are you sure you want to delete this configuration?`,deleteSuccess:`Configuration deleted successfully`,dataIdRequired:`Data ID is required`,groupRequired:`Group is required`,contentRequired:`Content is required`,maxTags:`Maximum 5 tags allowed`,noSpecialChars:`Special characters not allowed`,configExists:`Configuration already exists`,totalConfigs:`{{total}} configurations`,publish:`Publish`,history:`History`,listeners:`Listeners`,rollback:`Rollback`,clone:`Clone`,export:`Export`,import:`Import`,backToList:`Back to List`,sampleCode:`Sample Code`,formatContent:`Format`,fullscreen:`Fullscreen`,batchDelete:`Batch Delete`,batchDeleteConfirm:`Delete {{count}} selected configurations?`,exportAll:`Export All`,exportSelected:`Export Selected`,importTitle:`Import Configurations`,importSuccess:`Import successful`,importFailed:`Import failed`,cloneTitle:`Clone Configurations`,sourceNamespace:`Source Namespace`,targetNamespace:`Target Namespace`,conflictPolicy:`Conflict Policy`,policyAbort:`Abort Import`,policySkip:`Skip Existing`,policyOverwrite:`Overwrite Existing`,selectedCount:`{{count}} selected`,selectFile:`Select File`,noFileSelected:`Please select a ZIP file`,noTargetNamespace:`Please select target namespace`,noSelection:`Please select configurations first`,startClone:`Start Clone`,cloneSuccess:`Clone successful`,configCount:`Config Count`,uploadZip:`Upload ZIP File`,batchDeleteSuccess:`Batch delete successful`,importResult:`{{success}} succeeded, {{skip}} skipped`,betaPublish:`Beta Publish`,production:`Production`,beta:`Beta`,betaIps:`Beta IPs`,betaIpsPlaceholder:`Enter IPs, separated by commas`,stopBeta:`Stop Beta`,stopBetaConfirm:`Stop beta publish?`,betaPublishSuccess:`Beta published successfully`,betaStopSuccess:`Beta stopped`,noBeta:`No beta config`},listener:{title:`Listener Query`,queryByConfig:`Query by Config`,queryByIp:`Query by IP`,ip:`IP Address`,md5:`MD5`,noListeners:`No listeners found`,totalListeners:`{{total}} listeners`},history:{title:`Historical Versions`,detailTitle:`History Detail`,rollbackTitle:`Rollback Configuration`,publishType:`Publish Type`,formal:`Release`,gray:`Gray`,grayRule:`Gray Rule`,operator:`Operator`,sourceIp:`Source IP`,opType:`Operation Type`,opInsert:`Insert`,opUpdate:`Update`,opDelete:`Delete`,compare:`Compare`,rollback:`Rollback`,rollbackConfirm:`Are you sure you want to rollback to this version?`,rollbackInsertWarn:`Rollback will delete this configuration`,rollbackUpdateWarn:`Rollback will restore to this version`,rollbackDeleteWarn:`Rollback will re-publish this configuration`,rollbackSuccess:`Rollback successful`,rollbackFailed:`Rollback failed`,selectedVersion:`Selected Version`,currentVersion:`Current Version`,totalHistory:`{{total}} history records`},service:{title:`Service List`,createService:`Create Service`,editService:`Edit Service`,serviceDetail:`Service Detail`,serviceName:`Service Name`,groupName:`Group Name`,clusterCount:`Cluster Count`,ipCount:`IP Count`,healthyInstanceCount:`Healthy Instances`,triggerFlag:`Threshold Triggered`,ignoreEmptyService:`Hide Empty Services`,protectThreshold:`Protect Threshold`,metadata:`Metadata`,selectorType:`Selector Type`,selectorExpression:`Selector Expression`,ephemeral:`Ephemeral`,deleteConfirm:`Delete service {{name}}?`,deleteSuccess:`Service deleted`,deleteHasInstances:`Service has active instances and cannot be deleted. Please deregister all instances first.`,createSuccess:`Service created`,updateSuccess:`Service updated`,namespaceSwitchConfirm:`You have an unfinished service operation. Switch namespace and close it?`,totalServices:`{{total}} services`,clusterName:`Cluster Name`,editCluster:`Edit Cluster`,checkType:`Health Check Type`,checkPort:`Check Port`,checkPath:`Check Path`,checkHeaders:`Check Headers`,useInstancePort:`Use Instance Port`,clusterUpdateSuccess:`Cluster updated`,ip:`IP`,port:`Port`,weight:`Weight`,healthy:`Healthy`,enabled:`Enabled`,instanceEphemeral:`Ephemeral`,editInstance:`Edit Instance`,deleteInstance:`Delete Instance`,instanceUpdateSuccess:`Instance updated`,instanceDeleteSuccess:`Instance deleted`,instanceDeleteConfirm:`Delete instance {{ip}}:{{port}}?`,online:`Online`,offline:`Offline`,noInstances:`No instances in this cluster`,subscriberTitle:`Subscribers`,subscriberName:`Subscriber`,subscribeCount:`Subscribe Count`,clusters:`Clusters`,totalSubscribers:`{{total}} subscribers`,metadataInvalid:`Invalid metadata, please enter valid JSON`,serviceNameRequired:`Service name is required`},mcp:{title:`MCP Server Management`,createServer:`Create MCP Server`,editServer:`Edit MCP Server`,serverDetail:`MCP Server Detail`,serverName:`Server Name`,protocol:`Protocol`,frontProtocol:`Front Protocol`,description:`Description`,repository:`Repository`,websiteUrl:`Website`,version:`Version`,createTime:`Created At`,author:`Author`,status:`Status`,enabled:`Enabled`,disabled:`Disabled`,enable:`Enable`,disable:`Disable`,capabilities:`Capabilities`,tools:`Tools`,toolName:`Tool Name`,toolDescription:`Tool Description`,parameters:`Parameters`,paramName:`Name`,paramType:`Type`,paramRequired:`Required`,paramDescription:`Description`,packages:`Packages`,endpoints:`Endpoints`,frontendEndpoints:`Frontend Endpoints`,backendEndpoints:`Backend Endpoints`,serverSpecification:`Server Specification`,toolSpecification:`Tool Specification`,endpointSpecification:`Endpoint Specification`,localServerConfig:`Local Server Config`,remoteServerConfig:`Remote Server Config`,serviceRef:`Service Reference`,exportPath:`Export Path`,securitySchemes:`Security Schemes`,addSecurityScheme:`Add Security Scheme`,overrideExisting:`Override Existing`,setAsLatest:`Set as Latest`,copyConfig:`Copy Config`,copySuccess:`Config copied to clipboard`,newVersion:`New Version`,importFromRegistry:`Import from MCP Registry`,importSource:`Import Source`,importUrl:`MCP Server URL`,importType:`Import Type`,importTypeJson:`JSON`,importTypeUrl:`URL`,importTypeFile:`File`,importSearchPlaceholder:`Search MCP Server name`,importValidate:`Validate`,importExecute:`Execute Import`,importSuccess:`Import successful`,importFailed:`Import failed`,importResult:`Import result: {{success}} succeeded, {{failed}} failed, {{skipped}} skipped`,importSkipInvalid:`Skip Invalid`,importOverride:`Override Existing`,importConflictPolicy:`Conflict Policy`,importConflictSkip:`Skip`,importConflictOverwrite:`Overwrite`,importAll:`Import all valid`,importSelectedCount:`{{count}} selected`,importValidatedCount:`{{count}} valid after validation`,importSelectAll:`Select all`,importClearSelection:`Clear`,importLoadMore:`Load more`,noImportSource:`No import source available`,importStatusValid:`Valid`,importStatusWarning:`Warning`,importStatusConflict:`Conflict`,importStatusInvalid:`Invalid`,importUnnamed:`Unnamed`,importMetadata:{protocol:`Protocol`,status:`Status`},selectedServers:`Selected Servers`,importTools:`Import Tools`,importToolsFromUrl:`Import Tools from Endpoint`,searchPlaceholder:`Search MCP Server name`,deleteConfirm:`Delete MCP Server "{{name}}"?`,batchDeleteConfirm:`Delete {{count}} selected MCP Servers?`,deleteSuccess:`Deleted successfully`,batchDelete:`Batch Delete`,batchDeleteSuccess:`Batch delete successful`,enableSuccess:`Enabled`,disableSuccess:`Disabled`,createSuccess:`MCP Server created`,updateSuccess:`MCP Server updated`,totalServers:`{{total}} MCP Servers`,noTools:`No tools`,noPackages:`No packages`,noEndpoints:`No endpoints`,serverNameRequired:`Server name is required`,serverSpecRequired:`Server specification is required`,invalidJson:`Invalid JSON format`,formatJson:`Format JSON`,advancedConfig:`Advanced Config`,statusActive:`Active`,statusDeprecated:`Deprecated`,statusDeleted:`Deleted`,protocolStdio:`Stdio`,protocolSse:`MCP-SSE`,protocolStreamable:`MCP-Streamable`,protocolHttp:`HTTP`,protocolDubbo:`Dubbo`,useExistService:`Use Existing Service`,directConnect:`Direct Connect`,httpToMcp:`HTTP to MCP`,mcpServerEndpoint:`MCP Server Endpoint URL`,address:`Address`,port:`Port`,transportProtocol:`Transport Protocol`,selectService:`Select Service`,selectVersion:`Select Version`,inputSchema:`Input Schema`,outputSchema:`Output Schema`,noDescription:`No description`,allVersions:`All Versions`,latestVersion:`Latest`,versionHistory:`Version History`,totalVersions:`{{count}} versions in total`,currentVersion:`Current`,toolCount:`{{count}} tools`,expandAll:`Expand All`,collapseAll:`Collapse All`,backToList:`Back to List`,basicInfo:`Basic Info`,noSecuritySchemes:`No security schemes`,noCapabilities:`No capabilities`,securityType:`Type`,securitySchemeField:`Scheme`,securityIn:`In`,securityDefaultCredential:`Default Credential`,packageIdentifier:`Identifier`,packageRuntime:`Runtime`,packageVersion:`Package Version`,runtimeArguments:`Runtime Arguments`,packageArguments:`Package Arguments`,environmentVariables:`Environment Variables`,noEnvironmentVariables:`No environment variables`,envVarName:`Name`,envVarDefault:`Default`,envVarRequired:`Required`,envVarSecret:`Sensitive`,endpointProtocol:`Protocol`,endpointAddress:`Address`,endpointPort:`Port`,endpointPath:`Path`,endpointHeaders:`Headers`,noHeaders:`No headers`,serviceName:`Service Name`,groupName:`Group Name`,loadFailed:`Failed to load data`,retry:`Retry`,restToMcpSwitch:`HTTP to MCP`,restToMcpSwitchTip:`Convert existing HTTP service to MCP protocol`,useExistServiceOption:`Use Existing Service`,directConnectOption:`Direct Connect`,mcpEndpointUrl:`MCP Server Endpoint URL`,mcpEndpointUrlPlaceholder:`e.g. http://127.0.0.1:8080/sse`,addressPlaceholder:`e.g. 127.0.0.1`,portPlaceholder:`e.g. 8080`,localServerConfigTip:`Enter MCP local server JSON configuration`,versionPlaceholder:`e.g. 1.0.0`,descriptionPlaceholder:`Describe the MCP Server functionality`,securitySchemeId:`Scheme ID`,securitySchemeType:`Auth Type`,schemeBasic:`Basic`,schemeBearer:`Bearer`,inHeader:`Header`,inQuery:`Query`,defaultDownstreamSecurity:`Default Downstream Security`,defaultDownstreamSecurityDesc:`Applies to client-to-gateway requests (e.g., tools/list) when no tool override exists.`,defaultUpstreamSecurity:`Default Upstream Security`,defaultUpstreamSecurityDesc:`Applies to gateway-to-backend calls (e.g., default requestTemplate.security).`,passthroughAuth:`Passthrough Auth`,credentialOverride:`Credential Override`,addRow:`Add`,removeRow:`Remove`,publishAndCreate:`Create & Publish`,publishAndUpdate:`Update & Publish`,saving:`Saving...`,protocolRequired:`Protocol is required`,versionRequired:`Version is required`,addressRequired:`Address is required`,portRequired:`Port is required`,mcpEndpointRequired:`MCP endpoint URL is required`,localConfigRequired:`Local server config is required`,toolManagement:`Tool Management`,noToolsYet:`No tools yet. Add or import tools to get started.`,newTool:`New Tool`,editTool:`Edit Tool`,deleteTool:`Delete Tool`,deleteToolConfirm:`Delete tool "{{name}}"?`,toolNamePlaceholder:`e.g. get_user_info`,toolNameRequired:`Tool name is required`,toolNameExists:`Tool name already exists`,toolEnabled:`Enabled`,searchTools:`Search tools...`,importFromMcpInstance:`Import from MCP Instance`,importFromOpenApi:`Import from OpenAPI`,importToolsFromMCPTitle:`Import Tools from MCP Instance`,importToolsFromOpenAPITitle:`Import Tools from OpenAPI File`,dragOrClickToUpload:`Drag and drop file here or click to select`,supportedFormats:`Supports .json, .yaml, .yml formats`,fileReadFailed:`Failed to read file`,fileInvalidFormat:`Invalid file format`,pleaseSelectFile:`Please select a file`,noHealthyInstance:`No healthy instance found`,importingTools:`Importing...`,importToolsSuccess:`Successfully imported {{count}} tools`,importToolsFailed:`Failed to import tools`,authToken:`Auth Token`,authTokenPlaceholder:`Optional, for authenticated MCP servers`,serverAddress:`Server Address`,toolBasicInfo:`Basic Info`,toolInputSchema:`Input Schema`,toolOutputSchema:`Output Schema`,toolMeta:`Meta`,toolAnnotations:`Annotations`,toolAdvancedConfig:`Advanced Config`,requestTemplate:`Request Template`,responseTemplate:`Response Template`,addParameter:`Add Parameter`,addProperty:`Add Property`,deleteParam:`Delete Parameter`,deleteParamConfirm:`Delete this parameter and its children?`,typeString:`String`,typeNumber:`Number`,typeInteger:`Integer`,typeBoolean:`Boolean`,typeArray:`Array`,typeObject:`Object`,annotationsTitle:`Title`,readOnlyHint:`Read-Only`,readOnlyHintDesc:`Whether this tool only performs read operations`,destructiveHint:`Destructive Action`,destructiveHintDesc:`Whether this tool may perform destructive operations`,idempotentHint:`Idempotent`,idempotentHintDesc:`Whether this tool is idempotent`,openWorldHint:`Open World`,openWorldHintDesc:`Whether this tool may interact with external entities`,foundOperations:`Found {{count}} API operations`,foundSecuritySchemes:`Found {{count}} security schemes`,createTool:`Create Tool`,previewTool:`Preview Tool`,toolParams:`{{count}} params`,noParameters:`No parameters`,selectToolToView:`Select a tool to view details`},agent:{title:`Agent Management`,createAgent:`Create Agent`,editAgent:`Edit Agent`,agentDetail:`Agent Detail`,agentName:`Agent Name`,version:`Version`,protocolVersion:`Protocol Version`,serviceUrl:`Service URL`,transport:`Transport`,selectTransport:`Select transport protocol`,description:`Description`,descriptionPlaceholder:`Describe the Agent functionality...`,noDescription:`No description`,provider:`Provider`,providerName:`Provider Name`,providerNamePlaceholder:`Enter provider name`,providerUrl:`Provider URL`,iconUrl:`Icon URL`,documentationUrl:`Documentation URL`,documentation:`Documentation`,capabilities:`Capabilities`,streaming:`Streaming`,streamingDesc:`Whether streaming data transfer is supported`,pushNotifications:`Push Notifications`,pushNotificationsDesc:`Whether push notification is supported`,stateHistory:`State History`,stateHistoryDesc:`Whether state transition history is supported`,skills:`Skills`,skillsHelp:`Agent skill list in JSON array format`,skillCount:`{{count}} skills`,noSkills:`No skills configured`,inputModes:`Input Modes`,outputModes:`Output Modes`,ioModes:`Input/Output Modes`,security:`Security`,securitySchemes:`Security Schemes`,supportedInterfaces:`Supported Interfaces`,additionalInterfaces:`Additional Interfaces`,interfaceItem:`Interface {{index}}`,preferredInterface:`Preferred Interface`,tenant:`Tenant`,extendedCardSupport:`Extended Card Support`,extendedCardSupportDesc:`Whether authenticated extended card is supported`,advancedConfig:`Advanced Config`,showAdvanced:`Show Advanced Config`,hideAdvanced:`Hide Advanced Config`,setAsLatest:`Set as Latest`,setAsLatestDesc:`When enabled, this version will become the published version`,basicInfo:`Basic Info`,versionHistory:`Version History`,totalVersions:`{{count}} versions in total`,currentVersion:`Current`,isLatest:`Is Latest`,selectVersion:`Select Version`,searchPlaceholder:`Search Agent name`,totalAgents:`{{total}} Agents`,deleteConfirm:`Delete Agent "{{name}}"?`,batchDeleteConfirm:`Delete {{count}} selected Agents?`,deleteSuccess:`Deleted successfully`,batchDelete:`Batch Delete`,batchDeleteSuccess:`Batch delete successful`,createSuccess:`Agent created`,updateSuccess:`Agent updated`,create:`Create`,update:`Update`,publish:`Publish`,confirmPublish:`Confirm Publish`,publishStrategy:`Publish Strategy`,publishStrategyDesc:`Choose how to handle this change. Version:`,strategyNewVersion:`Publish New Version`,strategyNewVersionDesc:`Publish as a new version without setting it as default`,strategySetLatest:`Set as Latest Version`,strategySetLatestDesc:`Set as default version, used when client doesn't specify a version`,strategyEditCurrent:`Update Current Version`,strategyEditCurrentDesc:`Override current version config, keeping version number and latest flag unchanged`,strategyReasonVersionExists:`Version already exists`,strategyReasonVersionNotExists:`Version does not exist`,strategyReasonAlreadyLatest:`Already the latest version`,loadFailed:`Failed to load data`,backToList:`Back to List`,nameRequired:`Agent name is required`,versionRequired:`Version is required`,urlRequired:`Service URL is required`,protocolVersionRequired:`Protocol version is required`,transportRequired:`Please select transport protocol`,jsonFormatError:`JSON Format Error`},prompt:{title:`Prompt Management`,createPrompt:`Create Prompt`,editPrompt:`Edit Prompt`,editMetadata:`Edit Description & Tags`,promptDetail:`Prompt Detail`,promptKey:`Prompt Key`,version:`Version`,latestVersion:`Latest`,template:`Template`,versionDiff:`Version Diff`,diffBeforeVersion:`Base version`,diffAfterVersion:`Target version`,diffSelectVersionPlaceholder:`Select version`,diffSwapVersions:`Swap versions`,diffModifiedContent:`Modified content`,diffTemplateFile:`Template`,diffSelectVersions:`Select a base version and target version to compare`,diffNoChanges:`No template changes between these versions`,diffSameVersion:`Select two different versions to compare`,variables:`Variables`,variableName:`Variable Name`,variableDefault:`Default Value`,variableDescription:`Description`,description:`Description`,commitMsg:`Commit Message`,bizTags:`Business Tags`,labels:`Labels`,labelName:`Label Name`,bindLabel:`Bind Label`,unbindLabel:`Unbind Label`,versionHistory:`Version History`,downloadMarkdown:`Download Markdown`,downloadMarkdownTip:`Export the currently selected version as a Markdown document`,downloadMarkdownSuccess:`Markdown document downloaded successfully`,downloadMarkdownFailed:`Failed to download Markdown document`,downloadCount:`{{count}} downloads`,downloads:`Downloads`,versionDownloads:`Version Downloads`,publishVersion:`Publish New Version`,debug:`Debug`,startDebug:`Start Debug`,clearResult:`Clear Result`,aiOptimize:`AI Optimize`,optimizeGoal:`Optimization Goal`,optimizeGoalPlaceholder:`Describe the optimization goal, e.g.: make it clearer, add examples (optional)`,startOptimize:`Start Optimize`,optimizing:`Optimizing...`,optimizedResult:`Optimized Result`,originalTemplate:`Original Template`,applyOptimize:`Apply Optimized Result`,noDescription:`No description`,noVariables:`No variables`,noLabels:`No labels`,noVersions:`No version history`,totalVersions:`{{count}} versions in total`,currentVersion:`Current Version`,searchPlaceholder:`Search Prompt Key`,totalPrompts:`{{total}} Prompts`,deleteConfirm:`Delete Prompt "{{name}}"?`,batchDeleteConfirm:`Delete {{count}} selected Prompts?`,deleteSuccess:`Deleted successfully`,batchDelete:`Batch Delete`,batchDeleteSuccess:`Batch delete successful`,createSuccess:`Prompt created`,updateSuccess:`Prompt updated`,publishSuccess:`Version published`,bindLabelSuccess:`Label bound successfully`,unbindLabelSuccess:`Label unbound successfully`,metadataUpdateSuccess:`Metadata updated`,loadFailed:`Failed to load data`,backToList:`Back to List`,keyRequired:`Prompt Key is required`,keyInvalid:`Prompt Key only supports letters, numbers, underscores and hyphens`,versionRequired:`Version is required`,versionInvalid:`Invalid version format, should be x.y.z`,versionMustGreater:`New version must be greater than current version {{current}}`,templateRequired:`Template content is required`,labelInvalid:`Label only supports letters, numbers, dots, underscores and hyphens`,selectVersion:`Select Version`,newVersion:`New Version`,newVersionPlaceholder:`e.g. 0.0.2 or v3 (leave empty for auto)`,templatePlaceholder:`Enter Prompt template, use {{variable}} syntax to define variables...`,commitMsgPlaceholder:`Describe the changes...`,descriptionPlaceholder:`Describe the purpose of this Prompt...`,tagPlaceholder:`Type and press Enter to add tags`,keyPlaceholder:`Enter Prompt Key`,versionPlaceholder:`e.g. 1.0.0`,thinking:`Thinking`,modelOutput:`Model Output`,streaming:`Streaming...`,userInput:`User Input`,userInputPlaceholder:`Enter the user message to send to the model`,renderedPrompt:`Rendered Prompt`,send:`Send`,basicInfo:`Basic Info`,templateEditor:`Template Editor`,debugPanel:`Debug Panel`,labelManagement:`Label Management`,addLabel:`Add Label`,manageLabels:`Manage Labels`,labelBoundTo:`Bound to`,publishTime:`Publish Time`,publisher:`Publisher`,retry:`Retry`,"versionStatus.draft":`Draft`,"versionStatus.reviewing":`Reviewing`,"versionStatus.reviewed":`Pending Publish`,"versionStatus.online":`Online`,"versionStatus.offline":`Offline`,"versionStatus.pendingPublish":`Pending Publish`,"versionStatus.rejected":`Rejected`,submit:`Submit for Review`,publish:`Publish`,forcePublish:`Force Publish`,online:`Online`,offline:`Offline`,createDraft:`Create Draft`,createDraftFrom:`Create Draft From`,deleteDraft:`Delete Draft`,saveDraft:`Save Draft`,draftExistsTip:`An editing or reviewing version already exists`,publishDisabledPipeline:`Pipeline has not been approved`,forcePublishConfirmTitle:`Confirm Force Publish`,forcePublishConfirmDesc:`Are you sure you want to force publish version {{version}}? This will bypass pipeline review.`,forcePublishConfirm:`Confirm Force Publish`,onlineCount:`Online: {{count}}`,hasDraft:`Has Draft`,hasReviewing:`Reviewing`,noOnlineVersion:`No Online Version`,submitSuccess:`Submitted for review`,draftSaveSuccess:`Draft saved`,draftDeleteSuccess:`Draft deleted`,onlineSuccess:`Version is now online`,offlineSuccess:`Version is now offline`,forcePublishSuccess:`Force published successfully`,redraft:`Re-edit`,redraftSuccess:`Version returned to draft for re-editing`,labelsUpdateSuccess:`Labels updated`,descriptionUpdateSuccess:`Description updated`,bizTagsUpdateSuccess:`Biz tags updated`,governanceInfo:`Governance Info`,versionTimeline:`Version Timeline`,pipelineStatus:`Review Pipeline`,pipelineInProgress:`Pipeline In Progress`,editDraft:`Edit Draft`,createPromptDesc:`Create a new Prompt draft with template and variables.`,createDraftFromDesc:`Create a new draft based on version {{version}}`,status:`Status`},namespace:{title:`Namespaces`,create:`Create Namespace`,edit:`Edit Namespace`,detail:`Namespace Detail`,name:`Namespace Name`,id:`Namespace ID`,description:`Description`,configCount:`Configs`,quota:`Quota`,namePlaceholder:`Enter namespace name`,idPlaceholder:`Leave empty for auto-generation`,descriptionPlaceholder:`Enter namespace description`,nameRequired:`Namespace name is required`,deleteConfirm:`Delete namespace "{{name}}"? This action cannot be undone.`,deleteSuccess:`Namespace deleted`,createSuccess:`Namespace created`,updateSuccess:`Namespace updated`,publicNamespace:`public`,publicNamespaceDesc:`Public namespace, cannot be edited or deleted`,total:`{{total}} namespaces`},cluster:{title:`Cluster Nodes`,nodeAddress:`Node Address`,nodeIp:`IP`,nodePort:`Port`,nodeState:`State`,searchPlaceholder:`Search node IP or address`,leave:`Deregister`,leaveConfirm:`Remove node "{{address}}" from the cluster?`,leaveSuccess:`Node deregistered`,total:`{{total}} nodes`,stateUp:`Running`,stateDown:`Down`,stateSuspicious:`Suspicious`,refresh:`Refresh`},plugin:{title:`Plugin Management`,pluginName:`Plugin Name`,pluginType:`Plugin Type`,enabled:`Enabled`,disabled:`Disabled`,critical:`Critical`,status:`Status`,allTypes:`All Types`,enable:`Enable`,disable:`Disable`,enableSuccess:`Plugin enabled`,disableSuccess:`Plugin disabled`,detail:`Plugin Detail`,version:`Version`,description:`Description`,configurable:`Configurable`,clusterAvailability:`Cluster Availability`,noPlugins:`No plugins`,total:`{{total}} plugins`,typeAuth:`Authentication`,typeDatasource:`Data Source`,typeConfigChange:`Config Change`,typeEncryption:`Encryption`,typeTrace:`Trace`,typeEnvironment:`Environment`,typeControl:`Control`,typeAiPipeline:`AI Pipeline`,typeAiStorage:`AI Storage`,typeVisibility:`Visibility`,availableNodes:`Available Nodes`,yes:`Yes`,no:`No`,pluginCount:`plugins`},authority:{title:`Authority Control`,userManagement:`User Management`,roleManagement:`Role Management`,permissionManagement:`Permission Management`,username:`Username`,password:`Password`,newPassword:`New Password`,confirmPassword:`Confirm Password`,role:`Role`,resource:`Resource`,action:`Action`,createUser:`Create User`,deleteUser:`Delete User`,resetPassword:`Reset Password`,bindRole:`Bind Role`,addPermission:`Add Permission`,usernamePlaceholder:`Enter username`,passwordPlaceholder:`Enter password`,confirmPasswordPlaceholder:`Enter password again`,newPasswordPlaceholder:`Enter new password`,rolePlaceholder:`Enter role name`,selectRolePlaceholder:`Select role`,selectUserPlaceholder:`Select user`,resourcePlaceholder:`Select resource`,searchUserPlaceholder:`Search username`,searchRolePlaceholder:`Search role name`,usernameRequired:`Username is required`,passwordRequired:`Password is required`,passwordMismatch:`Passwords do not match`,roleRequired:`Role name is required`,resourceRequired:`Resource is required`,actionRequired:`Please select an action`,deleteUserConfirm:`Delete user "{{name}}"?`,deleteRoleConfirm:`Remove role "{{role}}" from user "{{username}}"?`,deletePermissionConfirm:`Delete this permission?`,createUserSuccess:`User created`,deleteUserSuccess:`User deleted`,resetPasswordSuccess:`Password reset`,createRoleSuccess:`Role bound`,deleteRoleSuccess:`Role binding deleted`,createPermissionSuccess:`Permission added`,deletePermissionSuccess:`Permission deleted`,totalUsers:`{{total}} users`,totalRoles:`{{total}} role bindings`,totalPermissions:`{{total}} permissions`,actionRead:`Read Only`,actionWrite:`Write Only`,actionReadWrite:`Read & Write`,fuzzySearch:`Fuzzy Search`,exactSearch:`Exact Search`,adminCannotDelete:`Admin role cannot be deleted`},settings:{title:`Settings`,description:`Manage Nacos console preferences and integrations`,copilotConfig:`Copilot Configuration`,copilotConfigDesc:`Configure the AI assistant model and credentials for intelligent configuration management`,apiKey:`API Key`,apiKeyHint:`It is recommended to set this via the environment variable COPILOT_API_KEY, which takes precedence over this setting.`,apiKeyPlaceholder:`Enter API Key`,model:`Model`,modelPlaceholder:`Select a model`,saveSuccess:`Saved successfully`},agentSpec:{title:`AgentSpec Management`,totalAgentSpecs:`{{total}} AgentSpecs`,upload:`Upload`,createAgentSpec:`Create AgentSpec`,createAgentSpecDesc:`Fill in basic info to create the first draft version`,createSuccess:`AgentSpec created`,createFailed:`Failed to create AgentSpec`,editAgentSpec:`Edit AgentSpec`,newFile:`New File`,newFolder:`New Folder`,basicInfo:`Basic Info`,editor:`Editor`,editorLoading:`Loading editor...`,editBasicInfo:`Edit Basic Info`,agentSpecName:`AgentSpec Name`,namespace:`Namespace`,files:`File Overview`,fileTreeTitle:`Files`,fileTree:`File tree`,activeFile:`Active File`,workspaceSummary:`Workspace Summary`,unnamedAgentSpec:`Untitled AgentSpec`,noDescriptionPreview:`The manifest description will appear here so you can verify the AgentSpec positioning while editing.`,modeNew:`New`,modeEdit:`Draft`,modeVersion:`New Version`,modePreview:`Preview`,modified:`Modified`,readOnly:`Read Only`,resizeFileTreePanel:`Resize file tree panel`,renameNode:`Rename {{name}}`,deleteNode:`Delete {{name}}`,createFileIn:`New file in {{name}}`,createFolderIn:`New folder in {{name}}`,resourceRenameFile:`Rename file`,resourceCopyFile:`Copy file content`,resourceDownloadFile:`Download file`,resourceDeleteFile:`Delete file`,resourceCopySuccess:`File content copied`,resourceCopyFailed:`Failed to copy file content`,createFile:`Create File`,createFolder:`Create Folder`,createFileDesc:`Enter a file path and choose the resource type.`,createFolderDesc:`Enter a folder path and choose the resource type.`,resourceType:`Resource Type`,resourceTypeRequired:`Resource type is required`,customFolder:`Custom`,filePath:`File Path`,folderPath:`Folder Path`,filePathPlaceholderCompact:`e.g. tools/helper.py`,folderPathPlaceholderCompact:`e.g. tools`,filePathPlaceholder:`e.g. skill/tools/helper.py`,folderPathPlaceholder:`e.g. skill/tools`,pathRequired:`Path is required`,invalidFileName:`Invalid file name`,fileExists:`File already exists`,folderExists:`Folder already exists`,namePlaceholder:`Enter AgentSpec name`,descriptionPlaceholder:`Enter AgentSpec description`,searchPlaceholder:`Search AgentSpec name`,sortDefault:`Default sort`,sortByDownloads:`Sort by downloads`,filterByOwner:`Filter by owner`,filterOwnerPlaceholder:`Enter owner name`,filterOnlyMine:`Only mine`,filterScopeAll:`All scopes`,filterScopePublic:`Public only`,filterScopePrivate:`Private only`,batchDelete:`Batch Delete`,deleteConfirm:`Delete AgentSpec "{{name}}"?`,batchDeleteConfirm:`Delete {{count}} selected AgentSpecs?`,deleteSuccess:`Deleted successfully`,batchDeleteSuccess:`Batch delete successful`,loadListError:`Failed to fetch agent specs`,loadError:`Failed to load AgentSpec details`,nameRequired:`AgentSpec name is required`,descriptionRequired:`Description is required`,saveSuccess:`Saved successfully`,unsaved:`Unsaved`,enabled:`Enabled`,disabled:`Disabled`,version:`Version`,createTime:`Created At`,status:`Status`,versionStatus:{draft:`Draft`,reviewing:`Reviewing`,reviewed:`Pending Publish`,pendingPublish:`Pending Publish`,rejected:`Rejected`,online:`Online`,offline:`Offline`},updateTime:`Updated At`,onlineCountLabel:`Online Status`,description:`Description`,noDescription:`No description`,editing:`Editing`,hasDraft:`Has Draft`,draftVersion:`Draft`,reviewing:`Reviewing`,reviewingVersion:`Reviewing`,submit:`Submit`,resubmit:`Resubmit`,saveAndPublish:`Save and Publish`,publish:`Publish`,online:`Version Online`,offline:`Version Offline`,onlineCount:`Online versions: {{count}}`,noOnlineVersion:`No online versions`,downloadCount:`{{count}} downloads`,downloads:`Downloads`,versionDownloads:`Version Downloads`,author:`Author`,backToList:`Back to List`,selectVersion:`Select version`,latestVersion:`Latest`,totalVersions:`{{count}} versions`,versionHistory:`Version History`,resources:`Resources`,agentsFile:`AGENTS.md`,noAgentsFile:`No AGENTS.md content`,labels:`Labels`,newVersion:`New Version`,createDraft:`Create Draft`,createDraftFrom:`Create draft from this version`,draftExistsTip:`A draft or unpublished version already exists`,createDraftSuccess:`Draft created`,deleteDraft:`Delete Draft`,editDraft:`Edit Draft`,deleteDraftSuccess:`Draft deleted`,submitSuccess:`Submitted for review`,publishSuccess:`Published successfully`,redraft:`Re-edit`,redraftSuccess:`Version returned to draft for re-editing`,onlineSuccess:`Online`,offlineSuccess:`Offline`,enableSuccess:`AgentSpec enabled`,disableSuccess:`AgentSpec disabled`,scopePublic:`Public`,scopePrivate:`Private`,scopeUpdateSuccess:`Scope updated`,pipelineStatus:`Review Pipeline`,pipelineInProgress:`In Progress`,pipelineApproved:`Approved`,pipelineRejected:`Review Failed`,pipelineNone:`No review pipeline available`,pipelineNodePassed:`Passed`,pipelineNodeFailed:`Failed`,pipelineNoMessage:`No output message`,labelsUpdateSuccess:`Labels updated`,bizTagsUpdateSuccess:`Tags updated`,bizTagPlaceholder:`Add a tag`,noBizTags:`No tags`,noLabels:`No labels`,noVersions:`No versions`,labelKeyRequired:`Label key is required`,labelKeyDuplicate:`Label key already exists`,labelKeyInvalid:`Label key contains invalid characters`,labelKey:`Key`,labelValue:`Value`,saveLabels:`Save Labels`,uploadZip:`Upload AgentSpec ZIP`,uploadZipDesc:`Select a .zip file to import an AgentSpec package.`,invalidZipFile:`Please select a valid .zip file`,uploadSuccess:`Upload successful`,uploadFailed:`Upload failed`,dragOrClick:`Click to select a .zip file`,cancelEdit:`Cancel`,saveDraft:`Save Draft`,draftSaveSuccess:`Draft saved`},skill:JSON.parse(`{"title":"Skill Management","totalSkills":"{{total}} Skills","upload":"Upload","searchPlaceholder":"Search Skill name","skillName":"Skill Name","description":"Description","noDescription":"No description","enabled":"Enabled","disabled":"Disabled","editing":"Editing","hasDraft":"Has Draft","reviewing":"Reviewing","online":"Version Online","offline":"Version Offline","versionStatus":{"draft":"Draft","reviewing":"Reviewing","reviewed":"Pending Publish","pendingPublish":"Pending Publish","rejected":"Rejected","online":"Online","offline":"Offline"},"onlineCount":"Online versions: {{count}}","noOnlineVersion":"No online versions","downloadCount":"{{count}} downloads","downloads":"Downloads","versionDownloads":"Version Downloads","basicInfo":"Basic Info","instruction":"SKILL.md","skillMd":"SKILL.md","resources":"Resources","noResources":"No resources","resourceRenameFile":"Rename file","resourceCopyFile":"Copy file content","resourceDownloadFile":"Download file","resourceDeleteFile":"Delete file","resourceCopySuccess":"File content copied","resourceCopyFailed":"Failed to copy file content","versionDiff":"Version Diff","diffBeforeVersion":"Base version","diffAfterVersion":"Target version","diffSelectVersionPlaceholder":"Select version","diffSwapVersions":"Swap versions","diffAddedFiles":"Added files","diffRemovedFiles":"Removed files","diffModifiedFiles":"Modified files","diffChangedFiles":"Changed files","diffSelectVersions":"Select a base version and target version to compare","diffNoChanges":"No file changes between these versions","diffSameVersion":"Select two different versions to compare","diffTextUnsupported":"Text diff is not available for this file","diffTextUnsupportedDesc":"Images, presentations, archives, and similar files cannot be compared as text. Only the version change status is shown.","diffStatusAdded":"Added","diffStatusRemoved":"Removed","diffStatusModified":"Modified","resourceName":"Resource Name","resourceType":"Resource Type","resourceContent":"Content","labels":"Labels","noLabels":"No labels","version":"Version","status":"Status","createTime":"Created At","updateTime":"Updated At","author":"Author","versionHistory":"Version History","totalVersions":"{{count}} versions","noVersions":"No versions","selectVersion":"Select version","latestVersion":"Latest","newVersion":"New Version","createDraft":"Create Draft","createDraftSuccess":"Draft created","submit":"Submit","resubmit":"Resubmit","submitSuccess":"Submitted for review","publish":"Publish","publishSuccess":"Published successfully","onlineSuccess":"Online","offlineSuccess":"Offline","enableSuccess":"Skill enabled","disableSuccess":"Skill disabled","scopePublic":"Public","scopePrivate":"Private","toggleScope":"Toggle visibility scope","scopeUpdateSuccess":"Scope updated","masterSwitch":"Skill Master Switch","masterSwitchDesc":"Disabling hides all versions from Agents","skillDisabledWarning":"Skill is disabled, version changes won't take effect","versionActions":"Version Actions","labelsUpdateSuccess":"Labels updated","labelManagement":"Version Labels","labelBindDesc":"Select labels to bind to version {{version}}, or create new ones.","searchOrCreateLabel":"Search or create label...","createLabel":"Create label \\"{{name}}\\"","selectAll":"Select all {{count}}","clearAll":"Clear","deleteConfirm":"Delete Skill \\"{{name}}\\"?","batchDeleteConfirm":"Delete {{count}} selected Skills?","deleteSuccess":"Deleted successfully","batchDelete":"Batch Delete","batchDeleteSuccess":"Batch delete successful","backToList":"Back to List","loadFailed":"Failed to load data","uploadZip":"Upload Skill ZIP","uploadZipDesc":"Select a .zip file to import a Skill package.","invalidZipFile":"Please select a valid .zip file","uploadSuccess":"Upload successful","uploadSuccessWithName":"Skill {{name}} uploaded successfully","uploadFailed":"Upload failed","precheckUpload":"Parse and Check","confirmUpload":"Confirm Upload","confirmForceOverwriteUpload":"Force Overwrite and Upload","confirmBatchUpload":"Confirm Upload","uploadChecking":"Checking","uploadPrecheckBlocked":"This Skill package cannot be uploaded","precheckNewSkill":"This Skill does not exist. Uploading will create draft version {{version}}.","precheckExistingSkillCreateDraft":"This Skill already exists. Uploading will create draft version {{version}}.","precheckVersionExists":"Parsed version {{version}} already exists.","precheckDraftOverwriteOnly":"Draft {{draftVersion}} already exists. Continuing will overwrite it, and the draft version after upload will be {{version}}. Do you want to overwrite it?","precheckVersionConverted":"Uploaded version {{parsedVersion}} has been converted to valid version {{version}}.","precheckVersionNormalizedAndAdjusted":"Uploaded version {{parsedVersion}} was normalized to {{normalizedVersion}}; because {{normalizedVersion}} already exists, this upload will create draft version {{version}}.","precheckCreateVersionAdjusted":"Parsed version {{parsedVersion}} is already occupied. This upload will create draft version {{version}}.","precheckReviewingBlocked":"Reviewing version {{version}} already exists. Uploading is blocked until review finishes.","precheckNoPermission":"You do not have permission to edit this Skill.","parsedVersion":"Parsed version","uploadedVersion":"Uploaded version","resolvedVersion":"Target version","resultVersion":"Target version: {{version}}","dragOrClick":"Drag .zip here or click to select","dragDropHint":"Supports dragging .zip files to upload","dropFileHere":"Drop .zip file here to upload","batchUploadResult":"Batch upload: {{succeeded}} succeeded, {{failed}} failed","batchUploadAllSuccess":"All {{count}} skills uploaded successfully","batchUploadSuccessWithSkipped":"{{succeeded}} skills uploaded successfully, {{skipped}} skipped","batchUploadSkipped":"{{count}} skills skipped","batchUploadPartialFail":"{{succeeded}} succeeded, {{failed}} failed: {{reasons}}","batchUploadAllFailed":"All {{count}} skills failed to upload","batchUploadAborted":"{{existing}} existing skills and {{blocked}} blocked items detected. Upload aborted.","batchUploadNothingToUpload":"No skills to upload.","targetNamespace":"Target Namespace","batchSkillCount":"Skill Count","batchUploadOnlyEntryCount":"Invalid / non-skill","batchPrecheckSummary":"Checked {{total}} skills: {{fresh}} new, {{existing}} existing, {{blocked}} blocked.","batchPrecheckBlockedTip":"Skills without permission or under review will be reported as failed in the batch result.","batchPrecheckUploadOnlyTip":"{{count}} folder(s) are not valid skills. They are excluded from skill conflict checks and will still be shown in the batch upload result.","sameSkillPolicy":"Same Skill:","batchPolicyAbort":"Abort upload","batchPolicySkip":"Skip","batchPolicyOverwrite":"Overwrite","batchPolicySkipDrafts":"Skip existing drafts","batchPolicyOverwriteDrafts":"Overwrite existing drafts","batchPolicySkipDraftsDesc":"Skills with existing drafts will not be uploaded. Other skills use normal upload rules, and failures remain in the batch result.","batchPolicyOverwriteDraftsDesc":"Skills with existing drafts will be overwritten by this ZIP. Skills without permission or under review can still fail.","batchItemNew":"New","batchItemExisting":"Existing","batchItemDraft":"Existing draft","batchItemBlocked":"Blocked","batchItemInvalidSkill":"Parse failed","batchItemInvalidSkillDesc":"SKILL.md is not a valid skill descriptor","batchItemNonSkillFolder":"Non-skill","batchItemNonSkillFolderDesc":"This folder has no SKILL.md and is not prechecked as a skill","batchItemVersionConverted":"Uploaded version {{parsedVersion}} -> {{version}}","batchItemVersionNormalizedAndAdjusted":"Uploaded version {{parsedVersion}} -> {{normalizedVersion}} -> {{version}}","resultSucceeded":"Succeeded ({{count}})","resultFailed":"Failed ({{count}})","importFromRegistry":"Import from Skill Registry","importSource":"Import Source","importSearchPlaceholder":"Search Skill name","importValidate":"Validate","importExecute":"Execute Import","importSuccess":"Import successful","importFailed":"Import failed","importResult":"Import result: {{success}} succeeded, {{failed}} failed, {{skipped}} skipped","importSkipInvalid":"Skip Invalid","importOverride":"Override Existing","importConflictPolicy":"Conflict Policy","importConflictSkip":"Skip","importConflictOverwrite":"Overwrite","importAll":"Import all valid","importSelectedCount":"{{count}} selected","importValidatedCount":"{{count}} valid after validation","importSelectAll":"Select all","importClearSelection":"Clear","importLoadMore":"Load more","noImportSource":"No import source available","importStatusValid":"valid","importStatusWarning":"warning","importStatusConflict":"conflict","importStatusInvalid":"invalid","importUnnamed":"Unnamed","importMetadata":{"fileCount":"Files","source":"Source"},"download":"Download","downloadVersion":"Download Version","createSkill":"Create Skill","createSkillDesc":"Create a new Skill with a draft version. You can edit the content later.","namePlaceholder":"Enter Skill name","descPlaceholder":"Describe the Skill functionality","skillMdPlaceholder":"Enter the full SKILL.md content...","skillMdHint":"When an Agent loads this Skill, the full SKILL.md content (frontmatter and body) is injected into the execution context.","nameRequired":"Skill name is required","nameTooLong":"Skill name must be 1–64 characters","nameInvalidFormat":"Skill name may only contain lowercase letters, numbers, and hyphens, and must not start or end with a hyphen","nameNoConsecutiveHyphens":"Skill name must not contain consecutive hyphens (--)","descriptionRequired":"Description is required","instructionRequired":"SKILL.md content is required","skillMdRequired":"SKILL.md content is required","submitRequiresFields":"Please fill in the description and SKILL.md content before submitting.","createSuccess":"Skill created","createFailed":"Failed to create Skill","retry":"Retry","editDraft":"Edit Draft","saveDraft":"Save Draft","cancelEdit":"Cancel","draftSaveSuccess":"Draft saved","commitMsg":"Version commit message","commitMsgPlaceholder":"Optional: describe what changed in this draft version","commitMsgHint":"This is stored on the draft version only (shown in version history). It is not the skill description above.","toggleEnable":"Enable/Disable Skill","pipelineStatus":"Review Pipeline","pipelineInProgress":"In Progress","pipelineApproved":"Approved","pipelineRejected":"Review Failed","pipelineNone":"No review pipeline configured","pipelineNodePassed":"Passed","pipelineNodeFailed":"Failed","pipelineNoMessage":"No output message","publishDisabledPipeline":"Publish requires pipeline approval","forcePublish":"Force Publish","forcePublishSuccess":"Force-published successfully","redraft":"Re-edit","redraftSuccess":"Version returned to draft for re-editing","forcePublishConfirmTitle":"Force Publish (Bypass Pipeline)","forcePublishConfirmDesc":"You are about to force-publish version \\"{{version}}\\" by bypassing the review pipeline. This will publish the skill immediately regardless of pipeline results. This action is irreversible. Are you sure?","forcePublishConfirm":"Confirm Force Publish","createDraftFrom":"Create draft from this version","newVersionTitle":"Create New Draft Version","newVersionDesc":"Create a new draft from version {{current}}. Enter the target version","newVersionPlaceholder":"e.g. 1.0.1 or v2","newVersionRequired":"New version is required","versionInvalid":"Invalid version format, should be x.y.z or vN","versionMustGreater":"New version must be greater than current version {{current}}","draftExistsTip":"A draft or unpublished version already exists","bizTagsUpdateSuccess":"Tags updated","bizTagPlaceholder":"Add a tag","noBizTags":"No business tags","sortDefault":"Default sort","sortByDownloads":"Sort by downloads","filterByOwner":"Filter by owner","filterOwnerPlaceholder":"Enter owner name","filterOnlyMine":"Only mine","filterScopeAll":"All scopes","filterScopePublic":"Public only","filterScopePrivate":"Private only","filterByBizTag":"Filter by business tag","filterBizTagPlaceholder":"Enter business tag","labelKeyRequired":"Label key is required","labelKeyInvalid":"Invalid label key format","labelKeyDuplicate":"Label key already exists","saveLabels":"Save Labels","labelKey":"Key","labelValue":"Version","deleteDraft":"Delete Draft","deleteDraftSuccess":"Draft deleted","manualCreate":"Manual","aiGenerate":"AI Generate","aiOptimize":"AI Optimize","backgroundInfo":"Background Information","backgroundInfoPlaceholder":"Describe the functionality, purpose, and target users of the Skill you want to create...","backgroundInfoRequired":"Please enter background information","generateSkill":"Generate Skill","generating":"Generating...","generateSuccess":"Skill generated successfully","generateFailed":"Failed to generate Skill","regenerate":"Regenerate","applyGenerated":"Apply Generated Result","optimizationGoal":"Optimization Goal","optimizationGoalPlaceholder":"Describe how you want to optimize this Skill (optional)...","selectTargetFile":"Select target file","startOptimize":"Start Optimization","optimizing":"Optimizing...","optimizeSuccess":"Skill optimized successfully","optimizeFailed":"Failed to optimize Skill","applyOptimize":"Apply Optimization","originalContent":"Original Content","optimizedContent":"Optimized Content","mcpToolsOptional":"MCP Tools (Optional)","selectMcpServer":"Select MCP Server","availableTools":"Available Tools","searchTools":"Search tools...","selectedToolsCount":"{{count}} selected","noToolsAvailable":"No tools available","loadingTools":"Loading tools...","conversationHistory":"Conversation History (Optional)","conversationHistoryPlaceholder":"Conversation history in JSON format for multi-round generation...","conversationHistoryInvalid":"Invalid conversation history JSON format","thinking":"Thinking Process","generatedContent":"Generated Content","streamContent":"Stream Output","selectResource":"Select a file to view its content","editMode":"Edit Mode","binaryFileHint":"Preview is not supported for this file","imagePreviewUnavailable":"Unable to preview this image file"}`)}}};o.use(a).init({resources:b,lng:p(),fallbackLng:`en-US`,interpolation:{escapeValue:!1},react:{useSuspense:!1}});var x=o;function S(e){if(!e||typeof document>`u`)return;let t=document.head||document.getElementsByTagName(`head`)[0],n=document.createElement(`style`);n.type=`text/css`,t.appendChild(n),n.styleSheet?n.styleSheet.cssText=e:n.appendChild(document.createTextNode(e))}var C=e=>{switch(e){case`success`:return E;case`info`:return O;case`warning`:return D;case`error`:return k;default:return null}},w=Array(12).fill(0),T=({visible:e,className:t})=>s.createElement(`div`,{className:[`sonner-loading-wrapper`,t].filter(Boolean).join(` `),"data-visible":e},s.createElement(`div`,{className:`sonner-spinner`},w.map((e,t)=>s.createElement(`div`,{className:`sonner-loading-bar`,key:`spinner-bar-${t}`})))),E=s.createElement(`svg`,{xmlns:`http://www.w3.org/2000/svg`,viewBox:`0 0 20 20`,fill:`currentColor`,height:`20`,width:`20`},s.createElement(`path`,{fillRule:`evenodd`,d:`M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z`,clipRule:`evenodd`})),D=s.createElement(`svg`,{xmlns:`http://www.w3.org/2000/svg`,viewBox:`0 0 24 24`,fill:`currentColor`,height:`20`,width:`20`},s.createElement(`path`,{fillRule:`evenodd`,d:`M9.401 3.003c1.155-2 4.043-2 5.197 0l7.355 12.748c1.154 2-.29 4.5-2.599 4.5H4.645c-2.309 0-3.752-2.5-2.598-4.5L9.4 3.003zM12 8.25a.75.75 0 01.75.75v3.75a.75.75 0 01-1.5 0V9a.75.75 0 01.75-.75zm0 8.25a.75.75 0 100-1.5.75.75 0 000 1.5z`,clipRule:`evenodd`})),O=s.createElement(`svg`,{xmlns:`http://www.w3.org/2000/svg`,viewBox:`0 0 20 20`,fill:`currentColor`,height:`20`,width:`20`},s.createElement(`path`,{fillRule:`evenodd`,d:`M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z`,clipRule:`evenodd`})),k=s.createElement(`svg`,{xmlns:`http://www.w3.org/2000/svg`,viewBox:`0 0 20 20`,fill:`currentColor`,height:`20`,width:`20`},s.createElement(`path`,{fillRule:`evenodd`,d:`M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-8-5a.75.75 0 01.75.75v4.5a.75.75 0 01-1.5 0v-4.5A.75.75 0 0110 5zm0 10a1 1 0 100-2 1 1 0 000 2z`,clipRule:`evenodd`})),A=s.createElement(`svg`,{xmlns:`http://www.w3.org/2000/svg`,width:`12`,height:`12`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`1.5`,strokeLinecap:`round`,strokeLinejoin:`round`},s.createElement(`line`,{x1:`18`,y1:`6`,x2:`6`,y2:`18`}),s.createElement(`line`,{x1:`6`,y1:`6`,x2:`18`,y2:`18`})),j=()=>{let[e,t]=s.useState(document.hidden);return s.useEffect(()=>{let e=()=>{t(document.hidden)};return document.addEventListener(`visibilitychange`,e),()=>window.removeEventListener(`visibilitychange`,e)},[]),e},M=1,N=new class{constructor(){this.subscribe=e=>(this.subscribers.push(e),()=>{let t=this.subscribers.indexOf(e);this.subscribers.splice(t,1)}),this.publish=e=>{this.subscribers.forEach(t=>t(e))},this.addToast=e=>{this.publish(e),this.toasts=[...this.toasts,e]},this.create=e=>{let{message:t,...n}=e,r=typeof e?.id==`number`||e.id?.length>0?e.id:M++,i=this.toasts.find(e=>e.id===r),a=e.dismissible===void 0?!0:e.dismissible;return this.dismissedToasts.has(r)&&this.dismissedToasts.delete(r),i?this.toasts=this.toasts.map(n=>n.id===r?(this.publish({...n,...e,id:r,title:t}),{...n,...e,id:r,dismissible:a,title:t}):n):this.addToast({title:t,...n,dismissible:a,id:r}),r},this.dismiss=e=>(e?(this.dismissedToasts.add(e),requestAnimationFrame(()=>this.subscribers.forEach(t=>t({id:e,dismiss:!0})))):this.toasts.forEach(e=>{this.subscribers.forEach(t=>t({id:e.id,dismiss:!0}))}),e),this.message=(e,t)=>this.create({...t,message:e}),this.error=(e,t)=>this.create({...t,message:e,type:`error`}),this.success=(e,t)=>this.create({...t,type:`success`,message:e}),this.info=(e,t)=>this.create({...t,type:`info`,message:e}),this.warning=(e,t)=>this.create({...t,type:`warning`,message:e}),this.loading=(e,t)=>this.create({...t,type:`loading`,message:e}),this.promise=(e,t)=>{if(!t)return;let n;t.loading!==void 0&&(n=this.create({...t,promise:e,type:`loading`,message:t.loading,description:typeof t.description==`function`?void 0:t.description}));let r=Promise.resolve(e instanceof Function?e():e),i=n!==void 0,a,o=r.then(async e=>{if(a=[`resolve`,e],s.isValidElement(e))i=!1,this.create({id:n,type:`default`,message:e});else if(P(e)&&!e.ok){i=!1;let r=typeof t.error==`function`?await t.error(`HTTP error! status: ${e.status}`):t.error,a=typeof t.description==`function`?await t.description(`HTTP error! status: ${e.status}`):t.description,o=typeof r==`object`&&!s.isValidElement(r)?r:{message:r};this.create({id:n,type:`error`,description:a,...o})}else if(e instanceof Error){i=!1;let r=typeof t.error==`function`?await t.error(e):t.error,a=typeof t.description==`function`?await t.description(e):t.description,o=typeof r==`object`&&!s.isValidElement(r)?r:{message:r};this.create({id:n,type:`error`,description:a,...o})}else if(t.success!==void 0){i=!1;let r=typeof t.success==`function`?await t.success(e):t.success,a=typeof t.description==`function`?await t.description(e):t.description,o=typeof r==`object`&&!s.isValidElement(r)?r:{message:r};this.create({id:n,type:`success`,description:a,...o})}}).catch(async e=>{if(a=[`reject`,e],t.error!==void 0){i=!1;let r=typeof t.error==`function`?await t.error(e):t.error,a=typeof t.description==`function`?await t.description(e):t.description,o=typeof r==`object`&&!s.isValidElement(r)?r:{message:r};this.create({id:n,type:`error`,description:a,...o})}}).finally(()=>{i&&(this.dismiss(n),n=void 0),t.finally==null||t.finally.call(t)}),c=()=>new Promise((e,t)=>o.then(()=>a[0]===`reject`?t(a[1]):e(a[1])).catch(t));return typeof n!=`string`&&typeof n!=`number`?{unwrap:c}:Object.assign(n,{unwrap:c})},this.custom=(e,t)=>{let n=t?.id||M++;return this.create({jsx:e(n),id:n,...t}),n},this.getActiveToasts=()=>this.toasts.filter(e=>!this.dismissedToasts.has(e.id)),this.subscribers=[],this.toasts=[],this.dismissedToasts=new Set}},ee=(e,t)=>{let n=t?.id||M++;return N.addToast({title:e,...t,id:n}),n},P=e=>e&&typeof e==`object`&&`ok`in e&&typeof e.ok==`boolean`&&`status`in e&&typeof e.status==`number`,F=Object.assign(ee,{success:N.success,info:N.info,warning:N.warning,error:N.error,custom:N.custom,message:N.message,promise:N.promise,dismiss:N.dismiss,loading:N.loading},{getHistory:()=>N.toasts,getToasts:()=>N.getActiveToasts()});S(`[data-sonner-toaster][dir=ltr],html[dir=ltr]{--toast-icon-margin-start:-3px;--toast-icon-margin-end:4px;--toast-svg-margin-start:-1px;--toast-svg-margin-end:0px;--toast-button-margin-start:auto;--toast-button-margin-end:0;--toast-close-button-start:0;--toast-close-button-end:unset;--toast-close-button-transform:translate(-35%, -35%)}[data-sonner-toaster][dir=rtl],html[dir=rtl]{--toast-icon-margin-start:4px;--toast-icon-margin-end:-3px;--toast-svg-margin-start:0px;--toast-svg-margin-end:-1px;--toast-button-margin-start:0;--toast-button-margin-end:auto;--toast-close-button-start:unset;--toast-close-button-end:0;--toast-close-button-transform:translate(35%, -35%)}[data-sonner-toaster]{position:fixed;width:var(--width);font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;--gray1:hsl(0, 0%, 99%);--gray2:hsl(0, 0%, 97.3%);--gray3:hsl(0, 0%, 95.1%);--gray4:hsl(0, 0%, 93%);--gray5:hsl(0, 0%, 90.9%);--gray6:hsl(0, 0%, 88.7%);--gray7:hsl(0, 0%, 85.8%);--gray8:hsl(0, 0%, 78%);--gray9:hsl(0, 0%, 56.1%);--gray10:hsl(0, 0%, 52.3%);--gray11:hsl(0, 0%, 43.5%);--gray12:hsl(0, 0%, 9%);--border-radius:8px;box-sizing:border-box;padding:0;margin:0;list-style:none;outline:0;z-index:999999999;transition:transform .4s ease}@media (hover:none) and (pointer:coarse){[data-sonner-toaster][data-lifted=true]{transform:none}}[data-sonner-toaster][data-x-position=right]{right:var(--offset-right)}[data-sonner-toaster][data-x-position=left]{left:var(--offset-left)}[data-sonner-toaster][data-x-position=center]{left:50%;transform:translateX(-50%)}[data-sonner-toaster][data-y-position=top]{top:var(--offset-top)}[data-sonner-toaster][data-y-position=bottom]{bottom:var(--offset-bottom)}[data-sonner-toast]{--y:translateY(100%);--lift-amount:calc(var(--lift) * var(--gap));z-index:var(--z-index);position:absolute;opacity:0;transform:var(--y);touch-action:none;transition:transform .4s,opacity .4s,height .4s,box-shadow .2s;box-sizing:border-box;outline:0;overflow-wrap:anywhere}[data-sonner-toast][data-styled=true]{padding:16px;background:var(--normal-bg);border:1px solid var(--normal-border);color:var(--normal-text);border-radius:var(--border-radius);box-shadow:0 4px 12px rgba(0,0,0,.1);width:var(--width);font-size:13px;display:flex;align-items:center;gap:6px}[data-sonner-toast]:focus-visible{box-shadow:0 4px 12px rgba(0,0,0,.1),0 0 0 2px rgba(0,0,0,.2)}[data-sonner-toast][data-y-position=top]{top:0;--y:translateY(-100%);--lift:1;--lift-amount:calc(1 * var(--gap))}[data-sonner-toast][data-y-position=bottom]{bottom:0;--y:translateY(100%);--lift:-1;--lift-amount:calc(var(--lift) * var(--gap))}[data-sonner-toast][data-styled=true] [data-description]{font-weight:400;line-height:1.4;color:#3f3f3f}[data-rich-colors=true][data-sonner-toast][data-styled=true] [data-description]{color:inherit}[data-sonner-toaster][data-sonner-theme=dark] [data-description]{color:#e8e8e8}[data-sonner-toast][data-styled=true] [data-title]{font-weight:500;line-height:1.5;color:inherit}[data-sonner-toast][data-styled=true] [data-icon]{display:flex;height:16px;width:16px;position:relative;justify-content:flex-start;align-items:center;flex-shrink:0;margin-left:var(--toast-icon-margin-start);margin-right:var(--toast-icon-margin-end)}[data-sonner-toast][data-promise=true] [data-icon]>svg{opacity:0;transform:scale(.8);transform-origin:center;animation:sonner-fade-in .3s ease forwards}[data-sonner-toast][data-styled=true] [data-icon]>*{flex-shrink:0}[data-sonner-toast][data-styled=true] [data-icon] svg{margin-left:var(--toast-svg-margin-start);margin-right:var(--toast-svg-margin-end)}[data-sonner-toast][data-styled=true] [data-content]{display:flex;flex-direction:column;gap:2px}[data-sonner-toast][data-styled=true] [data-button]{border-radius:4px;padding-left:8px;padding-right:8px;height:24px;font-size:12px;color:var(--normal-bg);background:var(--normal-text);margin-left:var(--toast-button-margin-start);margin-right:var(--toast-button-margin-end);border:none;font-weight:500;cursor:pointer;outline:0;display:flex;align-items:center;flex-shrink:0;transition:opacity .4s,box-shadow .2s}[data-sonner-toast][data-styled=true] [data-button]:focus-visible{box-shadow:0 0 0 2px rgba(0,0,0,.4)}[data-sonner-toast][data-styled=true] [data-button]:first-of-type{margin-left:var(--toast-button-margin-start);margin-right:var(--toast-button-margin-end)}[data-sonner-toast][data-styled=true] [data-cancel]{color:var(--normal-text);background:rgba(0,0,0,.08)}[data-sonner-toaster][data-sonner-theme=dark] [data-sonner-toast][data-styled=true] [data-cancel]{background:rgba(255,255,255,.3)}[data-sonner-toast][data-styled=true] [data-close-button]{position:absolute;left:var(--toast-close-button-start);right:var(--toast-close-button-end);top:0;height:20px;width:20px;display:flex;justify-content:center;align-items:center;padding:0;color:var(--gray12);background:var(--normal-bg);border:1px solid var(--gray4);transform:var(--toast-close-button-transform);border-radius:50%;cursor:pointer;z-index:1;transition:opacity .1s,background .2s,border-color .2s}[data-sonner-toast][data-styled=true] [data-close-button]:focus-visible{box-shadow:0 4px 12px rgba(0,0,0,.1),0 0 0 2px rgba(0,0,0,.2)}[data-sonner-toast][data-styled=true] [data-disabled=true]{cursor:not-allowed}[data-sonner-toast][data-styled=true]:hover [data-close-button]:hover{background:var(--gray2);border-color:var(--gray5)}[data-sonner-toast][data-swiping=true]::before{content:'';position:absolute;left:-100%;right:-100%;height:100%;z-index:-1}[data-sonner-toast][data-y-position=top][data-swiping=true]::before{bottom:50%;transform:scaleY(3) translateY(50%)}[data-sonner-toast][data-y-position=bottom][data-swiping=true]::before{top:50%;transform:scaleY(3) translateY(-50%)}[data-sonner-toast][data-swiping=false][data-removed=true]::before{content:'';position:absolute;inset:0;transform:scaleY(2)}[data-sonner-toast][data-expanded=true]::after{content:'';position:absolute;left:0;height:calc(var(--gap) + 1px);bottom:100%;width:100%}[data-sonner-toast][data-mounted=true]{--y:translateY(0);opacity:1}[data-sonner-toast][data-expanded=false][data-front=false]{--scale:var(--toasts-before) * 0.05 + 1;--y:translateY(calc(var(--lift-amount) * var(--toasts-before))) scale(calc(-1 * var(--scale)));height:var(--front-toast-height)}[data-sonner-toast]>*{transition:opacity .4s}[data-sonner-toast][data-x-position=right]{right:0}[data-sonner-toast][data-x-position=left]{left:0}[data-sonner-toast][data-expanded=false][data-front=false][data-styled=true]>*{opacity:0}[data-sonner-toast][data-visible=false]{opacity:0;pointer-events:none}[data-sonner-toast][data-mounted=true][data-expanded=true]{--y:translateY(calc(var(--lift) * var(--offset)));height:var(--initial-height)}[data-sonner-toast][data-removed=true][data-front=true][data-swipe-out=false]{--y:translateY(calc(var(--lift) * -100%));opacity:0}[data-sonner-toast][data-removed=true][data-front=false][data-swipe-out=false][data-expanded=true]{--y:translateY(calc(var(--lift) * var(--offset) + var(--lift) * -100%));opacity:0}[data-sonner-toast][data-removed=true][data-front=false][data-swipe-out=false][data-expanded=false]{--y:translateY(40%);opacity:0;transition:transform .5s,opacity .2s}[data-sonner-toast][data-removed=true][data-front=false]::before{height:calc(var(--initial-height) + 20%)}[data-sonner-toast][data-swiping=true]{transform:var(--y) translateY(var(--swipe-amount-y,0)) translateX(var(--swipe-amount-x,0));transition:none}[data-sonner-toast][data-swiped=true]{user-select:none}[data-sonner-toast][data-swipe-out=true][data-y-position=bottom],[data-sonner-toast][data-swipe-out=true][data-y-position=top]{animation-duration:.2s;animation-timing-function:ease-out;animation-fill-mode:forwards}[data-sonner-toast][data-swipe-out=true][data-swipe-direction=left]{animation-name:swipe-out-left}[data-sonner-toast][data-swipe-out=true][data-swipe-direction=right]{animation-name:swipe-out-right}[data-sonner-toast][data-swipe-out=true][data-swipe-direction=up]{animation-name:swipe-out-up}[data-sonner-toast][data-swipe-out=true][data-swipe-direction=down]{animation-name:swipe-out-down}@keyframes swipe-out-left{from{transform:var(--y) translateX(var(--swipe-amount-x));opacity:1}to{transform:var(--y) translateX(calc(var(--swipe-amount-x) - 100%));opacity:0}}@keyframes swipe-out-right{from{transform:var(--y) translateX(var(--swipe-amount-x));opacity:1}to{transform:var(--y) translateX(calc(var(--swipe-amount-x) + 100%));opacity:0}}@keyframes swipe-out-up{from{transform:var(--y) translateY(var(--swipe-amount-y));opacity:1}to{transform:var(--y) translateY(calc(var(--swipe-amount-y) - 100%));opacity:0}}@keyframes swipe-out-down{from{transform:var(--y) translateY(var(--swipe-amount-y));opacity:1}to{transform:var(--y) translateY(calc(var(--swipe-amount-y) + 100%));opacity:0}}@media (max-width:600px){[data-sonner-toaster]{position:fixed;right:var(--mobile-offset-right);left:var(--mobile-offset-left);width:100%}[data-sonner-toaster][dir=rtl]{left:calc(var(--mobile-offset-left) * -1)}[data-sonner-toaster] [data-sonner-toast]{left:0;right:0;width:calc(100% - var(--mobile-offset-left) * 2)}[data-sonner-toaster][data-x-position=left]{left:var(--mobile-offset-left)}[data-sonner-toaster][data-y-position=bottom]{bottom:var(--mobile-offset-bottom)}[data-sonner-toaster][data-y-position=top]{top:var(--mobile-offset-top)}[data-sonner-toaster][data-x-position=center]{left:var(--mobile-offset-left);right:var(--mobile-offset-right);transform:none}}[data-sonner-toaster][data-sonner-theme=light]{--normal-bg:#fff;--normal-border:var(--gray4);--normal-text:var(--gray12);--success-bg:hsl(143, 85%, 96%);--success-border:hsl(145, 92%, 87%);--success-text:hsl(140, 100%, 27%);--info-bg:hsl(208, 100%, 97%);--info-border:hsl(221, 91%, 93%);--info-text:hsl(210, 92%, 45%);--warning-bg:hsl(49, 100%, 97%);--warning-border:hsl(49, 91%, 84%);--warning-text:hsl(31, 92%, 45%);--error-bg:hsl(359, 100%, 97%);--error-border:hsl(359, 100%, 94%);--error-text:hsl(360, 100%, 45%)}[data-sonner-toaster][data-sonner-theme=light] [data-sonner-toast][data-invert=true]{--normal-bg:#000;--normal-border:hsl(0, 0%, 20%);--normal-text:var(--gray1)}[data-sonner-toaster][data-sonner-theme=dark] [data-sonner-toast][data-invert=true]{--normal-bg:#fff;--normal-border:var(--gray3);--normal-text:var(--gray12)}[data-sonner-toaster][data-sonner-theme=dark]{--normal-bg:#000;--normal-bg-hover:hsl(0, 0%, 12%);--normal-border:hsl(0, 0%, 20%);--normal-border-hover:hsl(0, 0%, 25%);--normal-text:var(--gray1);--success-bg:hsl(150, 100%, 6%);--success-border:hsl(147, 100%, 12%);--success-text:hsl(150, 86%, 65%);--info-bg:hsl(215, 100%, 6%);--info-border:hsl(223, 43%, 17%);--info-text:hsl(216, 87%, 65%);--warning-bg:hsl(64, 100%, 6%);--warning-border:hsl(60, 100%, 9%);--warning-text:hsl(46, 87%, 65%);--error-bg:hsl(358, 76%, 10%);--error-border:hsl(357, 89%, 16%);--error-text:hsl(358, 100%, 81%)}[data-sonner-toaster][data-sonner-theme=dark] [data-sonner-toast] [data-close-button]{background:var(--normal-bg);border-color:var(--normal-border);color:var(--normal-text)}[data-sonner-toaster][data-sonner-theme=dark] [data-sonner-toast] [data-close-button]:hover{background:var(--normal-bg-hover);border-color:var(--normal-border-hover)}[data-rich-colors=true][data-sonner-toast][data-type=success]{background:var(--success-bg);border-color:var(--success-border);color:var(--success-text)}[data-rich-colors=true][data-sonner-toast][data-type=success] [data-close-button]{background:var(--success-bg);border-color:var(--success-border);color:var(--success-text)}[data-rich-colors=true][data-sonner-toast][data-type=info]{background:var(--info-bg);border-color:var(--info-border);color:var(--info-text)}[data-rich-colors=true][data-sonner-toast][data-type=info] [data-close-button]{background:var(--info-bg);border-color:var(--info-border);color:var(--info-text)}[data-rich-colors=true][data-sonner-toast][data-type=warning]{background:var(--warning-bg);border-color:var(--warning-border);color:var(--warning-text)}[data-rich-colors=true][data-sonner-toast][data-type=warning] [data-close-button]{background:var(--warning-bg);border-color:var(--warning-border);color:var(--warning-text)}[data-rich-colors=true][data-sonner-toast][data-type=error]{background:var(--error-bg);border-color:var(--error-border);color:var(--error-text)}[data-rich-colors=true][data-sonner-toast][data-type=error] [data-close-button]{background:var(--error-bg);border-color:var(--error-border);color:var(--error-text)}.sonner-loading-wrapper{--size:16px;height:var(--size);width:var(--size);position:absolute;inset:0;z-index:10}.sonner-loading-wrapper[data-visible=false]{transform-origin:center;animation:sonner-fade-out .2s ease forwards}.sonner-spinner{position:relative;top:50%;left:50%;height:var(--size);width:var(--size)}.sonner-loading-bar{animation:sonner-spin 1.2s linear infinite;background:var(--gray11);border-radius:6px;height:8%;left:-10%;position:absolute;top:-3.9%;width:24%}.sonner-loading-bar:first-child{animation-delay:-1.2s;transform:rotate(.0001deg) translate(146%)}.sonner-loading-bar:nth-child(2){animation-delay:-1.1s;transform:rotate(30deg) translate(146%)}.sonner-loading-bar:nth-child(3){animation-delay:-1s;transform:rotate(60deg) translate(146%)}.sonner-loading-bar:nth-child(4){animation-delay:-.9s;transform:rotate(90deg) translate(146%)}.sonner-loading-bar:nth-child(5){animation-delay:-.8s;transform:rotate(120deg) translate(146%)}.sonner-loading-bar:nth-child(6){animation-delay:-.7s;transform:rotate(150deg) translate(146%)}.sonner-loading-bar:nth-child(7){animation-delay:-.6s;transform:rotate(180deg) translate(146%)}.sonner-loading-bar:nth-child(8){animation-delay:-.5s;transform:rotate(210deg) translate(146%)}.sonner-loading-bar:nth-child(9){animation-delay:-.4s;transform:rotate(240deg) translate(146%)}.sonner-loading-bar:nth-child(10){animation-delay:-.3s;transform:rotate(270deg) translate(146%)}.sonner-loading-bar:nth-child(11){animation-delay:-.2s;transform:rotate(300deg) translate(146%)}.sonner-loading-bar:nth-child(12){animation-delay:-.1s;transform:rotate(330deg) translate(146%)}@keyframes sonner-fade-in{0%{opacity:0;transform:scale(.8)}100%{opacity:1;transform:scale(1)}}@keyframes sonner-fade-out{0%{opacity:1;transform:scale(1)}100%{opacity:0;transform:scale(.8)}}@keyframes sonner-spin{0%{opacity:1}100%{opacity:.15}}@media (prefers-reduced-motion){.sonner-loading-bar,[data-sonner-toast],[data-sonner-toast]>*{transition:none!important;animation:none!important}}.sonner-loader{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);transform-origin:center;transition:opacity .2s,transform .2s}.sonner-loader[data-visible=false]{opacity:0;transform:scale(.8) translate(-50%,-50%)}`);function I(e){return e.label!==void 0}var te=3,L=`24px`,R=`16px`,z=4e3,ne=356,re=14,ie=45,ae=200;function oe(...e){return e.filter(Boolean).join(` `)}function se(e){let[t,n]=e.split(`-`),r=[];return t&&r.push(t),n&&r.push(n),r}var ce=e=>{let{invert:t,toast:n,unstyled:r,interacting:i,setHeights:a,visibleToasts:o,heights:c,index:l,toasts:u,expanded:d,removeToast:f,defaultRichColors:p,closeButton:m,style:h,cancelButtonStyle:g,actionButtonStyle:_,className:v=``,descriptionClassName:y=``,duration:b,position:x,gap:S,expandByDefault:w,classNames:E,icons:D,closeButtonAriaLabel:O=`Close toast`}=e,[k,M]=s.useState(null),[N,ee]=s.useState(null),[P,F]=s.useState(!1),[te,L]=s.useState(!1),[R,ne]=s.useState(!1),[re,ce]=s.useState(!1),[le,ue]=s.useState(!1),[de,B]=s.useState(0),[fe,pe]=s.useState(0),me=s.useRef(n.duration||b||z),he=s.useRef(null),V=s.useRef(null),H=l===0,ge=l+1<=o,U=n.type,W=n.dismissible!==!1,_e=n.className||``,ve=n.descriptionClassName||``,ye=s.useMemo(()=>c.findIndex(e=>e.toastId===n.id)||0,[c,n.id]),be=s.useMemo(()=>n.closeButton??m,[n.closeButton,m]),G=s.useMemo(()=>n.duration||b||z,[n.duration,b]),xe=s.useRef(0),K=s.useRef(0),Se=s.useRef(0),q=s.useRef(null),[Ce,we]=x.split(`-`),Te=s.useMemo(()=>c.reduce((e,t,n)=>n>=ye?e:e+t.height,0),[c,ye]),Ee=j(),De=n.invert||t,Oe=U===`loading`;K.current=s.useMemo(()=>ye*S+Te,[ye,Te]),s.useEffect(()=>{me.current=G},[G]),s.useEffect(()=>{F(!0)},[]),s.useEffect(()=>{let e=V.current;if(e){let t=e.getBoundingClientRect().height;return pe(t),a(e=>[{toastId:n.id,height:t,position:n.position},...e]),()=>a(e=>e.filter(e=>e.toastId!==n.id))}},[a,n.id]),s.useLayoutEffect(()=>{if(!P)return;let e=V.current,t=e.style.height;e.style.height=`auto`;let r=e.getBoundingClientRect().height;e.style.height=t,pe(r),a(e=>e.find(e=>e.toastId===n.id)?e.map(e=>e.toastId===n.id?{...e,height:r}:e):[{toastId:n.id,height:r,position:n.position},...e])},[P,n.title,n.description,a,n.id,n.jsx,n.action,n.cancel]);let J=s.useCallback(()=>{L(!0),B(K.current),a(e=>e.filter(e=>e.toastId!==n.id)),setTimeout(()=>{f(n)},ae)},[n,f,a,K]);s.useEffect(()=>{if(n.promise&&U===`loading`||n.duration===1/0||n.type===`loading`)return;let e;return d||i||Ee?(()=>{if(Se.current{n.onAutoClose==null||n.onAutoClose.call(n,n),J()},me.current)),()=>clearTimeout(e)},[d,i,n,U,Ee,J]),s.useEffect(()=>{n.delete&&(J(),n.onDismiss==null||n.onDismiss.call(n,n))},[J,n.delete]);function ke(){return D?.loading?s.createElement(`div`,{className:oe(E?.loader,n?.classNames?.loader,`sonner-loader`),"data-visible":U===`loading`},D.loading):s.createElement(T,{className:oe(E?.loader,n?.classNames?.loader),visible:U===`loading`})}let Ae=n.icon||D?.[U]||C(U);return s.createElement(`li`,{tabIndex:0,ref:V,className:oe(v,_e,E?.toast,n?.classNames?.toast,E?.default,E?.[U],n?.classNames?.[U]),"data-sonner-toast":``,"data-rich-colors":n.richColors??p,"data-styled":!(n.jsx||n.unstyled||r),"data-mounted":P,"data-promise":!!n.promise,"data-swiped":le,"data-removed":te,"data-visible":ge,"data-y-position":Ce,"data-x-position":we,"data-index":l,"data-front":H,"data-swiping":R,"data-dismissible":W,"data-type":U,"data-invert":De,"data-swipe-out":re,"data-swipe-direction":N,"data-expanded":!!(d||w&&P),"data-testid":n.testId,style:{"--index":l,"--toasts-before":l,"--z-index":u.length-l,"--offset":`${te?de:K.current}px`,"--initial-height":w?`auto`:`${fe}px`,...h,...n.style},onDragEnd:()=>{ne(!1),M(null),q.current=null},onPointerDown:e=>{e.button!==2&&(Oe||!W||(he.current=new Date,B(K.current),e.target.setPointerCapture(e.pointerId),e.target.tagName!==`BUTTON`&&(ne(!0),q.current={x:e.clientX,y:e.clientY})))},onPointerUp:()=>{if(re||!W)return;q.current=null;let e=Number(V.current?.style.getPropertyValue(`--swipe-amount-x`).replace(`px`,``)||0),t=Number(V.current?.style.getPropertyValue(`--swipe-amount-y`).replace(`px`,``)||0),r=new Date().getTime()-he.current?.getTime(),i=k===`x`?e:t,a=Math.abs(i)/r;if(Math.abs(i)>=ie||a>.11){B(K.current),n.onDismiss==null||n.onDismiss.call(n,n),ee(k===`x`?e>0?`right`:`left`:t>0?`down`:`up`),J(),ce(!0);return}else{var o,s;(o=V.current)==null||o.style.setProperty(`--swipe-amount-x`,`0px`),(s=V.current)==null||s.style.setProperty(`--swipe-amount-y`,`0px`)}ue(!1),ne(!1),M(null)},onPointerMove:t=>{var n,r;if(!q.current||!W||window.getSelection()?.toString().length>0)return;let i=t.clientY-q.current.y,a=t.clientX-q.current.x,o=e.swipeDirections??se(x);!k&&(Math.abs(a)>1||Math.abs(i)>1)&&M(Math.abs(a)>Math.abs(i)?`x`:`y`);let s={x:0,y:0},c=e=>1/(1.5+Math.abs(e)/20);if(k===`y`){if(o.includes(`top`)||o.includes(`bottom`))if(o.includes(`top`)&&i<0||o.includes(`bottom`)&&i>0)s.y=i;else{let e=i*c(i);s.y=Math.abs(e)0)s.x=a;else{let e=a*c(a);s.x=Math.abs(e)0||Math.abs(s.y)>0)&&ue(!0),(n=V.current)==null||n.style.setProperty(`--swipe-amount-x`,`${s.x}px`),(r=V.current)==null||r.style.setProperty(`--swipe-amount-y`,`${s.y}px`)}},be&&!n.jsx&&U!==`loading`?s.createElement(`button`,{"aria-label":O,"data-disabled":Oe,"data-close-button":!0,onClick:Oe||!W?()=>{}:()=>{J(),n.onDismiss==null||n.onDismiss.call(n,n)},className:oe(E?.closeButton,n?.classNames?.closeButton)},D?.close??A):null,(U||n.icon||n.promise)&&n.icon!==null&&(D?.[U]!==null||n.icon)?s.createElement(`div`,{"data-icon":``,className:oe(E?.icon,n?.classNames?.icon)},n.promise||n.type===`loading`&&!n.icon?n.icon||ke():null,n.type===`loading`?null:Ae):null,s.createElement(`div`,{"data-content":``,className:oe(E?.content,n?.classNames?.content)},s.createElement(`div`,{"data-title":``,className:oe(E?.title,n?.classNames?.title)},n.jsx?n.jsx:typeof n.title==`function`?n.title():n.title),n.description?s.createElement(`div`,{"data-description":``,className:oe(y,ve,E?.description,n?.classNames?.description)},typeof n.description==`function`?n.description():n.description):null),s.isValidElement(n.cancel)?n.cancel:n.cancel&&I(n.cancel)?s.createElement(`button`,{"data-button":!0,"data-cancel":!0,style:n.cancelButtonStyle||g,onClick:e=>{I(n.cancel)&&W&&(n.cancel.onClick==null||n.cancel.onClick.call(n.cancel,e),J())},className:oe(E?.cancelButton,n?.classNames?.cancelButton)},n.cancel.label):null,s.isValidElement(n.action)?n.action:n.action&&I(n.action)?s.createElement(`button`,{"data-button":!0,"data-action":!0,style:n.actionButtonStyle||_,onClick:e=>{I(n.action)&&(n.action.onClick==null||n.action.onClick.call(n.action,e),!e.defaultPrevented&&J())},className:oe(E?.actionButton,n?.classNames?.actionButton)},n.action.label):null)};function le(){if(typeof window>`u`||typeof document>`u`)return`ltr`;let e=document.documentElement.getAttribute(`dir`);return e===`auto`||!e?window.getComputedStyle(document.documentElement).direction:e}function ue(e,t){let n={};return[e,t].forEach((e,t)=>{let r=t===1,i=r?`--mobile-offset`:`--offset`,a=r?R:L;function o(e){[`top`,`right`,`bottom`,`left`].forEach(t=>{n[`${i}-${t}`]=typeof e==`number`?`${e}px`:e})}typeof e==`number`||typeof e==`string`?o(e):typeof e==`object`?[`top`,`right`,`bottom`,`left`].forEach(t=>{e[t]===void 0?n[`${i}-${t}`]=a:n[`${i}-${t}`]=typeof e[t]==`number`?`${e[t]}px`:e[t]}):o(a)}),n}var de=s.forwardRef(function(e,t){let{id:n,invert:r,position:i=`bottom-right`,hotkey:a=[`altKey`,`KeyT`],expand:o,closeButton:l,className:u,offset:d,mobileOffset:f,theme:p=`light`,richColors:m,duration:h,style:g,visibleToasts:_=te,toastOptions:v,dir:y=le(),gap:b=re,icons:x,containerAriaLabel:S=`Notifications`}=e,[C,w]=s.useState([]),T=s.useMemo(()=>n?C.filter(e=>e.toasterId===n):C.filter(e=>!e.toasterId),[C,n]),E=s.useMemo(()=>Array.from(new Set([i].concat(T.filter(e=>e.position).map(e=>e.position)))),[T,i]),[D,O]=s.useState([]),[k,A]=s.useState(!1),[j,M]=s.useState(!1),[ee,P]=s.useState(p===`system`?typeof window<`u`&&window.matchMedia&&window.matchMedia(`(prefers-color-scheme: dark)`).matches?`dark`:`light`:p),F=s.useRef(null),I=a.join(`+`).replace(/Key/g,``).replace(/Digit/g,``),L=s.useRef(null),R=s.useRef(!1),z=s.useCallback(e=>{w(t=>(t.find(t=>t.id===e.id)?.delete||N.dismiss(e.id),t.filter(({id:t})=>t!==e.id)))},[]);return s.useEffect(()=>N.subscribe(e=>{if(e.dismiss){requestAnimationFrame(()=>{w(t=>t.map(t=>t.id===e.id?{...t,delete:!0}:t))});return}setTimeout(()=>{c.flushSync(()=>{w(t=>{let n=t.findIndex(t=>t.id===e.id);return n===-1?[e,...t]:[...t.slice(0,n),{...t[n],...e},...t.slice(n+1)]})})})}),[C]),s.useEffect(()=>{if(p!==`system`){P(p);return}if(p===`system`&&(window.matchMedia&&window.matchMedia(`(prefers-color-scheme: dark)`).matches?P(`dark`):P(`light`)),typeof window>`u`)return;let e=window.matchMedia(`(prefers-color-scheme: dark)`);try{e.addEventListener(`change`,({matches:e})=>{P(e?`dark`:`light`)})}catch{e.addListener(({matches:e})=>{try{P(e?`dark`:`light`)}catch(e){console.error(e)}})}},[p]),s.useEffect(()=>{C.length<=1&&A(!1)},[C]),s.useEffect(()=>{let e=e=>{if(a.every(t=>e[t]||e.code===t)){var t;A(!0),(t=F.current)==null||t.focus()}e.code===`Escape`&&(document.activeElement===F.current||F.current?.contains(document.activeElement))&&A(!1)};return document.addEventListener(`keydown`,e),()=>document.removeEventListener(`keydown`,e)},[a]),s.useEffect(()=>{if(F.current)return()=>{L.current&&(L.current.focus({preventScroll:!0}),L.current=null,R.current=!1)}},[F.current]),s.createElement(`section`,{ref:t,"aria-label":`${S} ${I}`,tabIndex:-1,"aria-live":`polite`,"aria-relevant":`additions text`,"aria-atomic":`false`,suppressHydrationWarning:!0},E.map((t,n)=>{let[i,a]=t.split(`-`);return T.length?s.createElement(`ol`,{key:t,dir:y===`auto`?le():y,tabIndex:-1,ref:F,className:u,"data-sonner-toaster":!0,"data-sonner-theme":ee,"data-y-position":i,"data-x-position":a,style:{"--front-toast-height":`${D[0]?.height||0}px`,"--width":`${ne}px`,"--gap":`${b}px`,...g,...ue(d,f)},onBlur:e=>{R.current&&!e.currentTarget.contains(e.relatedTarget)&&(R.current=!1,L.current&&=(L.current.focus({preventScroll:!0}),null))},onFocus:e=>{e.target instanceof HTMLElement&&e.target.dataset.dismissible===`false`||R.current||(R.current=!0,L.current=e.relatedTarget)},onMouseEnter:()=>A(!0),onMouseMove:()=>A(!0),onMouseLeave:()=>{j||A(!1)},onDragEnd:()=>A(!1),onPointerDown:e=>{e.target instanceof HTMLElement&&e.target.dataset.dismissible===`false`||M(!0)},onPointerUp:()=>M(!1)},T.filter(e=>!e.position&&n===0||e.position===t).map((n,i)=>s.createElement(ce,{key:n.id,icons:x,index:i,toast:n,defaultRichColors:m,duration:v?.duration??h,className:v?.className,descriptionClassName:v?.descriptionClassName,invert:r,visibleToasts:_,closeButton:v?.closeButton??l,interacting:j,position:t,style:v?.style,unstyled:v?.unstyled,classNames:v?.classNames,cancelButtonStyle:v?.cancelButtonStyle,actionButtonStyle:v?.actionButtonStyle,closeButtonAriaLabel:v?.closeButtonAriaLabel,removeToast:z,toasts:T.filter(e=>e.position==n.position),heights:D.filter(e=>e.position==n.position),setHeights:O,expandByDefault:o,gap:b,expanded:k,swipeDirections:e.swipeDirections}))):null}))});function B(e,t){return function(){return e.apply(t,arguments)}}var{toString:fe}=Object.prototype,{getPrototypeOf:pe}=Object,{iterator:me,toStringTag:he}=Symbol,V=(e=>t=>{let n=fe.call(t);return e[n]||(e[n]=n.slice(8,-1).toLowerCase())})(Object.create(null)),H=e=>(e=e.toLowerCase(),t=>V(t)===e),ge=e=>t=>typeof t===e,{isArray:U}=Array,W=ge(`undefined`);function _e(e){return e!==null&&!W(e)&&e.constructor!==null&&!W(e.constructor)&&G(e.constructor.isBuffer)&&e.constructor.isBuffer(e)}var ve=H(`ArrayBuffer`);function ye(e){let t;return t=typeof ArrayBuffer<`u`&&ArrayBuffer.isView?ArrayBuffer.isView(e):e&&e.buffer&&ve(e.buffer),t}var be=ge(`string`),G=ge(`function`),xe=ge(`number`),K=e=>typeof e==`object`&&!!e,Se=e=>e===!0||e===!1,q=e=>{if(V(e)!==`object`)return!1;let t=pe(e);return(t===null||t===Object.prototype||Object.getPrototypeOf(t)===null)&&!(he in e)&&!(me in e)},Ce=e=>{if(!K(e)||_e(e))return!1;try{return Object.keys(e).length===0&&Object.getPrototypeOf(e)===Object.prototype}catch{return!1}},we=H(`Date`),Te=H(`File`),Ee=e=>!!(e&&e.uri!==void 0),De=e=>e&&e.getParts!==void 0,Oe=H(`Blob`),J=H(`FileList`),ke=e=>K(e)&&G(e.pipe);function Ae(){return typeof globalThis<`u`?globalThis:typeof self<`u`?self:typeof window<`u`?window:typeof global<`u`?global:{}}var je=Ae(),Me=je.FormData===void 0?void 0:je.FormData,Ne=e=>{if(!e)return!1;if(Me&&e instanceof Me)return!0;let t=pe(e);if(!t||t===Object.prototype||!G(e.append))return!1;let n=V(e);return n===`formdata`||n===`object`&&G(e.toString)&&e.toString()===`[object FormData]`},Pe=H(`URLSearchParams`),[Fe,Ie,Le,Re]=[`ReadableStream`,`Request`,`Response`,`Headers`].map(H),ze=e=>e.trim?e.trim():e.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,``);function Be(e,t,{allOwnKeys:n=!1}={}){if(e==null)return;let r,i;if(typeof e!=`object`&&(e=[e]),U(e))for(r=0,i=e.length;r0;)if(i=n[r],t===i.toLowerCase())return i;return null}var He=typeof globalThis<`u`?globalThis:typeof self<`u`?self:typeof window<`u`?window:global,Ue=e=>!W(e)&&e!==He;function We(...e){let{caseless:t,skipUndefined:n}=Ue(this)&&this||{},r={},i=(e,i)=>{if(i===`__proto__`||i===`constructor`||i===`prototype`)return;let a=t&&Ve(r,i)||i,o=nt(r,a)?r[a]:void 0;q(o)&&q(e)?r[a]=We(o,e):q(e)?r[a]=We({},e):U(e)?r[a]=e.slice():(!n||!W(e))&&(r[a]=e)};for(let t=0,n=e.length;t(Be(t,(t,r)=>{n&&G(t)?Object.defineProperty(e,r,{__proto__:null,value:B(t,n),writable:!0,enumerable:!0,configurable:!0}):Object.defineProperty(e,r,{__proto__:null,value:t,writable:!0,enumerable:!0,configurable:!0})},{allOwnKeys:r}),e),Ke=e=>(e.charCodeAt(0)===65279&&(e=e.slice(1)),e),qe=(e,t,n,r)=>{e.prototype=Object.create(t.prototype,r),Object.defineProperty(e.prototype,"constructor",{__proto__:null,value:e,writable:!0,enumerable:!1,configurable:!0}),Object.defineProperty(e,"super",{__proto__:null,value:t.prototype}),n&&Object.assign(e.prototype,n)},Je=(e,t,n,r)=>{let i,a,o,s={};if(t||={},e==null)return t;do{for(i=Object.getOwnPropertyNames(e),a=i.length;a-- >0;)o=i[a],(!r||r(o,e,t))&&!s[o]&&(t[o]=e[o],s[o]=!0);e=n!==!1&&pe(e)}while(e&&(!n||n(e,t))&&e!==Object.prototype);return t},Ye=(e,t,n)=>{e=String(e),(n===void 0||n>e.length)&&(n=e.length),n-=t.length;let r=e.indexOf(t,n);return r!==-1&&r===n},Xe=e=>{if(!e)return null;if(U(e))return e;let t=e.length;if(!xe(t))return null;let n=Array(t);for(;t-- >0;)n[t]=e[t];return n},Ze=(e=>t=>e&&t instanceof e)(typeof Uint8Array<`u`&&pe(Uint8Array)),Qe=(e,t)=>{let n=(e&&e[me]).call(e),r;for(;(r=n.next())&&!r.done;){let n=r.value;t.call(e,n[0],n[1])}},$e=(e,t)=>{let n,r=[];for(;(n=e.exec(t))!==null;)r.push(n);return r},et=H(`HTMLFormElement`),tt=e=>e.toLowerCase().replace(/[-_\s]([a-z\d])(\w*)/g,function(e,t,n){return t.toUpperCase()+n}),nt=(({hasOwnProperty:e})=>(t,n)=>e.call(t,n))(Object.prototype),rt=H(`RegExp`),it=(e,t)=>{let n=Object.getOwnPropertyDescriptors(e),r={};Be(n,(n,i)=>{let a;(a=t(n,i,e))!==!1&&(r[i]=a||n)}),Object.defineProperties(e,r)},at=e=>{it(e,(t,n)=>{if(G(e)&&[`arguments`,`caller`,`callee`].includes(n))return!1;let r=e[n];if(G(r)){if(t.enumerable=!1,`writable`in t){t.writable=!1;return}t.set||=()=>{throw Error(`Can not rewrite read-only method '`+n+`'`)}}})},ot=(e,t)=>{let n={},r=e=>{e.forEach(e=>{n[e]=!0})};return U(e)?r(e):r(String(e).split(t)),n},st=()=>{},ct=(e,t)=>e!=null&&Number.isFinite(e=+e)?e:t;function lt(e){return!!(e&&G(e.append)&&e[he]===`FormData`&&e[me])}var ut=e=>{let t=new WeakSet,n=e=>{if(K(e)){if(t.has(e))return;if(_e(e))return e;if(!(`toJSON`in e)){t.add(e);let r=U(e)?[]:{};return Be(e,(e,t)=>{let i=n(e);!W(i)&&(r[t]=i)}),t.delete(e),r}}return e};return n(e)},dt=H(`AsyncFunction`),ft=e=>e&&(K(e)||G(e))&&G(e.then)&&G(e.catch),pt=((e,t)=>e?setImmediate:t?((e,t)=>(He.addEventListener(`message`,({source:n,data:r})=>{n===He&&r===e&&t.length&&t.shift()()},!1),n=>{t.push(n),He.postMessage(e,`*`)}))(`axios@${Math.random()}`,[]):e=>setTimeout(e))(typeof setImmediate==`function`,G(He.postMessage)),Y={isArray:U,isArrayBuffer:ve,isBuffer:_e,isFormData:Ne,isArrayBufferView:ye,isString:be,isNumber:xe,isBoolean:Se,isObject:K,isPlainObject:q,isEmptyObject:Ce,isReadableStream:Fe,isRequest:Ie,isResponse:Le,isHeaders:Re,isUndefined:W,isDate:we,isFile:Te,isReactNativeBlob:Ee,isReactNative:De,isBlob:Oe,isRegExp:rt,isFunction:G,isStream:ke,isURLSearchParams:Pe,isTypedArray:Ze,isFileList:J,forEach:Be,merge:We,extend:Ge,trim:ze,stripBOM:Ke,inherits:qe,toFlatObject:Je,kindOf:V,kindOfTest:H,endsWith:Ye,toArray:Xe,forEachEntry:Qe,matchAll:$e,isHTMLForm:et,hasOwnProperty:nt,hasOwnProp:nt,reduceDescriptors:it,freezeMethods:at,toObjectSet:ot,toCamelCase:tt,noop:st,toFiniteNumber:ct,findKey:Ve,global:He,isContextDefined:Ue,isSpecCompliantForm:lt,toJSONObject:ut,isAsyncFn:dt,isThenable:ft,setImmediate:pt,asap:typeof queueMicrotask<`u`?queueMicrotask.bind(He):typeof process<`u`&&process.nextTick||pt,isIterable:e=>e!=null&&G(e[me])},mt=Y.toObjectSet([`age`,`authorization`,`content-length`,`content-type`,`etag`,`expires`,`from`,`host`,`if-modified-since`,`if-unmodified-since`,`last-modified`,`location`,`max-forwards`,`proxy-authorization`,`referer`,`retry-after`,`user-agent`]),ht=e=>{let t={},n,r,i;return e&&e.split(` +import{o as e,r as t,t as n}from"./rolldown-runtime.js";import{en as r}from"./vendor-icons.js";import{C as i,b as a,x as o}from"./vendor-react.js";var s=e(r(),1),c=e(i(),1),l=`docsite_language`,u=`nacos_theme`,d=`nacos_sidebar_collapsed`,f=`token`;function p(){let e=localStorage.getItem(l);return e===`zh-CN`||e===`en-US`?e:(navigator.language||navigator.userLanguage)?.startsWith(`zh`)?`zh-CN`:`en-US`}function m(e){localStorage.setItem(l,e)}function h(){let e=localStorage.getItem(u);return e===`light`||e===`dark`?e:`light`}function g(e){localStorage.setItem(u,e)}function _(){return localStorage.getItem(d)===`true`}function v(e){localStorage.setItem(d,String(e))}function y(){try{let e=localStorage.getItem(f);if(e)return JSON.parse(e)}catch{}return null}var b={"zh-CN":{translation:{header:{home:`首页`,docs:`文档`,blog:`博客`,community:`社区`,enterprise:`Nacos企业版`,mcp:`MCP市场`,languageSwitch:`En`,logout:`登出`,changePassword:`修改密码`},login:{login:`登录`,initPassword:`初始化密码`,internalSysTip1:`内部系统,不可暴露到公网`,internalSysTip3:"初始化管理员用户`nacos`的密码",internalSysTip4:`建议使用高强度密码并妥善保管`,submit:`提交`,pleaseInputUsername:`请输入用户名`,pleaseInputPassword:`请输入密码`,pleaseInputPasswordTips:`请输入密码(若密码为空,将生成随机密码)`,invalidUsernameOrPassword:`用户名或密码错误`,passwordRequired:`密码不能为空`,usernameRequired:`用户名不能为空`,productDesc:`一个易于构建 AI Agent 应用的动态服务发现、配置管理和 AI 智能体管理平台`,consoleClosed:`控制台已关闭`,consoleClosedDesc:`Nacos 控制台已关闭。`,signInWithSSO:`使用 SSO 登录`,ssoLoginTip:`您的管理员已启用单点登录。点击下方按钮跳转到身份提供商完成登录。`,oidcRedirectFailed:`跳转到 SSO 登录页失败,请联系管理员。`,oidcAuthFailed:`SSO 认证失败`},menu:{configManagement:`配置管理`,configList:`配置列表`,historyRollback:`历史版本`,listeningQuery:`监听查询`,serviceManagement:`服务管理`,serviceList:`服务列表`,subscriberList:`订阅者列表`,aiRegistry:`AI 注册中心`,mcpRegistry:`MCP 管理`,agentRegistry:`Agent 管理`,skillRegistry:`Skill 管理`,promptRegistry:`Prompt 管理`,agentSpecRegistry:`AgentSpec 管理`,platformManagement:`平台管理`,namespace:`命名空间`,clusterManagement:`集群管理`,clusterNodeList:`节点列表`,pluginManagement:`插件管理`,authorityControl:`权限控制`,userList:`用户列表`,roleManagement:`角色管理`,privilegeManagement:`权限管理`,settingCenter:`设置中心`},common:{confirm:`确认`,cancel:`取消`,retry:`重试`,delete:`删除`,edit:`编辑`,create:`新建`,search:`搜索`,reset:`重置`,loading:`加载中...`,noData:`暂无数据`,operation:`操作`,detail:`详情`,save:`保存`,bizTags:`业务标签`,from:`来源`,add:`添加`,optional:`可选`,back:`返回`,success:`操作成功`,failed:`操作失败`,legacyConsole:`旧版控制台`,switchToLegacy:`切换到旧版控制台`,collapse:`收起侧栏`,expand:`展开侧栏`,mode:`模式`,version:`版本`,pageSize:`每页条数`,selectNamespace:`命名空间`,sessionExpired:`会话已过期,请重新登录`,noPermission:`无权限执行此操作`,requestFailed:`请求失败`,versionLabels:{title:`版本标签`,noLabels:`暂无版本标签`,keyPlaceholder:`标签名`,valuePlaceholder:`版本号`,keyRequired:`标签键不能为空`,keyDuplicate:`标签键已存在`,keyInvalid:`标签键格式不合法(仅支持字母、数字、. _ -)`,reservedLatest:`latest 由服务端维护,不支持手动设置或取消`,save:`保存版本标签`,updateSuccess:`版本标签更新成功`,editLabels:`编辑版本标签`,labelManagement:`版本标签`,labelBindDesc:`选择要绑定到版本 {{version}} 的标签,或创建新标签。`,searchOrCreateLabel:`搜索或创建标签...`,createLabel:`创建标签「{{name}}」`,selectAll:`全选 {{count}} 个`,clearAll:`清除`},bizTagEditor:{title:`业务标签`,description:`编辑业务标签列表。`},cliUsage:{title:`安装`,cliInstall:`CLI 安装`,cliDoc:`CLI 使用文档`,downloadFile:`下载文件`,manualDownload:`手动下载`,downloadZip:`下载 .zip 文件`,description:`通过 Nacos CLI 在本地获取当前资源,需先安装 Node.js。`,copied:`命令已复制`,latest:`获取最新版本`,byVersion:`指定版本`,byLabel:`指定标签`}},config:{title:`配置列表`,newConfig:`新建配置`,editConfig:`编辑配置`,configDetail:`配置详情`,dataId:`Data ID`,group:`Group`,content:`配置内容`,type:`配置格式`,tags:`标签`,appName:`归属应用`,description:`描述`,md5:`MD5`,createTime:`创建时间`,modifyTime:`最后更新时间`,fuzzySearch:`模糊搜索`,exactSearch:`精确搜索`,contentSearch:`内容搜索`,advancedSearch:`高级搜索`,publishSuccess:`配置发布成功`,publishFailed:`配置发布失败`,publishConfirmTitle:`确认发布`,publishConfirmDescription:`发布前请确认本次配置内容变更。`,currentPublishedContent:`当前已发布内容`,pendingPublishContent:`待发布内容`,notFoundInNamespace:`当前命名空间下不存在该配置,已返回配置列表。`,unsavedNamespaceSwitchConfirm:`当前配置有未发布的修改,切换命名空间将丢弃这些修改,是否继续?`,deleteConfirm:`确定要删除该配置吗?`,deleteSuccess:`配置删除成功`,dataIdRequired:`Data ID 不能为空`,groupRequired:`Group 不能为空`,contentRequired:`配置内容不能为空`,maxTags:`最多只能添加 5 个标签`,noSpecialChars:`不允许输入特殊字符`,configExists:`该配置已存在`,totalConfigs:`共 {{total}} 条配置`,publish:`发布`,history:`历史版本`,listeners:`监听查询`,rollback:`回滚`,clone:`克隆`,export:`导出`,import:`导入`,backToList:`返回列表`,sampleCode:`示例代码`,formatContent:`格式化`,fullscreen:`全屏编辑`,batchDelete:`批量删除`,batchDeleteConfirm:`确定要删除选中的 {{count}} 个配置吗?`,exportAll:`导出全部`,exportSelected:`导出选中`,importTitle:`导入配置`,importSuccess:`导入成功`,importFailed:`导入失败`,cloneTitle:`克隆配置`,sourceNamespace:`源命名空间`,targetNamespace:`目标命名空间`,conflictPolicy:`冲突处理策略`,policyAbort:`中止导入`,policySkip:`跳过已有`,policyOverwrite:`覆盖已有`,selectedCount:`已选择 {{count}} 项`,selectFile:`选择文件`,noFileSelected:`请选择 ZIP 文件`,noTargetNamespace:`请选择目标命名空间`,noSelection:`请先选择配置`,startClone:`开始克隆`,cloneSuccess:`克隆成功`,configCount:`配置数量`,uploadZip:`上传 ZIP 文件`,batchDeleteSuccess:`批量删除成功`,importResult:`成功 {{success}} 条,跳过 {{skip}} 条`,betaPublish:`Beta 发布`,production:`正式`,beta:`Beta`,betaIps:`Beta IP 列表`,betaIpsPlaceholder:`输入 IP 地址,多个用逗号分隔`,stopBeta:`停止 Beta`,stopBetaConfirm:`确定要停止 Beta 发布吗?`,betaPublishSuccess:`Beta 发布成功`,betaStopSuccess:`Beta 已停止`,noBeta:`当前无 Beta 配置`},listener:{title:`监听查询`,queryByConfig:`按配置查询`,queryByIp:`按 IP 查询`,ip:`IP 地址`,md5:`MD5`,noListeners:`暂无监听者`,totalListeners:`共 {{total}} 个监听者`},history:{title:`历史版本`,detailTitle:`历史详情`,rollbackTitle:`配置回滚`,publishType:`发布类型`,formal:`正式`,gray:`灰度`,grayRule:`灰度规则`,operator:`操作人`,sourceIp:`来源IP`,opType:`操作类型`,opInsert:`插入`,opUpdate:`更新`,opDelete:`删除`,compare:`对比`,rollback:`回滚`,rollbackConfirm:`确定要回滚到此版本吗?`,rollbackInsertWarn:`回滚将删除此配置`,rollbackUpdateWarn:`回滚将恢复到此版本内容`,rollbackDeleteWarn:`回滚将重新发布此配置`,rollbackSuccess:`回滚成功`,rollbackFailed:`回滚失败`,selectedVersion:`选中版本`,currentVersion:`当前版本`,totalHistory:`共 {{total}} 条历史记录`},service:{title:`服务列表`,createService:`创建服务`,editService:`编辑服务`,serviceDetail:`服务详情`,serviceName:`服务名`,groupName:`分组名`,clusterCount:`集群数`,ipCount:`IP 数`,healthyInstanceCount:`健康实例数`,triggerFlag:`触发保护阈值`,ignoreEmptyService:`隐藏空服务`,protectThreshold:`保护阈值`,metadata:`元数据`,selectorType:`选择器类型`,selectorExpression:`选择器表达式`,ephemeral:`临时服务`,deleteConfirm:`确定要删除服务 {{name}} 吗?`,deleteSuccess:`服务删除成功`,deleteHasInstances:`服务下存在实例,无法删除。请先注销所有实例后再试。`,createSuccess:`服务创建成功`,updateSuccess:`服务更新成功`,namespaceSwitchConfirm:`当前有未完成的服务操作,切换命名空间将关闭该操作,是否继续?`,totalServices:`共 {{total}} 个服务`,clusterName:`集群名称`,editCluster:`编辑集群`,checkType:`健康检查类型`,checkPort:`检查端口`,checkPath:`检查路径`,checkHeaders:`检查请求头`,useInstancePort:`使用实例端口`,clusterUpdateSuccess:`集群更新成功`,ip:`IP`,port:`端口`,weight:`权重`,healthy:`健康`,enabled:`启用`,instanceEphemeral:`临时`,editInstance:`编辑实例`,deleteInstance:`删除实例`,instanceUpdateSuccess:`实例更新成功`,instanceDeleteSuccess:`实例删除成功`,instanceDeleteConfirm:`确定要删除实例 {{ip}}:{{port}} 吗?`,online:`上线`,offline:`下线`,noInstances:`该集群暂无实例`,subscriberTitle:`订阅者列表`,subscriberName:`订阅者`,subscribeCount:`订阅数`,clusters:`集群`,totalSubscribers:`共 {{total}} 个订阅者`,metadataInvalid:`元数据格式不正确,请输入有效的 JSON`,serviceNameRequired:`服务名不能为空`},mcp:{title:`MCP Server 管理`,createServer:`创建 MCP Server`,editServer:`编辑 MCP Server`,serverDetail:`MCP Server 详情`,serverName:`服务名称`,protocol:`协议类型`,frontProtocol:`前端协议`,description:`描述`,repository:`代码仓库`,websiteUrl:`官网链接`,version:`版本`,createTime:`创建时间`,author:`作者`,status:`状态`,enabled:`已启用`,disabled:`已禁用`,enable:`启用`,disable:`禁用`,capabilities:`能力列表`,tools:`工具列表`,toolName:`工具名`,toolDescription:`工具描述`,parameters:`参数`,paramName:`参数名`,paramType:`类型`,paramRequired:`必填`,paramDescription:`描述`,packages:`包信息`,endpoints:`端点信息`,frontendEndpoints:`前端端点`,backendEndpoints:`后端端点`,serverSpecification:`服务规格`,toolSpecification:`工具规格`,endpointSpecification:`端点规格`,localServerConfig:`本地服务配置`,remoteServerConfig:`远程服务配置`,serviceRef:`服务引用`,exportPath:`导出路径`,securitySchemes:`安全方案`,addSecurityScheme:`添加安全方案`,overrideExisting:`覆盖已有版本`,setAsLatest:`设为最新版本`,copyConfig:`复制配置`,copySuccess:`配置已复制到剪贴板`,newVersion:`新建版本`,importFromRegistry:`从 MCP 市场导入`,importSource:`导入来源`,importUrl:`MCP 服务地址`,importType:`导入类型`,importTypeJson:`JSON`,importTypeUrl:`URL`,importTypeFile:`文件`,importSearchPlaceholder:`搜索 MCP Server 名称`,importValidate:`验证`,importExecute:`执行导入`,importSuccess:`导入成功`,importFailed:`导入失败`,importResult:`导入结果:成功 {{success}} 个,失败 {{failed}} 个,跳过 {{skipped}} 个`,importSkipInvalid:`跳过无效项`,importOverride:`覆盖已有`,importConflictPolicy:`冲突策略`,importConflictSkip:`跳过`,importConflictOverwrite:`覆盖`,importAll:`导入全部有效项`,importSelectedCount:`已选择 {{count}} 项`,importValidatedCount:`已校验有效 {{count}} 项`,importSelectAll:`全选`,importClearSelection:`清空选择`,importLoadMore:`加载更多`,noImportSource:`暂无可用导入来源`,importStatusValid:`有效`,importStatusWarning:`警告`,importStatusConflict:`冲突`,importStatusInvalid:`无效`,importUnnamed:`未命名`,importMetadata:{protocol:`协议`,status:`状态`},selectedServers:`已选服务`,importTools:`导入工具`,importToolsFromUrl:`从端点导入工具`,searchPlaceholder:`搜索 MCP Server 名称`,deleteConfirm:`确定要删除 MCP Server「{{name}}」吗?`,batchDeleteConfirm:`确定要删除选中的 {{count}} 个 MCP Server 吗?`,deleteSuccess:`删除成功`,batchDelete:`批量删除`,batchDeleteSuccess:`批量删除成功`,enableSuccess:`已启用`,disableSuccess:`已禁用`,createSuccess:`MCP Server 创建成功`,updateSuccess:`MCP Server 更新成功`,totalServers:`共 {{total}} 个 MCP Server`,noTools:`暂无工具`,noPackages:`暂无包信息`,noEndpoints:`暂无端点信息`,serverNameRequired:`服务名称不能为空`,serverSpecRequired:`服务规格不能为空`,invalidJson:`JSON 格式不正确`,formatJson:`格式化 JSON`,advancedConfig:`高级配置`,statusActive:`正常`,statusDeprecated:`已废弃`,statusDeleted:`已删除`,protocolStdio:`Stdio`,protocolSse:`MCP-SSE`,protocolStreamable:`MCP-Streamable`,protocolHttp:`HTTP`,protocolDubbo:`Dubbo`,useExistService:`使用已有服务`,directConnect:`直连新服务`,httpToMcp:`HTTP 转 MCP`,mcpServerEndpoint:`MCP Server 端点 URL`,address:`地址`,port:`端口`,transportProtocol:`传输协议`,selectService:`选择服务`,selectVersion:`选择版本`,inputSchema:`输入参数`,outputSchema:`输出参数`,noDescription:`暂无描述`,allVersions:`所有版本`,latestVersion:`最新`,versionHistory:`版本历史`,totalVersions:`共 {{count}} 个版本`,currentVersion:`当前版本`,toolCount:`{{count}} 个工具`,expandAll:`展开全部`,collapseAll:`收起全部`,backToList:`返回列表`,basicInfo:`基本信息`,noSecuritySchemes:`暂无安全方案`,noCapabilities:`暂无能力`,securityType:`类型`,securitySchemeField:`Scheme`,securityIn:`参数位置`,securityDefaultCredential:`默认凭证`,packageIdentifier:`包标识`,packageRuntime:`运行时`,packageVersion:`包版本`,runtimeArguments:`运行时参数`,packageArguments:`包参数`,environmentVariables:`环境变量`,noEnvironmentVariables:`无环境变量`,envVarName:`变量名`,envVarDefault:`默认值`,envVarRequired:`必需`,envVarSecret:`敏感`,endpointProtocol:`协议`,endpointAddress:`地址`,endpointPort:`端口`,endpointPath:`路径`,endpointHeaders:`请求头`,noHeaders:`无请求头`,serviceName:`服务名`,groupName:`分组名`,loadFailed:`数据加载失败`,retry:`重试`,restToMcpSwitch:`HTTP 转 MCP`,restToMcpSwitchTip:`开启后可将已有 HTTP 服务转为 MCP 协议`,useExistServiceOption:`使用已有服务`,directConnectOption:`直连新服务`,mcpEndpointUrl:`MCP Server 端点 URL`,mcpEndpointUrlPlaceholder:`例如: http://127.0.0.1:8080/sse`,addressPlaceholder:`例如: 127.0.0.1`,portPlaceholder:`例如: 8080`,localServerConfigTip:`输入 MCP 本地服务的 JSON 配置`,versionPlaceholder:`例如: 1.0.0`,descriptionPlaceholder:`描述该 MCP Server 的功能`,securitySchemeId:`方案 ID`,securitySchemeType:`认证类型`,schemeBasic:`Basic`,schemeBearer:`Bearer`,inHeader:`Header`,inQuery:`Query`,defaultDownstreamSecurity:`默认下游安全认证`,defaultDownstreamSecurityDesc:`适用于客户端到网关的请求(如 tools/list),当没有工具级别覆盖时生效。`,defaultUpstreamSecurity:`默认上游安全认证`,defaultUpstreamSecurityDesc:`适用于网关到后端服务的调用(如默认的 requestTemplate.security)。`,passthroughAuth:`透传认证`,credentialOverride:`凭证覆盖`,addRow:`添加`,removeRow:`移除`,publishAndCreate:`创建并发布`,publishAndUpdate:`更新并发布`,saving:`保存中...`,protocolRequired:`请选择协议类型`,versionRequired:`版本号不能为空`,addressRequired:`地址不能为空`,portRequired:`端口不能为空`,mcpEndpointRequired:`MCP 端点 URL 不能为空`,localConfigRequired:`本地服务配置不能为空`,toolManagement:`工具管理`,noToolsYet:`暂无工具,请添加或导入`,newTool:`新建工具`,editTool:`编辑工具`,deleteTool:`删除工具`,deleteToolConfirm:`确定要删除工具「{{name}}」吗?`,toolNamePlaceholder:`例如: get_user_info`,toolNameRequired:`工具名称不能为空`,toolNameExists:`工具名称已存在`,toolEnabled:`启用状态`,searchTools:`搜索工具...`,importFromMcpInstance:`从 MCP 实例导入`,importFromOpenApi:`从 OpenAPI 导入`,importToolsFromMCPTitle:`从 MCP 实例导入工具`,importToolsFromOpenAPITitle:`从 OpenAPI 文件导入工具`,dragOrClickToUpload:`拖拽文件到此处或点击选择`,supportedFormats:`支持 .json, .yaml, .yml 格式`,fileReadFailed:`文件读取失败`,fileInvalidFormat:`文件格式无效`,pleaseSelectFile:`请选择文件`,noHealthyInstance:`未找到健康实例`,importingTools:`导入中...`,importToolsSuccess:`工具导入成功,共 {{count}} 个`,importToolsFailed:`导入工具失败`,authToken:`认证令牌`,authTokenPlaceholder:`可选,用于访问需要认证的 MCP 服务`,serverAddress:`服务地址`,toolBasicInfo:`基本信息`,toolInputSchema:`入参配置`,toolOutputSchema:`出参配置`,toolMeta:`Meta`,toolAnnotations:`Annotations`,toolAdvancedConfig:`高级配置`,requestTemplate:`请求模板`,responseTemplate:`响应模板`,addParameter:`添加参数`,addProperty:`添加属性`,deleteParam:`删除参数`,deleteParamConfirm:`确定要删除此参数及其子属性吗?`,typeString:`字符串`,typeNumber:`数字`,typeInteger:`整数`,typeBoolean:`布尔值`,typeArray:`数组`,typeObject:`对象`,annotationsTitle:`标题`,readOnlyHint:`只读提示`,readOnlyHintDesc:`此工具是否只执行读取操作`,destructiveHint:`破坏性提示`,destructiveHintDesc:`此工具是否可能执行破坏性操作`,idempotentHint:`幂等提示`,idempotentHintDesc:`此工具是否是幂等的`,openWorldHint:`开放世界提示`,openWorldHintDesc:`此工具是否可能与外部实体交互`,foundOperations:`发现 {{count}} 个 API 操作`,foundSecuritySchemes:`发现 {{count}} 个安全方案`,createTool:`创建工具`,previewTool:`预览工具`,toolParams:`{{count}} 个参数`,noParameters:`暂无参数`,selectToolToView:`请选择一个工具查看详情`},agent:{title:`Agent 管理`,createAgent:`创建 Agent`,editAgent:`编辑 Agent`,agentDetail:`Agent 详情`,agentName:`Agent 名称`,version:`版本号`,protocolVersion:`协议版本`,serviceUrl:`服务地址`,transport:`传输协议`,selectTransport:`请选择传输协议`,description:`描述`,descriptionPlaceholder:`请输入 Agent 的功能描述...`,noDescription:`暂无描述`,provider:`提供商`,providerName:`提供商名称`,providerNamePlaceholder:`请输入提供商名称`,providerUrl:`提供商 URL`,iconUrl:`图标 URL`,documentationUrl:`文档 URL`,documentation:`文档`,capabilities:`能力配置`,streaming:`流式传输`,streamingDesc:`是否支持流式数据传输`,pushNotifications:`推送通知`,pushNotificationsDesc:`是否支持推送通知功能`,stateHistory:`状态历史`,stateHistoryDesc:`是否支持记录状态转换历史`,skills:`技能列表`,skillsHelp:`Agent 具备的技能清单,JSON 数组格式`,skillCount:`{{count}} 个技能`,noSkills:`暂无技能配置`,inputModes:`输入模式`,outputModes:`输出模式`,ioModes:`输入/输出模式`,security:`安全配置`,securitySchemes:`安全方案`,supportedInterfaces:`支持接口`,additionalInterfaces:`附加接口`,interfaceItem:`接口 {{index}}`,preferredInterface:`首选项`,tenant:`租户`,extendedCardSupport:`扩展卡片支持`,extendedCardSupportDesc:`是否支持认证扩展卡片功能`,advancedConfig:`高级配置`,showAdvanced:`展开高级配置`,hideAdvanced:`收起高级配置`,setAsLatest:`设为最新版本`,setAsLatestDesc:`开启后,此版本将成为发布版本`,basicInfo:`基本信息`,versionHistory:`版本历史`,totalVersions:`共 {{count}} 个版本`,currentVersion:`当前版本`,isLatest:`是否最新`,selectVersion:`选择版本`,searchPlaceholder:`搜索 Agent 名称`,totalAgents:`共 {{total}} 个 Agent`,deleteConfirm:`确定要删除 Agent「{{name}}」吗?`,batchDeleteConfirm:`确定要删除选中的 {{count}} 个 Agent 吗?`,deleteSuccess:`删除成功`,batchDelete:`批量删除`,batchDeleteSuccess:`批量删除成功`,createSuccess:`Agent 创建成功`,updateSuccess:`Agent 更新成功`,create:`创建`,update:`更新`,publish:`发布`,confirmPublish:`确认发布`,publishStrategy:`发布策略`,publishStrategyDesc:`选择如何处理此次修改。版本号:`,strategyNewVersion:`发布新版本`,strategyNewVersionDesc:`以新版本号发布,但不设为默认版本`,strategySetLatest:`设为最新版本`,strategySetLatestDesc:`设为默认版本,客户端未指定版本时自动使用`,strategyEditCurrent:`更新当前版本`,strategyEditCurrentDesc:`覆盖当前版本配置,版本号和最新标记不变`,strategyReasonVersionExists:`版本已存在`,strategyReasonVersionNotExists:`版本不存在`,strategyReasonAlreadyLatest:`已是最新版本`,loadFailed:`数据加载失败`,backToList:`返回列表`,nameRequired:`Agent 名称不能为空`,versionRequired:`版本号不能为空`,urlRequired:`服务地址不能为空`,protocolVersionRequired:`协议版本不能为空`,transportRequired:`请选择传输协议`,jsonFormatError:`格式错误`},prompt:{title:`Prompt 管理`,createPrompt:`创建 Prompt`,editPrompt:`编辑 Prompt`,editMetadata:`编辑描述与标签`,promptDetail:`Prompt 详情`,promptKey:`Prompt Key`,version:`版本号`,latestVersion:`最新版本`,template:`模板内容`,versionDiff:`版本 Diff`,diffBeforeVersion:`基准版本`,diffAfterVersion:`目标版本`,diffSelectVersionPlaceholder:`请选择版本`,diffSwapVersions:`交换对比版本`,diffModifiedContent:`修改内容`,diffTemplateFile:`模板内容`,diffSelectVersions:`请选择基准版本和目标版本进行对比`,diffNoChanges:`两个版本没有模板内容差异`,diffSameVersion:`请选择两个不同版本进行对比`,variables:`变量列表`,variableName:`变量名`,variableDefault:`默认值`,variableDescription:`描述`,description:`描述`,commitMsg:`提交信息`,bizTags:`业务标签`,labels:`标签`,labelName:`标签名`,bindLabel:`绑定标签`,unbindLabel:`解绑标签`,versionHistory:`版本历史`,downloadMarkdown:`下载 Markdown`,downloadMarkdownTip:`将当前选中的版本导出为 Markdown 文档`,downloadMarkdownSuccess:`Markdown 文档下载成功`,downloadMarkdownFailed:`Markdown 文档下载失败`,downloadCount:`{{count}} 次下载`,downloads:`下载量`,versionDownloads:`版本下载量`,publishVersion:`发布新版本`,debug:`调试`,startDebug:`开始调试`,clearResult:`清除结果`,aiOptimize:`AI 优化`,optimizeGoal:`优化目标`,optimizeGoalPlaceholder:`描述优化目标,如:让表达更清晰、增加示例等(可选)`,startOptimize:`开始优化`,optimizing:`优化中...`,optimizedResult:`优化结果`,originalTemplate:`原始模板`,applyOptimize:`应用优化结果`,noDescription:`暂无描述`,noVariables:`暂无变量`,noLabels:`暂无标签`,noVersions:`暂无版本历史`,totalVersions:`共 {{count}} 个版本`,currentVersion:`当前版本`,searchPlaceholder:`搜索 Prompt Key`,totalPrompts:`共 {{total}} 个 Prompt`,deleteConfirm:`确定要删除 Prompt「{{name}}」吗?`,batchDeleteConfirm:`确定要删除选中的 {{count}} 个 Prompt 吗?`,deleteSuccess:`删除成功`,batchDelete:`批量删除`,batchDeleteSuccess:`批量删除成功`,createSuccess:`Prompt 创建成功`,updateSuccess:`Prompt 更新成功`,publishSuccess:`版本发布成功`,bindLabelSuccess:`标签绑定成功`,unbindLabelSuccess:`标签解绑成功`,metadataUpdateSuccess:`信息更新成功`,loadFailed:`数据加载失败`,backToList:`返回列表`,keyRequired:`Prompt Key 不能为空`,keyInvalid:`Prompt Key 仅支持字母、数字、下划线和横杠`,versionRequired:`版本号不能为空`,versionInvalid:`版本号格式不正确,格式为 x.y.z`,versionMustGreater:`新版本号必须大于当前版本 {{current}}`,templateRequired:`模板内容不能为空`,labelInvalid:`标签仅支持字母、数字、点号、下划线和横杠`,selectVersion:`选择版本`,newVersion:`新版本号`,newVersionPlaceholder:`如 0.0.2 或 v3(留空自动递增)`,templatePlaceholder:`输入 Prompt 模板,使用 {{variable}} 语法定义变量...`,commitMsgPlaceholder:`描述此次变更的内容...`,descriptionPlaceholder:`描述该 Prompt 的用途...`,tagPlaceholder:`输入标签后按回车添加`,keyPlaceholder:`请输入 Prompt Key`,versionPlaceholder:`例如: 1.0.0`,thinking:`思考过程`,modelOutput:`模型输出`,streaming:`输出中...`,userInput:`用户输入`,userInputPlaceholder:`输入要发送给模型的用户消息`,renderedPrompt:`渲染后的 Prompt`,send:`发送`,basicInfo:`基本信息`,templateEditor:`模板编辑器`,debugPanel:`调试面板`,labelManagement:`标签管理`,addLabel:`添加标签`,manageLabels:`管理标签`,labelBoundTo:`绑定到`,publishTime:`发布时间`,publisher:`发布者`,retry:`重试`,"versionStatus.draft":`草稿`,"versionStatus.reviewing":`审核中`,"versionStatus.reviewed":`待发布`,"versionStatus.online":`已上线`,"versionStatus.offline":`已下线`,"versionStatus.pendingPublish":`待发布`,"versionStatus.rejected":`审核未通过`,submit:`提交审核`,publish:`发布`,forcePublish:`强制发布`,online:`上线`,offline:`下线`,createDraft:`创建草稿`,createDraftFrom:`基于此版本创建草稿`,deleteDraft:`删除草稿`,saveDraft:`保存草稿`,draftExistsTip:`已存在编辑中或审核中的版本`,publishDisabledPipeline:`Pipeline 未通过审核`,forcePublishConfirmTitle:`确认强制发布`,forcePublishConfirmDesc:`确定要强制发布版本 {{version}} 吗?此操作将跳过 Pipeline 审核。`,forcePublishConfirm:`确认强制发布`,onlineCount:`在线: {{count}}`,hasDraft:`有草稿`,hasReviewing:`审核中`,noOnlineVersion:`无在线版本`,submitSuccess:`提交审核成功`,draftSaveSuccess:`草稿保存成功`,draftDeleteSuccess:`草稿删除成功`,onlineSuccess:`上线成功`,offlineSuccess:`下线成功`,forcePublishSuccess:`强制发布成功`,redraft:`重新编辑`,redraftSuccess:`版本已退回草稿,可重新编辑`,labelsUpdateSuccess:`标签更新成功`,descriptionUpdateSuccess:`描述更新成功`,bizTagsUpdateSuccess:`业务标签更新成功`,governanceInfo:`治理信息`,versionTimeline:`版本时间线`,pipelineStatus:`审核流水线`,pipelineInProgress:`审核中`,editDraft:`编辑草稿`,createPromptDesc:`创建新的 Prompt 草稿,包含模板和变量定义。`,createDraftFromDesc:`基于版本 {{version}} 创建新草稿`,status:`状态`},namespace:{title:`命名空间`,create:`新建命名空间`,edit:`编辑命名空间`,detail:`命名空间详情`,name:`命名空间名称`,id:`命名空间 ID`,description:`描述`,configCount:`配置数`,quota:`配额`,namePlaceholder:`请输入命名空间名称`,idPlaceholder:`不填则自动生成`,descriptionPlaceholder:`请输入命名空间描述`,nameRequired:`命名空间名称不能为空`,deleteConfirm:`确定要删除命名空间「{{name}}」吗?该操作不可撤销。`,deleteSuccess:`命名空间删除成功`,createSuccess:`命名空间创建成功`,updateSuccess:`命名空间更新成功`,publicNamespace:`public`,publicNamespaceDesc:`公共命名空间,不可编辑和删除`,total:`共 {{total}} 个命名空间`},cluster:{title:`集群节点`,nodeAddress:`节点地址`,nodeIp:`IP`,nodePort:`端口`,nodeState:`状态`,searchPlaceholder:`搜索节点 IP 或地址`,leave:`下线`,leaveConfirm:`确定要将节点「{{address}}」从集群中移除吗?`,leaveSuccess:`节点下线成功`,total:`共 {{total}} 个节点`,stateUp:`运行中`,stateDown:`已下线`,stateSuspicious:`可疑`,refresh:`刷新`},plugin:{title:`插件管理`,pluginName:`插件名称`,pluginType:`插件类型`,enabled:`已启用`,disabled:`已禁用`,critical:`关键`,status:`状态`,allTypes:`全部类型`,enable:`启用`,disable:`禁用`,enableSuccess:`插件已启用`,disableSuccess:`插件已禁用`,detail:`插件详情`,version:`版本`,description:`描述`,configurable:`可配置`,clusterAvailability:`集群可用性`,noPlugins:`暂无插件`,total:`共 {{total}} 个插件`,typeAuth:`认证`,typeDatasource:`数据源`,typeConfigChange:`配置变更`,typeEncryption:`加密`,typeTrace:`追踪`,typeEnvironment:`环境`,typeControl:`控制`,typeAiPipeline:`AI 发布流水线`,typeAiStorage:`AI 资源存储`,typeVisibility:`可见性`,availableNodes:`可用节点`,yes:`是`,no:`否`,pluginCount:`个插件`},authority:{title:`权限控制`,userManagement:`用户管理`,roleManagement:`角色管理`,permissionManagement:`权限管理`,username:`用户名`,password:`密码`,newPassword:`新密码`,confirmPassword:`确认密码`,role:`角色`,resource:`资源`,action:`动作`,createUser:`创建用户`,deleteUser:`删除用户`,resetPassword:`重置密码`,bindRole:`绑定角色`,addPermission:`添加权限`,usernamePlaceholder:`请输入用户名`,passwordPlaceholder:`请输入密码`,confirmPasswordPlaceholder:`请再次输入密码`,newPasswordPlaceholder:`请输入新密码`,rolePlaceholder:`请输入角色名`,selectRolePlaceholder:`请选择角色`,selectUserPlaceholder:`请选择用户`,resourcePlaceholder:`请选择资源`,searchUserPlaceholder:`搜索用户名`,searchRolePlaceholder:`搜索角色名`,usernameRequired:`用户名不能为空`,passwordRequired:`密码不能为空`,passwordMismatch:`两次输入的密码不一致`,roleRequired:`角色名不能为空`,resourceRequired:`资源不能为空`,actionRequired:`请选择动作`,deleteUserConfirm:`确定要删除用户「{{name}}」吗?`,deleteRoleConfirm:`确定要删除角色「{{role}}」与用户「{{username}}」的绑定吗?`,deletePermissionConfirm:`确定要删除该权限配置吗?`,createUserSuccess:`用户创建成功`,deleteUserSuccess:`用户删除成功`,resetPasswordSuccess:`密码重置成功`,createRoleSuccess:`角色绑定成功`,deleteRoleSuccess:`角色绑定删除成功`,createPermissionSuccess:`权限添加成功`,deletePermissionSuccess:`权限删除成功`,totalUsers:`共 {{total}} 个用户`,totalRoles:`共 {{total}} 条角色绑定`,totalPermissions:`共 {{total}} 条权限配置`,actionRead:`只读`,actionWrite:`只写`,actionReadWrite:`读写`,fuzzySearch:`模糊搜索`,exactSearch:`精确搜索`,adminCannotDelete:`管理员角色不能删除`},settings:{title:`设置`,description:`管理 Nacos 控制台的偏好与集成配置`,copilotConfig:`Copilot 配置`,copilotConfigDesc:`配置 AI 助手的模型与密钥,用于智能化配置管理`,apiKey:`API 密钥`,apiKeyHint:`建议通过环境变量 COPILOT_API_KEY 设置,优先级高于此处配置`,apiKeyPlaceholder:`请输入 API Key`,model:`模型`,modelPlaceholder:`请选择模型`,saveSuccess:`保存成功`},agentSpec:{title:`AgentSpec 管理`,totalAgentSpecs:`共 {{total}} 个 AgentSpec`,upload:`上传`,createAgentSpec:`创建 AgentSpec`,createAgentSpecDesc:`填写基础信息后将创建第一个草稿版本`,createSuccess:`AgentSpec 创建成功`,createFailed:`创建 AgentSpec 失败`,editAgentSpec:`编辑 AgentSpec`,newFile:`新建文件`,newFolder:`新建文件夹`,basicInfo:`基础信息`,editor:`编辑器`,editorLoading:`编辑器加载中...`,editBasicInfo:`编辑基础信息`,agentSpecName:`AgentSpec 名称`,namespace:`命名空间`,files:`文件概览`,fileTreeTitle:`文件`,fileTree:`文件树`,activeFile:`当前文件`,workspaceSummary:`工作区摘要`,unnamedAgentSpec:`未命名 AgentSpec`,noDescriptionPreview:`manifest 中的描述将显示在这里,便于在编辑时快速确认 AgentSpec 的定位。`,modeNew:`新建`,modeEdit:`草稿`,modeVersion:`新版本`,modePreview:`预览`,modified:`已修改`,readOnly:`只读`,resizeFileTreePanel:`调整文件树面板大小`,renameNode:`重命名 {{name}}`,deleteNode:`删除 {{name}}`,createFileIn:`在 {{name}} 中新建文件`,createFolderIn:`在 {{name}} 中新建文件夹`,resourceRenameFile:`重命名文件`,resourceCopyFile:`复制文件内容`,resourceDownloadFile:`下载文件`,resourceDeleteFile:`删除文件`,resourceCopySuccess:`文件内容已复制`,resourceCopyFailed:`复制文件内容失败`,createFile:`创建文件`,createFolder:`创建文件夹`,createFileDesc:`输入文件路径并选择资源类型。`,createFolderDesc:`输入文件夹路径并选择资源类型。`,resourceType:`资源类型`,resourceTypeRequired:`资源类型不能为空`,customFolder:`自定义`,filePath:`文件路径`,folderPath:`文件夹路径`,filePathPlaceholderCompact:`例如: tools/helper.py`,folderPathPlaceholderCompact:`例如: tools`,filePathPlaceholder:`例如: skill/tools/helper.py`,folderPathPlaceholder:`例如: skill/tools`,pathRequired:`路径不能为空`,invalidFileName:`文件名无效`,fileExists:`文件已存在`,folderExists:`文件夹已存在`,namePlaceholder:`请输入 AgentSpec 名称`,descriptionPlaceholder:`请输入 AgentSpec 描述`,searchPlaceholder:`搜索 AgentSpec 名称`,sortDefault:`默认排序`,sortByDownloads:`按下载量排序`,filterByOwner:`按创建者过滤`,filterOwnerPlaceholder:`输入创建者名称`,filterOnlyMine:`只看我的`,filterScopeAll:`所有范围`,filterScopePublic:`仅公开`,filterScopePrivate:`仅私有`,batchDelete:`批量删除`,deleteConfirm:`确定要删除 AgentSpec「{{name}}」吗?`,batchDeleteConfirm:`确定要删除选中的 {{count}} 个 AgentSpec 吗?`,deleteSuccess:`删除成功`,batchDeleteSuccess:`批量删除成功`,loadListError:`获取 AgentSpec 列表失败`,loadError:`加载 AgentSpec 详情失败`,nameRequired:`AgentSpec 名称不能为空`,descriptionRequired:`描述不能为空`,saveSuccess:`保存成功`,unsaved:`未保存`,enabled:`已启用`,disabled:`已禁用`,version:`版本`,createTime:`创建时间`,status:`状态`,versionStatus:{draft:`草稿`,reviewing:`审核中`,reviewed:`待发布`,pendingPublish:`待发布`,rejected:`审核未通过`,online:`已上线`,offline:`已下线`},updateTime:`更新时间`,onlineCountLabel:`在线情况`,description:`描述`,noDescription:`暂无描述`,editing:`编辑中`,hasDraft:`有草稿`,draftVersion:`编辑中`,reviewing:`审核中`,reviewingVersion:`审核中`,submit:`提交审核`,resubmit:`重新提交审核`,saveAndPublish:`保存并发布`,publish:`发布`,online:`版本上线`,offline:`版本下线`,onlineCount:`已上线版本数:{{count}}`,noOnlineVersion:`无上线版本`,downloadCount:`{{count}} 次下载`,downloads:`下载量`,versionDownloads:`版本下载量`,author:`作者`,backToList:`返回列表`,selectVersion:`选择版本`,latestVersion:`最新`,totalVersions:`共 {{count}} 个版本`,versionHistory:`版本历史`,resources:`资源文件`,agentsFile:`AGENTS.md`,noAgentsFile:`暂无 AGENTS.md 内容`,labels:`标签`,newVersion:`新建版本`,createDraft:`创建草稿`,createDraftFrom:`基于此版本创建草稿`,draftExistsTip:`已存在草稿/未发布版本`,createDraftSuccess:`草稿创建成功`,deleteDraft:`删除草稿`,editDraft:`编辑草稿`,cancelEdit:`取消`,saveDraft:`保存草稿`,draftSaveSuccess:`草稿已保存`,deleteDraftSuccess:`草稿已删除`,submitSuccess:`提交审核成功`,publishSuccess:`发布成功`,redraft:`重新编辑`,redraftSuccess:`版本已退回草稿,可重新编辑`,onlineSuccess:`上线成功`,offlineSuccess:`下线成功`,enableSuccess:`已启用 AgentSpec`,disableSuccess:`已禁用 AgentSpec`,scopePublic:`公开`,scopePrivate:`私有`,scopeUpdateSuccess:`可见范围已更新`,pipelineStatus:`审核流水线`,pipelineInProgress:`审核中`,pipelineApproved:`已通过`,pipelineRejected:`审核未通过`,pipelineNone:`暂无审核流水线`,pipelineNodePassed:`通过`,pipelineNodeFailed:`失败`,pipelineNoMessage:`暂无输出信息`,pipelineCopy:`复制结果`,pipelineCopied:`已复制`,pipelineCopySuccess:`扫描结果已复制`,pipelineCopyFailed:`复制失败`,labelsUpdateSuccess:`标签更新成功`,bizTagsUpdateSuccess:`业务标签更新成功`,bizTagPlaceholder:`添加标签`,noBizTags:`暂无业务标签`,noLabels:`暂无标签`,noVersions:`暂无版本`,labelKeyRequired:`标签 key 不能为空`,labelKeyDuplicate:`标签 key 已存在`,labelKeyInvalid:`标签 key 包含非法字符`,labelKey:`Key`,labelValue:`Value`,saveLabels:`保存标签`,uploadZip:`上传 AgentSpec ZIP`,uploadZipDesc:`选择一个 .zip 文件导入 AgentSpec 包。`,invalidZipFile:`请选择有效的 .zip 文件`,uploadSuccess:`上传成功`,uploadFailed:`上传失败`,dragOrClick:`点击选择 .zip 文件`},skill:JSON.parse(`{"title":"Skill 管理","totalSkills":"共 {{total}} 个 Skill","upload":"上传","searchPlaceholder":"搜索 Skill 名称","skillName":"Skill 名称","description":"描述","noDescription":"暂无描述","enabled":"已启用","disabled":"已禁用","editing":"编辑中","hasDraft":"有草稿","reviewing":"审核中","online":"版本上线","offline":"版本下线","versionStatus":{"draft":"草稿","reviewing":"审核中","reviewed":"待发布","pendingPublish":"待发布","rejected":"审核未通过","online":"已上线","offline":"已下线"},"onlineCount":"已上线版本数:{{count}}","noOnlineVersion":"无上线版本","downloadCount":"{{count}} 次下载","downloads":"下载量","versionDownloads":"版本下载量","basicInfo":"基础信息","instruction":"SKILL.md","skillMd":"SKILL.md","resources":"资源文件","noResources":"暂无资源","resourceRenameFile":"重命名文件","resourceCopyFile":"复制文件内容","resourceDownloadFile":"下载文件","resourceDeleteFile":"删除文件","resourceCopySuccess":"文件内容已复制","resourceCopyFailed":"复制文件内容失败","versionDiff":"版本 Diff","diffBeforeVersion":"基准版本","diffAfterVersion":"目标版本","diffSelectVersionPlaceholder":"请选择版本","diffSwapVersions":"交换对比版本","diffAddedFiles":"新增文件","diffRemovedFiles":"删除文件","diffModifiedFiles":"修改文件","diffChangedFiles":"变更文件","diffSelectVersions":"请选择基准版本和目标版本进行对比","diffNoChanges":"两个版本没有文件差异","diffSameVersion":"请选择两个不同版本进行对比","diffTextUnsupported":"该文件不支持文本 Diff","diffTextUnsupportedDesc":"图片、PPT、压缩包等文件无法以文本方式对比,当前仅展示版本间的变更状态。","diffStatusAdded":"新增","diffStatusRemoved":"删除","diffStatusModified":"修改","resourceName":"资源名称","resourceType":"资源类型","resourceContent":"内容","labels":"标签","noLabels":"暂无标签","version":"版本","status":"状态","createTime":"创建时间","updateTime":"更新时间","author":"作者","versionHistory":"版本历史","totalVersions":"共 {{count}} 个版本","noVersions":"暂无版本","selectVersion":"选择版本","latestVersion":"最新","newVersion":"新建版本","createDraft":"创建草稿","createDraftSuccess":"草稿创建成功","submit":"提交审核","resubmit":"重新提交审核","submitSuccess":"提交审核成功","publish":"发布","publishSuccess":"发布成功","onlineSuccess":"上线成功","offlineSuccess":"下线成功","enableSuccess":"已启用 Skill","disableSuccess":"已禁用 Skill","scopePublic":"公开","scopePrivate":"私有","toggleScope":"切换可见范围","scopeUpdateSuccess":"可见范围已更新","masterSwitch":"Skill 主控开关","masterSwitchDesc":"禁用后所有版本将对 Agent 不可见","skillDisabledWarning":"Skill 已禁用,版本状态变更不会生效","versionActions":"当前版本操作","labelsUpdateSuccess":"标签已更新","labelManagement":"版本标签","labelBindDesc":"选择要绑定到版本 {{version}} 的标签,或创建新标签。","searchOrCreateLabel":"搜索或创建标签...","createLabel":"创建标签「{{name}}」","selectAll":"全选 {{count}} 个","clearAll":"清除","deleteConfirm":"确定要删除 Skill「{{name}}」吗?","batchDeleteConfirm":"确定要删除选中的 {{count}} 个 Skill 吗?","deleteSuccess":"删除成功","batchDelete":"批量删除","batchDeleteSuccess":"批量删除成功","backToList":"返回列表","loadFailed":"加载数据失败","uploadZip":"上传 Skill ZIP","uploadZipDesc":"选择一个 .zip 文件导入 Skill 包。","invalidZipFile":"请选择有效的 .zip 文件","uploadSuccess":"上传成功","uploadSuccessWithName":"Skill {{name}} 上传成功","uploadFailed":"上传失败","precheckUpload":"解析并校验","confirmUpload":"确认上传","confirmForceOverwriteUpload":"强制覆盖并上传","confirmBatchUpload":"确认上传","uploadChecking":"校验中","uploadPrecheckBlocked":"当前 Skill 包不能上传","precheckNewSkill":"当前 Skill 不存在,上传后会创建草稿版本 {{version}}。","precheckExistingSkillCreateDraft":"当前 Skill 已存在,上传后会创建草稿版本 {{version}}。","precheckVersionExists":"解析版本 {{version}} 已存在。","precheckDraftOverwriteOnly":"当前已有草稿 {{draftVersion}};继续上传会覆盖该草稿,上传后草稿版本为 {{version}}。是否覆盖?","precheckVersionConverted":"用户上传版本是 {{parsedVersion}},已转化为合法版本 {{version}}。","precheckVersionNormalizedAndAdjusted":"用户上传版本 {{parsedVersion}} 已规范化为 {{normalizedVersion}};因版本 {{normalizedVersion}} 已存在,本次会创建草稿版本 {{version}}。","precheckCreateVersionAdjusted":"解析版本 {{parsedVersion}} 已被占用,本次会创建草稿版本 {{version}}。","precheckReviewingBlocked":"当前已有审核中版本 {{version}},审核结束前不能上传。","precheckNoPermission":"你没有这个 Skill 的编辑权限,不能上传。","parsedVersion":"解析版本","uploadedVersion":"上传版本","resolvedVersion":"目标版本","resultVersion":"目标版本:{{version}}","dragOrClick":"拖拽 .zip 文件到此处,或点击选择","dragDropHint":"支持拖拽 .zip 文件上传","dropFileHere":"释放 .zip 文件以上传","batchUploadResult":"批量上传:{{succeeded}} 个成功,{{failed}} 个失败","batchUploadAllSuccess":"全部 {{count}} 个 Skill 上传成功","batchUploadSuccessWithSkipped":"{{succeeded}} 个 Skill 上传成功,已跳过 {{skipped}} 个","batchUploadSkipped":"已跳过 {{count}} 个 Skill","batchUploadPartialFail":"{{succeeded}} 个成功,{{failed}} 个失败:{{reasons}}","batchUploadAllFailed":"全部 {{count}} 个 Skill 上传失败","batchUploadAborted":"检测到 {{existing}} 个已有 Skill、{{blocked}} 个阻断项,已终止上传。","batchUploadNothingToUpload":"没有可上传的 Skill。","targetNamespace":"目标命名空间","batchSkillCount":"Skill 数量","batchUploadOnlyEntryCount":"无效/非 Skill","batchPrecheckSummary":"检查完成:共 {{total}} 个,新增 {{fresh}} 个,已有 {{existing}} 个,阻断 {{blocked}} 个。","batchPrecheckBlockedTip":"无权限或审核中的 Skill 会按原批量上传结果返回失败。","batchPrecheckUploadOnlyTip":"另有 {{count}} 个目录不是有效 Skill,不参与 Skill 冲突检查,上传后仍按批量结果展示。","sameSkillPolicy":"相同 Skill:","batchPolicyAbort":"终止上传","batchPolicySkip":"跳过","batchPolicyOverwrite":"覆盖","batchPolicySkipDrafts":"跳过已有草稿","batchPolicyOverwriteDrafts":"覆盖已有草稿","batchPolicySkipDraftsDesc":"已有草稿的 Skill 不上传;其他 Skill 按正常规则上传,失败仍在批量结果中展示。","batchPolicyOverwriteDraftsDesc":"已有草稿的 Skill 会被当前 ZIP 内容覆盖;无权限或审核中的 Skill 仍会失败。","batchItemNew":"新增","batchItemExisting":"已有","batchItemDraft":"已有草稿","batchItemBlocked":"阻断","batchItemInvalidSkill":"解析失败","batchItemInvalidSkillDesc":"SKILL.md 不是有效 Skill 描述文件","batchItemNonSkillFolder":"非 Skill","batchItemNonSkillFolderDesc":"目录内没有 SKILL.md,不作为 Skill 预检","batchItemVersionConverted":"上传版本 {{parsedVersion}} -> {{version}}","batchItemVersionNormalizedAndAdjusted":"上传版本 {{parsedVersion}} -> {{normalizedVersion}} -> {{version}}","resultSucceeded":"成功 ({{count}})","resultFailed":"失败 ({{count}})","importFromRegistry":"从 Skill 市场导入","importSource":"导入来源","importSearchPlaceholder":"搜索 Skill 名称","importValidate":"验证","importExecute":"执行导入","importSuccess":"导入成功","importFailed":"导入失败","importResult":"导入结果:成功 {{success}} 个,失败 {{failed}} 个,跳过 {{skipped}} 个","importSkipInvalid":"跳过无效项","importOverride":"覆盖已有","importConflictPolicy":"冲突策略","importConflictSkip":"跳过","importConflictOverwrite":"覆盖","importAll":"导入全部有效项","importSelectedCount":"已选择 {{count}} 项","importValidatedCount":"已校验有效 {{count}} 项","importSelectAll":"全选","importClearSelection":"清空选择","importLoadMore":"加载更多","noImportSource":"暂无可用导入来源","importStatusValid":"有效","importStatusWarning":"警告","importStatusConflict":"冲突","importStatusInvalid":"无效","importUnnamed":"未命名","importMetadata":{"fileCount":"文件数","source":"来源"},"download":"下载","downloadVersion":"下载版本","createSkill":"创建 Skill","createSkillDesc":"创建一个新的 Skill 草稿版本,后续可以继续编辑内容。","namePlaceholder":"请输入 Skill 名称","descPlaceholder":"描述该 Skill 的功能","skillMdPlaceholder":"输入完整的 SKILL.md 内容...","skillMdHint":"当 Agent 加载该 Skill 时,将注入完整的 SKILL.md 内容(包含 frontmatter 与正文)作为执行上下文。","nameRequired":"Skill 名称不能为空","nameTooLong":"Skill 名称长度须在 1–64 个字符之间","nameInvalidFormat":"Skill 名称只能包含小写字母、数字和连字符,且不能以连字符开头或结尾","nameNoConsecutiveHyphens":"Skill 名称不能包含连续的连字符(--)","descriptionRequired":"描述不能为空","instructionRequired":"SKILL.md 内容不能为空","skillMdRequired":"SKILL.md 内容不能为空","submitRequiresFields":"提交前请填写描述和 SKILL.md 内容","createSuccess":"Skill 创建成功","createFailed":"创建 Skill 失败","retry":"重试","editDraft":"编辑草稿","saveDraft":"保存草稿","cancelEdit":"取消","draftSaveSuccess":"草稿已保存","commitMsg":"版本提交说明","commitMsgPlaceholder":"可选:说明本草稿版本的变更内容","commitMsgHint":"仅保存在该草稿版本上(版本历史中展示),与上方的 Skill 描述无关。","toggleEnable":"启用/禁用 Skill","pipelineStatus":"审核流水线","pipelineInProgress":"审核中","pipelineApproved":"已通过","pipelineRejected":"审核未通过","pipelineNone":"暂无审核流水线","pipelineNodePassed":"通过","pipelineNodeFailed":"失败","pipelineNoMessage":"暂无输出信息","pipelineCopy":"复制结果","pipelineCopied":"已复制","pipelineCopySuccess":"扫描结果已复制","pipelineCopyFailed":"复制失败","publishDisabledPipeline":"需等待审核通过后才能发布","forcePublish":"强制发布","forcePublishSuccess":"强制发布成功","redraft":"重新编辑","redraftSuccess":"版本已退回草稿,可重新编辑","forcePublishConfirmTitle":"强制发布(跳过审核流水线)","forcePublishConfirmDesc":"您将强制发布版本 \\"{{version}}\\",跳过审核流水线校验,立即上线。此操作不可撤销,请确认。","forcePublishConfirm":"确认强制发布","createDraftFrom":"基于此版本创建草稿","newVersionTitle":"创建新草稿版本","newVersionDesc":"基于版本 {{current}} 创建新草稿,请输入新版本号","newVersionPlaceholder":"例如: 1.0.1 或 v2","newVersionRequired":"新版本号不能为空","versionInvalid":"版本号格式不正确,格式为 x.y.z 或 vN","versionMustGreater":"新版本号必须大于当前版本 {{current}}","draftExistsTip":"已存在草稿/未发布版本","bizTagsUpdateSuccess":"业务标签更新成功","bizTagPlaceholder":"添加标签","noBizTags":"暂无业务标签","sortDefault":"默认排序","sortByDownloads":"按下载量排序","filterByOwner":"按创建者过滤","filterOwnerPlaceholder":"输入创建者名称","filterOnlyMine":"只看我的","filterScopeAll":"所有范围","filterScopePublic":"仅公开","filterScopePrivate":"仅私有","filterByBizTag":"按业务标签过滤","filterBizTagPlaceholder":"输入业务标签","labelKeyRequired":"标签键不能为空","labelKeyInvalid":"标签键格式不合法","labelKeyDuplicate":"标签键已存在","saveLabels":"保存标签","labelKey":"键","labelValue":"版本","deleteDraft":"删除草稿","deleteDraftSuccess":"草稿已删除","manualCreate":"手动创建","aiGenerate":"AI 生成","aiOptimize":"AI 优化","backgroundInfo":"背景信息","backgroundInfoPlaceholder":"描述你想要创建的 Skill 的功能、用途、目标用户等...","backgroundInfoRequired":"请输入背景信息","generateSkill":"生成 Skill","generating":"生成中...","generateSuccess":"Skill 生成成功","generateFailed":"Skill 生成失败","regenerate":"重新生成","applyGenerated":"应用生成结果","optimizationGoal":"优化目标","optimizationGoalPlaceholder":"描述你希望如何优化这个 Skill(可选)...","selectTargetFile":"选择优化目标文件","startOptimize":"开始优化","optimizing":"优化中...","optimizeSuccess":"Skill 优化成功","optimizeFailed":"Skill 优化失败","applyOptimize":"应用优化","originalContent":"原始内容","optimizedContent":"优化后内容","mcpToolsOptional":"MCP 工具 (可选)","selectMcpServer":"选择 MCP Server","availableTools":"可用工具","searchTools":"搜索工具...","selectedToolsCount":"已选 {{count}} 个","noToolsAvailable":"暂无可用工具","loadingTools":"加载工具中...","conversationHistory":"对话历史 (可选)","conversationHistoryPlaceholder":"JSON 格式的对话历史,用于多轮生成...","conversationHistoryInvalid":"对话历史 JSON 格式无效","thinking":"思考过程","generatedContent":"生成内容","streamContent":"流式输出","selectResource":"选择一个文件查看内容","editMode":"编辑模式","binaryFileHint":"暂不支持预览该文件内容","imagePreviewUnavailable":"无法预览该图片文件"}`)}},"en-US":{translation:{header:{home:`HOME`,docs:`DOCS`,blog:`BLOG`,community:`COMMUNITY`,enterprise:`ENTERPRISE EDITION`,mcp:`MCP Marketplace`,languageSwitch:`中`,logout:`Logout`,changePassword:`Change Password`},login:{login:`Login`,initPassword:`Initialize Password`,internalSysTip1:`Internal system. Do not expose to the public network.`,internalSysTip3:"Init admin user `nacos` password",internalSysTip4:`It is recommended to use a strong password and keep it secure.`,submit:`Submit`,pleaseInputUsername:`Please enter username`,pleaseInputPassword:`Please enter password`,pleaseInputPasswordTips:`Please enter a password (a random password will be generated if left empty).`,invalidUsernameOrPassword:`Invalid username or password`,passwordRequired:`Password is required`,usernameRequired:`Username is required`,productDesc:`An easy-to-use dynamic service discovery, configuration and AI agent management platform for building AI agent applications`,consoleClosed:`Console Closed`,consoleClosedDesc:`The Nacos console has been closed.`,signInWithSSO:`Sign in with SSO`,ssoLoginTip:`Single sign-on is enabled by your administrator. Click the button below to continue to the identity provider.`,oidcRedirectFailed:`Failed to redirect to SSO login page. Please contact your administrator.`,oidcAuthFailed:`SSO authentication failed`},menu:{configManagement:`Configuration`,configList:`Configurations`,historyRollback:`Historical Versions`,listeningQuery:`Listener Query`,serviceManagement:`Services`,serviceList:`Service List`,subscriberList:`Subscribers`,aiRegistry:`AI Registry`,mcpRegistry:`MCP Registry`,agentRegistry:`Agent Registry`,skillRegistry:`Skill Registry`,promptRegistry:`Prompt Registry`,agentSpecRegistry:`AgentSpec Registry`,platformManagement:`Platform`,namespace:`Namespace`,clusterManagement:`Cluster`,clusterNodeList:`Cluster Nodes`,pluginManagement:`Plugins`,authorityControl:`Authority Control`,userList:`User List`,roleManagement:`Role Management`,privilegeManagement:`Privileges`,settingCenter:`Settings`},common:{confirm:`Confirm`,cancel:`Cancel`,retry:`Retry`,delete:`Delete`,edit:`Edit`,create:`Create`,search:`Search`,reset:`Reset`,loading:`Loading...`,noData:`No Data`,operation:`Operation`,detail:`Detail`,save:`Save`,bizTags:`Business Tags`,from:`Source`,add:`Add`,optional:`Optional`,back:`Back`,success:`Success`,failed:`Failed`,legacyConsole:`Legacy Console`,switchToLegacy:`Switch to legacy console`,collapse:`Collapse`,expand:`Expand`,mode:`Mode`,version:`Version`,pageSize:`Page Size`,selectNamespace:`Namespace`,sessionExpired:`Session expired, please login again`,noPermission:`No permission to perform this operation`,requestFailed:`Request failed`,versionLabels:{title:`Version Labels`,noLabels:`No version labels`,keyPlaceholder:`Label name`,valuePlaceholder:`Version`,keyRequired:`Label key is required`,keyDuplicate:`Label key already exists`,keyInvalid:`Invalid label key format (only letters, digits, . _ -)`,reservedLatest:`latest is managed by the server and cannot be set or removed manually`,save:`Save Version Labels`,updateSuccess:`Version labels updated`,editLabels:`Edit Version Labels`,labelManagement:`Version Labels`,labelBindDesc:`Select labels to bind to version {{version}}, or create new ones.`,searchOrCreateLabel:`Search or create label...`,createLabel:`Create label "{{name}}"`,selectAll:`Select all {{count}}`,clearAll:`Clear`},bizTagEditor:{title:`Business Tags`,description:`Edit the business tag list.`},cliUsage:{title:`Install`,cliInstall:`CLI Install`,cliDoc:`CLI Documentation`,downloadFile:`Download File`,manualDownload:`Manual Download`,downloadZip:`Download .zip`,description:`Get this resource locally via Nacos CLI. Requires Node.js.`,copied:`Command copied`,latest:`Get latest`,byVersion:`By version`,byLabel:`By label`}},config:{title:`Configuration List`,newConfig:`New Config`,editConfig:`Edit Config`,configDetail:`Config Detail`,dataId:`Data ID`,group:`Group`,content:`Content`,type:`Format`,tags:`Tags`,appName:`Application`,description:`Description`,md5:`MD5`,createTime:`Created`,modifyTime:`Last Modified`,fuzzySearch:`Fuzzy Search`,exactSearch:`Exact Search`,contentSearch:`Content Search`,advancedSearch:`Advanced Search`,publishSuccess:`Configuration published successfully`,publishFailed:`Configuration publish failed`,publishConfirmTitle:`Confirm Publish`,publishConfirmDescription:`Review the content changes before publishing this configuration.`,currentPublishedContent:`Current published content`,pendingPublishContent:`Pending publish content`,notFoundInNamespace:`This configuration does not exist in the selected namespace. Returning to the configuration list.`,unsavedNamespaceSwitchConfirm:`You have unpublished changes. Switch namespace and discard them?`,deleteConfirm:`Are you sure you want to delete this configuration?`,deleteSuccess:`Configuration deleted successfully`,dataIdRequired:`Data ID is required`,groupRequired:`Group is required`,contentRequired:`Content is required`,maxTags:`Maximum 5 tags allowed`,noSpecialChars:`Special characters not allowed`,configExists:`Configuration already exists`,totalConfigs:`{{total}} configurations`,publish:`Publish`,history:`History`,listeners:`Listeners`,rollback:`Rollback`,clone:`Clone`,export:`Export`,import:`Import`,backToList:`Back to List`,sampleCode:`Sample Code`,formatContent:`Format`,fullscreen:`Fullscreen`,batchDelete:`Batch Delete`,batchDeleteConfirm:`Delete {{count}} selected configurations?`,exportAll:`Export All`,exportSelected:`Export Selected`,importTitle:`Import Configurations`,importSuccess:`Import successful`,importFailed:`Import failed`,cloneTitle:`Clone Configurations`,sourceNamespace:`Source Namespace`,targetNamespace:`Target Namespace`,conflictPolicy:`Conflict Policy`,policyAbort:`Abort Import`,policySkip:`Skip Existing`,policyOverwrite:`Overwrite Existing`,selectedCount:`{{count}} selected`,selectFile:`Select File`,noFileSelected:`Please select a ZIP file`,noTargetNamespace:`Please select target namespace`,noSelection:`Please select configurations first`,startClone:`Start Clone`,cloneSuccess:`Clone successful`,configCount:`Config Count`,uploadZip:`Upload ZIP File`,batchDeleteSuccess:`Batch delete successful`,importResult:`{{success}} succeeded, {{skip}} skipped`,betaPublish:`Beta Publish`,production:`Production`,beta:`Beta`,betaIps:`Beta IPs`,betaIpsPlaceholder:`Enter IPs, separated by commas`,stopBeta:`Stop Beta`,stopBetaConfirm:`Stop beta publish?`,betaPublishSuccess:`Beta published successfully`,betaStopSuccess:`Beta stopped`,noBeta:`No beta config`},listener:{title:`Listener Query`,queryByConfig:`Query by Config`,queryByIp:`Query by IP`,ip:`IP Address`,md5:`MD5`,noListeners:`No listeners found`,totalListeners:`{{total}} listeners`},history:{title:`Historical Versions`,detailTitle:`History Detail`,rollbackTitle:`Rollback Configuration`,publishType:`Publish Type`,formal:`Release`,gray:`Gray`,grayRule:`Gray Rule`,operator:`Operator`,sourceIp:`Source IP`,opType:`Operation Type`,opInsert:`Insert`,opUpdate:`Update`,opDelete:`Delete`,compare:`Compare`,rollback:`Rollback`,rollbackConfirm:`Are you sure you want to rollback to this version?`,rollbackInsertWarn:`Rollback will delete this configuration`,rollbackUpdateWarn:`Rollback will restore to this version`,rollbackDeleteWarn:`Rollback will re-publish this configuration`,rollbackSuccess:`Rollback successful`,rollbackFailed:`Rollback failed`,selectedVersion:`Selected Version`,currentVersion:`Current Version`,totalHistory:`{{total}} history records`},service:{title:`Service List`,createService:`Create Service`,editService:`Edit Service`,serviceDetail:`Service Detail`,serviceName:`Service Name`,groupName:`Group Name`,clusterCount:`Cluster Count`,ipCount:`IP Count`,healthyInstanceCount:`Healthy Instances`,triggerFlag:`Threshold Triggered`,ignoreEmptyService:`Hide Empty Services`,protectThreshold:`Protect Threshold`,metadata:`Metadata`,selectorType:`Selector Type`,selectorExpression:`Selector Expression`,ephemeral:`Ephemeral`,deleteConfirm:`Delete service {{name}}?`,deleteSuccess:`Service deleted`,deleteHasInstances:`Service has active instances and cannot be deleted. Please deregister all instances first.`,createSuccess:`Service created`,updateSuccess:`Service updated`,namespaceSwitchConfirm:`You have an unfinished service operation. Switch namespace and close it?`,totalServices:`{{total}} services`,clusterName:`Cluster Name`,editCluster:`Edit Cluster`,checkType:`Health Check Type`,checkPort:`Check Port`,checkPath:`Check Path`,checkHeaders:`Check Headers`,useInstancePort:`Use Instance Port`,clusterUpdateSuccess:`Cluster updated`,ip:`IP`,port:`Port`,weight:`Weight`,healthy:`Healthy`,enabled:`Enabled`,instanceEphemeral:`Ephemeral`,editInstance:`Edit Instance`,deleteInstance:`Delete Instance`,instanceUpdateSuccess:`Instance updated`,instanceDeleteSuccess:`Instance deleted`,instanceDeleteConfirm:`Delete instance {{ip}}:{{port}}?`,online:`Online`,offline:`Offline`,noInstances:`No instances in this cluster`,subscriberTitle:`Subscribers`,subscriberName:`Subscriber`,subscribeCount:`Subscribe Count`,clusters:`Clusters`,totalSubscribers:`{{total}} subscribers`,metadataInvalid:`Invalid metadata, please enter valid JSON`,serviceNameRequired:`Service name is required`},mcp:{title:`MCP Server Management`,createServer:`Create MCP Server`,editServer:`Edit MCP Server`,serverDetail:`MCP Server Detail`,serverName:`Server Name`,protocol:`Protocol`,frontProtocol:`Front Protocol`,description:`Description`,repository:`Repository`,websiteUrl:`Website`,version:`Version`,createTime:`Created At`,author:`Author`,status:`Status`,enabled:`Enabled`,disabled:`Disabled`,enable:`Enable`,disable:`Disable`,capabilities:`Capabilities`,tools:`Tools`,toolName:`Tool Name`,toolDescription:`Tool Description`,parameters:`Parameters`,paramName:`Name`,paramType:`Type`,paramRequired:`Required`,paramDescription:`Description`,packages:`Packages`,endpoints:`Endpoints`,frontendEndpoints:`Frontend Endpoints`,backendEndpoints:`Backend Endpoints`,serverSpecification:`Server Specification`,toolSpecification:`Tool Specification`,endpointSpecification:`Endpoint Specification`,localServerConfig:`Local Server Config`,remoteServerConfig:`Remote Server Config`,serviceRef:`Service Reference`,exportPath:`Export Path`,securitySchemes:`Security Schemes`,addSecurityScheme:`Add Security Scheme`,overrideExisting:`Override Existing`,setAsLatest:`Set as Latest`,copyConfig:`Copy Config`,copySuccess:`Config copied to clipboard`,newVersion:`New Version`,importFromRegistry:`Import from MCP Registry`,importSource:`Import Source`,importUrl:`MCP Server URL`,importType:`Import Type`,importTypeJson:`JSON`,importTypeUrl:`URL`,importTypeFile:`File`,importSearchPlaceholder:`Search MCP Server name`,importValidate:`Validate`,importExecute:`Execute Import`,importSuccess:`Import successful`,importFailed:`Import failed`,importResult:`Import result: {{success}} succeeded, {{failed}} failed, {{skipped}} skipped`,importSkipInvalid:`Skip Invalid`,importOverride:`Override Existing`,importConflictPolicy:`Conflict Policy`,importConflictSkip:`Skip`,importConflictOverwrite:`Overwrite`,importAll:`Import all valid`,importSelectedCount:`{{count}} selected`,importValidatedCount:`{{count}} valid after validation`,importSelectAll:`Select all`,importClearSelection:`Clear`,importLoadMore:`Load more`,noImportSource:`No import source available`,importStatusValid:`Valid`,importStatusWarning:`Warning`,importStatusConflict:`Conflict`,importStatusInvalid:`Invalid`,importUnnamed:`Unnamed`,importMetadata:{protocol:`Protocol`,status:`Status`},selectedServers:`Selected Servers`,importTools:`Import Tools`,importToolsFromUrl:`Import Tools from Endpoint`,searchPlaceholder:`Search MCP Server name`,deleteConfirm:`Delete MCP Server "{{name}}"?`,batchDeleteConfirm:`Delete {{count}} selected MCP Servers?`,deleteSuccess:`Deleted successfully`,batchDelete:`Batch Delete`,batchDeleteSuccess:`Batch delete successful`,enableSuccess:`Enabled`,disableSuccess:`Disabled`,createSuccess:`MCP Server created`,updateSuccess:`MCP Server updated`,totalServers:`{{total}} MCP Servers`,noTools:`No tools`,noPackages:`No packages`,noEndpoints:`No endpoints`,serverNameRequired:`Server name is required`,serverSpecRequired:`Server specification is required`,invalidJson:`Invalid JSON format`,formatJson:`Format JSON`,advancedConfig:`Advanced Config`,statusActive:`Active`,statusDeprecated:`Deprecated`,statusDeleted:`Deleted`,protocolStdio:`Stdio`,protocolSse:`MCP-SSE`,protocolStreamable:`MCP-Streamable`,protocolHttp:`HTTP`,protocolDubbo:`Dubbo`,useExistService:`Use Existing Service`,directConnect:`Direct Connect`,httpToMcp:`HTTP to MCP`,mcpServerEndpoint:`MCP Server Endpoint URL`,address:`Address`,port:`Port`,transportProtocol:`Transport Protocol`,selectService:`Select Service`,selectVersion:`Select Version`,inputSchema:`Input Schema`,outputSchema:`Output Schema`,noDescription:`No description`,allVersions:`All Versions`,latestVersion:`Latest`,versionHistory:`Version History`,totalVersions:`{{count}} versions in total`,currentVersion:`Current`,toolCount:`{{count}} tools`,expandAll:`Expand All`,collapseAll:`Collapse All`,backToList:`Back to List`,basicInfo:`Basic Info`,noSecuritySchemes:`No security schemes`,noCapabilities:`No capabilities`,securityType:`Type`,securitySchemeField:`Scheme`,securityIn:`In`,securityDefaultCredential:`Default Credential`,packageIdentifier:`Identifier`,packageRuntime:`Runtime`,packageVersion:`Package Version`,runtimeArguments:`Runtime Arguments`,packageArguments:`Package Arguments`,environmentVariables:`Environment Variables`,noEnvironmentVariables:`No environment variables`,envVarName:`Name`,envVarDefault:`Default`,envVarRequired:`Required`,envVarSecret:`Sensitive`,endpointProtocol:`Protocol`,endpointAddress:`Address`,endpointPort:`Port`,endpointPath:`Path`,endpointHeaders:`Headers`,noHeaders:`No headers`,serviceName:`Service Name`,groupName:`Group Name`,loadFailed:`Failed to load data`,retry:`Retry`,restToMcpSwitch:`HTTP to MCP`,restToMcpSwitchTip:`Convert existing HTTP service to MCP protocol`,useExistServiceOption:`Use Existing Service`,directConnectOption:`Direct Connect`,mcpEndpointUrl:`MCP Server Endpoint URL`,mcpEndpointUrlPlaceholder:`e.g. http://127.0.0.1:8080/sse`,addressPlaceholder:`e.g. 127.0.0.1`,portPlaceholder:`e.g. 8080`,localServerConfigTip:`Enter MCP local server JSON configuration`,versionPlaceholder:`e.g. 1.0.0`,descriptionPlaceholder:`Describe the MCP Server functionality`,securitySchemeId:`Scheme ID`,securitySchemeType:`Auth Type`,schemeBasic:`Basic`,schemeBearer:`Bearer`,inHeader:`Header`,inQuery:`Query`,defaultDownstreamSecurity:`Default Downstream Security`,defaultDownstreamSecurityDesc:`Applies to client-to-gateway requests (e.g., tools/list) when no tool override exists.`,defaultUpstreamSecurity:`Default Upstream Security`,defaultUpstreamSecurityDesc:`Applies to gateway-to-backend calls (e.g., default requestTemplate.security).`,passthroughAuth:`Passthrough Auth`,credentialOverride:`Credential Override`,addRow:`Add`,removeRow:`Remove`,publishAndCreate:`Create & Publish`,publishAndUpdate:`Update & Publish`,saving:`Saving...`,protocolRequired:`Protocol is required`,versionRequired:`Version is required`,addressRequired:`Address is required`,portRequired:`Port is required`,mcpEndpointRequired:`MCP endpoint URL is required`,localConfigRequired:`Local server config is required`,toolManagement:`Tool Management`,noToolsYet:`No tools yet. Add or import tools to get started.`,newTool:`New Tool`,editTool:`Edit Tool`,deleteTool:`Delete Tool`,deleteToolConfirm:`Delete tool "{{name}}"?`,toolNamePlaceholder:`e.g. get_user_info`,toolNameRequired:`Tool name is required`,toolNameExists:`Tool name already exists`,toolEnabled:`Enabled`,searchTools:`Search tools...`,importFromMcpInstance:`Import from MCP Instance`,importFromOpenApi:`Import from OpenAPI`,importToolsFromMCPTitle:`Import Tools from MCP Instance`,importToolsFromOpenAPITitle:`Import Tools from OpenAPI File`,dragOrClickToUpload:`Drag and drop file here or click to select`,supportedFormats:`Supports .json, .yaml, .yml formats`,fileReadFailed:`Failed to read file`,fileInvalidFormat:`Invalid file format`,pleaseSelectFile:`Please select a file`,noHealthyInstance:`No healthy instance found`,importingTools:`Importing...`,importToolsSuccess:`Successfully imported {{count}} tools`,importToolsFailed:`Failed to import tools`,authToken:`Auth Token`,authTokenPlaceholder:`Optional, for authenticated MCP servers`,serverAddress:`Server Address`,toolBasicInfo:`Basic Info`,toolInputSchema:`Input Schema`,toolOutputSchema:`Output Schema`,toolMeta:`Meta`,toolAnnotations:`Annotations`,toolAdvancedConfig:`Advanced Config`,requestTemplate:`Request Template`,responseTemplate:`Response Template`,addParameter:`Add Parameter`,addProperty:`Add Property`,deleteParam:`Delete Parameter`,deleteParamConfirm:`Delete this parameter and its children?`,typeString:`String`,typeNumber:`Number`,typeInteger:`Integer`,typeBoolean:`Boolean`,typeArray:`Array`,typeObject:`Object`,annotationsTitle:`Title`,readOnlyHint:`Read-Only`,readOnlyHintDesc:`Whether this tool only performs read operations`,destructiveHint:`Destructive Action`,destructiveHintDesc:`Whether this tool may perform destructive operations`,idempotentHint:`Idempotent`,idempotentHintDesc:`Whether this tool is idempotent`,openWorldHint:`Open World`,openWorldHintDesc:`Whether this tool may interact with external entities`,foundOperations:`Found {{count}} API operations`,foundSecuritySchemes:`Found {{count}} security schemes`,createTool:`Create Tool`,previewTool:`Preview Tool`,toolParams:`{{count}} params`,noParameters:`No parameters`,selectToolToView:`Select a tool to view details`},agent:{title:`Agent Management`,createAgent:`Create Agent`,editAgent:`Edit Agent`,agentDetail:`Agent Detail`,agentName:`Agent Name`,version:`Version`,protocolVersion:`Protocol Version`,serviceUrl:`Service URL`,transport:`Transport`,selectTransport:`Select transport protocol`,description:`Description`,descriptionPlaceholder:`Describe the Agent functionality...`,noDescription:`No description`,provider:`Provider`,providerName:`Provider Name`,providerNamePlaceholder:`Enter provider name`,providerUrl:`Provider URL`,iconUrl:`Icon URL`,documentationUrl:`Documentation URL`,documentation:`Documentation`,capabilities:`Capabilities`,streaming:`Streaming`,streamingDesc:`Whether streaming data transfer is supported`,pushNotifications:`Push Notifications`,pushNotificationsDesc:`Whether push notification is supported`,stateHistory:`State History`,stateHistoryDesc:`Whether state transition history is supported`,skills:`Skills`,skillsHelp:`Agent skill list in JSON array format`,skillCount:`{{count}} skills`,noSkills:`No skills configured`,inputModes:`Input Modes`,outputModes:`Output Modes`,ioModes:`Input/Output Modes`,security:`Security`,securitySchemes:`Security Schemes`,supportedInterfaces:`Supported Interfaces`,additionalInterfaces:`Additional Interfaces`,interfaceItem:`Interface {{index}}`,preferredInterface:`Preferred Interface`,tenant:`Tenant`,extendedCardSupport:`Extended Card Support`,extendedCardSupportDesc:`Whether authenticated extended card is supported`,advancedConfig:`Advanced Config`,showAdvanced:`Show Advanced Config`,hideAdvanced:`Hide Advanced Config`,setAsLatest:`Set as Latest`,setAsLatestDesc:`When enabled, this version will become the published version`,basicInfo:`Basic Info`,versionHistory:`Version History`,totalVersions:`{{count}} versions in total`,currentVersion:`Current`,isLatest:`Is Latest`,selectVersion:`Select Version`,searchPlaceholder:`Search Agent name`,totalAgents:`{{total}} Agents`,deleteConfirm:`Delete Agent "{{name}}"?`,batchDeleteConfirm:`Delete {{count}} selected Agents?`,deleteSuccess:`Deleted successfully`,batchDelete:`Batch Delete`,batchDeleteSuccess:`Batch delete successful`,createSuccess:`Agent created`,updateSuccess:`Agent updated`,create:`Create`,update:`Update`,publish:`Publish`,confirmPublish:`Confirm Publish`,publishStrategy:`Publish Strategy`,publishStrategyDesc:`Choose how to handle this change. Version:`,strategyNewVersion:`Publish New Version`,strategyNewVersionDesc:`Publish as a new version without setting it as default`,strategySetLatest:`Set as Latest Version`,strategySetLatestDesc:`Set as default version, used when client doesn't specify a version`,strategyEditCurrent:`Update Current Version`,strategyEditCurrentDesc:`Override current version config, keeping version number and latest flag unchanged`,strategyReasonVersionExists:`Version already exists`,strategyReasonVersionNotExists:`Version does not exist`,strategyReasonAlreadyLatest:`Already the latest version`,loadFailed:`Failed to load data`,backToList:`Back to List`,nameRequired:`Agent name is required`,versionRequired:`Version is required`,urlRequired:`Service URL is required`,protocolVersionRequired:`Protocol version is required`,transportRequired:`Please select transport protocol`,jsonFormatError:`JSON Format Error`},prompt:{title:`Prompt Management`,createPrompt:`Create Prompt`,editPrompt:`Edit Prompt`,editMetadata:`Edit Description & Tags`,promptDetail:`Prompt Detail`,promptKey:`Prompt Key`,version:`Version`,latestVersion:`Latest`,template:`Template`,versionDiff:`Version Diff`,diffBeforeVersion:`Base version`,diffAfterVersion:`Target version`,diffSelectVersionPlaceholder:`Select version`,diffSwapVersions:`Swap versions`,diffModifiedContent:`Modified content`,diffTemplateFile:`Template`,diffSelectVersions:`Select a base version and target version to compare`,diffNoChanges:`No template changes between these versions`,diffSameVersion:`Select two different versions to compare`,variables:`Variables`,variableName:`Variable Name`,variableDefault:`Default Value`,variableDescription:`Description`,description:`Description`,commitMsg:`Commit Message`,bizTags:`Business Tags`,labels:`Labels`,labelName:`Label Name`,bindLabel:`Bind Label`,unbindLabel:`Unbind Label`,versionHistory:`Version History`,downloadMarkdown:`Download Markdown`,downloadMarkdownTip:`Export the currently selected version as a Markdown document`,downloadMarkdownSuccess:`Markdown document downloaded successfully`,downloadMarkdownFailed:`Failed to download Markdown document`,downloadCount:`{{count}} downloads`,downloads:`Downloads`,versionDownloads:`Version Downloads`,publishVersion:`Publish New Version`,debug:`Debug`,startDebug:`Start Debug`,clearResult:`Clear Result`,aiOptimize:`AI Optimize`,optimizeGoal:`Optimization Goal`,optimizeGoalPlaceholder:`Describe the optimization goal, e.g.: make it clearer, add examples (optional)`,startOptimize:`Start Optimize`,optimizing:`Optimizing...`,optimizedResult:`Optimized Result`,originalTemplate:`Original Template`,applyOptimize:`Apply Optimized Result`,noDescription:`No description`,noVariables:`No variables`,noLabels:`No labels`,noVersions:`No version history`,totalVersions:`{{count}} versions in total`,currentVersion:`Current Version`,searchPlaceholder:`Search Prompt Key`,totalPrompts:`{{total}} Prompts`,deleteConfirm:`Delete Prompt "{{name}}"?`,batchDeleteConfirm:`Delete {{count}} selected Prompts?`,deleteSuccess:`Deleted successfully`,batchDelete:`Batch Delete`,batchDeleteSuccess:`Batch delete successful`,createSuccess:`Prompt created`,updateSuccess:`Prompt updated`,publishSuccess:`Version published`,bindLabelSuccess:`Label bound successfully`,unbindLabelSuccess:`Label unbound successfully`,metadataUpdateSuccess:`Metadata updated`,loadFailed:`Failed to load data`,backToList:`Back to List`,keyRequired:`Prompt Key is required`,keyInvalid:`Prompt Key only supports letters, numbers, underscores and hyphens`,versionRequired:`Version is required`,versionInvalid:`Invalid version format, should be x.y.z`,versionMustGreater:`New version must be greater than current version {{current}}`,templateRequired:`Template content is required`,labelInvalid:`Label only supports letters, numbers, dots, underscores and hyphens`,selectVersion:`Select Version`,newVersion:`New Version`,newVersionPlaceholder:`e.g. 0.0.2 or v3 (leave empty for auto)`,templatePlaceholder:`Enter Prompt template, use {{variable}} syntax to define variables...`,commitMsgPlaceholder:`Describe the changes...`,descriptionPlaceholder:`Describe the purpose of this Prompt...`,tagPlaceholder:`Type and press Enter to add tags`,keyPlaceholder:`Enter Prompt Key`,versionPlaceholder:`e.g. 1.0.0`,thinking:`Thinking`,modelOutput:`Model Output`,streaming:`Streaming...`,userInput:`User Input`,userInputPlaceholder:`Enter the user message to send to the model`,renderedPrompt:`Rendered Prompt`,send:`Send`,basicInfo:`Basic Info`,templateEditor:`Template Editor`,debugPanel:`Debug Panel`,labelManagement:`Label Management`,addLabel:`Add Label`,manageLabels:`Manage Labels`,labelBoundTo:`Bound to`,publishTime:`Publish Time`,publisher:`Publisher`,retry:`Retry`,"versionStatus.draft":`Draft`,"versionStatus.reviewing":`Reviewing`,"versionStatus.reviewed":`Pending Publish`,"versionStatus.online":`Online`,"versionStatus.offline":`Offline`,"versionStatus.pendingPublish":`Pending Publish`,"versionStatus.rejected":`Rejected`,submit:`Submit for Review`,publish:`Publish`,forcePublish:`Force Publish`,online:`Online`,offline:`Offline`,createDraft:`Create Draft`,createDraftFrom:`Create Draft From`,deleteDraft:`Delete Draft`,saveDraft:`Save Draft`,draftExistsTip:`An editing or reviewing version already exists`,publishDisabledPipeline:`Pipeline has not been approved`,forcePublishConfirmTitle:`Confirm Force Publish`,forcePublishConfirmDesc:`Are you sure you want to force publish version {{version}}? This will bypass pipeline review.`,forcePublishConfirm:`Confirm Force Publish`,onlineCount:`Online: {{count}}`,hasDraft:`Has Draft`,hasReviewing:`Reviewing`,noOnlineVersion:`No Online Version`,submitSuccess:`Submitted for review`,draftSaveSuccess:`Draft saved`,draftDeleteSuccess:`Draft deleted`,onlineSuccess:`Version is now online`,offlineSuccess:`Version is now offline`,forcePublishSuccess:`Force published successfully`,redraft:`Re-edit`,redraftSuccess:`Version returned to draft for re-editing`,labelsUpdateSuccess:`Labels updated`,descriptionUpdateSuccess:`Description updated`,bizTagsUpdateSuccess:`Biz tags updated`,governanceInfo:`Governance Info`,versionTimeline:`Version Timeline`,pipelineStatus:`Review Pipeline`,pipelineInProgress:`Pipeline In Progress`,editDraft:`Edit Draft`,createPromptDesc:`Create a new Prompt draft with template and variables.`,createDraftFromDesc:`Create a new draft based on version {{version}}`,status:`Status`},namespace:{title:`Namespaces`,create:`Create Namespace`,edit:`Edit Namespace`,detail:`Namespace Detail`,name:`Namespace Name`,id:`Namespace ID`,description:`Description`,configCount:`Configs`,quota:`Quota`,namePlaceholder:`Enter namespace name`,idPlaceholder:`Leave empty for auto-generation`,descriptionPlaceholder:`Enter namespace description`,nameRequired:`Namespace name is required`,deleteConfirm:`Delete namespace "{{name}}"? This action cannot be undone.`,deleteSuccess:`Namespace deleted`,createSuccess:`Namespace created`,updateSuccess:`Namespace updated`,publicNamespace:`public`,publicNamespaceDesc:`Public namespace, cannot be edited or deleted`,total:`{{total}} namespaces`},cluster:{title:`Cluster Nodes`,nodeAddress:`Node Address`,nodeIp:`IP`,nodePort:`Port`,nodeState:`State`,searchPlaceholder:`Search node IP or address`,leave:`Deregister`,leaveConfirm:`Remove node "{{address}}" from the cluster?`,leaveSuccess:`Node deregistered`,total:`{{total}} nodes`,stateUp:`Running`,stateDown:`Down`,stateSuspicious:`Suspicious`,refresh:`Refresh`},plugin:{title:`Plugin Management`,pluginName:`Plugin Name`,pluginType:`Plugin Type`,enabled:`Enabled`,disabled:`Disabled`,critical:`Critical`,status:`Status`,allTypes:`All Types`,enable:`Enable`,disable:`Disable`,enableSuccess:`Plugin enabled`,disableSuccess:`Plugin disabled`,detail:`Plugin Detail`,version:`Version`,description:`Description`,configurable:`Configurable`,clusterAvailability:`Cluster Availability`,noPlugins:`No plugins`,total:`{{total}} plugins`,typeAuth:`Authentication`,typeDatasource:`Data Source`,typeConfigChange:`Config Change`,typeEncryption:`Encryption`,typeTrace:`Trace`,typeEnvironment:`Environment`,typeControl:`Control`,typeAiPipeline:`AI Pipeline`,typeAiStorage:`AI Storage`,typeVisibility:`Visibility`,availableNodes:`Available Nodes`,yes:`Yes`,no:`No`,pluginCount:`plugins`},authority:{title:`Authority Control`,userManagement:`User Management`,roleManagement:`Role Management`,permissionManagement:`Permission Management`,username:`Username`,password:`Password`,newPassword:`New Password`,confirmPassword:`Confirm Password`,role:`Role`,resource:`Resource`,action:`Action`,createUser:`Create User`,deleteUser:`Delete User`,resetPassword:`Reset Password`,bindRole:`Bind Role`,addPermission:`Add Permission`,usernamePlaceholder:`Enter username`,passwordPlaceholder:`Enter password`,confirmPasswordPlaceholder:`Enter password again`,newPasswordPlaceholder:`Enter new password`,rolePlaceholder:`Enter role name`,selectRolePlaceholder:`Select role`,selectUserPlaceholder:`Select user`,resourcePlaceholder:`Select resource`,searchUserPlaceholder:`Search username`,searchRolePlaceholder:`Search role name`,usernameRequired:`Username is required`,passwordRequired:`Password is required`,passwordMismatch:`Passwords do not match`,roleRequired:`Role name is required`,resourceRequired:`Resource is required`,actionRequired:`Please select an action`,deleteUserConfirm:`Delete user "{{name}}"?`,deleteRoleConfirm:`Remove role "{{role}}" from user "{{username}}"?`,deletePermissionConfirm:`Delete this permission?`,createUserSuccess:`User created`,deleteUserSuccess:`User deleted`,resetPasswordSuccess:`Password reset`,createRoleSuccess:`Role bound`,deleteRoleSuccess:`Role binding deleted`,createPermissionSuccess:`Permission added`,deletePermissionSuccess:`Permission deleted`,totalUsers:`{{total}} users`,totalRoles:`{{total}} role bindings`,totalPermissions:`{{total}} permissions`,actionRead:`Read Only`,actionWrite:`Write Only`,actionReadWrite:`Read & Write`,fuzzySearch:`Fuzzy Search`,exactSearch:`Exact Search`,adminCannotDelete:`Admin role cannot be deleted`},settings:{title:`Settings`,description:`Manage Nacos console preferences and integrations`,copilotConfig:`Copilot Configuration`,copilotConfigDesc:`Configure the AI assistant model and credentials for intelligent configuration management`,apiKey:`API Key`,apiKeyHint:`It is recommended to set this via the environment variable COPILOT_API_KEY, which takes precedence over this setting.`,apiKeyPlaceholder:`Enter API Key`,model:`Model`,modelPlaceholder:`Select a model`,saveSuccess:`Saved successfully`},agentSpec:{title:`AgentSpec Management`,totalAgentSpecs:`{{total}} AgentSpecs`,upload:`Upload`,createAgentSpec:`Create AgentSpec`,createAgentSpecDesc:`Fill in basic info to create the first draft version`,createSuccess:`AgentSpec created`,createFailed:`Failed to create AgentSpec`,editAgentSpec:`Edit AgentSpec`,newFile:`New File`,newFolder:`New Folder`,basicInfo:`Basic Info`,editor:`Editor`,editorLoading:`Loading editor...`,editBasicInfo:`Edit Basic Info`,agentSpecName:`AgentSpec Name`,namespace:`Namespace`,files:`File Overview`,fileTreeTitle:`Files`,fileTree:`File tree`,activeFile:`Active File`,workspaceSummary:`Workspace Summary`,unnamedAgentSpec:`Untitled AgentSpec`,noDescriptionPreview:`The manifest description will appear here so you can verify the AgentSpec positioning while editing.`,modeNew:`New`,modeEdit:`Draft`,modeVersion:`New Version`,modePreview:`Preview`,modified:`Modified`,readOnly:`Read Only`,resizeFileTreePanel:`Resize file tree panel`,renameNode:`Rename {{name}}`,deleteNode:`Delete {{name}}`,createFileIn:`New file in {{name}}`,createFolderIn:`New folder in {{name}}`,resourceRenameFile:`Rename file`,resourceCopyFile:`Copy file content`,resourceDownloadFile:`Download file`,resourceDeleteFile:`Delete file`,resourceCopySuccess:`File content copied`,resourceCopyFailed:`Failed to copy file content`,createFile:`Create File`,createFolder:`Create Folder`,createFileDesc:`Enter a file path and choose the resource type.`,createFolderDesc:`Enter a folder path and choose the resource type.`,resourceType:`Resource Type`,resourceTypeRequired:`Resource type is required`,customFolder:`Custom`,filePath:`File Path`,folderPath:`Folder Path`,filePathPlaceholderCompact:`e.g. tools/helper.py`,folderPathPlaceholderCompact:`e.g. tools`,filePathPlaceholder:`e.g. skill/tools/helper.py`,folderPathPlaceholder:`e.g. skill/tools`,pathRequired:`Path is required`,invalidFileName:`Invalid file name`,fileExists:`File already exists`,folderExists:`Folder already exists`,namePlaceholder:`Enter AgentSpec name`,descriptionPlaceholder:`Enter AgentSpec description`,searchPlaceholder:`Search AgentSpec name`,sortDefault:`Default sort`,sortByDownloads:`Sort by downloads`,filterByOwner:`Filter by owner`,filterOwnerPlaceholder:`Enter owner name`,filterOnlyMine:`Only mine`,filterScopeAll:`All scopes`,filterScopePublic:`Public only`,filterScopePrivate:`Private only`,batchDelete:`Batch Delete`,deleteConfirm:`Delete AgentSpec "{{name}}"?`,batchDeleteConfirm:`Delete {{count}} selected AgentSpecs?`,deleteSuccess:`Deleted successfully`,batchDeleteSuccess:`Batch delete successful`,loadListError:`Failed to fetch agent specs`,loadError:`Failed to load AgentSpec details`,nameRequired:`AgentSpec name is required`,descriptionRequired:`Description is required`,saveSuccess:`Saved successfully`,unsaved:`Unsaved`,enabled:`Enabled`,disabled:`Disabled`,version:`Version`,createTime:`Created At`,status:`Status`,versionStatus:{draft:`Draft`,reviewing:`Reviewing`,reviewed:`Pending Publish`,pendingPublish:`Pending Publish`,rejected:`Rejected`,online:`Online`,offline:`Offline`},updateTime:`Updated At`,onlineCountLabel:`Online Status`,description:`Description`,noDescription:`No description`,editing:`Editing`,hasDraft:`Has Draft`,draftVersion:`Draft`,reviewing:`Reviewing`,reviewingVersion:`Reviewing`,submit:`Submit`,resubmit:`Resubmit`,saveAndPublish:`Save and Publish`,publish:`Publish`,online:`Version Online`,offline:`Version Offline`,onlineCount:`Online versions: {{count}}`,noOnlineVersion:`No online versions`,downloadCount:`{{count}} downloads`,downloads:`Downloads`,versionDownloads:`Version Downloads`,author:`Author`,backToList:`Back to List`,selectVersion:`Select version`,latestVersion:`Latest`,totalVersions:`{{count}} versions`,versionHistory:`Version History`,resources:`Resources`,agentsFile:`AGENTS.md`,noAgentsFile:`No AGENTS.md content`,labels:`Labels`,newVersion:`New Version`,createDraft:`Create Draft`,createDraftFrom:`Create draft from this version`,draftExistsTip:`A draft or unpublished version already exists`,createDraftSuccess:`Draft created`,deleteDraft:`Delete Draft`,editDraft:`Edit Draft`,deleteDraftSuccess:`Draft deleted`,submitSuccess:`Submitted for review`,publishSuccess:`Published successfully`,redraft:`Re-edit`,redraftSuccess:`Version returned to draft for re-editing`,onlineSuccess:`Online`,offlineSuccess:`Offline`,enableSuccess:`AgentSpec enabled`,disableSuccess:`AgentSpec disabled`,scopePublic:`Public`,scopePrivate:`Private`,scopeUpdateSuccess:`Scope updated`,pipelineStatus:`Review Pipeline`,pipelineInProgress:`In Progress`,pipelineApproved:`Approved`,pipelineRejected:`Review Failed`,pipelineNone:`No review pipeline available`,pipelineNodePassed:`Passed`,pipelineNodeFailed:`Failed`,pipelineNoMessage:`No output message`,pipelineCopy:`Copy result`,pipelineCopied:`Copied`,pipelineCopySuccess:`Scan result copied`,pipelineCopyFailed:`Failed to copy`,labelsUpdateSuccess:`Labels updated`,bizTagsUpdateSuccess:`Tags updated`,bizTagPlaceholder:`Add a tag`,noBizTags:`No tags`,noLabels:`No labels`,noVersions:`No versions`,labelKeyRequired:`Label key is required`,labelKeyDuplicate:`Label key already exists`,labelKeyInvalid:`Label key contains invalid characters`,labelKey:`Key`,labelValue:`Value`,saveLabels:`Save Labels`,uploadZip:`Upload AgentSpec ZIP`,uploadZipDesc:`Select a .zip file to import an AgentSpec package.`,invalidZipFile:`Please select a valid .zip file`,uploadSuccess:`Upload successful`,uploadFailed:`Upload failed`,dragOrClick:`Click to select a .zip file`,cancelEdit:`Cancel`,saveDraft:`Save Draft`,draftSaveSuccess:`Draft saved`},skill:JSON.parse(`{"title":"Skill Management","totalSkills":"{{total}} Skills","upload":"Upload","searchPlaceholder":"Search Skill name","skillName":"Skill Name","description":"Description","noDescription":"No description","enabled":"Enabled","disabled":"Disabled","editing":"Editing","hasDraft":"Has Draft","reviewing":"Reviewing","online":"Version Online","offline":"Version Offline","versionStatus":{"draft":"Draft","reviewing":"Reviewing","reviewed":"Pending Publish","pendingPublish":"Pending Publish","rejected":"Rejected","online":"Online","offline":"Offline"},"onlineCount":"Online versions: {{count}}","noOnlineVersion":"No online versions","downloadCount":"{{count}} downloads","downloads":"Downloads","versionDownloads":"Version Downloads","basicInfo":"Basic Info","instruction":"SKILL.md","skillMd":"SKILL.md","resources":"Resources","noResources":"No resources","resourceRenameFile":"Rename file","resourceCopyFile":"Copy file content","resourceDownloadFile":"Download file","resourceDeleteFile":"Delete file","resourceCopySuccess":"File content copied","resourceCopyFailed":"Failed to copy file content","versionDiff":"Version Diff","diffBeforeVersion":"Base version","diffAfterVersion":"Target version","diffSelectVersionPlaceholder":"Select version","diffSwapVersions":"Swap versions","diffAddedFiles":"Added files","diffRemovedFiles":"Removed files","diffModifiedFiles":"Modified files","diffChangedFiles":"Changed files","diffSelectVersions":"Select a base version and target version to compare","diffNoChanges":"No file changes between these versions","diffSameVersion":"Select two different versions to compare","diffTextUnsupported":"Text diff is not available for this file","diffTextUnsupportedDesc":"Images, presentations, archives, and similar files cannot be compared as text. Only the version change status is shown.","diffStatusAdded":"Added","diffStatusRemoved":"Removed","diffStatusModified":"Modified","resourceName":"Resource Name","resourceType":"Resource Type","resourceContent":"Content","labels":"Labels","noLabels":"No labels","version":"Version","status":"Status","createTime":"Created At","updateTime":"Updated At","author":"Author","versionHistory":"Version History","totalVersions":"{{count}} versions","noVersions":"No versions","selectVersion":"Select version","latestVersion":"Latest","newVersion":"New Version","createDraft":"Create Draft","createDraftSuccess":"Draft created","submit":"Submit","resubmit":"Resubmit","submitSuccess":"Submitted for review","publish":"Publish","publishSuccess":"Published successfully","onlineSuccess":"Online","offlineSuccess":"Offline","enableSuccess":"Skill enabled","disableSuccess":"Skill disabled","scopePublic":"Public","scopePrivate":"Private","toggleScope":"Toggle visibility scope","scopeUpdateSuccess":"Scope updated","masterSwitch":"Skill Master Switch","masterSwitchDesc":"Disabling hides all versions from Agents","skillDisabledWarning":"Skill is disabled, version changes won't take effect","versionActions":"Version Actions","labelsUpdateSuccess":"Labels updated","labelManagement":"Version Labels","labelBindDesc":"Select labels to bind to version {{version}}, or create new ones.","searchOrCreateLabel":"Search or create label...","createLabel":"Create label \\"{{name}}\\"","selectAll":"Select all {{count}}","clearAll":"Clear","deleteConfirm":"Delete Skill \\"{{name}}\\"?","batchDeleteConfirm":"Delete {{count}} selected Skills?","deleteSuccess":"Deleted successfully","batchDelete":"Batch Delete","batchDeleteSuccess":"Batch delete successful","backToList":"Back to List","loadFailed":"Failed to load data","uploadZip":"Upload Skill ZIP","uploadZipDesc":"Select a .zip file to import a Skill package.","invalidZipFile":"Please select a valid .zip file","uploadSuccess":"Upload successful","uploadSuccessWithName":"Skill {{name}} uploaded successfully","uploadFailed":"Upload failed","precheckUpload":"Parse and Check","confirmUpload":"Confirm Upload","confirmForceOverwriteUpload":"Force Overwrite and Upload","confirmBatchUpload":"Confirm Upload","uploadChecking":"Checking","uploadPrecheckBlocked":"This Skill package cannot be uploaded","precheckNewSkill":"This Skill does not exist. Uploading will create draft version {{version}}.","precheckExistingSkillCreateDraft":"This Skill already exists. Uploading will create draft version {{version}}.","precheckVersionExists":"Parsed version {{version}} already exists.","precheckDraftOverwriteOnly":"Draft {{draftVersion}} already exists. Continuing will overwrite it, and the draft version after upload will be {{version}}. Do you want to overwrite it?","precheckVersionConverted":"Uploaded version {{parsedVersion}} has been converted to valid version {{version}}.","precheckVersionNormalizedAndAdjusted":"Uploaded version {{parsedVersion}} was normalized to {{normalizedVersion}}; because {{normalizedVersion}} already exists, this upload will create draft version {{version}}.","precheckCreateVersionAdjusted":"Parsed version {{parsedVersion}} is already occupied. This upload will create draft version {{version}}.","precheckReviewingBlocked":"Reviewing version {{version}} already exists. Uploading is blocked until review finishes.","precheckNoPermission":"You do not have permission to edit this Skill.","parsedVersion":"Parsed version","uploadedVersion":"Uploaded version","resolvedVersion":"Target version","resultVersion":"Target version: {{version}}","dragOrClick":"Drag .zip here or click to select","dragDropHint":"Supports dragging .zip files to upload","dropFileHere":"Drop .zip file here to upload","batchUploadResult":"Batch upload: {{succeeded}} succeeded, {{failed}} failed","batchUploadAllSuccess":"All {{count}} skills uploaded successfully","batchUploadSuccessWithSkipped":"{{succeeded}} skills uploaded successfully, {{skipped}} skipped","batchUploadSkipped":"{{count}} skills skipped","batchUploadPartialFail":"{{succeeded}} succeeded, {{failed}} failed: {{reasons}}","batchUploadAllFailed":"All {{count}} skills failed to upload","batchUploadAborted":"{{existing}} existing skills and {{blocked}} blocked items detected. Upload aborted.","batchUploadNothingToUpload":"No skills to upload.","targetNamespace":"Target Namespace","batchSkillCount":"Skill Count","batchUploadOnlyEntryCount":"Invalid / non-skill","batchPrecheckSummary":"Checked {{total}} skills: {{fresh}} new, {{existing}} existing, {{blocked}} blocked.","batchPrecheckBlockedTip":"Skills without permission or under review will be reported as failed in the batch result.","batchPrecheckUploadOnlyTip":"{{count}} folder(s) are not valid skills. They are excluded from skill conflict checks and will still be shown in the batch upload result.","sameSkillPolicy":"Same Skill:","batchPolicyAbort":"Abort upload","batchPolicySkip":"Skip","batchPolicyOverwrite":"Overwrite","batchPolicySkipDrafts":"Skip existing drafts","batchPolicyOverwriteDrafts":"Overwrite existing drafts","batchPolicySkipDraftsDesc":"Skills with existing drafts will not be uploaded. Other skills use normal upload rules, and failures remain in the batch result.","batchPolicyOverwriteDraftsDesc":"Skills with existing drafts will be overwritten by this ZIP. Skills without permission or under review can still fail.","batchItemNew":"New","batchItemExisting":"Existing","batchItemDraft":"Existing draft","batchItemBlocked":"Blocked","batchItemInvalidSkill":"Parse failed","batchItemInvalidSkillDesc":"SKILL.md is not a valid skill descriptor","batchItemNonSkillFolder":"Non-skill","batchItemNonSkillFolderDesc":"This folder has no SKILL.md and is not prechecked as a skill","batchItemVersionConverted":"Uploaded version {{parsedVersion}} -> {{version}}","batchItemVersionNormalizedAndAdjusted":"Uploaded version {{parsedVersion}} -> {{normalizedVersion}} -> {{version}}","resultSucceeded":"Succeeded ({{count}})","resultFailed":"Failed ({{count}})","importFromRegistry":"Import from Skill Registry","importSource":"Import Source","importSearchPlaceholder":"Search Skill name","importValidate":"Validate","importExecute":"Execute Import","importSuccess":"Import successful","importFailed":"Import failed","importResult":"Import result: {{success}} succeeded, {{failed}} failed, {{skipped}} skipped","importSkipInvalid":"Skip Invalid","importOverride":"Override Existing","importConflictPolicy":"Conflict Policy","importConflictSkip":"Skip","importConflictOverwrite":"Overwrite","importAll":"Import all valid","importSelectedCount":"{{count}} selected","importValidatedCount":"{{count}} valid after validation","importSelectAll":"Select all","importClearSelection":"Clear","importLoadMore":"Load more","noImportSource":"No import source available","importStatusValid":"valid","importStatusWarning":"warning","importStatusConflict":"conflict","importStatusInvalid":"invalid","importUnnamed":"Unnamed","importMetadata":{"fileCount":"Files","source":"Source"},"download":"Download","downloadVersion":"Download Version","createSkill":"Create Skill","createSkillDesc":"Create a new Skill with a draft version. You can edit the content later.","namePlaceholder":"Enter Skill name","descPlaceholder":"Describe the Skill functionality","skillMdPlaceholder":"Enter the full SKILL.md content...","skillMdHint":"When an Agent loads this Skill, the full SKILL.md content (frontmatter and body) is injected into the execution context.","nameRequired":"Skill name is required","nameTooLong":"Skill name must be 1–64 characters","nameInvalidFormat":"Skill name may only contain lowercase letters, numbers, and hyphens, and must not start or end with a hyphen","nameNoConsecutiveHyphens":"Skill name must not contain consecutive hyphens (--)","descriptionRequired":"Description is required","instructionRequired":"SKILL.md content is required","skillMdRequired":"SKILL.md content is required","submitRequiresFields":"Please fill in the description and SKILL.md content before submitting.","createSuccess":"Skill created","createFailed":"Failed to create Skill","retry":"Retry","editDraft":"Edit Draft","saveDraft":"Save Draft","cancelEdit":"Cancel","draftSaveSuccess":"Draft saved","commitMsg":"Version commit message","commitMsgPlaceholder":"Optional: describe what changed in this draft version","commitMsgHint":"This is stored on the draft version only (shown in version history). It is not the skill description above.","toggleEnable":"Enable/Disable Skill","pipelineStatus":"Review Pipeline","pipelineInProgress":"In Progress","pipelineApproved":"Approved","pipelineRejected":"Review Failed","pipelineNone":"No review pipeline configured","pipelineNodePassed":"Passed","pipelineNodeFailed":"Failed","pipelineNoMessage":"No output message","pipelineCopy":"Copy result","pipelineCopied":"Copied","pipelineCopySuccess":"Scan result copied","pipelineCopyFailed":"Failed to copy","publishDisabledPipeline":"Publish requires pipeline approval","forcePublish":"Force Publish","forcePublishSuccess":"Force-published successfully","redraft":"Re-edit","redraftSuccess":"Version returned to draft for re-editing","forcePublishConfirmTitle":"Force Publish (Bypass Pipeline)","forcePublishConfirmDesc":"You are about to force-publish version \\"{{version}}\\" by bypassing the review pipeline. This will publish the skill immediately regardless of pipeline results. This action is irreversible. Are you sure?","forcePublishConfirm":"Confirm Force Publish","createDraftFrom":"Create draft from this version","newVersionTitle":"Create New Draft Version","newVersionDesc":"Create a new draft from version {{current}}. Enter the target version","newVersionPlaceholder":"e.g. 1.0.1 or v2","newVersionRequired":"New version is required","versionInvalid":"Invalid version format, should be x.y.z or vN","versionMustGreater":"New version must be greater than current version {{current}}","draftExistsTip":"A draft or unpublished version already exists","bizTagsUpdateSuccess":"Tags updated","bizTagPlaceholder":"Add a tag","noBizTags":"No business tags","sortDefault":"Default sort","sortByDownloads":"Sort by downloads","filterByOwner":"Filter by owner","filterOwnerPlaceholder":"Enter owner name","filterOnlyMine":"Only mine","filterScopeAll":"All scopes","filterScopePublic":"Public only","filterScopePrivate":"Private only","filterByBizTag":"Filter by business tag","filterBizTagPlaceholder":"Enter business tag","labelKeyRequired":"Label key is required","labelKeyInvalid":"Invalid label key format","labelKeyDuplicate":"Label key already exists","saveLabels":"Save Labels","labelKey":"Key","labelValue":"Version","deleteDraft":"Delete Draft","deleteDraftSuccess":"Draft deleted","manualCreate":"Manual","aiGenerate":"AI Generate","aiOptimize":"AI Optimize","backgroundInfo":"Background Information","backgroundInfoPlaceholder":"Describe the functionality, purpose, and target users of the Skill you want to create...","backgroundInfoRequired":"Please enter background information","generateSkill":"Generate Skill","generating":"Generating...","generateSuccess":"Skill generated successfully","generateFailed":"Failed to generate Skill","regenerate":"Regenerate","applyGenerated":"Apply Generated Result","optimizationGoal":"Optimization Goal","optimizationGoalPlaceholder":"Describe how you want to optimize this Skill (optional)...","selectTargetFile":"Select target file","startOptimize":"Start Optimization","optimizing":"Optimizing...","optimizeSuccess":"Skill optimized successfully","optimizeFailed":"Failed to optimize Skill","applyOptimize":"Apply Optimization","originalContent":"Original Content","optimizedContent":"Optimized Content","mcpToolsOptional":"MCP Tools (Optional)","selectMcpServer":"Select MCP Server","availableTools":"Available Tools","searchTools":"Search tools...","selectedToolsCount":"{{count}} selected","noToolsAvailable":"No tools available","loadingTools":"Loading tools...","conversationHistory":"Conversation History (Optional)","conversationHistoryPlaceholder":"Conversation history in JSON format for multi-round generation...","conversationHistoryInvalid":"Invalid conversation history JSON format","thinking":"Thinking Process","generatedContent":"Generated Content","streamContent":"Stream Output","selectResource":"Select a file to view its content","editMode":"Edit Mode","binaryFileHint":"Preview is not supported for this file","imagePreviewUnavailable":"Unable to preview this image file"}`)}}};o.use(a).init({resources:b,lng:p(),fallbackLng:`en-US`,interpolation:{escapeValue:!1},react:{useSuspense:!1}});var x=o;function S(e){if(!e||typeof document>`u`)return;let t=document.head||document.getElementsByTagName(`head`)[0],n=document.createElement(`style`);n.type=`text/css`,t.appendChild(n),n.styleSheet?n.styleSheet.cssText=e:n.appendChild(document.createTextNode(e))}var C=e=>{switch(e){case`success`:return E;case`info`:return O;case`warning`:return D;case`error`:return k;default:return null}},w=Array(12).fill(0),T=({visible:e,className:t})=>s.createElement(`div`,{className:[`sonner-loading-wrapper`,t].filter(Boolean).join(` `),"data-visible":e},s.createElement(`div`,{className:`sonner-spinner`},w.map((e,t)=>s.createElement(`div`,{className:`sonner-loading-bar`,key:`spinner-bar-${t}`})))),E=s.createElement(`svg`,{xmlns:`http://www.w3.org/2000/svg`,viewBox:`0 0 20 20`,fill:`currentColor`,height:`20`,width:`20`},s.createElement(`path`,{fillRule:`evenodd`,d:`M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z`,clipRule:`evenodd`})),D=s.createElement(`svg`,{xmlns:`http://www.w3.org/2000/svg`,viewBox:`0 0 24 24`,fill:`currentColor`,height:`20`,width:`20`},s.createElement(`path`,{fillRule:`evenodd`,d:`M9.401 3.003c1.155-2 4.043-2 5.197 0l7.355 12.748c1.154 2-.29 4.5-2.599 4.5H4.645c-2.309 0-3.752-2.5-2.598-4.5L9.4 3.003zM12 8.25a.75.75 0 01.75.75v3.75a.75.75 0 01-1.5 0V9a.75.75 0 01.75-.75zm0 8.25a.75.75 0 100-1.5.75.75 0 000 1.5z`,clipRule:`evenodd`})),O=s.createElement(`svg`,{xmlns:`http://www.w3.org/2000/svg`,viewBox:`0 0 20 20`,fill:`currentColor`,height:`20`,width:`20`},s.createElement(`path`,{fillRule:`evenodd`,d:`M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z`,clipRule:`evenodd`})),k=s.createElement(`svg`,{xmlns:`http://www.w3.org/2000/svg`,viewBox:`0 0 20 20`,fill:`currentColor`,height:`20`,width:`20`},s.createElement(`path`,{fillRule:`evenodd`,d:`M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-8-5a.75.75 0 01.75.75v4.5a.75.75 0 01-1.5 0v-4.5A.75.75 0 0110 5zm0 10a1 1 0 100-2 1 1 0 000 2z`,clipRule:`evenodd`})),A=s.createElement(`svg`,{xmlns:`http://www.w3.org/2000/svg`,width:`12`,height:`12`,viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`1.5`,strokeLinecap:`round`,strokeLinejoin:`round`},s.createElement(`line`,{x1:`18`,y1:`6`,x2:`6`,y2:`18`}),s.createElement(`line`,{x1:`6`,y1:`6`,x2:`18`,y2:`18`})),j=()=>{let[e,t]=s.useState(document.hidden);return s.useEffect(()=>{let e=()=>{t(document.hidden)};return document.addEventListener(`visibilitychange`,e),()=>window.removeEventListener(`visibilitychange`,e)},[]),e},M=1,N=new class{constructor(){this.subscribe=e=>(this.subscribers.push(e),()=>{let t=this.subscribers.indexOf(e);this.subscribers.splice(t,1)}),this.publish=e=>{this.subscribers.forEach(t=>t(e))},this.addToast=e=>{this.publish(e),this.toasts=[...this.toasts,e]},this.create=e=>{let{message:t,...n}=e,r=typeof e?.id==`number`||e.id?.length>0?e.id:M++,i=this.toasts.find(e=>e.id===r),a=e.dismissible===void 0?!0:e.dismissible;return this.dismissedToasts.has(r)&&this.dismissedToasts.delete(r),i?this.toasts=this.toasts.map(n=>n.id===r?(this.publish({...n,...e,id:r,title:t}),{...n,...e,id:r,dismissible:a,title:t}):n):this.addToast({title:t,...n,dismissible:a,id:r}),r},this.dismiss=e=>(e?(this.dismissedToasts.add(e),requestAnimationFrame(()=>this.subscribers.forEach(t=>t({id:e,dismiss:!0})))):this.toasts.forEach(e=>{this.subscribers.forEach(t=>t({id:e.id,dismiss:!0}))}),e),this.message=(e,t)=>this.create({...t,message:e}),this.error=(e,t)=>this.create({...t,message:e,type:`error`}),this.success=(e,t)=>this.create({...t,type:`success`,message:e}),this.info=(e,t)=>this.create({...t,type:`info`,message:e}),this.warning=(e,t)=>this.create({...t,type:`warning`,message:e}),this.loading=(e,t)=>this.create({...t,type:`loading`,message:e}),this.promise=(e,t)=>{if(!t)return;let n;t.loading!==void 0&&(n=this.create({...t,promise:e,type:`loading`,message:t.loading,description:typeof t.description==`function`?void 0:t.description}));let r=Promise.resolve(e instanceof Function?e():e),i=n!==void 0,a,o=r.then(async e=>{if(a=[`resolve`,e],s.isValidElement(e))i=!1,this.create({id:n,type:`default`,message:e});else if(P(e)&&!e.ok){i=!1;let r=typeof t.error==`function`?await t.error(`HTTP error! status: ${e.status}`):t.error,a=typeof t.description==`function`?await t.description(`HTTP error! status: ${e.status}`):t.description,o=typeof r==`object`&&!s.isValidElement(r)?r:{message:r};this.create({id:n,type:`error`,description:a,...o})}else if(e instanceof Error){i=!1;let r=typeof t.error==`function`?await t.error(e):t.error,a=typeof t.description==`function`?await t.description(e):t.description,o=typeof r==`object`&&!s.isValidElement(r)?r:{message:r};this.create({id:n,type:`error`,description:a,...o})}else if(t.success!==void 0){i=!1;let r=typeof t.success==`function`?await t.success(e):t.success,a=typeof t.description==`function`?await t.description(e):t.description,o=typeof r==`object`&&!s.isValidElement(r)?r:{message:r};this.create({id:n,type:`success`,description:a,...o})}}).catch(async e=>{if(a=[`reject`,e],t.error!==void 0){i=!1;let r=typeof t.error==`function`?await t.error(e):t.error,a=typeof t.description==`function`?await t.description(e):t.description,o=typeof r==`object`&&!s.isValidElement(r)?r:{message:r};this.create({id:n,type:`error`,description:a,...o})}}).finally(()=>{i&&(this.dismiss(n),n=void 0),t.finally==null||t.finally.call(t)}),c=()=>new Promise((e,t)=>o.then(()=>a[0]===`reject`?t(a[1]):e(a[1])).catch(t));return typeof n!=`string`&&typeof n!=`number`?{unwrap:c}:Object.assign(n,{unwrap:c})},this.custom=(e,t)=>{let n=t?.id||M++;return this.create({jsx:e(n),id:n,...t}),n},this.getActiveToasts=()=>this.toasts.filter(e=>!this.dismissedToasts.has(e.id)),this.subscribers=[],this.toasts=[],this.dismissedToasts=new Set}},ee=(e,t)=>{let n=t?.id||M++;return N.addToast({title:e,...t,id:n}),n},P=e=>e&&typeof e==`object`&&`ok`in e&&typeof e.ok==`boolean`&&`status`in e&&typeof e.status==`number`,F=Object.assign(ee,{success:N.success,info:N.info,warning:N.warning,error:N.error,custom:N.custom,message:N.message,promise:N.promise,dismiss:N.dismiss,loading:N.loading},{getHistory:()=>N.toasts,getToasts:()=>N.getActiveToasts()});S(`[data-sonner-toaster][dir=ltr],html[dir=ltr]{--toast-icon-margin-start:-3px;--toast-icon-margin-end:4px;--toast-svg-margin-start:-1px;--toast-svg-margin-end:0px;--toast-button-margin-start:auto;--toast-button-margin-end:0;--toast-close-button-start:0;--toast-close-button-end:unset;--toast-close-button-transform:translate(-35%, -35%)}[data-sonner-toaster][dir=rtl],html[dir=rtl]{--toast-icon-margin-start:4px;--toast-icon-margin-end:-3px;--toast-svg-margin-start:0px;--toast-svg-margin-end:-1px;--toast-button-margin-start:0;--toast-button-margin-end:auto;--toast-close-button-start:unset;--toast-close-button-end:0;--toast-close-button-transform:translate(35%, -35%)}[data-sonner-toaster]{position:fixed;width:var(--width);font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;--gray1:hsl(0, 0%, 99%);--gray2:hsl(0, 0%, 97.3%);--gray3:hsl(0, 0%, 95.1%);--gray4:hsl(0, 0%, 93%);--gray5:hsl(0, 0%, 90.9%);--gray6:hsl(0, 0%, 88.7%);--gray7:hsl(0, 0%, 85.8%);--gray8:hsl(0, 0%, 78%);--gray9:hsl(0, 0%, 56.1%);--gray10:hsl(0, 0%, 52.3%);--gray11:hsl(0, 0%, 43.5%);--gray12:hsl(0, 0%, 9%);--border-radius:8px;box-sizing:border-box;padding:0;margin:0;list-style:none;outline:0;z-index:999999999;transition:transform .4s ease}@media (hover:none) and (pointer:coarse){[data-sonner-toaster][data-lifted=true]{transform:none}}[data-sonner-toaster][data-x-position=right]{right:var(--offset-right)}[data-sonner-toaster][data-x-position=left]{left:var(--offset-left)}[data-sonner-toaster][data-x-position=center]{left:50%;transform:translateX(-50%)}[data-sonner-toaster][data-y-position=top]{top:var(--offset-top)}[data-sonner-toaster][data-y-position=bottom]{bottom:var(--offset-bottom)}[data-sonner-toast]{--y:translateY(100%);--lift-amount:calc(var(--lift) * var(--gap));z-index:var(--z-index);position:absolute;opacity:0;transform:var(--y);touch-action:none;transition:transform .4s,opacity .4s,height .4s,box-shadow .2s;box-sizing:border-box;outline:0;overflow-wrap:anywhere}[data-sonner-toast][data-styled=true]{padding:16px;background:var(--normal-bg);border:1px solid var(--normal-border);color:var(--normal-text);border-radius:var(--border-radius);box-shadow:0 4px 12px rgba(0,0,0,.1);width:var(--width);font-size:13px;display:flex;align-items:center;gap:6px}[data-sonner-toast]:focus-visible{box-shadow:0 4px 12px rgba(0,0,0,.1),0 0 0 2px rgba(0,0,0,.2)}[data-sonner-toast][data-y-position=top]{top:0;--y:translateY(-100%);--lift:1;--lift-amount:calc(1 * var(--gap))}[data-sonner-toast][data-y-position=bottom]{bottom:0;--y:translateY(100%);--lift:-1;--lift-amount:calc(var(--lift) * var(--gap))}[data-sonner-toast][data-styled=true] [data-description]{font-weight:400;line-height:1.4;color:#3f3f3f}[data-rich-colors=true][data-sonner-toast][data-styled=true] [data-description]{color:inherit}[data-sonner-toaster][data-sonner-theme=dark] [data-description]{color:#e8e8e8}[data-sonner-toast][data-styled=true] [data-title]{font-weight:500;line-height:1.5;color:inherit}[data-sonner-toast][data-styled=true] [data-icon]{display:flex;height:16px;width:16px;position:relative;justify-content:flex-start;align-items:center;flex-shrink:0;margin-left:var(--toast-icon-margin-start);margin-right:var(--toast-icon-margin-end)}[data-sonner-toast][data-promise=true] [data-icon]>svg{opacity:0;transform:scale(.8);transform-origin:center;animation:sonner-fade-in .3s ease forwards}[data-sonner-toast][data-styled=true] [data-icon]>*{flex-shrink:0}[data-sonner-toast][data-styled=true] [data-icon] svg{margin-left:var(--toast-svg-margin-start);margin-right:var(--toast-svg-margin-end)}[data-sonner-toast][data-styled=true] [data-content]{display:flex;flex-direction:column;gap:2px}[data-sonner-toast][data-styled=true] [data-button]{border-radius:4px;padding-left:8px;padding-right:8px;height:24px;font-size:12px;color:var(--normal-bg);background:var(--normal-text);margin-left:var(--toast-button-margin-start);margin-right:var(--toast-button-margin-end);border:none;font-weight:500;cursor:pointer;outline:0;display:flex;align-items:center;flex-shrink:0;transition:opacity .4s,box-shadow .2s}[data-sonner-toast][data-styled=true] [data-button]:focus-visible{box-shadow:0 0 0 2px rgba(0,0,0,.4)}[data-sonner-toast][data-styled=true] [data-button]:first-of-type{margin-left:var(--toast-button-margin-start);margin-right:var(--toast-button-margin-end)}[data-sonner-toast][data-styled=true] [data-cancel]{color:var(--normal-text);background:rgba(0,0,0,.08)}[data-sonner-toaster][data-sonner-theme=dark] [data-sonner-toast][data-styled=true] [data-cancel]{background:rgba(255,255,255,.3)}[data-sonner-toast][data-styled=true] [data-close-button]{position:absolute;left:var(--toast-close-button-start);right:var(--toast-close-button-end);top:0;height:20px;width:20px;display:flex;justify-content:center;align-items:center;padding:0;color:var(--gray12);background:var(--normal-bg);border:1px solid var(--gray4);transform:var(--toast-close-button-transform);border-radius:50%;cursor:pointer;z-index:1;transition:opacity .1s,background .2s,border-color .2s}[data-sonner-toast][data-styled=true] [data-close-button]:focus-visible{box-shadow:0 4px 12px rgba(0,0,0,.1),0 0 0 2px rgba(0,0,0,.2)}[data-sonner-toast][data-styled=true] [data-disabled=true]{cursor:not-allowed}[data-sonner-toast][data-styled=true]:hover [data-close-button]:hover{background:var(--gray2);border-color:var(--gray5)}[data-sonner-toast][data-swiping=true]::before{content:'';position:absolute;left:-100%;right:-100%;height:100%;z-index:-1}[data-sonner-toast][data-y-position=top][data-swiping=true]::before{bottom:50%;transform:scaleY(3) translateY(50%)}[data-sonner-toast][data-y-position=bottom][data-swiping=true]::before{top:50%;transform:scaleY(3) translateY(-50%)}[data-sonner-toast][data-swiping=false][data-removed=true]::before{content:'';position:absolute;inset:0;transform:scaleY(2)}[data-sonner-toast][data-expanded=true]::after{content:'';position:absolute;left:0;height:calc(var(--gap) + 1px);bottom:100%;width:100%}[data-sonner-toast][data-mounted=true]{--y:translateY(0);opacity:1}[data-sonner-toast][data-expanded=false][data-front=false]{--scale:var(--toasts-before) * 0.05 + 1;--y:translateY(calc(var(--lift-amount) * var(--toasts-before))) scale(calc(-1 * var(--scale)));height:var(--front-toast-height)}[data-sonner-toast]>*{transition:opacity .4s}[data-sonner-toast][data-x-position=right]{right:0}[data-sonner-toast][data-x-position=left]{left:0}[data-sonner-toast][data-expanded=false][data-front=false][data-styled=true]>*{opacity:0}[data-sonner-toast][data-visible=false]{opacity:0;pointer-events:none}[data-sonner-toast][data-mounted=true][data-expanded=true]{--y:translateY(calc(var(--lift) * var(--offset)));height:var(--initial-height)}[data-sonner-toast][data-removed=true][data-front=true][data-swipe-out=false]{--y:translateY(calc(var(--lift) * -100%));opacity:0}[data-sonner-toast][data-removed=true][data-front=false][data-swipe-out=false][data-expanded=true]{--y:translateY(calc(var(--lift) * var(--offset) + var(--lift) * -100%));opacity:0}[data-sonner-toast][data-removed=true][data-front=false][data-swipe-out=false][data-expanded=false]{--y:translateY(40%);opacity:0;transition:transform .5s,opacity .2s}[data-sonner-toast][data-removed=true][data-front=false]::before{height:calc(var(--initial-height) + 20%)}[data-sonner-toast][data-swiping=true]{transform:var(--y) translateY(var(--swipe-amount-y,0)) translateX(var(--swipe-amount-x,0));transition:none}[data-sonner-toast][data-swiped=true]{user-select:none}[data-sonner-toast][data-swipe-out=true][data-y-position=bottom],[data-sonner-toast][data-swipe-out=true][data-y-position=top]{animation-duration:.2s;animation-timing-function:ease-out;animation-fill-mode:forwards}[data-sonner-toast][data-swipe-out=true][data-swipe-direction=left]{animation-name:swipe-out-left}[data-sonner-toast][data-swipe-out=true][data-swipe-direction=right]{animation-name:swipe-out-right}[data-sonner-toast][data-swipe-out=true][data-swipe-direction=up]{animation-name:swipe-out-up}[data-sonner-toast][data-swipe-out=true][data-swipe-direction=down]{animation-name:swipe-out-down}@keyframes swipe-out-left{from{transform:var(--y) translateX(var(--swipe-amount-x));opacity:1}to{transform:var(--y) translateX(calc(var(--swipe-amount-x) - 100%));opacity:0}}@keyframes swipe-out-right{from{transform:var(--y) translateX(var(--swipe-amount-x));opacity:1}to{transform:var(--y) translateX(calc(var(--swipe-amount-x) + 100%));opacity:0}}@keyframes swipe-out-up{from{transform:var(--y) translateY(var(--swipe-amount-y));opacity:1}to{transform:var(--y) translateY(calc(var(--swipe-amount-y) - 100%));opacity:0}}@keyframes swipe-out-down{from{transform:var(--y) translateY(var(--swipe-amount-y));opacity:1}to{transform:var(--y) translateY(calc(var(--swipe-amount-y) + 100%));opacity:0}}@media (max-width:600px){[data-sonner-toaster]{position:fixed;right:var(--mobile-offset-right);left:var(--mobile-offset-left);width:100%}[data-sonner-toaster][dir=rtl]{left:calc(var(--mobile-offset-left) * -1)}[data-sonner-toaster] [data-sonner-toast]{left:0;right:0;width:calc(100% - var(--mobile-offset-left) * 2)}[data-sonner-toaster][data-x-position=left]{left:var(--mobile-offset-left)}[data-sonner-toaster][data-y-position=bottom]{bottom:var(--mobile-offset-bottom)}[data-sonner-toaster][data-y-position=top]{top:var(--mobile-offset-top)}[data-sonner-toaster][data-x-position=center]{left:var(--mobile-offset-left);right:var(--mobile-offset-right);transform:none}}[data-sonner-toaster][data-sonner-theme=light]{--normal-bg:#fff;--normal-border:var(--gray4);--normal-text:var(--gray12);--success-bg:hsl(143, 85%, 96%);--success-border:hsl(145, 92%, 87%);--success-text:hsl(140, 100%, 27%);--info-bg:hsl(208, 100%, 97%);--info-border:hsl(221, 91%, 93%);--info-text:hsl(210, 92%, 45%);--warning-bg:hsl(49, 100%, 97%);--warning-border:hsl(49, 91%, 84%);--warning-text:hsl(31, 92%, 45%);--error-bg:hsl(359, 100%, 97%);--error-border:hsl(359, 100%, 94%);--error-text:hsl(360, 100%, 45%)}[data-sonner-toaster][data-sonner-theme=light] [data-sonner-toast][data-invert=true]{--normal-bg:#000;--normal-border:hsl(0, 0%, 20%);--normal-text:var(--gray1)}[data-sonner-toaster][data-sonner-theme=dark] [data-sonner-toast][data-invert=true]{--normal-bg:#fff;--normal-border:var(--gray3);--normal-text:var(--gray12)}[data-sonner-toaster][data-sonner-theme=dark]{--normal-bg:#000;--normal-bg-hover:hsl(0, 0%, 12%);--normal-border:hsl(0, 0%, 20%);--normal-border-hover:hsl(0, 0%, 25%);--normal-text:var(--gray1);--success-bg:hsl(150, 100%, 6%);--success-border:hsl(147, 100%, 12%);--success-text:hsl(150, 86%, 65%);--info-bg:hsl(215, 100%, 6%);--info-border:hsl(223, 43%, 17%);--info-text:hsl(216, 87%, 65%);--warning-bg:hsl(64, 100%, 6%);--warning-border:hsl(60, 100%, 9%);--warning-text:hsl(46, 87%, 65%);--error-bg:hsl(358, 76%, 10%);--error-border:hsl(357, 89%, 16%);--error-text:hsl(358, 100%, 81%)}[data-sonner-toaster][data-sonner-theme=dark] [data-sonner-toast] [data-close-button]{background:var(--normal-bg);border-color:var(--normal-border);color:var(--normal-text)}[data-sonner-toaster][data-sonner-theme=dark] [data-sonner-toast] [data-close-button]:hover{background:var(--normal-bg-hover);border-color:var(--normal-border-hover)}[data-rich-colors=true][data-sonner-toast][data-type=success]{background:var(--success-bg);border-color:var(--success-border);color:var(--success-text)}[data-rich-colors=true][data-sonner-toast][data-type=success] [data-close-button]{background:var(--success-bg);border-color:var(--success-border);color:var(--success-text)}[data-rich-colors=true][data-sonner-toast][data-type=info]{background:var(--info-bg);border-color:var(--info-border);color:var(--info-text)}[data-rich-colors=true][data-sonner-toast][data-type=info] [data-close-button]{background:var(--info-bg);border-color:var(--info-border);color:var(--info-text)}[data-rich-colors=true][data-sonner-toast][data-type=warning]{background:var(--warning-bg);border-color:var(--warning-border);color:var(--warning-text)}[data-rich-colors=true][data-sonner-toast][data-type=warning] [data-close-button]{background:var(--warning-bg);border-color:var(--warning-border);color:var(--warning-text)}[data-rich-colors=true][data-sonner-toast][data-type=error]{background:var(--error-bg);border-color:var(--error-border);color:var(--error-text)}[data-rich-colors=true][data-sonner-toast][data-type=error] [data-close-button]{background:var(--error-bg);border-color:var(--error-border);color:var(--error-text)}.sonner-loading-wrapper{--size:16px;height:var(--size);width:var(--size);position:absolute;inset:0;z-index:10}.sonner-loading-wrapper[data-visible=false]{transform-origin:center;animation:sonner-fade-out .2s ease forwards}.sonner-spinner{position:relative;top:50%;left:50%;height:var(--size);width:var(--size)}.sonner-loading-bar{animation:sonner-spin 1.2s linear infinite;background:var(--gray11);border-radius:6px;height:8%;left:-10%;position:absolute;top:-3.9%;width:24%}.sonner-loading-bar:first-child{animation-delay:-1.2s;transform:rotate(.0001deg) translate(146%)}.sonner-loading-bar:nth-child(2){animation-delay:-1.1s;transform:rotate(30deg) translate(146%)}.sonner-loading-bar:nth-child(3){animation-delay:-1s;transform:rotate(60deg) translate(146%)}.sonner-loading-bar:nth-child(4){animation-delay:-.9s;transform:rotate(90deg) translate(146%)}.sonner-loading-bar:nth-child(5){animation-delay:-.8s;transform:rotate(120deg) translate(146%)}.sonner-loading-bar:nth-child(6){animation-delay:-.7s;transform:rotate(150deg) translate(146%)}.sonner-loading-bar:nth-child(7){animation-delay:-.6s;transform:rotate(180deg) translate(146%)}.sonner-loading-bar:nth-child(8){animation-delay:-.5s;transform:rotate(210deg) translate(146%)}.sonner-loading-bar:nth-child(9){animation-delay:-.4s;transform:rotate(240deg) translate(146%)}.sonner-loading-bar:nth-child(10){animation-delay:-.3s;transform:rotate(270deg) translate(146%)}.sonner-loading-bar:nth-child(11){animation-delay:-.2s;transform:rotate(300deg) translate(146%)}.sonner-loading-bar:nth-child(12){animation-delay:-.1s;transform:rotate(330deg) translate(146%)}@keyframes sonner-fade-in{0%{opacity:0;transform:scale(.8)}100%{opacity:1;transform:scale(1)}}@keyframes sonner-fade-out{0%{opacity:1;transform:scale(1)}100%{opacity:0;transform:scale(.8)}}@keyframes sonner-spin{0%{opacity:1}100%{opacity:.15}}@media (prefers-reduced-motion){.sonner-loading-bar,[data-sonner-toast],[data-sonner-toast]>*{transition:none!important;animation:none!important}}.sonner-loader{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);transform-origin:center;transition:opacity .2s,transform .2s}.sonner-loader[data-visible=false]{opacity:0;transform:scale(.8) translate(-50%,-50%)}`);function I(e){return e.label!==void 0}var te=3,L=`24px`,R=`16px`,z=4e3,ne=356,re=14,ie=45,ae=200;function oe(...e){return e.filter(Boolean).join(` `)}function se(e){let[t,n]=e.split(`-`),r=[];return t&&r.push(t),n&&r.push(n),r}var ce=e=>{let{invert:t,toast:n,unstyled:r,interacting:i,setHeights:a,visibleToasts:o,heights:c,index:l,toasts:u,expanded:d,removeToast:f,defaultRichColors:p,closeButton:m,style:h,cancelButtonStyle:g,actionButtonStyle:_,className:v=``,descriptionClassName:y=``,duration:b,position:x,gap:S,expandByDefault:w,classNames:E,icons:D,closeButtonAriaLabel:O=`Close toast`}=e,[k,M]=s.useState(null),[N,ee]=s.useState(null),[P,F]=s.useState(!1),[te,L]=s.useState(!1),[R,ne]=s.useState(!1),[re,ce]=s.useState(!1),[le,ue]=s.useState(!1),[de,B]=s.useState(0),[fe,pe]=s.useState(0),me=s.useRef(n.duration||b||z),he=s.useRef(null),V=s.useRef(null),H=l===0,ge=l+1<=o,U=n.type,W=n.dismissible!==!1,_e=n.className||``,ve=n.descriptionClassName||``,ye=s.useMemo(()=>c.findIndex(e=>e.toastId===n.id)||0,[c,n.id]),be=s.useMemo(()=>n.closeButton??m,[n.closeButton,m]),G=s.useMemo(()=>n.duration||b||z,[n.duration,b]),xe=s.useRef(0),K=s.useRef(0),Se=s.useRef(0),q=s.useRef(null),[Ce,we]=x.split(`-`),Te=s.useMemo(()=>c.reduce((e,t,n)=>n>=ye?e:e+t.height,0),[c,ye]),Ee=j(),De=n.invert||t,Oe=U===`loading`;K.current=s.useMemo(()=>ye*S+Te,[ye,Te]),s.useEffect(()=>{me.current=G},[G]),s.useEffect(()=>{F(!0)},[]),s.useEffect(()=>{let e=V.current;if(e){let t=e.getBoundingClientRect().height;return pe(t),a(e=>[{toastId:n.id,height:t,position:n.position},...e]),()=>a(e=>e.filter(e=>e.toastId!==n.id))}},[a,n.id]),s.useLayoutEffect(()=>{if(!P)return;let e=V.current,t=e.style.height;e.style.height=`auto`;let r=e.getBoundingClientRect().height;e.style.height=t,pe(r),a(e=>e.find(e=>e.toastId===n.id)?e.map(e=>e.toastId===n.id?{...e,height:r}:e):[{toastId:n.id,height:r,position:n.position},...e])},[P,n.title,n.description,a,n.id,n.jsx,n.action,n.cancel]);let J=s.useCallback(()=>{L(!0),B(K.current),a(e=>e.filter(e=>e.toastId!==n.id)),setTimeout(()=>{f(n)},ae)},[n,f,a,K]);s.useEffect(()=>{if(n.promise&&U===`loading`||n.duration===1/0||n.type===`loading`)return;let e;return d||i||Ee?(()=>{if(Se.current{n.onAutoClose==null||n.onAutoClose.call(n,n),J()},me.current)),()=>clearTimeout(e)},[d,i,n,U,Ee,J]),s.useEffect(()=>{n.delete&&(J(),n.onDismiss==null||n.onDismiss.call(n,n))},[J,n.delete]);function ke(){return D?.loading?s.createElement(`div`,{className:oe(E?.loader,n?.classNames?.loader,`sonner-loader`),"data-visible":U===`loading`},D.loading):s.createElement(T,{className:oe(E?.loader,n?.classNames?.loader),visible:U===`loading`})}let Ae=n.icon||D?.[U]||C(U);return s.createElement(`li`,{tabIndex:0,ref:V,className:oe(v,_e,E?.toast,n?.classNames?.toast,E?.default,E?.[U],n?.classNames?.[U]),"data-sonner-toast":``,"data-rich-colors":n.richColors??p,"data-styled":!(n.jsx||n.unstyled||r),"data-mounted":P,"data-promise":!!n.promise,"data-swiped":le,"data-removed":te,"data-visible":ge,"data-y-position":Ce,"data-x-position":we,"data-index":l,"data-front":H,"data-swiping":R,"data-dismissible":W,"data-type":U,"data-invert":De,"data-swipe-out":re,"data-swipe-direction":N,"data-expanded":!!(d||w&&P),"data-testid":n.testId,style:{"--index":l,"--toasts-before":l,"--z-index":u.length-l,"--offset":`${te?de:K.current}px`,"--initial-height":w?`auto`:`${fe}px`,...h,...n.style},onDragEnd:()=>{ne(!1),M(null),q.current=null},onPointerDown:e=>{e.button!==2&&(Oe||!W||(he.current=new Date,B(K.current),e.target.setPointerCapture(e.pointerId),e.target.tagName!==`BUTTON`&&(ne(!0),q.current={x:e.clientX,y:e.clientY})))},onPointerUp:()=>{if(re||!W)return;q.current=null;let e=Number(V.current?.style.getPropertyValue(`--swipe-amount-x`).replace(`px`,``)||0),t=Number(V.current?.style.getPropertyValue(`--swipe-amount-y`).replace(`px`,``)||0),r=new Date().getTime()-he.current?.getTime(),i=k===`x`?e:t,a=Math.abs(i)/r;if(Math.abs(i)>=ie||a>.11){B(K.current),n.onDismiss==null||n.onDismiss.call(n,n),ee(k===`x`?e>0?`right`:`left`:t>0?`down`:`up`),J(),ce(!0);return}else{var o,s;(o=V.current)==null||o.style.setProperty(`--swipe-amount-x`,`0px`),(s=V.current)==null||s.style.setProperty(`--swipe-amount-y`,`0px`)}ue(!1),ne(!1),M(null)},onPointerMove:t=>{var n,r;if(!q.current||!W||window.getSelection()?.toString().length>0)return;let i=t.clientY-q.current.y,a=t.clientX-q.current.x,o=e.swipeDirections??se(x);!k&&(Math.abs(a)>1||Math.abs(i)>1)&&M(Math.abs(a)>Math.abs(i)?`x`:`y`);let s={x:0,y:0},c=e=>1/(1.5+Math.abs(e)/20);if(k===`y`){if(o.includes(`top`)||o.includes(`bottom`))if(o.includes(`top`)&&i<0||o.includes(`bottom`)&&i>0)s.y=i;else{let e=i*c(i);s.y=Math.abs(e)0)s.x=a;else{let e=a*c(a);s.x=Math.abs(e)0||Math.abs(s.y)>0)&&ue(!0),(n=V.current)==null||n.style.setProperty(`--swipe-amount-x`,`${s.x}px`),(r=V.current)==null||r.style.setProperty(`--swipe-amount-y`,`${s.y}px`)}},be&&!n.jsx&&U!==`loading`?s.createElement(`button`,{"aria-label":O,"data-disabled":Oe,"data-close-button":!0,onClick:Oe||!W?()=>{}:()=>{J(),n.onDismiss==null||n.onDismiss.call(n,n)},className:oe(E?.closeButton,n?.classNames?.closeButton)},D?.close??A):null,(U||n.icon||n.promise)&&n.icon!==null&&(D?.[U]!==null||n.icon)?s.createElement(`div`,{"data-icon":``,className:oe(E?.icon,n?.classNames?.icon)},n.promise||n.type===`loading`&&!n.icon?n.icon||ke():null,n.type===`loading`?null:Ae):null,s.createElement(`div`,{"data-content":``,className:oe(E?.content,n?.classNames?.content)},s.createElement(`div`,{"data-title":``,className:oe(E?.title,n?.classNames?.title)},n.jsx?n.jsx:typeof n.title==`function`?n.title():n.title),n.description?s.createElement(`div`,{"data-description":``,className:oe(y,ve,E?.description,n?.classNames?.description)},typeof n.description==`function`?n.description():n.description):null),s.isValidElement(n.cancel)?n.cancel:n.cancel&&I(n.cancel)?s.createElement(`button`,{"data-button":!0,"data-cancel":!0,style:n.cancelButtonStyle||g,onClick:e=>{I(n.cancel)&&W&&(n.cancel.onClick==null||n.cancel.onClick.call(n.cancel,e),J())},className:oe(E?.cancelButton,n?.classNames?.cancelButton)},n.cancel.label):null,s.isValidElement(n.action)?n.action:n.action&&I(n.action)?s.createElement(`button`,{"data-button":!0,"data-action":!0,style:n.actionButtonStyle||_,onClick:e=>{I(n.action)&&(n.action.onClick==null||n.action.onClick.call(n.action,e),!e.defaultPrevented&&J())},className:oe(E?.actionButton,n?.classNames?.actionButton)},n.action.label):null)};function le(){if(typeof window>`u`||typeof document>`u`)return`ltr`;let e=document.documentElement.getAttribute(`dir`);return e===`auto`||!e?window.getComputedStyle(document.documentElement).direction:e}function ue(e,t){let n={};return[e,t].forEach((e,t)=>{let r=t===1,i=r?`--mobile-offset`:`--offset`,a=r?R:L;function o(e){[`top`,`right`,`bottom`,`left`].forEach(t=>{n[`${i}-${t}`]=typeof e==`number`?`${e}px`:e})}typeof e==`number`||typeof e==`string`?o(e):typeof e==`object`?[`top`,`right`,`bottom`,`left`].forEach(t=>{e[t]===void 0?n[`${i}-${t}`]=a:n[`${i}-${t}`]=typeof e[t]==`number`?`${e[t]}px`:e[t]}):o(a)}),n}var de=s.forwardRef(function(e,t){let{id:n,invert:r,position:i=`bottom-right`,hotkey:a=[`altKey`,`KeyT`],expand:o,closeButton:l,className:u,offset:d,mobileOffset:f,theme:p=`light`,richColors:m,duration:h,style:g,visibleToasts:_=te,toastOptions:v,dir:y=le(),gap:b=re,icons:x,containerAriaLabel:S=`Notifications`}=e,[C,w]=s.useState([]),T=s.useMemo(()=>n?C.filter(e=>e.toasterId===n):C.filter(e=>!e.toasterId),[C,n]),E=s.useMemo(()=>Array.from(new Set([i].concat(T.filter(e=>e.position).map(e=>e.position)))),[T,i]),[D,O]=s.useState([]),[k,A]=s.useState(!1),[j,M]=s.useState(!1),[ee,P]=s.useState(p===`system`?typeof window<`u`&&window.matchMedia&&window.matchMedia(`(prefers-color-scheme: dark)`).matches?`dark`:`light`:p),F=s.useRef(null),I=a.join(`+`).replace(/Key/g,``).replace(/Digit/g,``),L=s.useRef(null),R=s.useRef(!1),z=s.useCallback(e=>{w(t=>(t.find(t=>t.id===e.id)?.delete||N.dismiss(e.id),t.filter(({id:t})=>t!==e.id)))},[]);return s.useEffect(()=>N.subscribe(e=>{if(e.dismiss){requestAnimationFrame(()=>{w(t=>t.map(t=>t.id===e.id?{...t,delete:!0}:t))});return}setTimeout(()=>{c.flushSync(()=>{w(t=>{let n=t.findIndex(t=>t.id===e.id);return n===-1?[e,...t]:[...t.slice(0,n),{...t[n],...e},...t.slice(n+1)]})})})}),[C]),s.useEffect(()=>{if(p!==`system`){P(p);return}if(p===`system`&&(window.matchMedia&&window.matchMedia(`(prefers-color-scheme: dark)`).matches?P(`dark`):P(`light`)),typeof window>`u`)return;let e=window.matchMedia(`(prefers-color-scheme: dark)`);try{e.addEventListener(`change`,({matches:e})=>{P(e?`dark`:`light`)})}catch{e.addListener(({matches:e})=>{try{P(e?`dark`:`light`)}catch(e){console.error(e)}})}},[p]),s.useEffect(()=>{C.length<=1&&A(!1)},[C]),s.useEffect(()=>{let e=e=>{if(a.every(t=>e[t]||e.code===t)){var t;A(!0),(t=F.current)==null||t.focus()}e.code===`Escape`&&(document.activeElement===F.current||F.current?.contains(document.activeElement))&&A(!1)};return document.addEventListener(`keydown`,e),()=>document.removeEventListener(`keydown`,e)},[a]),s.useEffect(()=>{if(F.current)return()=>{L.current&&(L.current.focus({preventScroll:!0}),L.current=null,R.current=!1)}},[F.current]),s.createElement(`section`,{ref:t,"aria-label":`${S} ${I}`,tabIndex:-1,"aria-live":`polite`,"aria-relevant":`additions text`,"aria-atomic":`false`,suppressHydrationWarning:!0},E.map((t,n)=>{let[i,a]=t.split(`-`);return T.length?s.createElement(`ol`,{key:t,dir:y===`auto`?le():y,tabIndex:-1,ref:F,className:u,"data-sonner-toaster":!0,"data-sonner-theme":ee,"data-y-position":i,"data-x-position":a,style:{"--front-toast-height":`${D[0]?.height||0}px`,"--width":`${ne}px`,"--gap":`${b}px`,...g,...ue(d,f)},onBlur:e=>{R.current&&!e.currentTarget.contains(e.relatedTarget)&&(R.current=!1,L.current&&=(L.current.focus({preventScroll:!0}),null))},onFocus:e=>{e.target instanceof HTMLElement&&e.target.dataset.dismissible===`false`||R.current||(R.current=!0,L.current=e.relatedTarget)},onMouseEnter:()=>A(!0),onMouseMove:()=>A(!0),onMouseLeave:()=>{j||A(!1)},onDragEnd:()=>A(!1),onPointerDown:e=>{e.target instanceof HTMLElement&&e.target.dataset.dismissible===`false`||M(!0)},onPointerUp:()=>M(!1)},T.filter(e=>!e.position&&n===0||e.position===t).map((n,i)=>s.createElement(ce,{key:n.id,icons:x,index:i,toast:n,defaultRichColors:m,duration:v?.duration??h,className:v?.className,descriptionClassName:v?.descriptionClassName,invert:r,visibleToasts:_,closeButton:v?.closeButton??l,interacting:j,position:t,style:v?.style,unstyled:v?.unstyled,classNames:v?.classNames,cancelButtonStyle:v?.cancelButtonStyle,actionButtonStyle:v?.actionButtonStyle,closeButtonAriaLabel:v?.closeButtonAriaLabel,removeToast:z,toasts:T.filter(e=>e.position==n.position),heights:D.filter(e=>e.position==n.position),setHeights:O,expandByDefault:o,gap:b,expanded:k,swipeDirections:e.swipeDirections}))):null}))});function B(e,t){return function(){return e.apply(t,arguments)}}var{toString:fe}=Object.prototype,{getPrototypeOf:pe}=Object,{iterator:me,toStringTag:he}=Symbol,V=(e=>t=>{let n=fe.call(t);return e[n]||(e[n]=n.slice(8,-1).toLowerCase())})(Object.create(null)),H=e=>(e=e.toLowerCase(),t=>V(t)===e),ge=e=>t=>typeof t===e,{isArray:U}=Array,W=ge(`undefined`);function _e(e){return e!==null&&!W(e)&&e.constructor!==null&&!W(e.constructor)&&G(e.constructor.isBuffer)&&e.constructor.isBuffer(e)}var ve=H(`ArrayBuffer`);function ye(e){let t;return t=typeof ArrayBuffer<`u`&&ArrayBuffer.isView?ArrayBuffer.isView(e):e&&e.buffer&&ve(e.buffer),t}var be=ge(`string`),G=ge(`function`),xe=ge(`number`),K=e=>typeof e==`object`&&!!e,Se=e=>e===!0||e===!1,q=e=>{if(V(e)!==`object`)return!1;let t=pe(e);return(t===null||t===Object.prototype||Object.getPrototypeOf(t)===null)&&!(he in e)&&!(me in e)},Ce=e=>{if(!K(e)||_e(e))return!1;try{return Object.keys(e).length===0&&Object.getPrototypeOf(e)===Object.prototype}catch{return!1}},we=H(`Date`),Te=H(`File`),Ee=e=>!!(e&&e.uri!==void 0),De=e=>e&&e.getParts!==void 0,Oe=H(`Blob`),J=H(`FileList`),ke=e=>K(e)&&G(e.pipe);function Ae(){return typeof globalThis<`u`?globalThis:typeof self<`u`?self:typeof window<`u`?window:typeof global<`u`?global:{}}var je=Ae(),Me=je.FormData===void 0?void 0:je.FormData,Ne=e=>{if(!e)return!1;if(Me&&e instanceof Me)return!0;let t=pe(e);if(!t||t===Object.prototype||!G(e.append))return!1;let n=V(e);return n===`formdata`||n===`object`&&G(e.toString)&&e.toString()===`[object FormData]`},Pe=H(`URLSearchParams`),[Fe,Ie,Le,Re]=[`ReadableStream`,`Request`,`Response`,`Headers`].map(H),ze=e=>e.trim?e.trim():e.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,``);function Be(e,t,{allOwnKeys:n=!1}={}){if(e==null)return;let r,i;if(typeof e!=`object`&&(e=[e]),U(e))for(r=0,i=e.length;r0;)if(i=n[r],t===i.toLowerCase())return i;return null}var He=typeof globalThis<`u`?globalThis:typeof self<`u`?self:typeof window<`u`?window:global,Ue=e=>!W(e)&&e!==He;function We(...e){let{caseless:t,skipUndefined:n}=Ue(this)&&this||{},r={},i=(e,i)=>{if(i===`__proto__`||i===`constructor`||i===`prototype`)return;let a=t&&Ve(r,i)||i,o=nt(r,a)?r[a]:void 0;q(o)&&q(e)?r[a]=We(o,e):q(e)?r[a]=We({},e):U(e)?r[a]=e.slice():(!n||!W(e))&&(r[a]=e)};for(let t=0,n=e.length;t(Be(t,(t,r)=>{n&&G(t)?Object.defineProperty(e,r,{__proto__:null,value:B(t,n),writable:!0,enumerable:!0,configurable:!0}):Object.defineProperty(e,r,{__proto__:null,value:t,writable:!0,enumerable:!0,configurable:!0})},{allOwnKeys:r}),e),Ke=e=>(e.charCodeAt(0)===65279&&(e=e.slice(1)),e),qe=(e,t,n,r)=>{e.prototype=Object.create(t.prototype,r),Object.defineProperty(e.prototype,"constructor",{__proto__:null,value:e,writable:!0,enumerable:!1,configurable:!0}),Object.defineProperty(e,"super",{__proto__:null,value:t.prototype}),n&&Object.assign(e.prototype,n)},Je=(e,t,n,r)=>{let i,a,o,s={};if(t||={},e==null)return t;do{for(i=Object.getOwnPropertyNames(e),a=i.length;a-- >0;)o=i[a],(!r||r(o,e,t))&&!s[o]&&(t[o]=e[o],s[o]=!0);e=n!==!1&&pe(e)}while(e&&(!n||n(e,t))&&e!==Object.prototype);return t},Ye=(e,t,n)=>{e=String(e),(n===void 0||n>e.length)&&(n=e.length),n-=t.length;let r=e.indexOf(t,n);return r!==-1&&r===n},Xe=e=>{if(!e)return null;if(U(e))return e;let t=e.length;if(!xe(t))return null;let n=Array(t);for(;t-- >0;)n[t]=e[t];return n},Ze=(e=>t=>e&&t instanceof e)(typeof Uint8Array<`u`&&pe(Uint8Array)),Qe=(e,t)=>{let n=(e&&e[me]).call(e),r;for(;(r=n.next())&&!r.done;){let n=r.value;t.call(e,n[0],n[1])}},$e=(e,t)=>{let n,r=[];for(;(n=e.exec(t))!==null;)r.push(n);return r},et=H(`HTMLFormElement`),tt=e=>e.toLowerCase().replace(/[-_\s]([a-z\d])(\w*)/g,function(e,t,n){return t.toUpperCase()+n}),nt=(({hasOwnProperty:e})=>(t,n)=>e.call(t,n))(Object.prototype),rt=H(`RegExp`),it=(e,t)=>{let n=Object.getOwnPropertyDescriptors(e),r={};Be(n,(n,i)=>{let a;(a=t(n,i,e))!==!1&&(r[i]=a||n)}),Object.defineProperties(e,r)},at=e=>{it(e,(t,n)=>{if(G(e)&&[`arguments`,`caller`,`callee`].includes(n))return!1;let r=e[n];if(G(r)){if(t.enumerable=!1,`writable`in t){t.writable=!1;return}t.set||=()=>{throw Error(`Can not rewrite read-only method '`+n+`'`)}}})},ot=(e,t)=>{let n={},r=e=>{e.forEach(e=>{n[e]=!0})};return U(e)?r(e):r(String(e).split(t)),n},st=()=>{},ct=(e,t)=>e!=null&&Number.isFinite(e=+e)?e:t;function lt(e){return!!(e&&G(e.append)&&e[he]===`FormData`&&e[me])}var ut=e=>{let t=new WeakSet,n=e=>{if(K(e)){if(t.has(e))return;if(_e(e))return e;if(!(`toJSON`in e)){t.add(e);let r=U(e)?[]:{};return Be(e,(e,t)=>{let i=n(e);!W(i)&&(r[t]=i)}),t.delete(e),r}}return e};return n(e)},dt=H(`AsyncFunction`),ft=e=>e&&(K(e)||G(e))&&G(e.then)&&G(e.catch),pt=((e,t)=>e?setImmediate:t?((e,t)=>(He.addEventListener(`message`,({source:n,data:r})=>{n===He&&r===e&&t.length&&t.shift()()},!1),n=>{t.push(n),He.postMessage(e,`*`)}))(`axios@${Math.random()}`,[]):e=>setTimeout(e))(typeof setImmediate==`function`,G(He.postMessage)),Y={isArray:U,isArrayBuffer:ve,isBuffer:_e,isFormData:Ne,isArrayBufferView:ye,isString:be,isNumber:xe,isBoolean:Se,isObject:K,isPlainObject:q,isEmptyObject:Ce,isReadableStream:Fe,isRequest:Ie,isResponse:Le,isHeaders:Re,isUndefined:W,isDate:we,isFile:Te,isReactNativeBlob:Ee,isReactNative:De,isBlob:Oe,isRegExp:rt,isFunction:G,isStream:ke,isURLSearchParams:Pe,isTypedArray:Ze,isFileList:J,forEach:Be,merge:We,extend:Ge,trim:ze,stripBOM:Ke,inherits:qe,toFlatObject:Je,kindOf:V,kindOfTest:H,endsWith:Ye,toArray:Xe,forEachEntry:Qe,matchAll:$e,isHTMLForm:et,hasOwnProperty:nt,hasOwnProp:nt,reduceDescriptors:it,freezeMethods:at,toObjectSet:ot,toCamelCase:tt,noop:st,toFiniteNumber:ct,findKey:Ve,global:He,isContextDefined:Ue,isSpecCompliantForm:lt,toJSONObject:ut,isAsyncFn:dt,isThenable:ft,setImmediate:pt,asap:typeof queueMicrotask<`u`?queueMicrotask.bind(He):typeof process<`u`&&process.nextTick||pt,isIterable:e=>e!=null&&G(e[me])},mt=Y.toObjectSet([`age`,`authorization`,`content-length`,`content-type`,`etag`,`expires`,`from`,`host`,`if-modified-since`,`if-unmodified-since`,`last-modified`,`location`,`max-forwards`,`proxy-authorization`,`referer`,`retry-after`,`user-agent`]),ht=e=>{let t={},n,r,i;return e&&e.split(` `).forEach(function(e){i=e.indexOf(`:`),n=e.substring(0,i).trim().toLowerCase(),r=e.substring(i+1).trim(),!(!n||t[n]&&mt[n])&&(n===`set-cookie`?t[n]?t[n].push(r):t[n]=[r]:t[n]=t[n]?t[n]+`, `+r:r)}),t};function gt(e){let t=0,n=e.length;for(;tt;){let t=e.charCodeAt(n-1);if(t!==9&&t!==32)break;--n}return t===0&&n===e.length?e:e.slice(t,n)}var _t=RegExp(`[\\u0000-\\u0008\\u000a-\\u001f\\u007f]+`,`g`),vt=RegExp(`[^\\u0009\\u0020-\\u007e\\u0080-\\u00ff]+`,`g`);function yt(e,t){return Y.isArray(e)?e.map(e=>yt(e,t)):gt(String(e).replace(t,``))}var bt=e=>yt(e,_t),xt=e=>yt(e,vt);function St(e){let t=Object.create(null);return Y.forEach(e.toJSON(),(e,n)=>{t[n]=xt(e)}),t}var Ct=Symbol(`internals`);function wt(e){return e&&String(e).trim().toLowerCase()}function Tt(e){return e===!1||e==null?e:Y.isArray(e)?e.map(Tt):bt(String(e))}function Et(e){let t=Object.create(null),n=/([^\s,;=]+)\s*(?:=\s*([^,;]+))?/g,r;for(;r=n.exec(e);)t[r[1]]=r[2];return t}var Dt=e=>/^[-_a-zA-Z0-9^`|~,!#$%&'*+.]+$/.test(e.trim());function Ot(e,t,n,r,i){if(Y.isFunction(r))return r.call(this,t,n);if(i&&(t=n),Y.isString(t)){if(Y.isString(r))return t.indexOf(r)!==-1;if(Y.isRegExp(r))return r.test(t)}}function kt(e){return e.trim().toLowerCase().replace(/([a-z\d])(\w*)/g,(e,t,n)=>t.toUpperCase()+n)}function At(e,t){let n=Y.toCamelCase(` `+t);[`get`,`set`,`has`].forEach(r=>{Object.defineProperty(e,r+n,{__proto__:null,value:function(e,n,i){return this[r].call(this,t,e,n,i)},configurable:!0})})}var X=class{constructor(e){e&&this.set(e)}set(e,t,n){let r=this;function i(e,t,n){let i=wt(t);if(!i)throw Error(`header name must be a non-empty string`);let a=Y.findKey(r,i);(!a||r[a]===void 0||n===!0||n===void 0&&r[a]!==!1)&&(r[a||t]=Tt(e))}let a=(e,t)=>Y.forEach(e,(e,n)=>i(e,n,t));if(Y.isPlainObject(e)||e instanceof this.constructor)a(e,t);else if(Y.isString(e)&&(e=e.trim())&&!Dt(e))a(ht(e),t);else if(Y.isObject(e)&&Y.isIterable(e)){let n={},r,i;for(let t of e){if(!Y.isArray(t))throw TypeError(`Object iterator must return a key-value pair`);n[i=t[0]]=(r=n[i])?Y.isArray(r)?[...r,t[1]]:[r,t[1]]:t[1]}a(n,t)}else e!=null&&i(t,e,n);return this}get(e,t){if(e=wt(e),e){let n=Y.findKey(this,e);if(n){let e=this[n];if(!t)return e;if(t===!0)return Et(e);if(Y.isFunction(t))return t.call(this,e,n);if(Y.isRegExp(t))return t.exec(e);throw TypeError(`parser must be boolean|regexp|function`)}}}has(e,t){if(e=wt(e),e){let n=Y.findKey(this,e);return!!(n&&this[n]!==void 0&&(!t||Ot(this,this[n],n,t)))}return!1}delete(e,t){let n=this,r=!1;function i(e){if(e=wt(e),e){let i=Y.findKey(n,e);i&&(!t||Ot(n,n[i],i,t))&&(delete n[i],r=!0)}}return Y.isArray(e)?e.forEach(i):i(e),r}clear(e){let t=Object.keys(this),n=t.length,r=!1;for(;n--;){let i=t[n];(!e||Ot(this,this[i],i,e,!0))&&(delete this[i],r=!0)}return r}normalize(e){let t=this,n={};return Y.forEach(this,(r,i)=>{let a=Y.findKey(n,i);if(a){t[a]=Tt(r),delete t[i];return}let o=e?kt(i):String(i).trim();o!==i&&delete t[i],t[o]=Tt(r),n[o]=!0}),this}concat(...e){return this.constructor.concat(this,...e)}toJSON(e){let t=Object.create(null);return Y.forEach(this,(n,r)=>{n!=null&&n!==!1&&(t[r]=e&&Y.isArray(n)?n.join(`, `):n)}),t}[Symbol.iterator](){return Object.entries(this.toJSON())[Symbol.iterator]()}toString(){return Object.entries(this.toJSON()).map(([e,t])=>e+`: `+t).join(` `)}getSetCookie(){return this.get(`set-cookie`)||[]}get[Symbol.toStringTag](){return`AxiosHeaders`}static from(e){return e instanceof this?e:new this(e)}static concat(e,...t){let n=new this(e);return t.forEach(e=>n.set(e)),n}static accessor(e){let t=(this[Ct]=this[Ct]={accessors:{}}).accessors,n=this.prototype;function r(e){let r=wt(e);t[r]||(At(n,e),t[r]=!0)}return Y.isArray(e)?e.forEach(r):r(e),this}};X.accessor([`Content-Type`,`Content-Length`,`Accept`,`Accept-Encoding`,`User-Agent`,`Authorization`]),Y.reduceDescriptors(X.prototype,({value:e},t)=>{let n=t[0].toUpperCase()+t.slice(1);return{get:()=>e,set(e){this[n]=e}}}),Y.freezeMethods(X);var jt=`[REDACTED ****]`;function Mt(e){if(Y.hasOwnProp(e,`toJSON`))return!0;let t=Object.getPrototypeOf(e);for(;t&&t!==Object.prototype;){if(Y.hasOwnProp(t,`toJSON`))return!0;t=Object.getPrototypeOf(t)}return!1}function Nt(e,t){let n=new Set(t.map(e=>String(e).toLowerCase())),r=[],i=e=>{if(typeof e!=`object`||!e||Y.isBuffer(e))return e;if(r.indexOf(e)!==-1)return;e instanceof X&&(e=e.toJSON()),r.push(e);let t;if(Y.isArray(e))t=[],e.forEach((e,n)=>{let r=i(e);Y.isUndefined(r)||(t[n]=r)});else{if(!Y.isPlainObject(e)&&Mt(e))return r.pop(),e;t=Object.create(null);for(let[r,a]of Object.entries(e)){let e=n.has(r.toLowerCase())?jt:i(a);Y.isUndefined(e)||(t[r]=e)}}return r.pop(),t};return i(e)}var Z=class e extends Error{static from(t,n,r,i,a,o){let s=new e(t.message,n||t.code,r,i,a);return s.cause=t,s.name=t.name,t.status!=null&&s.status==null&&(s.status=t.status),o&&Object.assign(s,o),s}constructor(e,t,n,r,i){super(e),Object.defineProperty(this,"message",{__proto__:null,value:e,enumerable:!0,writable:!0,configurable:!0}),this.name=`AxiosError`,this.isAxiosError=!0,t&&(this.code=t),n&&(this.config=n),r&&(this.request=r),i&&(this.response=i,this.status=i.status)}toJSON(){let e=this.config,t=e&&Y.hasOwnProp(e,`redact`)?e.redact:void 0,n=Y.isArray(t)&&t.length>0?Nt(e,t):Y.toJSONObject(e);return{message:this.message,name:this.name,description:this.description,number:this.number,fileName:this.fileName,lineNumber:this.lineNumber,columnNumber:this.columnNumber,stack:this.stack,config:n,code:this.code,status:this.status}}};Z.ERR_BAD_OPTION_VALUE=`ERR_BAD_OPTION_VALUE`,Z.ERR_BAD_OPTION=`ERR_BAD_OPTION`,Z.ECONNABORTED=`ECONNABORTED`,Z.ETIMEDOUT=`ETIMEDOUT`,Z.ECONNREFUSED=`ECONNREFUSED`,Z.ERR_NETWORK=`ERR_NETWORK`,Z.ERR_FR_TOO_MANY_REDIRECTS=`ERR_FR_TOO_MANY_REDIRECTS`,Z.ERR_DEPRECATED=`ERR_DEPRECATED`,Z.ERR_BAD_RESPONSE=`ERR_BAD_RESPONSE`,Z.ERR_BAD_REQUEST=`ERR_BAD_REQUEST`,Z.ERR_CANCELED=`ERR_CANCELED`,Z.ERR_NOT_SUPPORT=`ERR_NOT_SUPPORT`,Z.ERR_INVALID_URL=`ERR_INVALID_URL`,Z.ERR_FORM_DATA_DEPTH_EXCEEDED=`ERR_FORM_DATA_DEPTH_EXCEEDED`;function Pt(e){return Y.isPlainObject(e)||Y.isArray(e)}function Ft(e){return Y.endsWith(e,`[]`)?e.slice(0,-2):e}function It(e,t,n){return e?e.concat(t).map(function(e,t){return e=Ft(e),!n&&t?`[`+e+`]`:e}).join(n?`.`:``):t}function Lt(e){return Y.isArray(e)&&!e.some(Pt)}var Rt=Y.toFlatObject(Y,{},null,function(e){return/^is[A-Z]/.test(e)});function zt(e,t,n){if(!Y.isObject(e))throw TypeError(`target must be an object`);t||=new FormData,n=Y.toFlatObject(n,{metaTokens:!0,dots:!1,indexes:!1},!1,function(e,t){return!Y.isUndefined(t[e])});let r=n.metaTokens,i=n.visitor||d,a=n.dots,o=n.indexes,s=n.Blob||typeof Blob<`u`&&Blob,c=n.maxDepth===void 0?100:n.maxDepth,l=s&&Y.isSpecCompliantForm(t);if(!Y.isFunction(i))throw TypeError(`visitor must be a function`);function u(e){if(e===null)return``;if(Y.isDate(e))return e.toISOString();if(Y.isBoolean(e))return e.toString();if(!l&&Y.isBlob(e))throw new Z(`Blob is not supported. Use a Buffer instead.`);return Y.isArrayBuffer(e)||Y.isTypedArray(e)?l&&typeof Blob==`function`?new Blob([e]):Buffer.from(e):e}function d(e,n,i){let s=e;if(Y.isReactNative(t)&&Y.isReactNativeBlob(e))return t.append(It(i,n,a),u(e)),!1;if(e&&!i&&typeof e==`object`){if(Y.endsWith(n,`{}`))n=r?n:n.slice(0,-2),e=JSON.stringify(e);else if(Y.isArray(e)&&Lt(e)||(Y.isFileList(e)||Y.endsWith(n,`[]`))&&(s=Y.toArray(e)))return n=Ft(n),s.forEach(function(e,r){!(Y.isUndefined(e)||e===null)&&t.append(o===!0?It([n],r,a):o===null?n:n+`[]`,u(e))}),!1}return Pt(e)?!0:(t.append(It(i,n,a),u(e)),!1)}let f=[],p=Object.assign(Rt,{defaultVisitor:d,convertValue:u,isVisitable:Pt});function m(e,n,r=0){if(!Y.isUndefined(e)){if(r>c)throw new Z(`Object is too deeply nested (`+r+` levels). Max depth: `+c,Z.ERR_FORM_DATA_DEPTH_EXCEEDED);if(f.indexOf(e)!==-1)throw Error(`Circular reference detected in `+n.join(`.`));f.push(e),Y.forEach(e,function(e,a){(!(Y.isUndefined(e)||e===null)&&i.call(t,e,Y.isString(a)?a.trim():a,n,p))===!0&&m(e,n?n.concat(a):[a],r+1)}),f.pop()}}if(!Y.isObject(e))throw TypeError(`data must be an object`);return m(e),t}function Bt(e){let t={"!":`%21`,"'":`%27`,"(":`%28`,")":`%29`,"~":`%7E`,"%20":`+`};return encodeURIComponent(e).replace(/[!'()~]|%20/g,function(e){return t[e]})}function Vt(e,t){this._pairs=[],e&&zt(e,this,t)}var Ht=Vt.prototype;Ht.append=function(e,t){this._pairs.push([e,t])},Ht.toString=function(e){let t=e?function(t){return e.call(this,t,Bt)}:Bt;return this._pairs.map(function(e){return t(e[0])+`=`+t(e[1])},``).join(`&`)};function Ut(e){return encodeURIComponent(e).replace(/%3A/gi,`:`).replace(/%24/g,`$`).replace(/%2C/gi,`,`).replace(/%20/g,`+`)}function Wt(e,t,n){if(!t)return e;let r=n&&n.encode||Ut,i=Y.isFunction(n)?{serialize:n}:n,a=i&&i.serialize,o;if(o=a?a(t,i):Y.isURLSearchParams(t)?t.toString():new Vt(t,i).toString(r),o){let t=e.indexOf(`#`);t!==-1&&(e=e.slice(0,t)),e+=(e.indexOf(`?`)===-1?`?`:`&`)+o}return e}var Gt=class{constructor(){this.handlers=[]}use(e,t,n){return this.handlers.push({fulfilled:e,rejected:t,synchronous:n?n.synchronous:!1,runWhen:n?n.runWhen:null}),this.handlers.length-1}eject(e){this.handlers[e]&&(this.handlers[e]=null)}clear(){this.handlers&&=[]}forEach(e){Y.forEach(this.handlers,function(t){t!==null&&e(t)})}},Kt={silentJSONParsing:!0,forcedJSONParsing:!0,clarifyTimeoutError:!1,legacyInterceptorReqResOrdering:!0},qt={isBrowser:!0,classes:{URLSearchParams:typeof URLSearchParams<`u`?URLSearchParams:Vt,FormData:typeof FormData<`u`?FormData:null,Blob:typeof Blob<`u`?Blob:null},protocols:[`http`,`https`,`file`,`blob`,`url`,`data`]},Jt=t({hasBrowserEnv:()=>Yt,hasStandardBrowserEnv:()=>Zt,hasStandardBrowserWebWorkerEnv:()=>Qt,navigator:()=>Xt,origin:()=>$t}),Yt=typeof window<`u`&&typeof document<`u`,Xt=typeof navigator==`object`&&navigator||void 0,Zt=Yt&&(!Xt||[`ReactNative`,`NativeScript`,`NS`].indexOf(Xt.product)<0),Qt=typeof WorkerGlobalScope<`u`&&self instanceof WorkerGlobalScope&&typeof self.importScripts==`function`,$t=Yt&&window.location.href||`http://localhost`,Q={...Jt,...qt};function en(e,t){return zt(e,new Q.classes.URLSearchParams,{visitor:function(e,t,n,r){return Q.isNode&&Y.isBuffer(e)?(this.append(t,e.toString(`base64`)),!1):r.defaultVisitor.apply(this,arguments)},...t})}function tn(e){return Y.matchAll(/\w+|\[(\w*)]/g,e).map(e=>e[0]===`[]`?``:e[1]||e[0])}function nn(e){let t={},n=Object.keys(e),r,i=n.length,a;for(r=0;r=e.length;return a=!a&&Y.isArray(r)?r.length:a,s?(Y.hasOwnProp(r,a)?r[a]=Y.isArray(r[a])?r[a].concat(n):[r[a],n]:r[a]=n,!o):((!Y.hasOwnProp(r,a)||!Y.isObject(r[a]))&&(r[a]=[]),t(e,n,r[a],i)&&Y.isArray(r[a])&&(r[a]=nn(r[a])),!o)}if(Y.isFormData(e)&&Y.isFunction(e.entries)){let n={};return Y.forEachEntry(e,(e,r)=>{t(tn(e),r,n,0)}),n}return null}var an=(e,t)=>e!=null&&Y.hasOwnProp(e,t)?e[t]:void 0;function on(e,t,n){if(Y.isString(e))try{return(t||JSON.parse)(e),Y.trim(e)}catch(e){if(e.name!==`SyntaxError`)throw e}return(n||JSON.stringify)(e)}var sn={transitional:Kt,adapter:[`xhr`,`http`,`fetch`],transformRequest:[function(e,t){let n=t.getContentType()||``,r=n.indexOf(`application/json`)>-1,i=Y.isObject(e);if(i&&Y.isHTMLForm(e)&&(e=new FormData(e)),Y.isFormData(e))return r?JSON.stringify(rn(e)):e;if(Y.isArrayBuffer(e)||Y.isBuffer(e)||Y.isStream(e)||Y.isFile(e)||Y.isBlob(e)||Y.isReadableStream(e))return e;if(Y.isArrayBufferView(e))return e.buffer;if(Y.isURLSearchParams(e))return t.setContentType(`application/x-www-form-urlencoded;charset=utf-8`,!1),e.toString();let a;if(i){let t=an(this,`formSerializer`);if(n.indexOf(`application/x-www-form-urlencoded`)>-1)return en(e,t).toString();if((a=Y.isFileList(e))||n.indexOf(`multipart/form-data`)>-1){let n=an(this,`env`),r=n&&n.FormData;return zt(a?{"files[]":e}:e,r&&new r,t)}}return i||r?(t.setContentType(`application/json`,!1),on(e)):e}],transformResponse:[function(e){let t=an(this,`transitional`)||sn.transitional,n=t&&t.forcedJSONParsing,r=an(this,`responseType`),i=r===`json`;if(Y.isResponse(e)||Y.isReadableStream(e))return e;if(e&&Y.isString(e)&&(n&&!r||i)){let n=!(t&&t.silentJSONParsing)&&i;try{return JSON.parse(e,an(this,`parseReviver`))}catch(e){if(n)throw e.name===`SyntaxError`?Z.from(e,Z.ERR_BAD_RESPONSE,this,null,an(this,`response`)):e}}return e}],timeout:0,xsrfCookieName:`XSRF-TOKEN`,xsrfHeaderName:`X-XSRF-TOKEN`,maxContentLength:-1,maxBodyLength:-1,env:{FormData:Q.classes.FormData,Blob:Q.classes.Blob},validateStatus:function(e){return e>=200&&e<300},headers:{common:{Accept:`application/json, text/plain, */*`,"Content-Type":void 0}}};Y.forEach([`delete`,`get`,`head`,`post`,`put`,`patch`,`query`],e=>{sn.headers[e]={}});function cn(e,t){let n=this||sn,r=t||n,i=X.from(r.headers),a=r.data;return Y.forEach(e,function(e){a=e.call(n,a,i.normalize(),t?t.status:void 0)}),i.normalize(),a}function ln(e){return!!(e&&e.__CANCEL__)}var un=class extends Z{constructor(e,t,n){super(e??`canceled`,Z.ERR_CANCELED,t,n),this.name=`CanceledError`,this.__CANCEL__=!0}};function dn(e,t,n){let r=n.config.validateStatus;!n.status||!r||r(n.status)?e(n):t(new Z(`Request failed with status code `+n.status,n.status>=400&&n.status<500?Z.ERR_BAD_REQUEST:Z.ERR_BAD_RESPONSE,n.config,n.request,n))}function fn(e){let t=/^([-+\w]{1,25}):(?:\/\/)?/.exec(e);return t&&t[1]||``}function pn(e,t){e||=10;let n=Array(e),r=Array(e),i=0,a=0,o;return t=t===void 0?1e3:t,function(s){let c=Date.now(),l=r[a];o||=c,n[i]=s,r[i]=c;let u=a,d=0;for(;u!==i;)d+=n[u++],u%=e;if(i=(i+1)%e,i===a&&(a=(a+1)%e),c-o{n=r,i=null,a&&=(clearTimeout(a),null),e(...t)};return[(...e)=>{let t=Date.now(),s=t-n;s>=r?o(e,t):(i=e,a||=setTimeout(()=>{a=null,o(i)},r-s))},()=>i&&o(i)]}var hn=(e,t,n=3)=>{let r=0,i=pn(50,250);return mn(n=>{if(!n||typeof n.loaded!=`number`)return;let a=n.loaded,o=n.lengthComputable?n.total:void 0,s=o==null?a:Math.min(a,o),c=Math.max(0,s-r),l=i(c);r=Math.max(r,s),e({loaded:s,total:o,progress:o?s/o:void 0,bytes:c,rate:l||void 0,estimated:l&&o?(o-s)/l:void 0,event:n,lengthComputable:o!=null,[t?`download`:`upload`]:!0})},n)},gn=(e,t)=>{let n=e!=null;return[r=>t[0]({lengthComputable:n,total:e,loaded:r}),t[1]]},_n=e=>(...t)=>Y.asap(()=>e(...t)),vn=Q.hasStandardBrowserEnv?((e,t)=>n=>(n=new URL(n,Q.origin),e.protocol===n.protocol&&e.host===n.host&&(t||e.port===n.port)))(new URL(Q.origin),Q.navigator&&/(msie|trident)/i.test(Q.navigator.userAgent)):()=>!0,yn=Q.hasStandardBrowserEnv?{write(e,t,n,r,i,a,o){if(typeof document>`u`)return;let s=[`${e}=${encodeURIComponent(t)}`];Y.isNumber(n)&&s.push(`expires=${new Date(n).toUTCString()}`),Y.isString(r)&&s.push(`path=${r}`),Y.isString(i)&&s.push(`domain=${i}`),a===!0&&s.push(`secure`),Y.isString(o)&&s.push(`SameSite=${o}`),document.cookie=s.join(`; `)},read(e){if(typeof document>`u`)return null;let t=document.cookie.split(`;`);for(let n=0;ne instanceof X?{...e}:e;function wn(e,t){t||={};let n=Object.create(null);Object.defineProperty(n,"hasOwnProperty",{__proto__:null,value:Object.prototype.hasOwnProperty,enumerable:!1,writable:!0,configurable:!0});function r(e,t,n,r){return Y.isPlainObject(e)&&Y.isPlainObject(t)?Y.merge.call({caseless:r},e,t):Y.isPlainObject(t)?Y.merge({},t):Y.isArray(t)?t.slice():t}function i(e,t,n,i){if(!Y.isUndefined(t))return r(e,t,n,i);if(!Y.isUndefined(e))return r(void 0,e,n,i)}function a(e,t){if(!Y.isUndefined(t))return r(void 0,t)}function o(e,t){if(!Y.isUndefined(t))return r(void 0,t);if(!Y.isUndefined(e))return r(void 0,e)}function s(n,i,a){if(Y.hasOwnProp(t,a))return r(n,i);if(Y.hasOwnProp(e,a))return r(void 0,n)}let c={url:a,method:a,data:a,baseURL:o,transformRequest:o,transformResponse:o,paramsSerializer:o,timeout:o,timeoutMessage:o,withCredentials:o,withXSRFToken:o,adapter:o,responseType:o,xsrfCookieName:o,xsrfHeaderName:o,onUploadProgress:o,onDownloadProgress:o,decompress:o,maxContentLength:o,maxBodyLength:o,beforeRedirect:o,transport:o,httpAgent:o,httpsAgent:o,cancelToken:o,socketPath:o,allowedSocketPaths:o,responseEncoding:o,validateStatus:s,headers:(e,t,n)=>i(Cn(e),Cn(t),n,!0)};return Y.forEach(Object.keys({...e,...t}),function(r){if(r===`__proto__`||r===`constructor`||r===`prototype`)return;let a=Y.hasOwnProp(c,r)?c[r]:i,o=a(Y.hasOwnProp(e,r)?e[r]:void 0,Y.hasOwnProp(t,r)?t[r]:void 0,r);Y.isUndefined(o)&&a!==s||(n[r]=o)}),n}var Tn=[`content-type`,`content-length`];function En(e,t,n){if(n!==`content-only`){e.set(t);return}Object.entries(t).forEach(([t,n])=>{Tn.includes(t.toLowerCase())&&e.set(t,n)})}var Dn=e=>encodeURIComponent(e).replace(/%([0-9A-F]{2})/gi,(e,t)=>String.fromCharCode(parseInt(t,16))),On=e=>{let t=wn({},e),n=e=>Y.hasOwnProp(t,e)?t[e]:void 0,r=n(`data`),i=n(`withXSRFToken`),a=n(`xsrfHeaderName`),o=n(`xsrfCookieName`),s=n(`headers`),c=n(`auth`),l=n(`baseURL`),u=n(`allowAbsoluteUrls`),d=n(`url`);if(t.headers=s=X.from(s),t.url=Wt(Sn(l,d,u),e.params,e.paramsSerializer),c&&s.set(`Authorization`,`Basic `+btoa((c.username||``)+`:`+(c.password?Dn(c.password):``))),Y.isFormData(r)&&(Q.hasStandardBrowserEnv||Q.hasStandardBrowserWebWorkerEnv?s.setContentType(void 0):Y.isFunction(r.getHeaders)&&En(s,r.getHeaders(),n(`formDataHeaderPolicy`))),Q.hasStandardBrowserEnv&&(Y.isFunction(i)&&(i=i(t)),i===!0||i==null&&vn(t.url))){let e=a&&o&&yn.read(o);e&&s.set(a,e)}return t},kn=typeof XMLHttpRequest<`u`&&function(e){return new Promise(function(t,n){let r=On(e),i=r.data,a=X.from(r.headers).normalize(),{responseType:o,onUploadProgress:s,onDownloadProgress:c}=r,l,u,d,f,p;function m(){f&&f(),p&&p(),r.cancelToken&&r.cancelToken.unsubscribe(l),r.signal&&r.signal.removeEventListener(`abort`,l)}let h=new XMLHttpRequest;h.open(r.method.toUpperCase(),r.url,!0),h.timeout=r.timeout;function g(){if(!h)return;let r=X.from(`getAllResponseHeaders`in h&&h.getAllResponseHeaders());dn(function(e){t(e),m()},function(e){n(e),m()},{data:!o||o===`text`||o===`json`?h.responseText:h.response,status:h.status,statusText:h.statusText,headers:r,config:e,request:h}),h=null}`onloadend`in h?h.onloadend=g:h.onreadystatechange=function(){!h||h.readyState!==4||h.status===0&&!(h.responseURL&&h.responseURL.startsWith(`file:`))||setTimeout(g)},h.onabort=function(){h&&=(n(new Z(`Request aborted`,Z.ECONNABORTED,e,h)),m(),null)},h.onerror=function(t){let r=new Z(t&&t.message?t.message:`Network Error`,Z.ERR_NETWORK,e,h);r.event=t||null,n(r),m(),h=null},h.ontimeout=function(){let t=r.timeout?`timeout of `+r.timeout+`ms exceeded`:`timeout exceeded`,i=r.transitional||Kt;r.timeoutErrorMessage&&(t=r.timeoutErrorMessage),n(new Z(t,i.clarifyTimeoutError?Z.ETIMEDOUT:Z.ECONNABORTED,e,h)),m(),h=null},i===void 0&&a.setContentType(null),`setRequestHeader`in h&&Y.forEach(St(a),function(e,t){h.setRequestHeader(t,e)}),Y.isUndefined(r.withCredentials)||(h.withCredentials=!!r.withCredentials),o&&o!==`json`&&(h.responseType=r.responseType),c&&([d,p]=hn(c,!0),h.addEventListener(`progress`,d)),s&&h.upload&&([u,f]=hn(s),h.upload.addEventListener(`progress`,u),h.upload.addEventListener(`loadend`,f)),(r.cancelToken||r.signal)&&(l=t=>{h&&=(n(!t||t.type?new un(null,e,h):t),h.abort(),m(),null)},r.cancelToken&&r.cancelToken.subscribe(l),r.signal&&(r.signal.aborted?l():r.signal.addEventListener(`abort`,l)));let _=fn(r.url);if(_&&!Q.protocols.includes(_)){n(new Z(`Unsupported protocol `+_+`:`,Z.ERR_BAD_REQUEST,e));return}h.send(i||null)})},An=(e,t)=>{if(e=e?e.filter(Boolean):[],!t&&!e.length)return;let n=new AbortController,r=!1,i=function(e){if(!r){r=!0,o();let t=e instanceof Error?e:this.reason;n.abort(t instanceof Z?t:new un(t instanceof Error?t.message:t))}},a=t&&setTimeout(()=>{a=null,i(new Z(`timeout of ${t}ms exceeded`,Z.ETIMEDOUT))},t),o=()=>{e&&=(a&&clearTimeout(a),a=null,e.forEach(e=>{e.unsubscribe?e.unsubscribe(i):e.removeEventListener(`abort`,i)}),null)};e.forEach(e=>e.addEventListener(`abort`,i));let{signal:s}=n;return s.unsubscribe=()=>Y.asap(o),s},jn=function*(e,t){let n=e.byteLength;if(!t||n{let i=Mn(e,t),a=0,o,s=e=>{o||(o=!0,r&&r(e))};return new ReadableStream({async pull(e){try{let{done:t,value:r}=await i.next();if(t){s(),e.close();return}let o=r.byteLength;n&&n(a+=o),e.enqueue(new Uint8Array(r))}catch(e){throw s(e),e}},cancel(e){return s(e),i.return()}},{highWaterMark:2})};function Fn(e){if(!e||typeof e!=`string`||!e.startsWith(`data:`))return 0;let t=e.indexOf(`,`);if(t<0)return 0;let n=e.slice(5,t),r=e.slice(t+1);if(/;base64/i.test(n)){let e=r.length,t=r.length;for(let n=0;n=48&&t<=57||t>=65&&t<=70||t>=97&&t<=102)&&(i>=48&&i<=57||i>=65&&i<=70||i>=97&&i<=102)&&(e-=2,n+=2)}let n=0,i=t-1,a=e=>e>=2&&r.charCodeAt(e-2)===37&&r.charCodeAt(e-1)===51&&(r.charCodeAt(e)===68||r.charCodeAt(e)===100);i>=0&&(r.charCodeAt(i)===61?(n++,i--):a(i)&&(n++,i-=3)),n===1&&i>=0&&(r.charCodeAt(i)===61||a(i))&&n++;let o=Math.floor(e/4)*3-(n||0);return o>0?o:0}if(typeof Buffer<`u`&&typeof Buffer.byteLength==`function`)return Buffer.byteLength(r,`utf8`);let i=0;for(let e=0,t=r.length;e=55296&&n<=56319&&e+1=56320&&t<=57343?(i+=4,e++):i+=3}else i+=3}return i}var In=`1.16.1`,Ln=64*1024,{isFunction:Rn}=Y,zn=(e,...t)=>{try{return!!e(...t)}catch{return!1}},Bn=e=>{let t=Y.global!==void 0&&Y.global!==null?Y.global:globalThis,{ReadableStream:n,TextEncoder:r}=t;e=Y.merge.call({skipUndefined:!0},{Request:t.Request,Response:t.Response},e);let{fetch:i,Request:a,Response:o}=e,s=i?Rn(i):typeof fetch==`function`,c=Rn(a),l=Rn(o);if(!s)return!1;let u=s&&Rn(n),d=s&&(typeof r==`function`?(e=>t=>e.encode(t))(new r):async e=>new Uint8Array(await new a(e).arrayBuffer())),f=c&&u&&zn(()=>{let e=!1,t=new a(Q.origin,{body:new n,method:`POST`,get duplex(){return e=!0,`half`}}),r=t.headers.has(`Content-Type`);return t.body!=null&&t.body.cancel(),e&&!r}),p=l&&u&&zn(()=>Y.isReadableStream(new o(``).body)),m={stream:p&&(e=>e.body)};s&&[`text`,`arrayBuffer`,`blob`,`formData`,`stream`].forEach(e=>{!m[e]&&(m[e]=(t,n)=>{let r=t&&t[e];if(r)return r.call(t);throw new Z(`Response type '${e}' is not supported`,Z.ERR_NOT_SUPPORT,n)})});let h=async e=>{if(e==null)return 0;if(Y.isBlob(e))return e.size;if(Y.isSpecCompliantForm(e))return(await new a(Q.origin,{method:`POST`,body:e}).arrayBuffer()).byteLength;if(Y.isArrayBufferView(e)||Y.isArrayBuffer(e))return e.byteLength;if(Y.isURLSearchParams(e)&&(e+=``),Y.isString(e))return(await d(e)).byteLength},g=async(e,t)=>Y.toFiniteNumber(e.getContentLength())??h(t);return async e=>{let{url:t,method:n,data:s,signal:l,cancelToken:u,timeout:d,onDownloadProgress:h,onUploadProgress:_,responseType:v,headers:y,withCredentials:b=`same-origin`,fetchOptions:x,maxContentLength:S,maxBodyLength:C}=On(e),w=Y.isNumber(S)&&S>-1,T=Y.isNumber(C)&&C>-1,E=i||fetch;v=v?(v+``).toLowerCase():`text`;let D=An([l,u&&u.toAbortSignal()],d),O=null,k=D&&D.unsubscribe&&(()=>{D.unsubscribe()}),A;try{if(w&&typeof t==`string`&&t.startsWith(`data:`)&&Fn(t)>S)throw new Z(`maxContentLength size of `+S+` exceeded`,Z.ERR_BAD_RESPONSE,e,O);if(T&&n!==`get`&&n!==`head`){let t=await g(y,s);if(typeof t==`number`&&isFinite(t)&&t>C)throw new Z(`Request body larger than maxBodyLength limit`,Z.ERR_BAD_REQUEST,e,O)}if(_&&f&&n!==`get`&&n!==`head`&&(A=await g(y,s))!==0){let e=new a(t,{method:`POST`,body:s,duplex:`half`}),n;if(Y.isFormData(s)&&(n=e.headers.get(`content-type`))&&y.setContentType(n),e.body){let[t,n]=gn(A,hn(_n(_)));s=Pn(e.body,Ln,t,n)}}Y.isString(b)||(b=b?`include`:`omit`);let i=c&&`credentials`in a.prototype;if(Y.isFormData(s)){let e=y.getContentType();e&&/^multipart\/form-data/i.test(e)&&!/boundary=/i.test(e)&&y.delete(`content-type`)}y.set(`User-Agent`,`axios/`+In,!1);let l={...x,signal:D,method:n.toUpperCase(),headers:St(y.normalize()),body:s,duplex:`half`,credentials:i?b:void 0};O=c&&new a(t,l);let u=await(c?E(O,x):E(t,l));if(w){let t=Y.toFiniteNumber(u.headers.get(`content-length`));if(t!=null&&t>S)throw new Z(`maxContentLength size of `+S+` exceeded`,Z.ERR_BAD_RESPONSE,e,O)}let d=p&&(v===`stream`||v===`response`);if(p&&u.body&&(h||w||d&&k)){let t={};[`status`,`statusText`,`headers`].forEach(e=>{t[e]=u[e]});let n=Y.toFiniteNumber(u.headers.get(`content-length`)),[r,i]=h&&gn(n,hn(_n(h),!0))||[],a=0;u=new o(Pn(u.body,Ln,t=>{if(w&&(a=t,a>S))throw new Z(`maxContentLength size of `+S+` exceeded`,Z.ERR_BAD_RESPONSE,e,O);r&&r(t)},()=>{i&&i(),k&&k()}),t)}v||=`text`;let j=await m[Y.findKey(m,v)||`text`](u,e);if(w&&!p&&!d){let t;if(j!=null&&(typeof j.byteLength==`number`?t=j.byteLength:typeof j.size==`number`?t=j.size:typeof j==`string`&&(t=typeof r==`function`?new r().encode(j).byteLength:j.length)),typeof t==`number`&&t>S)throw new Z(`maxContentLength size of `+S+` exceeded`,Z.ERR_BAD_RESPONSE,e,O)}return!d&&k&&k(),await new Promise((t,n)=>{dn(t,n,{data:j,headers:X.from(u.headers),status:u.status,statusText:u.statusText,config:e,request:O})})}catch(t){if(k&&k(),D&&D.aborted&&D.reason instanceof Z){let n=D.reason;throw n.config=e,O&&(n.request=O),t!==n&&(n.cause=t),n}throw t&&t.name===`TypeError`&&/Load failed|fetch/i.test(t.message)?Object.assign(new Z(`Network Error`,Z.ERR_NETWORK,e,O,t&&t.response),{cause:t.cause||t}):Z.from(t,t&&t.code,e,O,t&&t.response)}}},Vn=new Map,Hn=e=>{let t=e&&e.env||{},{fetch:n,Request:r,Response:i}=t,a=[r,i,n],o=a.length,s,c,l=Vn;for(;o--;)s=a[o],c=l.get(s),c===void 0&&l.set(s,c=o?new Map:Bn(t)),l=c;return c};Hn();var Un={http:null,xhr:kn,fetch:{get:Hn}};Y.forEach(Un,(e,t)=>{if(e){try{Object.defineProperty(e,"name",{__proto__:null,value:t})}catch{}Object.defineProperty(e,"adapterName",{__proto__:null,value:t})}});var Wn=e=>`- ${e}`,Gn=e=>Y.isFunction(e)||e===null||e===!1;function Kn(e,t){e=Y.isArray(e)?e:[e];let{length:n}=e,r,i,a={};for(let o=0;o`adapter ${e} `+(t===!1?`is not supported by the environment`:`is not available in the build`));throw new Z(`There is no suitable adapter to dispatch the request `+(n?e.length>1?`since : `+e.map(Wn).join(` diff --git a/distribution/conf/application.properties b/distribution/conf/application.properties index de1fdae5a33..3dcf6810bfb 100644 --- a/distribution/conf/application.properties +++ b/distribution/conf/application.properties @@ -376,12 +376,27 @@ nacos.plugin.visibility.type=nacos nacos.istio.mcp.server.enabled=false #*************** AI Publish Pipeline Configurations ***************# -# Optional: total switch for ai-pipeline plugin. -#nacos.plugin.ai-pipeline.enabled=true -#nacos.plugin.ai-pipeline.type=skill-scanner -#nacos.plugin.ai-pipeline.skill-scanner.enabled=true +# AI publish pipeline is enabled by default with skill-spector and skill-scanner. +# Set enabled=false to turn off all publish pipeline checks. +#nacos.plugin.ai-pipeline.enabled=false +# Optional: override enabled pipeline plugins. Default: skill-spector,skill-scanner. +#nacos.plugin.ai-pipeline.type=skill-spector,skill-scanner +#nacos.plugin.ai-pipeline.skill-scanner.order=100 # Optional: specify an absolute executable path when PATH resolution differs between shell and server process. #nacos.plugin.ai-pipeline.skill-scanner.command=/Users/your-user/.local/bin/skill-scanner +# SkillSpector runtime should be installed first with nacos-setup skill-spector install. +# Nacos also checks the default command path: ~/ai-infra/ai-pipeline/bin/skill-spector. +# Configure the command only when the runtime is installed in another path. +#nacos.plugin.ai-pipeline.skill-spector.order=90 +#nacos.plugin.ai-pipeline.skill-spector.command=/Users/your-user/ai-infra/ai-pipeline/bin/skill-spector +#nacos.plugin.ai-pipeline.skill-spector.risk-score-threshold=50 +# Optional: maximum SkillSpector findings shown in review message, default 20, max 100. +#nacos.plugin.ai-pipeline.skill-spector.max-findings=20 +#nacos.plugin.ai-pipeline.skill-spector.use-llm=false +#nacos.plugin.ai-pipeline.skill-spector.provider=openai +#nacos.plugin.ai-pipeline.skill-spector.model=gpt-4.1-mini +#nacos.plugin.ai-pipeline.skill-spector.api-key= +#nacos.plugin.ai-pipeline.skill-spector.base-url= # Optional: auto-publish skill versions after pipeline approval, default false. #nacos.ai.skill.auto-publish-after-review.enabled=false diff --git a/plugin-default-impl/nacos-default-ai-pipeline-plugin/src/main/java/com/alibaba/nacos/plugin/ai/pipeline/spi/impl/SkillSpectorPipelineService.java b/plugin-default-impl/nacos-default-ai-pipeline-plugin/src/main/java/com/alibaba/nacos/plugin/ai/pipeline/spi/impl/SkillSpectorPipelineService.java new file mode 100644 index 00000000000..ca6a5b7b379 --- /dev/null +++ b/plugin-default-impl/nacos-default-ai-pipeline-plugin/src/main/java/com/alibaba/nacos/plugin/ai/pipeline/spi/impl/SkillSpectorPipelineService.java @@ -0,0 +1,392 @@ +/* + * Copyright 1999-2026 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.nacos.plugin.ai.pipeline.spi.impl; + +import com.alibaba.nacos.plugin.ai.pipeline.model.Checkpoint; +import com.alibaba.nacos.plugin.ai.pipeline.model.PublishPipelineContext; +import com.alibaba.nacos.plugin.ai.pipeline.model.PublishPipelineMessageType; +import com.alibaba.nacos.plugin.ai.pipeline.model.PublishPipelineResourceType; +import com.alibaba.nacos.plugin.ai.pipeline.model.PublishPipelineResult; +import com.alibaba.nacos.plugin.ai.pipeline.model.ResourceFileContent; +import com.alibaba.nacos.plugin.ai.pipeline.model.ResourceFilesPipelineContext; +import com.alibaba.nacos.plugin.ai.pipeline.spi.PublishPipelineService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Publish pipeline service that integrates SkillSpector for AI resource security scanning. + * + * @author nacos + */ +public class SkillSpectorPipelineService implements PublishPipelineService { + + static final String PIPELINE_ID = "skill-spector"; + + static final String DEFAULT_SKILL_SPECTOR_CMD = "skill-spector"; + + private static final Logger LOGGER = LoggerFactory.getLogger(SkillSpectorPipelineService.class); + + private static final String CHECKPOINT_AVAILABILITY = "SkillSpector runtime 安装状态"; + + private static final String CHECKPOINT_APPLICABILITY = "SkillSpector 扫描适用性"; + + private static final String CHECKPOINT_RUNTIME = "SkillSpector 扫描运行时"; + + private static final String CHECKPOINT_RISK_SCORE = "SkillSpector risk_score 阈值"; + + private static final int MAX_FIELD_LENGTH = 240; + + static final String INSTALLATION_HINT = + "SkillSpector runtime 未安装。请先通过 nacos-setup skill-spector install 安装," + + "默认路径为 ~/ai-infra/ai-pipeline/bin/skill-spector;" + + "如需自定义路径,请配置 nacos.plugin.ai-pipeline.skill-spector.command。"; + + private final String scannerCommand; + + private final SkillSpectorScanOptions scanOptions; + + SkillSpectorPipelineService(String scannerCommand, SkillSpectorScanOptions scanOptions) { + this.scannerCommand = scannerCommand; + this.scanOptions = scanOptions != null ? scanOptions : SkillSpectorScanOptions.none(); + } + + @Override + public String pipelineId() { + return PIPELINE_ID; + } + + @Override + public PublishPipelineResult execute(PublishPipelineContext context) { + if (scannerCommand == null || scannerCommand.isBlank()) { + return PublishPipelineResult.reject(INSTALLATION_HINT, + PublishPipelineMessageType.MARKDOWN, + List.of(new Checkpoint(CHECKPOINT_AVAILABILITY, false))); + } + if (!(context instanceof ResourceFilesPipelineContext)) { + return PublishPipelineResult.pass("资源不包含可扫描文件,跳过 SkillSpector 扫描", + PublishPipelineMessageType.MARKDOWN, + List.of(new Checkpoint(CHECKPOINT_APPLICABILITY, true))); + } + ResourceFilesPipelineContext resourceContext = (ResourceFilesPipelineContext) context; + List files = resourceContext.getFiles(); + if (files == null || files.isEmpty()) { + return PublishPipelineResult.pass("资源无文件内容,跳过扫描", + PublishPipelineMessageType.MARKDOWN, + List.of(new Checkpoint(CHECKPOINT_APPLICABILITY, true))); + } + + Path tempDir = null; + try { + tempDir = Files.createTempDirectory("nacos-skillspector-"); + writeResourceFiles(tempDir, normalizeFilesForScanner(context, files)); + Path reportPath = tempDir.resolve("skillspector-report.json"); + + Process process = startProcess(buildScanCommand(tempDir, reportPath)); + String scanOutput = readOutput(process); + int exitCode = waitForProcess(process); + if (exitCode != 0 && exitCode != 1) { + return rejectRuntimeFailure("SkillSpector 扫描失败,exitCode=" + exitCode + + "\n输出:\n" + scanOutput); + } + if (!Files.isRegularFile(reportPath)) { + return rejectRuntimeFailure("SkillSpector 未生成扫描报告,exitCode=" + exitCode + + "\n输出:\n" + scanOutput); + } + + SkillSpectorScanReport report = parseReport(reportPath); + if (report.getRiskScore() > scanOptions.getRiskScoreThreshold()) { + LOGGER.warn("[SkillSpectorPipeline] {} {} risk_score={} 超过阈值 {}", + context.getResourceType(), resourceContext.getResourceName(), + report.getRiskScore(), scanOptions.getRiskScoreThreshold()); + return PublishPipelineResult.reject(buildRejectMessage(report), + PublishPipelineMessageType.MARKDOWN, + List.of(new Checkpoint(CHECKPOINT_RISK_SCORE, false))); + } + return PublishPipelineResult.pass(buildPassMessage(report), + PublishPipelineMessageType.MARKDOWN, + List.of(new Checkpoint(CHECKPOINT_RISK_SCORE, true))); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + LOGGER.error("[SkillSpectorPipeline] 扫描被中断", e); + return rejectRuntimeFailure("SkillSpector 扫描被中断: " + e.getMessage()); + } catch (IOException | IllegalArgumentException e) { + LOGGER.warn("[SkillSpectorPipeline] 执行 SkillSpector runtime 失败, runtime={}: {}", + scannerCommand, e.getMessage()); + return rejectRuntimeFailure("执行 SkillSpector runtime 失败: " + e.getMessage()); + } finally { + if (tempDir != null) { + deleteRecursively(tempDir.toFile()); + } + } + } + + List buildScanCommand(Path tempDir, Path reportPath) { + List command = new ArrayList<>(); + command.add(scannerCommand); + command.add("scan"); + command.add(tempDir.toAbsolutePath().toString()); + command.add("--format"); + command.add("json"); + command.add("--output"); + command.add(reportPath.toAbsolutePath().toString()); + if (!scanOptions.isUseLlm()) { + command.add("--no-llm"); + } + return command; + } + + Process startProcess(List command) throws IOException { + ProcessBuilder pb = new ProcessBuilder(command); + Map env = pb.environment(); + scanOptions.applyLlmEnvironment(env); + pb.redirectErrorStream(true); + return pb.start(); + } + + int waitForProcess(Process process) throws InterruptedException { + return process.waitFor(); + } + + SkillSpectorScanReport parseReport(Path reportPath) throws IOException { + return SkillSpectorScanReport.parse(Files.readString(reportPath, StandardCharsets.UTF_8)); + } + + private String readOutput(Process process) throws IOException { + StringBuilder output = new StringBuilder(); + try (BufferedReader reader = new BufferedReader( + new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8))) { + String line; + while ((line = reader.readLine()) != null) { + output.append(line).append("\n"); + } + } + return output.toString(); + } + + private PublishPipelineResult rejectRuntimeFailure(String message) { + return PublishPipelineResult.reject(message, PublishPipelineMessageType.MARKDOWN, + List.of(new Checkpoint(CHECKPOINT_RUNTIME, false))); + } + + private String buildPassMessage(SkillSpectorScanReport report) { + return "SkillSpector 扫描通过,risk_score=" + report.getRiskScore() + + ",阈值=" + scanOptions.getRiskScoreThreshold() + + ",风险等级=" + report.getRiskSeverity() + + ",问题数=" + report.getIssueCount() + + buildFindingsMessage(report); + } + + private String buildRejectMessage(SkillSpectorScanReport report) { + return "SkillSpector 检测到安全风险,发布被拒绝。\n\n" + + "- risk_score: " + report.getRiskScore() + "\n" + + "- threshold: " + scanOptions.getRiskScoreThreshold() + "\n" + + "- severity: " + report.getRiskSeverity() + "\n" + + "- recommendation: " + report.getRiskRecommendation() + "\n" + + "- issues: " + report.getIssueCount() + + buildFindingsMessage(report); + } + + private String buildFindingsMessage(SkillSpectorScanReport report) { + if (report.getIssueCount() == 0) { + return ""; + } + List findings = report.getFindings(); + if (findings.isEmpty()) { + return "\n\n## 扫描结果\n\nSkillSpector 返回了 " + report.getIssueCount() + + " 个问题,但报告中没有包含可展示的明细。"; + } + StringBuilder builder = new StringBuilder("\n\n## 扫描结果\n\n"); + int limit = Math.min(findings.size(), scanOptions.getMaxFindings()); + for (int i = 0; i < limit; i++) { + appendFinding(builder, i + 1, findings.get(i)); + } + if (findings.size() > limit) { + builder.append("\n仅展示前 ").append(limit).append(" 条,共 ") + .append(findings.size()).append(" 条问题。"); + } + return builder.toString(); + } + + private void appendFinding(StringBuilder builder, int index, + SkillSpectorScanReport.Finding finding) { + builder.append(index).append(". **") + .append(defaultText(finding.getSeverity(), "UNKNOWN")) + .append(" / ") + .append(defaultText(finding.getId(), "UNKNOWN")) + .append("**"); + if (isNotBlank(finding.getCategory())) { + builder.append(" - ").append(compact(finding.getCategory())); + } + builder.append("\n"); + String location = formatLocation(finding); + if (isNotBlank(location)) { + builder.append(" - 位置: `").append(location).append("`\n"); + } + appendFindingField(builder, "说明", finding.getExplanation()); + appendFindingField(builder, "修复建议", finding.getRemediation()); + appendFindingField(builder, "代码片段", finding.getCodeSnippet()); + } + + private void appendFindingField(StringBuilder builder, String name, String value) { + if (isNotBlank(value)) { + builder.append(" - ").append(name).append(": ") + .append(compact(value)).append("\n"); + } + } + + private String formatLocation(SkillSpectorScanReport.Finding finding) { + if (!isNotBlank(finding.getFile())) { + return null; + } + if (finding.getStartLine() <= 0) { + return finding.getFile(); + } + if (finding.getEndLine() > 0 && finding.getEndLine() != finding.getStartLine()) { + return finding.getFile() + ":" + finding.getStartLine() + "-" + + finding.getEndLine(); + } + return finding.getFile() + ":" + finding.getStartLine(); + } + + private String defaultText(String value, String defaultValue) { + return isNotBlank(value) ? compact(value) : defaultValue; + } + + private String compact(String value) { + String compact = value.replaceAll("\\s+", " ").trim(); + if (compact.length() <= MAX_FIELD_LENGTH) { + return compact; + } + return compact.substring(0, MAX_FIELD_LENGTH) + "..."; + } + + private boolean isNotBlank(String value) { + return value != null && !value.isBlank(); + } + + private void writeResourceFiles(Path baseDir, List files) + throws IOException { + for (ResourceFileContent file : files) { + String filePath = file.getFilePath(); + if (filePath == null || filePath.isEmpty()) { + continue; + } + Path targetPath = baseDir.resolve(filePath).normalize(); + if (!targetPath.startsWith(baseDir)) { + LOGGER.warn("[SkillSpectorPipeline] 跳过非法路径: {}", filePath); + continue; + } + Files.createDirectories(targetPath.getParent()); + String content = file.getContent(); + Files.writeString(targetPath, content != null ? content : "", StandardCharsets.UTF_8); + } + } + + private List normalizeFilesForScanner(PublishPipelineContext context, + List files) { + if (containsSkillMarkdown(files)) { + return files; + } + if (context.getResourceType() == PublishPipelineResourceType.AGENTSPEC) { + List result = new ArrayList<>(files.size() + 1); + result.add(new ResourceFileContent("SKILL.md", + buildWrappedSkillMarkdown("AgentSpec", context, files))); + result.addAll(files); + return result; + } + if (context.getResourceType() == PublishPipelineResourceType.PROMPT) { + List result = new ArrayList<>(files.size() + 1); + result.add(new ResourceFileContent("SKILL.md", + buildWrappedSkillMarkdown("Prompt", context, files))); + result.addAll(files); + return result; + } + return files; + } + + private boolean containsSkillMarkdown(List files) { + for (ResourceFileContent each : files) { + if (each != null && "SKILL.md".equals(each.getFilePath())) { + return true; + } + } + return false; + } + + private String buildWrappedSkillMarkdown(String type, PublishPipelineContext context, + List files) { + StringBuilder builder = new StringBuilder(); + builder.append("# ").append(type).append(" ").append(context.getResourceName()) + .append("\n\n"); + builder.append("Generated from ").append(type) + .append(" pipeline context for SkillSpector compatibility.\n"); + for (ResourceFileContent file : files) { + if (file == null || file.getFilePath() == null) { + continue; + } + builder.append("\n## File: ").append(file.getFilePath()).append("\n\n"); + String content = file.getContent(); + if (content != null) { + builder.append(content); + } + builder.append("\n"); + } + return builder.toString(); + } + + private void deleteRecursively(File file) { + if (file == null || !file.exists()) { + return; + } + if (file.isDirectory()) { + File[] children = file.listFiles(); + if (children != null) { + for (File child : children) { + deleteRecursively(child); + } + } + } + if (!file.delete()) { + LOGGER.debug("[SkillSpectorPipeline] 无法删除临时文件: {}", file.getAbsolutePath()); + } + } + + @Override + public int getPreferOrder() { + return 90; + } + + @Override + public PublishPipelineResourceType[] pipelineResourceTypes() { + return new PublishPipelineResourceType[] { + PublishPipelineResourceType.SKILL, + PublishPipelineResourceType.AGENTSPEC, + PublishPipelineResourceType.PROMPT + }; + } +} diff --git a/plugin-default-impl/nacos-default-ai-pipeline-plugin/src/main/java/com/alibaba/nacos/plugin/ai/pipeline/spi/impl/SkillSpectorPipelineServiceBuilder.java b/plugin-default-impl/nacos-default-ai-pipeline-plugin/src/main/java/com/alibaba/nacos/plugin/ai/pipeline/spi/impl/SkillSpectorPipelineServiceBuilder.java new file mode 100644 index 00000000000..4dbc9d898f5 --- /dev/null +++ b/plugin-default-impl/nacos-default-ai-pipeline-plugin/src/main/java/com/alibaba/nacos/plugin/ai/pipeline/spi/impl/SkillSpectorPipelineServiceBuilder.java @@ -0,0 +1,161 @@ +/* + * Copyright 1999-2026 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.nacos.plugin.ai.pipeline.spi.impl; + +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.plugin.ai.pipeline.spi.PublishPipelineService; +import com.alibaba.nacos.plugin.ai.pipeline.spi.PublishPipelineServiceBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Properties; +import java.util.Set; + +/** + * Builder for {@link SkillSpectorPipelineService}. + * + * @author nacos + */ +public class SkillSpectorPipelineServiceBuilder implements PublishPipelineServiceBuilder { + + private static final Logger LOGGER = + LoggerFactory.getLogger(SkillSpectorPipelineServiceBuilder.class); + + private static final String PROPERTY_COMMAND = "command"; + + private static final String PROPERTY_EXECUTABLE = "executable"; + + private static final String PROPERTY_PATH = "path"; + + private static final String USER_AI_PIPELINE_BIN = "ai-infra/ai-pipeline/bin"; + + @Override + public String pipelineId() { + return SkillSpectorPipelineService.PIPELINE_ID; + } + + @Override + public PublishPipelineService build(Properties properties) { + SkillSpectorScanOptions scanOptions = SkillSpectorScanOptions.fromProperties(properties); + String resolvedCommand = resolveSkillSpectorCommand(properties); + if (StringUtils.isBlank(resolvedCommand)) { + LOGGER.warn("[SkillSpectorPipeline] SkillSpector runtime 未安装,插件将拒绝发布。{}", + SkillSpectorPipelineService.INSTALLATION_HINT); + } else { + LOGGER.info("[SkillSpectorPipeline] SkillSpector runtime 已就绪,runtime={}", + resolvedCommand); + } + return new SkillSpectorPipelineService(resolvedCommand, scanOptions); + } + + private String resolveSkillSpectorCommand(Properties properties) { + for (String configured : getConfiguredCandidates(properties)) { + String resolved = resolveCandidate(configured); + if (StringUtils.isNotBlank(resolved)) { + return resolved; + } + } + return resolveCandidate(SkillSpectorPipelineService.DEFAULT_SKILL_SPECTOR_CMD); + } + + private List getConfiguredCandidates(Properties properties) { + Set result = new LinkedHashSet<>(); + addConfiguredCandidate(result, properties.getProperty(PROPERTY_COMMAND)); + addConfiguredCandidate(result, properties.getProperty(PROPERTY_EXECUTABLE)); + addConfiguredCandidate(result, properties.getProperty(PROPERTY_PATH)); + return new ArrayList<>(result); + } + + private void addConfiguredCandidate(Set candidates, String value) { + if (StringUtils.isNotBlank(value)) { + candidates.add(value.trim()); + } + } + + private String resolveCandidate(String candidate) { + if (StringUtils.isBlank(candidate)) { + return null; + } + String expanded = expandHome(candidate.trim()); + if (!containsPathSeparator(expanded)) { + String pathResolved = findExecutableInPath(expanded); + if (StringUtils.isNotBlank(pathResolved)) { + return pathResolved; + } + LOGGER.debug("[SkillSpectorPipeline] 在 PATH 中未找到命令: {}", expanded); + return null; + } + + Path path = Paths.get(expanded).toAbsolutePath().normalize(); + if (Files.isRegularFile(path) && Files.isExecutable(path)) { + return path.toString(); + } + LOGGER.debug("[SkillSpectorPipeline] SkillSpector 路径不存在或不可执行: {}", path); + return null; + } + + private String findExecutableInPath(String command) { + String pathEnv = getPathEnv(); + String userHome = System.getProperty("user.home", ""); + Set directories = new LinkedHashSet<>(); + if (StringUtils.isNotBlank(pathEnv)) { + for (String each : pathEnv.split(File.pathSeparator)) { + if (StringUtils.isNotBlank(each)) { + directories.add(each.trim()); + } + } + } + if (StringUtils.isNotBlank(userHome)) { + directories.add(Paths.get(userHome, USER_AI_PIPELINE_BIN).toString()); + directories.add(Paths.get(userHome, ".local", "bin").toString()); + } + + for (String each : directories) { + Path path = Paths.get(expandHome(each), command).toAbsolutePath().normalize(); + if (Files.isRegularFile(path) && Files.isExecutable(path)) { + return path.toString(); + } + } + return null; + } + + private boolean containsPathSeparator(String candidate) { + return candidate.contains(File.separator) || candidate.contains("/") + || candidate.contains("\\"); + } + + private String expandHome(String candidate) { + if (candidate.startsWith("~/")) { + String userHome = System.getProperty("user.home", ""); + if (StringUtils.isNotBlank(userHome)) { + return Paths.get(userHome, candidate.substring(2)).toString(); + } + } + return candidate; + } + + String getPathEnv() { + return System.getenv("PATH"); + } +} diff --git a/plugin-default-impl/nacos-default-ai-pipeline-plugin/src/main/java/com/alibaba/nacos/plugin/ai/pipeline/spi/impl/SkillSpectorScanOptions.java b/plugin-default-impl/nacos-default-ai-pipeline-plugin/src/main/java/com/alibaba/nacos/plugin/ai/pipeline/spi/impl/SkillSpectorScanOptions.java new file mode 100644 index 00000000000..2f1ac3978f8 --- /dev/null +++ b/plugin-default-impl/nacos-default-ai-pipeline-plugin/src/main/java/com/alibaba/nacos/plugin/ai/pipeline/spi/impl/SkillSpectorScanOptions.java @@ -0,0 +1,234 @@ +/* + * Copyright 1999-2026 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.nacos.plugin.ai.pipeline.spi.impl; + +import com.alibaba.nacos.common.utils.StringUtils; + +import java.util.Locale; +import java.util.Map; +import java.util.Properties; + +/** + * SkillSpector runtime options derived from pipeline node {@link Properties}. + * + * @author nacos + */ +final class SkillSpectorScanOptions { + + static final String PROP_USE_LLM = "useLlm"; + + static final String PROP_USE_LLM_KEBAB = "use-llm"; + + static final String PROP_PROVIDER = "provider"; + + static final String PROP_MODEL = "model"; + + static final String PROP_API_KEY = "apiKey"; + + static final String PROP_API_KEY_KEBAB = "api-key"; + + static final String PROP_BASE_URL = "baseUrl"; + + static final String PROP_BASE_URL_KEBAB = "base-url"; + + static final String PROP_RISK_SCORE_THRESHOLD = "riskScoreThreshold"; + + static final String PROP_RISK_SCORE_THRESHOLD_KEBAB = "risk-score-threshold"; + + static final String PROP_MAX_FINDINGS = "maxFindings"; + + static final String PROP_MAX_FINDINGS_KEBAB = "max-findings"; + + static final int DEFAULT_RISK_SCORE_THRESHOLD = 50; + + static final int DEFAULT_MAX_FINDINGS = 20; + + static final int MAX_FINDINGS_LIMIT = 100; + + private static final String ENV_SKILLSPECTOR_PROVIDER = "SKILLSPECTOR_PROVIDER"; + + private static final String ENV_SKILLSPECTOR_MODEL = "SKILLSPECTOR_MODEL"; + + private static final String ENV_SKILLSPECTOR_API_KEY = "SKILLSPECTOR_API_KEY"; + + private static final String ENV_OPENAI_API_KEY = "OPENAI_API_KEY"; + + private static final String ENV_OPENAI_BASE_URL = "OPENAI_BASE_URL"; + + private static final String ENV_ANTHROPIC_API_KEY = "ANTHROPIC_API_KEY"; + + private static final String ENV_NVIDIA_INFERENCE_KEY = "NVIDIA_INFERENCE_KEY"; + + private final boolean useLlm; + + private final String provider; + + private final String model; + + private final String apiKey; + + private final String baseUrl; + + private final int riskScoreThreshold; + + private final int maxFindings; + + private SkillSpectorScanOptions(boolean useLlm, String provider, String model, String apiKey, + String baseUrl, int riskScoreThreshold, int maxFindings) { + this.useLlm = useLlm; + this.provider = provider; + this.model = model; + this.apiKey = apiKey; + this.baseUrl = baseUrl; + this.riskScoreThreshold = riskScoreThreshold; + this.maxFindings = maxFindings; + } + + static SkillSpectorScanOptions none() { + return new SkillSpectorScanOptions(false, null, null, null, null, + DEFAULT_RISK_SCORE_THRESHOLD, DEFAULT_MAX_FINDINGS); + } + + static SkillSpectorScanOptions fromProperties(Properties properties) { + if (properties == null || properties.isEmpty()) { + return none(); + } + boolean useLlm = Boolean.parseBoolean( + readProperty(properties, PROP_USE_LLM, PROP_USE_LLM_KEBAB, "false")); + int threshold = parseThreshold( + readProperty(properties, PROP_RISK_SCORE_THRESHOLD, + PROP_RISK_SCORE_THRESHOLD_KEBAB, null)); + int maxFindings = parseMaxFindings( + readProperty(properties, PROP_MAX_FINDINGS, PROP_MAX_FINDINGS_KEBAB, null)); + return new SkillSpectorScanOptions(useLlm, + trimToNull(properties.getProperty(PROP_PROVIDER)), + trimToNull(properties.getProperty(PROP_MODEL)), + readProperty(properties, PROP_API_KEY, PROP_API_KEY_KEBAB, null), + readProperty(properties, PROP_BASE_URL, PROP_BASE_URL_KEBAB, null), + threshold, maxFindings); + } + + private static int parseThreshold(String value) { + if (StringUtils.isBlank(value)) { + return DEFAULT_RISK_SCORE_THRESHOLD; + } + try { + int parsed = Integer.parseInt(value.trim()); + if (parsed < 0) { + return 0; + } + return Math.min(parsed, 100); + } catch (NumberFormatException e) { + return DEFAULT_RISK_SCORE_THRESHOLD; + } + } + + private static int parseMaxFindings(String value) { + if (StringUtils.isBlank(value)) { + return DEFAULT_MAX_FINDINGS; + } + try { + int parsed = Integer.parseInt(value.trim()); + if (parsed <= 0) { + return DEFAULT_MAX_FINDINGS; + } + return Math.min(parsed, MAX_FINDINGS_LIMIT); + } catch (NumberFormatException e) { + return DEFAULT_MAX_FINDINGS; + } + } + + private static String readProperty(Properties properties, String camelKey, String kebabKey, + String defaultValue) { + String value = trimToNull(properties.getProperty(camelKey)); + if (value != null) { + return value; + } + value = trimToNull(properties.getProperty(kebabKey)); + return value != null ? value : defaultValue; + } + + private static String trimToNull(String s) { + if (s == null) { + return null; + } + String t = s.trim(); + return t.isEmpty() ? null : t; + } + + boolean isUseLlm() { + return useLlm; + } + + int getRiskScoreThreshold() { + return riskScoreThreshold; + } + + int getMaxFindings() { + return maxFindings; + } + + void applyLlmEnvironment(Map env) { + if (!useLlm) { + return; + } + putIfConfigured(env, ENV_SKILLSPECTOR_PROVIDER, provider); + putIfConfigured(env, ENV_SKILLSPECTOR_MODEL, model); + putProviderCredentials(env); + } + + private void putProviderCredentials(Map env) { + String effectiveProvider = getEffectiveProvider(env); + if ("openai".equals(effectiveProvider)) { + putSecretIfAbsent(env, ENV_OPENAI_API_KEY, apiKey); + putIfConfigured(env, ENV_OPENAI_BASE_URL, baseUrl); + return; + } + if ("anthropic".equals(effectiveProvider)) { + putSecretIfAbsent(env, ENV_ANTHROPIC_API_KEY, apiKey); + return; + } + if ("nv_inference".equals(effectiveProvider) || "nv_build".equals(effectiveProvider)) { + putSecretIfAbsent(env, ENV_NVIDIA_INFERENCE_KEY, apiKey); + } + } + + private String getEffectiveProvider(Map env) { + String envProvider = trimToNull(env.get(ENV_SKILLSPECTOR_PROVIDER)); + String value = envProvider != null ? envProvider : provider; + return value == null ? "nv_inference" : value.toLowerCase(Locale.ROOT); + } + + private void putSecretIfAbsent(Map env, String key, String configuredValue) { + if (StringUtils.isNotBlank(env.get(key))) { + return; + } + String genericValue = trimToNull(env.get(ENV_SKILLSPECTOR_API_KEY)); + if (genericValue != null) { + env.put(key, genericValue); + return; + } + putIfConfigured(env, key, configuredValue); + } + + private void putIfConfigured(Map env, String key, String value) { + if (StringUtils.isBlank(value) || StringUtils.isNotBlank(env.get(key))) { + return; + } + env.put(key, value); + } +} diff --git a/plugin-default-impl/nacos-default-ai-pipeline-plugin/src/main/java/com/alibaba/nacos/plugin/ai/pipeline/spi/impl/SkillSpectorScanReport.java b/plugin-default-impl/nacos-default-ai-pipeline-plugin/src/main/java/com/alibaba/nacos/plugin/ai/pipeline/spi/impl/SkillSpectorScanReport.java new file mode 100644 index 00000000000..2a906d87e1c --- /dev/null +++ b/plugin-default-impl/nacos-default-ai-pipeline-plugin/src/main/java/com/alibaba/nacos/plugin/ai/pipeline/spi/impl/SkillSpectorScanReport.java @@ -0,0 +1,234 @@ +/* + * Copyright 1999-2026 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.nacos.plugin.ai.pipeline.spi.impl; + +import com.alibaba.nacos.common.utils.JacksonUtils; +import com.fasterxml.jackson.databind.JsonNode; + +import java.util.ArrayList; +import java.util.List; + +/** + * Parsed SkillSpector JSON report. + * + * @author nacos + */ +final class SkillSpectorScanReport { + + private final int riskScore; + + private final String riskSeverity; + + private final String riskRecommendation; + + private final int issueCount; + + private final List findings; + + private SkillSpectorScanReport(int riskScore, String riskSeverity, + String riskRecommendation, int issueCount, List findings) { + this.riskScore = riskScore; + this.riskSeverity = riskSeverity; + this.riskRecommendation = riskRecommendation; + this.issueCount = issueCount; + this.findings = findings; + } + + static SkillSpectorScanReport parse(String json) { + JsonNode root = JacksonUtils.toObj(json); + JsonNode risk = root.path("risk_assessment"); + int score = root.has("risk_score") ? root.path("risk_score").asInt() + : risk.path("score").asInt(-1); + if (score < 0) { + throw new IllegalArgumentException("Missing SkillSpector risk score"); + } + String severity = firstText(root.path("risk_severity"), risk.path("severity")); + String recommendation = firstText(root.path("risk_recommendation"), + risk.path("recommendation")); + JsonNode issues = firstArray(root.path("issues"), root.path("filtered_findings"), + root.path("findings")); + List findings = parseFindings(issues); + return new SkillSpectorScanReport(score, severity, recommendation, issues.size(), + findings); + } + + private static String firstText(JsonNode first, JsonNode second) { + if (first != null && first.isTextual()) { + return first.asText(); + } + if (second != null && second.isTextual()) { + return second.asText(); + } + return "UNKNOWN"; + } + + private static JsonNode firstArray(JsonNode... nodes) { + for (JsonNode node : nodes) { + if (node != null && node.isArray()) { + return node; + } + } + return JacksonUtils.createEmptyArrayNode(); + } + + private static List parseFindings(JsonNode issues) { + List result = new ArrayList<>(); + if (issues == null || !issues.isArray()) { + return result; + } + for (JsonNode issue : issues) { + result.add(Finding.from(issue)); + } + return result; + } + + int getRiskScore() { + return riskScore; + } + + String getRiskSeverity() { + return riskSeverity; + } + + String getRiskRecommendation() { + return riskRecommendation; + } + + int getIssueCount() { + return issueCount; + } + + List getFindings() { + return findings; + } + + static final class Finding { + + private final String id; + + private final String category; + + private final String severity; + + private final String file; + + private final int startLine; + + private final int endLine; + + private final String explanation; + + private final String remediation; + + private final String codeSnippet; + + private Finding(String id, String category, String severity, String file, + int startLine, int endLine, String explanation, String remediation, + String codeSnippet) { + this.id = id; + this.category = category; + this.severity = severity; + this.file = file; + this.startLine = startLine; + this.endLine = endLine; + this.explanation = explanation; + this.remediation = remediation; + this.codeSnippet = codeSnippet; + } + + private static Finding from(JsonNode issue) { + JsonNode location = issue.path("location"); + return new Finding( + text(issue, "id", "rule_id"), + text(issue, "category"), + text(issue, "severity"), + firstNonBlank(text(location, "file"), text(issue, "file")), + firstPositive(location.path("start_line").asInt(0), + issue.path("start_line").asInt(0)), + firstPositive(location.path("end_line").asInt(0), + issue.path("end_line").asInt(0)), + firstNonBlank(text(issue, "explanation"), text(issue, "message"), + text(issue, "finding")), + text(issue, "remediation"), + firstNonBlank(text(issue, "code_snippet"), text(issue, "context"))); + } + + private static String text(JsonNode node, String... fieldNames) { + for (String fieldName : fieldNames) { + JsonNode value = node.path(fieldName); + if (value.isTextual() && !value.asText().isBlank()) { + return value.asText(); + } + } + return null; + } + + private static String firstNonBlank(String... values) { + for (String value : values) { + if (value != null && !value.isBlank()) { + return value; + } + } + return null; + } + + private static int firstPositive(int... values) { + for (int value : values) { + if (value > 0) { + return value; + } + } + return 0; + } + + String getId() { + return id; + } + + String getCategory() { + return category; + } + + String getSeverity() { + return severity; + } + + String getFile() { + return file; + } + + int getStartLine() { + return startLine; + } + + int getEndLine() { + return endLine; + } + + String getExplanation() { + return explanation; + } + + String getRemediation() { + return remediation; + } + + String getCodeSnippet() { + return codeSnippet; + } + } +} diff --git a/plugin-default-impl/nacos-default-ai-pipeline-plugin/src/main/resources/META-INF/services/com.alibaba.nacos.plugin.ai.pipeline.spi.PublishPipelineServiceBuilder b/plugin-default-impl/nacos-default-ai-pipeline-plugin/src/main/resources/META-INF/services/com.alibaba.nacos.plugin.ai.pipeline.spi.PublishPipelineServiceBuilder index 2c5941dff33..1149593b5c3 100644 --- a/plugin-default-impl/nacos-default-ai-pipeline-plugin/src/main/resources/META-INF/services/com.alibaba.nacos.plugin.ai.pipeline.spi.PublishPipelineServiceBuilder +++ b/plugin-default-impl/nacos-default-ai-pipeline-plugin/src/main/resources/META-INF/services/com.alibaba.nacos.plugin.ai.pipeline.spi.PublishPipelineServiceBuilder @@ -15,3 +15,4 @@ # com.alibaba.nacos.plugin.ai.pipeline.spi.impl.SkillScannerPipelineServiceBuilder +com.alibaba.nacos.plugin.ai.pipeline.spi.impl.SkillSpectorPipelineServiceBuilder diff --git a/plugin-default-impl/nacos-default-ai-pipeline-plugin/src/test/java/com/alibaba/nacos/plugin/ai/pipeline/spi/impl/SkillSpectorPipelineServiceBuilderTest.java b/plugin-default-impl/nacos-default-ai-pipeline-plugin/src/test/java/com/alibaba/nacos/plugin/ai/pipeline/spi/impl/SkillSpectorPipelineServiceBuilderTest.java new file mode 100644 index 00000000000..cad0526a092 --- /dev/null +++ b/plugin-default-impl/nacos-default-ai-pipeline-plugin/src/test/java/com/alibaba/nacos/plugin/ai/pipeline/spi/impl/SkillSpectorPipelineServiceBuilderTest.java @@ -0,0 +1,169 @@ +/* + * Copyright 1999-2026 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.nacos.plugin.ai.pipeline.spi.impl; + +import com.alibaba.nacos.plugin.ai.pipeline.model.PublishPipelineContext; +import com.alibaba.nacos.plugin.ai.pipeline.model.PublishPipelineResourceType; +import com.alibaba.nacos.plugin.ai.pipeline.model.PublishPipelineResult; +import com.alibaba.nacos.plugin.ai.pipeline.spi.PublishPipelineService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Properties; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * {@link SkillSpectorPipelineServiceBuilder} unit test. + * + * @author nacos + */ +class SkillSpectorPipelineServiceBuilderTest { + + private SkillSpectorPipelineServiceBuilder builder; + + @BeforeEach + void setUp() { + builder = new SkillSpectorPipelineServiceBuilder(); + } + + @Test + void pipelineIdTest() { + assertEquals("skill-spector", builder.pipelineId()); + } + + @Test + void buildTest() { + PublishPipelineService service = builder.build(new Properties()); + + assertNotNull(service); + assertEquals("skill-spector", service.pipelineId()); + assertTrue(Arrays.asList(service.pipelineResourceTypes()) + .contains(PublishPipelineResourceType.SKILL)); + assertTrue(Arrays.asList(service.pipelineResourceTypes()) + .contains(PublishPipelineResourceType.AGENTSPEC)); + assertTrue(Arrays.asList(service.pipelineResourceTypes()) + .contains(PublishPipelineResourceType.PROMPT)); + assertEquals(90, service.getPreferOrder()); + } + + @Test + void buildWithConfiguredCommandTest() throws Exception { + Path runner = createExecutable(Files.createTempDirectory("nacos-skillspector"), + "skillspector"); + Path emptyHome = Files.createTempDirectory("nacos-home"); + String oldNacosHome = System.getProperty("nacos.home"); + String oldUserDir = System.getProperty("user.dir"); + Properties properties = new Properties(); + properties.setProperty("command", runner.toString()); + try { + System.setProperty("nacos.home", emptyHome.toString()); + System.setProperty("user.dir", emptyHome.toString()); + + PublishPipelineService service = builder.build(properties); + + PublishPipelineResult result = service.execute(new PublishPipelineContext()); + assertTrue(result.isPassed(), result.getMessage()); + } finally { + restoreSystemProperty("nacos.home", oldNacosHome); + restoreSystemProperty("user.dir", oldUserDir); + } + } + + @Test + void buildWithDefaultCommandFromPathTest() throws Exception { + Path dir = Files.createTempDirectory("nacos-skillspector-path"); + createExecutable(dir, "skill-spector"); + builder = new SkillSpectorPipelineServiceBuilder() { + + @Override + String getPathEnv() { + return dir.toString(); + } + }; + + PublishPipelineService service = builder.build(new Properties()); + + PublishPipelineResult result = service.execute(new PublishPipelineContext()); + assertTrue(result.isPassed(), result.getMessage()); + } + + @Test + void buildWithDefaultCommandFromAiPipelineBinTest() throws Exception { + Path home = Files.createTempDirectory("nacos-skillspector-home"); + Path commandDir = home.resolve("ai-infra").resolve("ai-pipeline").resolve("bin"); + createExecutable(commandDir, "skill-spector"); + String oldUserHome = System.getProperty("user.home"); + try { + System.setProperty("user.home", home.toString()); + builder = new SkillSpectorPipelineServiceBuilder() { + + @Override + String getPathEnv() { + return ""; + } + }; + + PublishPipelineService service = builder.build(new Properties()); + + PublishPipelineResult result = service.execute(new PublishPipelineContext()); + assertTrue(result.isPassed(), result.getMessage()); + } finally { + restoreSystemProperty("user.home", oldUserHome); + } + } + + @Test + void buildWithHomeExpandedCommandTest() throws Exception { + Path home = Files.createTempDirectory("nacos-skillspector-home"); + Path runner = createExecutable(home, "skill-spector"); + String oldUserHome = System.getProperty("user.home"); + try { + System.setProperty("user.home", home.toString()); + Properties properties = new Properties(); + properties.setProperty("command", "~/" + runner.getFileName()); + + PublishPipelineService service = builder.build(properties); + + PublishPipelineResult result = service.execute(new PublishPipelineContext()); + assertTrue(result.isPassed(), result.getMessage()); + } finally { + restoreSystemProperty("user.home", oldUserHome); + } + } + + private void restoreSystemProperty(String key, String value) { + if (value == null) { + System.clearProperty(key); + } else { + System.setProperty(key, value); + } + } + + private Path createExecutable(Path dir, String name) throws Exception { + Files.createDirectories(dir); + Path runner = dir.resolve(name); + Files.write(runner, Arrays.asList("#!/bin/sh", "exit 0")); + assertTrue(runner.toFile().setExecutable(true)); + return runner; + } +} diff --git a/plugin-default-impl/nacos-default-ai-pipeline-plugin/src/test/java/com/alibaba/nacos/plugin/ai/pipeline/spi/impl/SkillSpectorPipelineServiceTest.java b/plugin-default-impl/nacos-default-ai-pipeline-plugin/src/test/java/com/alibaba/nacos/plugin/ai/pipeline/spi/impl/SkillSpectorPipelineServiceTest.java new file mode 100644 index 00000000000..deddaa45c6d --- /dev/null +++ b/plugin-default-impl/nacos-default-ai-pipeline-plugin/src/test/java/com/alibaba/nacos/plugin/ai/pipeline/spi/impl/SkillSpectorPipelineServiceTest.java @@ -0,0 +1,403 @@ +/* + * Copyright 1999-2026 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.nacos.plugin.ai.pipeline.spi.impl; + +import com.alibaba.nacos.plugin.ai.pipeline.model.PublishPipelineMessageType; +import com.alibaba.nacos.plugin.ai.pipeline.model.PublishPipelineResourceType; +import com.alibaba.nacos.plugin.ai.pipeline.model.PublishPipelineResult; +import com.alibaba.nacos.plugin.ai.pipeline.model.ResourceFileContent; +import com.alibaba.nacos.plugin.ai.pipeline.model.ResourceFilesPipelineContext; +import com.alibaba.nacos.plugin.ai.pipeline.model.SkillPipelineContext; +import org.junit.jupiter.api.Test; + +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.List; +import java.util.Properties; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * {@link SkillSpectorPipelineService} unit test. + * + * @author nacos + */ +class SkillSpectorPipelineServiceTest { + + @Test + void pipelineMetadataTest() { + SkillSpectorPipelineService service = createStubService(StubScanMode.PASS_LOW_RISK, + SkillSpectorScanOptions.none()); + + assertEquals("skill-spector", service.pipelineId()); + assertEquals(90, service.getPreferOrder()); + assertTrue(Arrays.asList(service.pipelineResourceTypes()) + .contains(PublishPipelineResourceType.SKILL)); + assertTrue(Arrays.asList(service.pipelineResourceTypes()) + .contains(PublishPipelineResourceType.AGENTSPEC)); + assertTrue(Arrays.asList(service.pipelineResourceTypes()) + .contains(PublishPipelineResourceType.PROMPT)); + } + + @Test + void buildScanCommandStaticOnlyTest() { + SkillSpectorPipelineService service = new SkillSpectorPipelineService("skillspector", + SkillSpectorScanOptions.none()); + + List command = service.buildScanCommand(Path.of("/tmp/skill"), + Path.of("/tmp/report.json")); + + assertEquals(List.of("skillspector", "scan", "/tmp/skill", "--format", "json", + "--output", "/tmp/report.json", "--no-llm"), command); + } + + @Test + void buildScanCommandWithLlmTest() { + Properties properties = new Properties(); + properties.setProperty(SkillSpectorScanOptions.PROP_USE_LLM, "true"); + SkillSpectorPipelineService service = new SkillSpectorPipelineService("skillspector", + SkillSpectorScanOptions.fromProperties(properties)); + + List command = service.buildScanCommand(Path.of("/tmp/skill"), + Path.of("/tmp/report.json")); + + assertFalse(command.contains("--no-llm")); + } + + @Test + void executePassesWhenRiskScoreWithinDefaultThresholdTest() { + PublishPipelineResult result = createStubService(StubScanMode.PASS_LOW_RISK, + SkillSpectorScanOptions.none()).execute(createSkillContext("low-risk")); + + assertNotNull(result); + assertTrue(result.isPassed(), result.getMessage()); + assertTrue(result.getMessage().contains("risk_score=20")); + assertTrue(result.getMessage().contains("## 扫描结果")); + assertTrue(result.getMessage().contains("HIGH / R0")); + assertTrue(result.getMessage().contains("SKILL.md:3-4")); + assertTrue(result.getMessage().contains("修复建议")); + assertEquals(PublishPipelineMessageType.MARKDOWN, result.getType()); + } + + @Test + void executeRejectsWhenRiskScoreExceedsDefaultThresholdTest() { + PublishPipelineResult result = createStubService(StubScanMode.REJECT_HIGH_RISK, + SkillSpectorScanOptions.none()).execute(createSkillContext("high-risk")); + + assertNotNull(result); + assertFalse(result.isPassed(), result.getMessage()); + assertTrue(result.getMessage().contains("risk_score: 70")); + assertTrue(result.getMessage().contains("HIGH / R1")); + assertTrue(result.getMessage().contains("explanation R1")); + assertEquals("SkillSpector risk_score 阈值", result.getCheckpoints().get(0).getTitle()); + } + + @Test + void executeUsesConfiguredMaxFindingsTest() { + Properties properties = new Properties(); + properties.setProperty(SkillSpectorScanOptions.PROP_MAX_FINDINGS_KEBAB, "2"); + PublishPipelineResult result = createStubService(StubScanMode.REJECT_MANY_FINDINGS, + SkillSpectorScanOptions.fromProperties(properties)).execute(createSkillContext("many")); + + assertNotNull(result); + assertFalse(result.isPassed(), result.getMessage()); + assertTrue(result.getMessage().contains("HIGH / R0")); + assertTrue(result.getMessage().contains("HIGH / R1")); + assertFalse(result.getMessage().contains("HIGH / R2")); + assertTrue(result.getMessage().contains("仅展示前 2 条,共 6 条问题。")); + } + + @Test + void executePassesWhenThresholdIsRaisedEvenIfCliExitCodeIsOne() { + Properties properties = new Properties(); + properties.setProperty(SkillSpectorScanOptions.PROP_RISK_SCORE_THRESHOLD_KEBAB, "80"); + PublishPipelineResult result = createStubService(StubScanMode.REJECT_HIGH_RISK, + SkillSpectorScanOptions.fromProperties(properties)) + .execute(createSkillContext("high-risk")); + + assertNotNull(result); + assertTrue(result.isPassed(), result.getMessage()); + assertTrue(result.getMessage().contains("risk_score=70")); + } + + @Test + void executeRejectsOnCliErrorTest() { + PublishPipelineResult result = createStubService(StubScanMode.CLI_ERROR, + SkillSpectorScanOptions.none()).execute(createSkillContext("cli-error")); + + assertNotNull(result); + assertFalse(result.isPassed()); + assertTrue(result.getMessage().contains("exitCode=2")); + } + + @Test + void executeRejectsWithOutputWhenCliExitOneWithoutReportTest() { + PublishPipelineResult result = createStubService(StubScanMode.CLI_EXIT_ONE_WITHOUT_REPORT, + SkillSpectorScanOptions.none()).execute(createSkillContext("missing-report")); + + assertNotNull(result); + assertFalse(result.isPassed()); + assertTrue(result.getMessage().contains("未生成扫描报告")); + assertTrue(result.getMessage().contains("ModuleNotFoundError")); + } + + @Test + void executeAgentSpecGeneratesSkillMdTest() { + PublishPipelineResult result = createStubService(StubScanMode.PASS_AGENTSPEC, + SkillSpectorScanOptions.none()).execute(createAgentSpecContext("agent")); + + assertNotNull(result); + assertTrue(result.isPassed(), result.getMessage()); + } + + @Test + void executePromptGeneratesSkillMdTest() { + PublishPipelineResult result = createStubService(StubScanMode.PASS_PROMPT, + SkillSpectorScanOptions.none()).execute(createPromptContext("prompt")); + + assertNotNull(result); + assertTrue(result.isPassed(), result.getMessage()); + } + + @Test + void executeWithLlmOptionsExposesEnvironmentToSubprocessTest() { + Properties properties = new Properties(); + properties.setProperty(SkillSpectorScanOptions.PROP_USE_LLM, "true"); + properties.setProperty(SkillSpectorScanOptions.PROP_PROVIDER, "openai"); + properties.setProperty(SkillSpectorScanOptions.PROP_MODEL, "gpt-test"); + properties.setProperty(SkillSpectorScanOptions.PROP_API_KEY, "test-key"); + SkillSpectorScanOptions options = SkillSpectorScanOptions.fromProperties(properties); + + PublishPipelineResult result = + createStubService(StubScanMode.VERIFY_LLM_ENV, options) + .execute(createSkillContext("llm")); + + assertNotNull(result); + assertTrue(result.isPassed(), result.getMessage()); + } + + @Test + void executeSkipsUnsafeAndBlankFilePathsTest() { + SkillPipelineContext context = createSkillContext("path-boundary", Arrays.asList( + new ResourceFileContent(null, "ignored"), + new ResourceFileContent("", "ignored"), + new ResourceFileContent("../evil.txt", "ignored"), + new ResourceFileContent("valid.txt", null))); + + PublishPipelineResult result = createStubService(StubScanMode.PASS_SKIPPED_FILES, + SkillSpectorScanOptions.none()).execute(context); + + assertNotNull(result); + assertTrue(result.isPassed(), result.getMessage()); + } + + private SkillSpectorPipelineService createStubService(StubScanMode mode, + SkillSpectorScanOptions options) { + return new StubSkillSpectorPipelineService(mode, options); + } + + private SkillPipelineContext createSkillContext(String name) { + return createSkillContext(name, List.of(new ResourceFileContent("SKILL.md", + "---\nname: " + name + "\n---\n\nhello"))); + } + + private SkillPipelineContext createSkillContext(String name, List files) { + SkillPipelineContext context = new SkillPipelineContext(); + context.setResourceName(name); + context.setNamespaceId("public"); + context.setVersion("v1"); + context.setFiles(files); + return context; + } + + private ResourceFilesPipelineContext createAgentSpecContext(String name) { + ResourceFilesPipelineContext context = new ResourceFilesPipelineContext(); + context.setResourceType(PublishPipelineResourceType.AGENTSPEC); + context.setResourceName(name); + context.setNamespaceId("public"); + context.setVersion("v1"); + context.setFiles(List.of( + new ResourceFileContent("manifest.json", "{\"name\":\"" + name + "\"}"), + new ResourceFileContent("config/SOUL.md", "helpful assistant"))); + return context; + } + + private ResourceFilesPipelineContext createPromptContext(String name) { + ResourceFilesPipelineContext context = new ResourceFilesPipelineContext(); + context.setResourceType(PublishPipelineResourceType.PROMPT); + context.setResourceName(name); + context.setNamespaceId("public"); + context.setVersion("v1"); + context.setFiles(List.of(new ResourceFileContent("prompt-main.json", + "{\"template\":\"helpful assistant\"}"))); + return context; + } + + private enum StubScanMode { + PASS_LOW_RISK, + REJECT_HIGH_RISK, + REJECT_MANY_FINDINGS, + CLI_ERROR, + PASS_AGENTSPEC, + PASS_PROMPT, + VERIFY_LLM_ENV, + PASS_SKIPPED_FILES, + CLI_EXIT_ONE_WITHOUT_REPORT + } + + private static class StubSkillSpectorPipelineService extends SkillSpectorPipelineService { + + private final StubScanMode mode; + + private StubSkillSpectorPipelineService(StubScanMode mode, + SkillSpectorScanOptions options) { + super("stub-skillspector", options); + this.mode = mode; + } + + @Override + List buildScanCommand(Path tempDir, Path reportPath) { + return Arrays.asList(currentJavaBinary(), "-cp", + System.getProperty("java.class.path"), + FakeSkillSpectorCli.class.getName(), mode.name(), + tempDir.toAbsolutePath().toString(), reportPath.toAbsolutePath().toString()); + } + + private static String currentJavaBinary() { + String executable = System.getProperty("os.name", "").toLowerCase().contains("win") + ? "java.exe" : "java"; + return Path.of(System.getProperty("java.home"), "bin", executable).toString(); + } + } + + public static final class FakeSkillSpectorCli { + + public static void main(String[] args) throws Exception { + StubScanMode mode = StubScanMode.valueOf(args[0]); + Path root = Path.of(args[1]); + Path reportPath = Path.of(args[2]); + switch (mode) { + case PASS_LOW_RISK: + requireContains(root.resolve("SKILL.md"), "hello"); + writeReport(reportPath, 20, "LOW", "SAFE", 1); + return; + case REJECT_HIGH_RISK: + requireContains(root.resolve("SKILL.md"), "hello"); + writeReport(reportPath, 70, "HIGH", "DO_NOT_INSTALL", 2); + System.exit(1); + return; + case REJECT_MANY_FINDINGS: + requireContains(root.resolve("SKILL.md"), "hello"); + writeReport(reportPath, 70, "HIGH", "DO_NOT_INSTALL", 6); + System.exit(1); + return; + case CLI_ERROR: + System.out.println("boom"); + System.exit(2); + return; + case PASS_AGENTSPEC: + requireContains(root.resolve("SKILL.md"), + "Generated from AgentSpec pipeline context"); + requireContains(root.resolve("SKILL.md"), "File: config/SOUL.md"); + writeReport(reportPath, 10, "LOW", "SAFE", 0); + return; + case PASS_PROMPT: + requireContains(root.resolve("SKILL.md"), + "Generated from Prompt pipeline context"); + requireContains(root.resolve("SKILL.md"), "File: prompt-main.json"); + writeReport(reportPath, 10, "LOW", "SAFE", 0); + return; + case VERIFY_LLM_ENV: + requireEnv("SKILLSPECTOR_PROVIDER", "openai"); + requireEnv("SKILLSPECTOR_MODEL", "gpt-test"); + requireEnv("OPENAI_API_KEY", "test-key"); + writeReport(reportPath, 20, "LOW", "SAFE", 0); + return; + case PASS_SKIPPED_FILES: + requireEmpty(root.resolve("valid.txt")); + requireNotExists(root.resolve("../evil.txt").normalize()); + writeReport(reportPath, 20, "LOW", "SAFE", 0); + return; + case CLI_EXIT_ONE_WITHOUT_REPORT: + System.out.println("ModuleNotFoundError: No module named 'skillspector'"); + System.exit(1); + return; + default: + throw new IllegalStateException("Unsupported mode: " + mode); + } + } + + private static void writeReport(Path reportPath, int score, String severity, + String recommendation, int issueCount) throws Exception { + StringBuilder issues = new StringBuilder("["); + for (int i = 0; i < issueCount; i++) { + if (i > 0) { + issues.append(","); + } + issues.append("{\"id\":\"R").append(i) + .append("\",\"category\":\"prompt-injection\"") + .append(",\"severity\":\"HIGH\"") + .append(",\"location\":{\"file\":\"SKILL.md\"") + .append(",\"start_line\":3,\"end_line\":4}") + .append(",\"explanation\":\"explanation R").append(i).append("\"") + .append(",\"remediation\":\"fix R").append(i).append("\"") + .append(",\"code_snippet\":\"snippet R").append(i).append("\"}"); + } + issues.append("]"); + String report = "{\"risk_assessment\":{\"score\":" + score + + ",\"severity\":\"" + severity + + "\",\"recommendation\":\"" + recommendation + + "\"},\"issues\":" + issues + "}"; + Files.writeString(reportPath, report, StandardCharsets.UTF_8); + } + + private static void requireContains(Path path, String expected) throws Exception { + String content = Files.readString(path, StandardCharsets.UTF_8); + if (!content.contains(expected)) { + throw new IllegalStateException( + "Expected '" + expected + "' in " + path + ", actual=" + content); + } + } + + private static void requireEmpty(Path path) throws Exception { + String content = Files.readString(path, StandardCharsets.UTF_8); + if (!content.isEmpty()) { + throw new IllegalStateException( + "Expected empty content in " + path + ", actual=" + content); + } + } + + private static void requireNotExists(Path path) { + if (Files.exists(path)) { + throw new IllegalStateException("Expected path not to exist: " + path); + } + } + + private static void requireEnv(String key, String expected) { + String actual = System.getenv(key); + if (!expected.equals(actual)) { + throw new IllegalStateException( + "Expected env " + key + "=" + expected + ", actual=" + actual); + } + } + } +} diff --git a/plugin-default-impl/nacos-default-ai-pipeline-plugin/src/test/java/com/alibaba/nacos/plugin/ai/pipeline/spi/impl/SkillSpectorScanOptionsTest.java b/plugin-default-impl/nacos-default-ai-pipeline-plugin/src/test/java/com/alibaba/nacos/plugin/ai/pipeline/spi/impl/SkillSpectorScanOptionsTest.java new file mode 100644 index 00000000000..795c2a278f6 --- /dev/null +++ b/plugin-default-impl/nacos-default-ai-pipeline-plugin/src/test/java/com/alibaba/nacos/plugin/ai/pipeline/spi/impl/SkillSpectorScanOptionsTest.java @@ -0,0 +1,127 @@ +/* + * Copyright 1999-2026 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.nacos.plugin.ai.pipeline.spi.impl; + +import org.junit.jupiter.api.Test; + +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * {@link SkillSpectorScanOptions} unit test. + * + * @author nacos + */ +class SkillSpectorScanOptionsTest { + + @Test + void fromPropertiesEmptyTest() { + SkillSpectorScanOptions options = SkillSpectorScanOptions.fromProperties(new Properties()); + + assertFalse(options.isUseLlm()); + assertEquals(50, options.getRiskScoreThreshold()); + assertEquals(20, options.getMaxFindings()); + Map env = new HashMap<>(); + options.applyLlmEnvironment(env); + assertTrue(env.isEmpty()); + } + + @Test + void fromPropertiesWithKebabAliasesTest() { + Properties properties = new Properties(); + properties.setProperty(SkillSpectorScanOptions.PROP_USE_LLM_KEBAB, "true"); + properties.setProperty(SkillSpectorScanOptions.PROP_RISK_SCORE_THRESHOLD_KEBAB, "80"); + properties.setProperty(SkillSpectorScanOptions.PROP_MAX_FINDINGS_KEBAB, "12"); + properties.setProperty(SkillSpectorScanOptions.PROP_PROVIDER, "openai"); + properties.setProperty(SkillSpectorScanOptions.PROP_MODEL, "gpt-test"); + properties.setProperty(SkillSpectorScanOptions.PROP_API_KEY_KEBAB, "configured-key"); + properties.setProperty(SkillSpectorScanOptions.PROP_BASE_URL_KEBAB, + "https://example.com/v1"); + + SkillSpectorScanOptions options = SkillSpectorScanOptions.fromProperties(properties); + + assertTrue(options.isUseLlm()); + assertEquals(80, options.getRiskScoreThreshold()); + assertEquals(12, options.getMaxFindings()); + Map env = new HashMap<>(); + options.applyLlmEnvironment(env); + assertEquals("openai", env.get("SKILLSPECTOR_PROVIDER")); + assertEquals("gpt-test", env.get("SKILLSPECTOR_MODEL")); + assertEquals("configured-key", env.get("OPENAI_API_KEY")); + assertEquals("https://example.com/v1", env.get("OPENAI_BASE_URL")); + } + + @Test + void maxFindingsShouldBeCappedAtLimitTest() { + Properties properties = new Properties(); + properties.setProperty(SkillSpectorScanOptions.PROP_MAX_FINDINGS_KEBAB, "1000"); + + SkillSpectorScanOptions options = SkillSpectorScanOptions.fromProperties(properties); + + assertEquals(100, options.getMaxFindings()); + } + + @Test + void environmentApiKeyHasPriorityTest() { + Properties properties = new Properties(); + properties.setProperty(SkillSpectorScanOptions.PROP_USE_LLM, "true"); + properties.setProperty(SkillSpectorScanOptions.PROP_PROVIDER, "anthropic"); + properties.setProperty(SkillSpectorScanOptions.PROP_API_KEY, "configured-key"); + + SkillSpectorScanOptions options = SkillSpectorScanOptions.fromProperties(properties); + Map env = new HashMap<>(); + env.put("ANTHROPIC_API_KEY", "env-key"); + + options.applyLlmEnvironment(env); + + assertEquals("env-key", env.get("ANTHROPIC_API_KEY")); + } + + @Test + void genericApiKeyMapsToProviderKeyTest() { + Properties properties = new Properties(); + properties.setProperty(SkillSpectorScanOptions.PROP_USE_LLM, "true"); + properties.setProperty(SkillSpectorScanOptions.PROP_PROVIDER, "nv_inference"); + + SkillSpectorScanOptions options = SkillSpectorScanOptions.fromProperties(properties); + Map env = new HashMap<>(); + env.put("SKILLSPECTOR_API_KEY", "generic-key"); + + options.applyLlmEnvironment(env); + + assertEquals("generic-key", env.get("NVIDIA_INFERENCE_KEY")); + } + + @Test + void defaultProviderMapsApiKeyToNvidiaTest() { + Properties properties = new Properties(); + properties.setProperty(SkillSpectorScanOptions.PROP_USE_LLM, "true"); + properties.setProperty(SkillSpectorScanOptions.PROP_API_KEY, "configured-key"); + + SkillSpectorScanOptions options = SkillSpectorScanOptions.fromProperties(properties); + Map env = new HashMap<>(); + + options.applyLlmEnvironment(env); + + assertEquals("configured-key", env.get("NVIDIA_INFERENCE_KEY")); + } +}