mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-07-17 18:06:06 +00:00
feat: youhua qianduan
This commit is contained in:
@@ -116,61 +116,63 @@ function AddExtensionContent() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const extensionActions = (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
variant="default"
|
||||||
|
className="px-4 py-2 cursor-pointer"
|
||||||
|
onClick={() => navigate('/home/mcp?id=new')}
|
||||||
|
>
|
||||||
|
<PlusIcon className="w-4 h-4" />
|
||||||
|
{t('mcp.addMCPServer')}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button variant="default" className="px-4 py-2 cursor-pointer">
|
||||||
|
<PlusIcon className="w-4 h-4" />
|
||||||
|
{t('skills.addSkill')}
|
||||||
|
<ChevronDownIcon className="ml-2 w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end">
|
||||||
|
<DropdownMenuItem onClick={() => navigate('/home/skills?action=create')}>
|
||||||
|
{t('skills.createManually')}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem onClick={() => navigate('/home/skills?action=upload')}>
|
||||||
|
{t('skills.uploadZip')}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem onClick={() => navigate('/home/skills?action=github')}>
|
||||||
|
{t('skills.importFromGithub')}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button variant="default" className="px-4 py-2 cursor-pointer">
|
||||||
|
<PlusIcon className="w-4 h-4" />
|
||||||
|
{t('plugins.newPlugin')}
|
||||||
|
<ChevronDownIcon className="ml-2 w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end">
|
||||||
|
<DropdownMenuItem onClick={() => navigate('/home/add-plugin?action=github')}>
|
||||||
|
{t('plugins.installFromGithub')}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem onClick={() => navigate('/home/add-plugin?action=upload')}>
|
||||||
|
{t('plugins.uploadLocal')}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="h-full flex flex-col">
|
<div className="h-full flex flex-col">
|
||||||
<div className="flex flex-row justify-end items-center px-[0.8rem] pb-4 flex-shrink-0 gap-2">
|
|
||||||
<Button
|
|
||||||
variant="default"
|
|
||||||
className="px-6 py-4 cursor-pointer"
|
|
||||||
onClick={() => navigate('/home/mcp?id=new')}
|
|
||||||
>
|
|
||||||
<PlusIcon className="w-4 h-4" />
|
|
||||||
{t('mcp.addMCPServer')}
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<Button variant="default" className="px-6 py-4 cursor-pointer">
|
|
||||||
<PlusIcon className="w-4 h-4" />
|
|
||||||
{t('skills.addSkill')}
|
|
||||||
<ChevronDownIcon className="ml-2 w-4 h-4" />
|
|
||||||
</Button>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent align="end">
|
|
||||||
<DropdownMenuItem onClick={() => navigate('/home/skills?action=create')}>
|
|
||||||
{t('skills.createManually')}
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem onClick={() => navigate('/home/skills?action=upload')}>
|
|
||||||
{t('skills.uploadZip')}
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem onClick={() => navigate('/home/skills?action=github')}>
|
|
||||||
{t('skills.importFromGithub')}
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<Button variant="default" className="px-6 py-4 cursor-pointer">
|
|
||||||
<PlusIcon className="w-4 h-4" />
|
|
||||||
{t('plugins.newPlugin')}
|
|
||||||
<ChevronDownIcon className="ml-2 w-4 h-4" />
|
|
||||||
</Button>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent align="end">
|
|
||||||
<DropdownMenuItem onClick={() => navigate('/home/add-plugin?action=github')}>
|
|
||||||
{t('plugins.installFromGithub')}
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem onClick={() => navigate('/home/add-plugin?action=upload')}>
|
|
||||||
{t('plugins.uploadLocal')}
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex-1 overflow-y-auto">
|
<div className="flex-1 overflow-y-auto">
|
||||||
<MarketPage installPlugin={handleInstallPlugin} />
|
<MarketPage installPlugin={handleInstallPlugin} headerActions={extensionActions} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -50,8 +50,10 @@ interface SortOption {
|
|||||||
// 内部组件,用于处理搜索参数
|
// 内部组件,用于处理搜索参数
|
||||||
function MarketPageContent({
|
function MarketPageContent({
|
||||||
installPlugin,
|
installPlugin,
|
||||||
|
headerActions,
|
||||||
}: {
|
}: {
|
||||||
installPlugin: (plugin: PluginV4) => void;
|
installPlugin: (plugin: PluginV4) => void;
|
||||||
|
headerActions?: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
@@ -572,9 +574,9 @@ function MarketPageContent({
|
|||||||
<div className="h-full flex flex-col">
|
<div className="h-full flex flex-col">
|
||||||
{/* Fixed header with search and sort controls */}
|
{/* Fixed header with search and sort controls */}
|
||||||
<div className="flex-shrink-0 space-y-4 px-3 sm:px-4 py-4 sm:py-6">
|
<div className="flex-shrink-0 space-y-4 px-3 sm:px-4 py-4 sm:py-6">
|
||||||
{/* Search box */}
|
{/* Search box and actions */}
|
||||||
<div className="flex flex-col lg:flex-row items-stretch lg:items-center justify-center gap-3">
|
<div className="flex flex-col lg:flex-row items-stretch lg:items-center justify-center gap-3">
|
||||||
<div className="relative w-full lg:max-w-xl">
|
<div className="relative flex-1 lg:max-w-xl">
|
||||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground h-4 w-4" />
|
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground h-4 w-4" />
|
||||||
<Input
|
<Input
|
||||||
placeholder={t('market.searchPlaceholder')}
|
placeholder={t('market.searchPlaceholder')}
|
||||||
@@ -598,13 +600,17 @@ function MarketPageContent({
|
|||||||
className="pl-10 pr-4 text-sm sm:text-base"
|
className="pl-10 pr-4 text-sm sm:text-base"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
{headerActions && (
|
||||||
|
<div className="flex items-center gap-2 flex-shrink-0">
|
||||||
|
{headerActions}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Sort and more filters */}
|
{/* Sort, filters and tags in one row */}
|
||||||
<div className="flex flex-col sm:flex-row items-center justify-center gap-3 sm:gap-4 px-3 sm:px-4">
|
<div className="flex flex-col sm:flex-row items-center justify-center gap-3 sm:gap-4 px-0">
|
||||||
{/* Sort dropdown */}
|
{/* Sort dropdown */}
|
||||||
<div className="flex items-center gap-2 sm:gap-3">
|
<div className="flex items-center gap-2 sm:gap-3 flex-shrink-0">
|
||||||
<span className="text-xs sm:text-sm text-muted-foreground whitespace-nowrap">
|
<span className="text-xs sm:text-sm text-muted-foreground whitespace-nowrap">
|
||||||
{t('market.sortBy')}:
|
{t('market.sortBy')}:
|
||||||
</span>
|
</span>
|
||||||
@@ -623,7 +629,7 @@ function MarketPageContent({
|
|||||||
|
|
||||||
<Popover>
|
<Popover>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<Button variant="outline" className="relative">
|
<Button variant="outline" className="relative flex-shrink-0">
|
||||||
<SlidersHorizontal className="h-4 w-4" />
|
<SlidersHorizontal className="h-4 w-4" />
|
||||||
<span className="hidden sm:inline">{t('market.filters.more')}</span>
|
<span className="hidden sm:inline">{t('market.filters.more')}</span>
|
||||||
{activeAdvancedFilters > 0 && (
|
{activeAdvancedFilters > 0 && (
|
||||||
@@ -674,40 +680,43 @@ function MarketPageContent({
|
|||||||
</PopoverContent>
|
</PopoverContent>
|
||||||
</Popover>
|
</Popover>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Quick tag filter buttons */}
|
{/* Separator */}
|
||||||
<div className="mx-auto flex w-full max-w-4xl items-center gap-2 overflow-x-auto pb-1 sm:flex-wrap sm:justify-center sm:overflow-visible">
|
<div className="hidden sm:block w-px h-6 bg-border flex-shrink-0"></div>
|
||||||
<Button
|
|
||||||
type="button"
|
{/* Quick tag filter buttons */}
|
||||||
variant={selectedTags.length === 0 ? 'secondary' : 'ghost'}
|
<div className="flex items-center gap-2 overflow-x-auto pb-1 sm:flex-wrap sm:overflow-visible flex-shrink-0">
|
||||||
size="sm"
|
<Button
|
||||||
className="h-8 shrink-0"
|
type="button"
|
||||||
onClick={() => handleTagsChange([])}
|
variant={selectedTags.length === 0 ? 'secondary' : 'ghost'}
|
||||||
>
|
size="sm"
|
||||||
{t('market.allExtensions')}
|
className="h-8 shrink-0"
|
||||||
</Button>
|
onClick={() => handleTagsChange([])}
|
||||||
{availableTags.map((tag) => {
|
>
|
||||||
const selected = selectedTags.includes(tag.tag);
|
{t('market.allExtensions')}
|
||||||
return (
|
</Button>
|
||||||
<Button
|
{availableTags.map((tag) => {
|
||||||
key={tag.tag}
|
const selected = selectedTags.includes(tag.tag);
|
||||||
type="button"
|
return (
|
||||||
variant={selected ? 'secondary' : 'ghost'}
|
<Button
|
||||||
size="sm"
|
key={tag.tag}
|
||||||
className="h-8 shrink-0"
|
type="button"
|
||||||
onClick={() => {
|
variant={selected ? 'secondary' : 'ghost'}
|
||||||
const newTags = selected
|
size="sm"
|
||||||
? selectedTags.filter((t) => t !== tag.tag)
|
className="h-8 shrink-0"
|
||||||
: [...selectedTags, tag.tag];
|
onClick={() => {
|
||||||
handleTagsChange(newTags);
|
const newTags = selected
|
||||||
}}
|
? selectedTags.filter((t) => t !== tag.tag)
|
||||||
>
|
: [...selectedTags, tag.tag];
|
||||||
{tagNames[tag.tag] || tag.tag}
|
handleTagsChange(newTags);
|
||||||
{selected && <X className="h-3.5 w-3.5" />}
|
}}
|
||||||
</Button>
|
>
|
||||||
);
|
{tagNames[tag.tag] || tag.tag}
|
||||||
})}
|
{selected && <X className="h-3.5 w-3.5" />}
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Search results stats */}
|
{/* Search results stats */}
|
||||||
@@ -799,8 +808,10 @@ function MarketPageContent({
|
|||||||
// 主组件,包装在 Suspense 中
|
// 主组件,包装在 Suspense 中
|
||||||
export default function MarketPage({
|
export default function MarketPage({
|
||||||
installPlugin,
|
installPlugin,
|
||||||
|
headerActions,
|
||||||
}: {
|
}: {
|
||||||
installPlugin: (plugin: PluginV4) => void;
|
installPlugin: (plugin: PluginV4) => void;
|
||||||
|
headerActions?: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Suspense
|
<Suspense
|
||||||
@@ -812,7 +823,7 @@ export default function MarketPage({
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<MarketPageContent installPlugin={installPlugin} />
|
<MarketPageContent installPlugin={installPlugin} headerActions={headerActions} />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user