mirror of
https://github.com/langbot-app/LangBot.git
synced 2025-11-26 03:44:58 +08:00
feat: tag debugging plugins in webui
This commit is contained in:
@@ -22,6 +22,7 @@
|
|||||||
"@hookform/resolvers": "^5.0.1",
|
"@hookform/resolvers": "^5.0.1",
|
||||||
"@radix-ui/react-checkbox": "^1.3.1",
|
"@radix-ui/react-checkbox": "^1.3.1",
|
||||||
"@radix-ui/react-dialog": "^1.1.14",
|
"@radix-ui/react-dialog": "^1.1.14",
|
||||||
|
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
||||||
"@radix-ui/react-hover-card": "^1.1.13",
|
"@radix-ui/react-hover-card": "^1.1.13",
|
||||||
"@radix-ui/react-label": "^2.1.6",
|
"@radix-ui/react-label": "^2.1.6",
|
||||||
"@radix-ui/react-popover": "^1.1.14",
|
"@radix-ui/react-popover": "^1.1.14",
|
||||||
|
|||||||
@@ -7,7 +7,13 @@ import PluginSortDialog from '@/app/home/plugins/plugin-sort/PluginSortDialog';
|
|||||||
import styles from './plugins.module.css';
|
import styles from './plugins.module.css';
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { PlusIcon } from 'lucide-react';
|
import { PlusIcon, ChevronDownIcon, UploadIcon, StoreIcon } from 'lucide-react';
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from '@/components/ui/dropdown-menu';
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
@@ -32,6 +38,7 @@ export default function PluginConfigPage() {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [modalOpen, setModalOpen] = useState(false);
|
const [modalOpen, setModalOpen] = useState(false);
|
||||||
const [sortModalOpen, setSortModalOpen] = useState(false);
|
const [sortModalOpen, setSortModalOpen] = useState(false);
|
||||||
|
const [activeTab, setActiveTab] = useState('installed');
|
||||||
const [pluginInstallStatus, setPluginInstallStatus] =
|
const [pluginInstallStatus, setPluginInstallStatus] =
|
||||||
useState<PluginInstallStatus>(PluginInstallStatus.WAIT_INPUT);
|
useState<PluginInstallStatus>(PluginInstallStatus.WAIT_INPUT);
|
||||||
const [installError, setInstallError] = useState<string | null>(null);
|
const [installError, setInstallError] = useState<string | null>(null);
|
||||||
@@ -83,7 +90,7 @@ export default function PluginConfigPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.pageContainer}>
|
<div className={styles.pageContainer}>
|
||||||
<Tabs defaultValue="installed" className="w-full">
|
<Tabs value={activeTab} onValueChange={setActiveTab} className="w-full">
|
||||||
<div className="flex flex-row justify-between items-center px-[0.8rem]">
|
<div className="flex flex-row justify-between items-center px-[0.8rem]">
|
||||||
<TabsList className="shadow-md py-5 bg-[#f0f0f0]">
|
<TabsList className="shadow-md py-5 bg-[#f0f0f0]">
|
||||||
<TabsTrigger value="installed" className="px-6 py-4 cursor-pointer">
|
<TabsTrigger value="installed" className="px-6 py-4 cursor-pointer">
|
||||||
@@ -104,18 +111,34 @@ export default function PluginConfigPage() {
|
|||||||
>
|
>
|
||||||
{t('plugins.arrange')}
|
{t('plugins.arrange')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<DropdownMenu>
|
||||||
variant="default"
|
<DropdownMenuTrigger asChild>
|
||||||
className="px-6 py-4 cursor-pointer"
|
<Button variant="default" className="px-6 py-4 cursor-pointer">
|
||||||
onClick={() => {
|
<PlusIcon className="w-4 h-4" />
|
||||||
setModalOpen(true);
|
{t('plugins.install')}
|
||||||
setPluginInstallStatus(PluginInstallStatus.WAIT_INPUT);
|
<ChevronDownIcon className="ml-2 w-4 h-4" />
|
||||||
setInstallError(null);
|
</Button>
|
||||||
}}
|
</DropdownMenuTrigger>
|
||||||
>
|
<DropdownMenuContent align="end">
|
||||||
<PlusIcon className="w-4 h-4" />
|
<DropdownMenuItem
|
||||||
{t('plugins.install')}
|
onClick={() => {
|
||||||
</Button>
|
// TODO: 本地上传功能待实现
|
||||||
|
console.log('本地上传功能待实现');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<UploadIcon className="w-4 h-4" />
|
||||||
|
{t('plugins.uploadLocal')}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={() => {
|
||||||
|
setActiveTab('market');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<StoreIcon className="w-4 h-4" />
|
||||||
|
{t('plugins.marketplace')}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<TabsContent value="installed">
|
<TabsContent value="installed">
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ export interface IPluginCardVO {
|
|||||||
tools: object[];
|
tools: object[];
|
||||||
event_handlers: object;
|
event_handlers: object;
|
||||||
repository: string;
|
repository: string;
|
||||||
|
debug: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PluginCardVO implements IPluginCardVO {
|
export class PluginCardVO implements IPluginCardVO {
|
||||||
@@ -18,6 +19,7 @@ export class PluginCardVO implements IPluginCardVO {
|
|||||||
version: string;
|
version: string;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
priority: number;
|
priority: number;
|
||||||
|
debug: boolean;
|
||||||
status: string;
|
status: string;
|
||||||
tools: object[];
|
tools: object[];
|
||||||
event_handlers: object;
|
event_handlers: object;
|
||||||
@@ -34,5 +36,6 @@ export class PluginCardVO implements IPluginCardVO {
|
|||||||
this.status = prop.status;
|
this.status = prop.status;
|
||||||
this.tools = prop.tools;
|
this.tools = prop.tools;
|
||||||
this.version = prop.version;
|
this.version = prop.version;
|
||||||
|
this.debug = prop.debug;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ const PluginInstalledComponent = forwardRef<PluginInstalledComponentRef>(
|
|||||||
zh_Hans: '',
|
zh_Hans: '',
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
debug: plugin.debug,
|
||||||
enabled: plugin.enabled,
|
enabled: plugin.enabled,
|
||||||
name: plugin.manifest.manifest.metadata.name,
|
name: plugin.manifest.manifest.metadata.name,
|
||||||
version: plugin.manifest.manifest.metadata.version ?? '',
|
version: plugin.manifest.manifest.metadata.version ?? '',
|
||||||
|
|||||||
@@ -58,6 +58,14 @@ export default function PluginCardComponent({
|
|||||||
<Badge variant="outline" className="text-[0.7rem]">
|
<Badge variant="outline" className="text-[0.7rem]">
|
||||||
v{cardVO.version}
|
v{cardVO.version}
|
||||||
</Badge>
|
</Badge>
|
||||||
|
{cardVO.debug && (
|
||||||
|
<Badge
|
||||||
|
variant="outline"
|
||||||
|
className="text-[0.7rem] border-orange-400 text-orange-400"
|
||||||
|
>
|
||||||
|
{t('plugins.debugging')}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export interface Plugin {
|
|||||||
manifest: {
|
manifest: {
|
||||||
manifest: ComponentManifest;
|
manifest: ComponentManifest;
|
||||||
};
|
};
|
||||||
|
debug: boolean;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
components: {
|
components: {
|
||||||
component_config: object;
|
component_config: object;
|
||||||
|
|||||||
257
web/src/components/ui/dropdown-menu.tsx
Normal file
257
web/src/components/ui/dropdown-menu.tsx
Normal file
@@ -0,0 +1,257 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import * as React from 'react';
|
||||||
|
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu';
|
||||||
|
import { CheckIcon, ChevronRightIcon, CircleIcon } from 'lucide-react';
|
||||||
|
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
|
function DropdownMenu({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
|
||||||
|
return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function DropdownMenuPortal({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
|
||||||
|
return (
|
||||||
|
<DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DropdownMenuTrigger({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
|
||||||
|
return (
|
||||||
|
<DropdownMenuPrimitive.Trigger
|
||||||
|
data-slot="dropdown-menu-trigger"
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DropdownMenuContent({
|
||||||
|
className,
|
||||||
|
sideOffset = 4,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
|
||||||
|
return (
|
||||||
|
<DropdownMenuPrimitive.Portal>
|
||||||
|
<DropdownMenuPrimitive.Content
|
||||||
|
data-slot="dropdown-menu-content"
|
||||||
|
sideOffset={sideOffset}
|
||||||
|
className={cn(
|
||||||
|
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</DropdownMenuPrimitive.Portal>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DropdownMenuGroup({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
|
||||||
|
return (
|
||||||
|
<DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DropdownMenuItem({
|
||||||
|
className,
|
||||||
|
inset,
|
||||||
|
variant = 'default',
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
|
||||||
|
inset?: boolean;
|
||||||
|
variant?: 'default' | 'destructive';
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<DropdownMenuPrimitive.Item
|
||||||
|
data-slot="dropdown-menu-item"
|
||||||
|
data-inset={inset}
|
||||||
|
data-variant={variant}
|
||||||
|
className={cn(
|
||||||
|
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DropdownMenuCheckboxItem({
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
checked,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
|
||||||
|
return (
|
||||||
|
<DropdownMenuPrimitive.CheckboxItem
|
||||||
|
data-slot="dropdown-menu-checkbox-item"
|
||||||
|
className={cn(
|
||||||
|
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
checked={checked}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
||||||
|
<DropdownMenuPrimitive.ItemIndicator>
|
||||||
|
<CheckIcon className="size-4" />
|
||||||
|
</DropdownMenuPrimitive.ItemIndicator>
|
||||||
|
</span>
|
||||||
|
{children}
|
||||||
|
</DropdownMenuPrimitive.CheckboxItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DropdownMenuRadioGroup({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
|
||||||
|
return (
|
||||||
|
<DropdownMenuPrimitive.RadioGroup
|
||||||
|
data-slot="dropdown-menu-radio-group"
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DropdownMenuRadioItem({
|
||||||
|
className,
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
|
||||||
|
return (
|
||||||
|
<DropdownMenuPrimitive.RadioItem
|
||||||
|
data-slot="dropdown-menu-radio-item"
|
||||||
|
className={cn(
|
||||||
|
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
|
||||||
|
<DropdownMenuPrimitive.ItemIndicator>
|
||||||
|
<CircleIcon className="size-2 fill-current" />
|
||||||
|
</DropdownMenuPrimitive.ItemIndicator>
|
||||||
|
</span>
|
||||||
|
{children}
|
||||||
|
</DropdownMenuPrimitive.RadioItem>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DropdownMenuLabel({
|
||||||
|
className,
|
||||||
|
inset,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
|
||||||
|
inset?: boolean;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<DropdownMenuPrimitive.Label
|
||||||
|
data-slot="dropdown-menu-label"
|
||||||
|
data-inset={inset}
|
||||||
|
className={cn(
|
||||||
|
'px-2 py-1.5 text-sm font-medium data-[inset]:pl-8',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DropdownMenuSeparator({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
|
||||||
|
return (
|
||||||
|
<DropdownMenuPrimitive.Separator
|
||||||
|
data-slot="dropdown-menu-separator"
|
||||||
|
className={cn('bg-border -mx-1 my-1 h-px', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DropdownMenuShortcut({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<'span'>) {
|
||||||
|
return (
|
||||||
|
<span
|
||||||
|
data-slot="dropdown-menu-shortcut"
|
||||||
|
className={cn(
|
||||||
|
'text-muted-foreground ml-auto text-xs tracking-widest',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DropdownMenuSub({
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
|
||||||
|
return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function DropdownMenuSubTrigger({
|
||||||
|
className,
|
||||||
|
inset,
|
||||||
|
children,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
|
||||||
|
inset?: boolean;
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<DropdownMenuPrimitive.SubTrigger
|
||||||
|
data-slot="dropdown-menu-sub-trigger"
|
||||||
|
data-inset={inset}
|
||||||
|
className={cn(
|
||||||
|
'focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<ChevronRightIcon className="ml-auto size-4" />
|
||||||
|
</DropdownMenuPrimitive.SubTrigger>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function DropdownMenuSubContent({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
|
||||||
|
return (
|
||||||
|
<DropdownMenuPrimitive.SubContent
|
||||||
|
data-slot="dropdown-menu-sub-content"
|
||||||
|
className={cn(
|
||||||
|
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg',
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuPortal,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuGroup,
|
||||||
|
DropdownMenuLabel,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuCheckboxItem,
|
||||||
|
DropdownMenuRadioGroup,
|
||||||
|
DropdownMenuRadioItem,
|
||||||
|
DropdownMenuSeparator,
|
||||||
|
DropdownMenuShortcut,
|
||||||
|
DropdownMenuSub,
|
||||||
|
DropdownMenuSubTrigger,
|
||||||
|
DropdownMenuSubContent,
|
||||||
|
};
|
||||||
@@ -179,6 +179,8 @@ const enUS = {
|
|||||||
eventCount: 'Events: {{count}}',
|
eventCount: 'Events: {{count}}',
|
||||||
toolCount: 'Tools: {{count}}',
|
toolCount: 'Tools: {{count}}',
|
||||||
starCount: 'Stars: {{count}}',
|
starCount: 'Stars: {{count}}',
|
||||||
|
uploadLocal: 'Upload Local',
|
||||||
|
debugging: 'Debugging',
|
||||||
},
|
},
|
||||||
pipelines: {
|
pipelines: {
|
||||||
title: 'Pipelines',
|
title: 'Pipelines',
|
||||||
|
|||||||
@@ -179,6 +179,8 @@ const jaJP = {
|
|||||||
eventCount: 'イベント:{{count}}',
|
eventCount: 'イベント:{{count}}',
|
||||||
toolCount: 'ツール:{{count}}',
|
toolCount: 'ツール:{{count}}',
|
||||||
starCount: 'スター:{{count}}',
|
starCount: 'スター:{{count}}',
|
||||||
|
uploadLocal: 'ローカルアップロード',
|
||||||
|
debugging: 'デバッグ中',
|
||||||
},
|
},
|
||||||
pipelines: {
|
pipelines: {
|
||||||
title: 'パイプライン',
|
title: 'パイプライン',
|
||||||
|
|||||||
@@ -175,6 +175,8 @@ const zhHans = {
|
|||||||
eventCount: '事件:{{count}}',
|
eventCount: '事件:{{count}}',
|
||||||
toolCount: '工具:{{count}}',
|
toolCount: '工具:{{count}}',
|
||||||
starCount: '星标:{{count}}',
|
starCount: '星标:{{count}}',
|
||||||
|
uploadLocal: '本地上传',
|
||||||
|
debugging: '调试中',
|
||||||
},
|
},
|
||||||
pipelines: {
|
pipelines: {
|
||||||
title: '流水线',
|
title: '流水线',
|
||||||
|
|||||||
Reference in New Issue
Block a user