mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-08-11 13:20:58 +00:00
feat(web): add icons/emoji to selectors, sync bot enable status and plugin list in sidebar
- Bot adapter selector: show adapter icon in trigger and dropdown items - Knowledge engine selector: show plugin icon derived from plugin_id - Pipeline binding selector: show pipeline emoji in trigger and dropdown items - Knowledge base selectors (single/multi): show KB emoji in all views - Sidebar bot entries: show green/gray status dot on adapter icon for enable/disable state - Sidebar plugin list: sync after install/uninstall from all entry points (PluginInstalledComponent, plugins page, marketplace page) - Pipeline form: add cursor-pointer to left-side tab list buttons - Clean up unused onBotDeleted prop from BotForm
This commit is contained in:
@@ -144,7 +144,6 @@ export default function BotDetailContent({ id }: { id: string }) {
|
|||||||
<BotForm
|
<BotForm
|
||||||
initBotId={undefined}
|
initBotId={undefined}
|
||||||
onFormSubmit={handleFormSubmit}
|
onFormSubmit={handleFormSubmit}
|
||||||
onBotDeleted={handleBotDeleted}
|
|
||||||
onNewBotCreated={handleNewBotCreated}
|
onNewBotCreated={handleNewBotCreated}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -238,7 +237,6 @@ export default function BotDetailContent({ id }: { id: string }) {
|
|||||||
<BotForm
|
<BotForm
|
||||||
initBotId={id}
|
initBotId={id}
|
||||||
onFormSubmit={handleFormSubmit}
|
onFormSubmit={handleFormSubmit}
|
||||||
onBotDeleted={handleBotDeleted}
|
|
||||||
onNewBotCreated={handleNewBotCreated}
|
onNewBotCreated={handleNewBotCreated}
|
||||||
onDirtyChange={setFormDirty}
|
onDirtyChange={setFormDirty}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -65,13 +65,11 @@ const getFormSchema = (t: (key: string) => string) =>
|
|||||||
export default function BotForm({
|
export default function BotForm({
|
||||||
initBotId,
|
initBotId,
|
||||||
onFormSubmit,
|
onFormSubmit,
|
||||||
onBotDeleted,
|
|
||||||
onNewBotCreated,
|
onNewBotCreated,
|
||||||
onDirtyChange,
|
onDirtyChange,
|
||||||
}: {
|
}: {
|
||||||
initBotId?: string;
|
initBotId?: string;
|
||||||
onFormSubmit: (value: z.infer<ReturnType<typeof getFormSchema>>) => void;
|
onFormSubmit: (value: z.infer<ReturnType<typeof getFormSchema>>) => void;
|
||||||
onBotDeleted: () => void;
|
|
||||||
onNewBotCreated: (botId: string) => void;
|
onNewBotCreated: (botId: string) => void;
|
||||||
onDirtyChange?: (dirty: boolean) => void;
|
onDirtyChange?: (dirty: boolean) => void;
|
||||||
}) {
|
}) {
|
||||||
@@ -234,6 +232,7 @@ export default function BotForm({
|
|||||||
return {
|
return {
|
||||||
label: item.name,
|
label: item.name,
|
||||||
value: item.uuid ?? '',
|
value: item.uuid ?? '',
|
||||||
|
emoji: item.emoji,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -461,13 +460,40 @@ export default function BotForm({
|
|||||||
<FormControl>
|
<FormControl>
|
||||||
<Select onValueChange={field.onChange} {...field}>
|
<Select onValueChange={field.onChange} {...field}>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder={t('bots.selectPipeline')} />
|
{field.value ? (
|
||||||
|
(() => {
|
||||||
|
const pipeline = pipelineNameList.find(
|
||||||
|
(p) => p.value === field.value,
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{pipeline?.emoji && (
|
||||||
|
<span className="text-sm shrink-0">
|
||||||
|
{pipeline.emoji}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<span>{pipeline?.label ?? field.value}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})()
|
||||||
|
) : (
|
||||||
|
<SelectValue
|
||||||
|
placeholder={t('bots.selectPipeline')}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectGroup>
|
<SelectGroup>
|
||||||
{pipelineNameList.map((item) => (
|
{pipelineNameList.map((item) => (
|
||||||
<SelectItem key={item.value} value={item.value}>
|
<SelectItem key={item.value} value={item.value}>
|
||||||
{item.label}
|
<div className="flex items-center gap-2">
|
||||||
|
{item.emoji && (
|
||||||
|
<span className="text-sm shrink-0">
|
||||||
|
{item.emoji}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<span>{item.label}</span>
|
||||||
|
</div>
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectGroup>
|
</SelectGroup>
|
||||||
@@ -508,13 +534,35 @@ export default function BotForm({
|
|||||||
value={field.value}
|
value={field.value}
|
||||||
>
|
>
|
||||||
<SelectTrigger className="w-[240px]">
|
<SelectTrigger className="w-[240px]">
|
||||||
<SelectValue placeholder={t('bots.selectAdapter')} />
|
{field.value ? (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<img
|
||||||
|
src={httpClient.getAdapterIconURL(field.value)}
|
||||||
|
alt=""
|
||||||
|
className="h-5 w-5 rounded"
|
||||||
|
/>
|
||||||
|
<span>
|
||||||
|
{adapterNameList.find(
|
||||||
|
(a) => a.value === field.value,
|
||||||
|
)?.label ?? field.value}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<SelectValue placeholder={t('bots.selectAdapter')} />
|
||||||
|
)}
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectGroup>
|
<SelectGroup>
|
||||||
{adapterNameList.map((item) => (
|
{adapterNameList.map((item) => (
|
||||||
<SelectItem key={item.value} value={item.value}>
|
<SelectItem key={item.value} value={item.value}>
|
||||||
{item.label}
|
<div className="flex items-center gap-2">
|
||||||
|
<img
|
||||||
|
src={httpClient.getAdapterIconURL(item.value)}
|
||||||
|
alt=""
|
||||||
|
className="h-5 w-5 rounded"
|
||||||
|
/>
|
||||||
|
<span>{item.label}</span>
|
||||||
|
</div>
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectGroup>
|
</SelectGroup>
|
||||||
|
|||||||
@@ -6,4 +6,5 @@ export interface IChooseAdapterEntity {
|
|||||||
export interface IPipelineEntity {
|
export interface IPipelineEntity {
|
||||||
label: string;
|
label: string;
|
||||||
value: string;
|
value: string;
|
||||||
|
emoji?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -541,7 +541,25 @@ export default function DynamicFormItemComponent({
|
|||||||
return (
|
return (
|
||||||
<Select value={field.value} onValueChange={field.onChange}>
|
<Select value={field.value} onValueChange={field.onChange}>
|
||||||
<SelectTrigger className="bg-[#ffffff] dark:bg-[#2a2a2e]">
|
<SelectTrigger className="bg-[#ffffff] dark:bg-[#2a2a2e]">
|
||||||
<SelectValue placeholder={t('knowledge.selectKnowledgeBase')} />
|
{field.value && field.value !== '__none__' ? (
|
||||||
|
(() => {
|
||||||
|
const selectedKb = knowledgeBases.find(
|
||||||
|
(kb) => kb.uuid === field.value,
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
{selectedKb?.emoji && (
|
||||||
|
<span className="text-sm shrink-0">
|
||||||
|
{selectedKb.emoji}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
<span>{selectedKb?.name ?? field.value}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})()
|
||||||
|
) : (
|
||||||
|
<SelectValue placeholder={t('knowledge.selectKnowledgeBase')} />
|
||||||
|
)}
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectGroup>
|
<SelectGroup>
|
||||||
@@ -553,7 +571,12 @@ export default function DynamicFormItemComponent({
|
|||||||
<SelectLabel>{engineName}</SelectLabel>
|
<SelectLabel>{engineName}</SelectLabel>
|
||||||
{kbs.map((base) => (
|
{kbs.map((base) => (
|
||||||
<SelectItem key={base.uuid} value={base.uuid ?? ''}>
|
<SelectItem key={base.uuid} value={base.uuid ?? ''}>
|
||||||
{base.name}
|
<div className="flex items-center gap-2">
|
||||||
|
{base.emoji && (
|
||||||
|
<span className="text-sm shrink-0">{base.emoji}</span>
|
||||||
|
)}
|
||||||
|
<span>{base.name}</span>
|
||||||
|
</div>
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectGroup>
|
</SelectGroup>
|
||||||
@@ -597,6 +620,11 @@ export default function DynamicFormItemComponent({
|
|||||||
<div className="flex items-center gap-2 flex-1">
|
<div className="flex items-center gap-2 flex-1">
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="font-medium flex items-center gap-2">
|
<div className="font-medium flex items-center gap-2">
|
||||||
|
{currentKb.emoji && (
|
||||||
|
<span className="text-sm shrink-0">
|
||||||
|
{currentKb.emoji}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
{currentKb.name}
|
{currentKb.name}
|
||||||
{currentKb.knowledge_engine?.name && (
|
{currentKb.knowledge_engine?.name && (
|
||||||
<span className="text-xs px-2 py-0.5 rounded-full bg-purple-100 text-purple-700 dark:bg-purple-900 dark:text-purple-300">
|
<span className="text-xs px-2 py-0.5 rounded-full bg-purple-100 text-purple-700 dark:bg-purple-900 dark:text-purple-300">
|
||||||
@@ -686,7 +714,14 @@ export default function DynamicFormItemComponent({
|
|||||||
aria-label={`Select ${base.name}`}
|
aria-label={`Select ${base.name}`}
|
||||||
/>
|
/>
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<div className="font-medium">{base.name}</div>
|
<div className="font-medium flex items-center gap-2">
|
||||||
|
{base.emoji && (
|
||||||
|
<span className="text-sm shrink-0">
|
||||||
|
{base.emoji}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{base.name}
|
||||||
|
</div>
|
||||||
{base.description && (
|
{base.description && (
|
||||||
<div className="text-sm text-muted-foreground">
|
<div className="text-sm text-muted-foreground">
|
||||||
{base.description}
|
{base.description}
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ import {
|
|||||||
CollapsibleTrigger,
|
CollapsibleTrigger,
|
||||||
} from '@/components/ui/collapsible';
|
} from '@/components/ui/collapsible';
|
||||||
import { ChevronRight, Plus } from 'lucide-react';
|
import { ChevronRight, Plus } from 'lucide-react';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
import { useSidebarData, SidebarEntityItem } from './SidebarDataContext';
|
import { useSidebarData, SidebarEntityItem } from './SidebarDataContext';
|
||||||
|
|
||||||
// Compare two version strings, returns true if v1 > v2
|
// Compare two version strings, returns true if v1 > v2
|
||||||
@@ -316,6 +317,7 @@ function NavItems({
|
|||||||
const canCreate = CREATABLE_CATEGORIES.includes(config.id);
|
const canCreate = CREATABLE_CATEGORIES.includes(config.id);
|
||||||
const isCollapseOnly = COLLAPSIBLE_ONLY_CATEGORIES.includes(config.id);
|
const isCollapseOnly = COLLAPSIBLE_ONLY_CATEGORIES.includes(config.id);
|
||||||
const isPlugin = config.id === 'plugins';
|
const isPlugin = config.id === 'plugins';
|
||||||
|
const isBot = config.id === 'bots';
|
||||||
const isActive =
|
const isActive =
|
||||||
selectedChild?.id === config.id ||
|
selectedChild?.id === config.id ||
|
||||||
pathname === routePrefix ||
|
pathname === routePrefix ||
|
||||||
@@ -391,9 +393,9 @@ function NavItems({
|
|||||||
>
|
>
|
||||||
<a
|
<a
|
||||||
href={itemRoute}
|
href={itemRoute}
|
||||||
className={
|
className={cn(
|
||||||
isPlugin && !item.debug ? 'pr-6' : ''
|
isPlugin && !item.debug ? 'pr-6' : '',
|
||||||
}
|
)}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
router.push(itemRoute);
|
router.push(itemRoute);
|
||||||
@@ -404,11 +406,23 @@ function NavItems({
|
|||||||
{item.emoji}
|
{item.emoji}
|
||||||
</span>
|
</span>
|
||||||
) : item.iconURL ? (
|
) : item.iconURL ? (
|
||||||
<img
|
<span className="relative shrink-0">
|
||||||
src={item.iconURL}
|
<img
|
||||||
alt=""
|
src={item.iconURL}
|
||||||
className="size-4 rounded shrink-0"
|
alt=""
|
||||||
/>
|
className="size-4 rounded"
|
||||||
|
/>
|
||||||
|
{isBot && (
|
||||||
|
<span
|
||||||
|
className={cn(
|
||||||
|
'absolute -bottom-0.5 -right-0.5 size-2 rounded-full border-2 border-sidebar',
|
||||||
|
item.enabled === false
|
||||||
|
? 'bg-muted-foreground/40'
|
||||||
|
: 'bg-green-500',
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
) : null}
|
) : null}
|
||||||
<span className="truncate">{item.name}</span>
|
<span className="truncate">{item.name}</span>
|
||||||
{item.debug && (
|
{item.debug && (
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ export interface SidebarEntityItem {
|
|||||||
emoji?: string;
|
emoji?: string;
|
||||||
iconURL?: string;
|
iconURL?: string;
|
||||||
updatedAt?: string; // ISO timestamp for sorting by most recently edited
|
updatedAt?: string; // ISO timestamp for sorting by most recently edited
|
||||||
|
// Bot-specific fields
|
||||||
|
enabled?: boolean;
|
||||||
// Plugin-specific fields
|
// Plugin-specific fields
|
||||||
installSource?: string;
|
installSource?: string;
|
||||||
installInfo?: Record<string, unknown>;
|
installInfo?: Record<string, unknown>;
|
||||||
@@ -63,6 +65,7 @@ export function SidebarDataProvider({
|
|||||||
name: bot.name,
|
name: bot.name,
|
||||||
iconURL: httpClient.getAdapterIconURL(bot.adapter),
|
iconURL: httpClient.getAdapterIconURL(bot.adapter),
|
||||||
updatedAt: bot.updated_at,
|
updatedAt: bot.updated_at,
|
||||||
|
enabled: bot.enable ?? true,
|
||||||
})),
|
})),
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -310,19 +310,58 @@ export default function KBForm({
|
|||||||
value={field.value}
|
value={field.value}
|
||||||
>
|
>
|
||||||
<SelectTrigger className="w-full bg-[#ffffff] dark:bg-[#2a2a2e]">
|
<SelectTrigger className="w-full bg-[#ffffff] dark:bg-[#2a2a2e]">
|
||||||
<SelectValue
|
{field.value ? (
|
||||||
placeholder={t('knowledge.selectKnowledgeEngine')}
|
(() => {
|
||||||
/>
|
const [author, name] = field.value.split('/');
|
||||||
|
const engine = ragEngines.find(
|
||||||
|
(e) => e.plugin_id === field.value,
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<img
|
||||||
|
src={httpClient.getPluginIconURL(
|
||||||
|
author,
|
||||||
|
name,
|
||||||
|
)}
|
||||||
|
alt=""
|
||||||
|
className="h-5 w-5 rounded"
|
||||||
|
/>
|
||||||
|
<span>
|
||||||
|
{engine
|
||||||
|
? extractI18nObject(engine.name)
|
||||||
|
: field.value}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})()
|
||||||
|
) : (
|
||||||
|
<SelectValue
|
||||||
|
placeholder={t('knowledge.selectKnowledgeEngine')}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent className="fixed z-[1000]">
|
<SelectContent className="fixed z-[1000]">
|
||||||
{ragEngines.map((engine) => (
|
{ragEngines.map((engine) => {
|
||||||
<SelectItem
|
const [author, name] = engine.plugin_id.split('/');
|
||||||
key={engine.plugin_id}
|
return (
|
||||||
value={engine.plugin_id}
|
<SelectItem
|
||||||
>
|
key={engine.plugin_id}
|
||||||
{extractI18nObject(engine.name)}
|
value={engine.plugin_id}
|
||||||
</SelectItem>
|
>
|
||||||
))}
|
<div className="flex items-center gap-2">
|
||||||
|
<img
|
||||||
|
src={httpClient.getPluginIconURL(
|
||||||
|
author,
|
||||||
|
name,
|
||||||
|
)}
|
||||||
|
alt=""
|
||||||
|
className="h-5 w-5 rounded"
|
||||||
|
/>
|
||||||
|
<span>{extractI18nObject(engine.name)}</span>
|
||||||
|
</div>
|
||||||
|
</SelectItem>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { systemInfo } from '@/app/infra/http/HttpClient';
|
|||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { PluginV4 } from '@/app/infra/entities/plugin';
|
import { PluginV4 } from '@/app/infra/entities/plugin';
|
||||||
|
import { useSidebarData } from '@/app/home/components/home-sidebar/SidebarDataContext';
|
||||||
|
|
||||||
enum PluginInstallStatus {
|
enum PluginInstallStatus {
|
||||||
ASK_CONFIRM = 'ask_confirm',
|
ASK_CONFIRM = 'ask_confirm',
|
||||||
@@ -39,6 +40,7 @@ export default function MarketplacePage() {
|
|||||||
|
|
||||||
function MarketplaceContent() {
|
function MarketplaceContent() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const { refreshPlugins } = useSidebarData();
|
||||||
const [modalOpen, setModalOpen] = useState(false);
|
const [modalOpen, setModalOpen] = useState(false);
|
||||||
const [installInfo, setInstallInfo] = useState<Record<string, string>>({});
|
const [installInfo, setInstallInfo] = useState<Record<string, string>>({});
|
||||||
const [pluginInstallStatus, setPluginInstallStatus] =
|
const [pluginInstallStatus, setPluginInstallStatus] =
|
||||||
@@ -83,6 +85,7 @@ function MarketplaceContent() {
|
|||||||
alreadySuccess = true;
|
alreadySuccess = true;
|
||||||
}
|
}
|
||||||
setModalOpen(false);
|
setModalOpen(false);
|
||||||
|
refreshPlugins();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -456,7 +456,7 @@ export default function PipelineFormComponent({
|
|||||||
type="button"
|
type="button"
|
||||||
onClick={() => setActiveSection(section.name)}
|
onClick={() => setActiveSection(section.name)}
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-full flex items-center gap-2 px-3 py-2 rounded-md text-sm font-medium transition-colors text-left',
|
'w-full flex items-center gap-2 px-3 py-2 rounded-md text-sm font-medium transition-colors text-left cursor-pointer',
|
||||||
activeSection === section.name
|
activeSection === section.name
|
||||||
? 'bg-accent text-accent-foreground'
|
? 'bg-accent text-accent-foreground'
|
||||||
: 'text-muted-foreground hover:bg-muted hover:text-foreground',
|
: 'text-muted-foreground hover:bg-muted hover:text-foreground',
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { extractI18nObject } from '@/i18n/I18nProvider';
|
import { extractI18nObject } from '@/i18n/I18nProvider';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { useAsyncTask, AsyncTaskStatus } from '@/hooks/useAsyncTask';
|
import { useAsyncTask, AsyncTaskStatus } from '@/hooks/useAsyncTask';
|
||||||
|
import { useSidebarData } from '@/app/home/components/home-sidebar/SidebarDataContext';
|
||||||
|
|
||||||
export interface PluginInstalledComponentRef {
|
export interface PluginInstalledComponentRef {
|
||||||
refreshPluginList: () => void;
|
refreshPluginList: () => void;
|
||||||
@@ -37,6 +38,7 @@ const PluginInstalledComponent = forwardRef<PluginInstalledComponentRef>(
|
|||||||
(props, ref) => {
|
(props, ref) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const { refreshPlugins } = useSidebarData();
|
||||||
const [pluginList, setPluginList] = useState<PluginCardVO[]>([]);
|
const [pluginList, setPluginList] = useState<PluginCardVO[]>([]);
|
||||||
const [showOperationModal, setShowOperationModal] = useState(false);
|
const [showOperationModal, setShowOperationModal] = useState(false);
|
||||||
const [operationType, setOperationType] = useState<PluginOperationType>(
|
const [operationType, setOperationType] = useState<PluginOperationType>(
|
||||||
@@ -54,6 +56,7 @@ const PluginInstalledComponent = forwardRef<PluginInstalledComponentRef>(
|
|||||||
toast.success(successMessage);
|
toast.success(successMessage);
|
||||||
setShowOperationModal(false);
|
setShowOperationModal(false);
|
||||||
getPluginList();
|
getPluginList();
|
||||||
|
refreshPlugins();
|
||||||
},
|
},
|
||||||
onError: () => {
|
onError: () => {
|
||||||
// Error is already handled in the hook state
|
// Error is already handled in the hook state
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ import { toast } from 'sonner';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { systemInfo } from '@/app/infra/http/HttpClient';
|
import { systemInfo } from '@/app/infra/http/HttpClient';
|
||||||
import { ApiRespPluginSystemStatus } from '@/app/infra/entities/api';
|
import { ApiRespPluginSystemStatus } from '@/app/infra/entities/api';
|
||||||
|
import { useSidebarData } from '@/app/home/components/home-sidebar/SidebarDataContext';
|
||||||
|
|
||||||
enum PluginInstallStatus {
|
enum PluginInstallStatus {
|
||||||
WAIT_INPUT = 'wait_input',
|
WAIT_INPUT = 'wait_input',
|
||||||
@@ -93,6 +94,7 @@ export default function PluginConfigPage() {
|
|||||||
function PluginListView() {
|
function PluginListView() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const { refreshPlugins } = useSidebarData();
|
||||||
const [modalOpen, setModalOpen] = useState(false);
|
const [modalOpen, setModalOpen] = useState(false);
|
||||||
const [installSource, setInstallSource] = useState<string>('local');
|
const [installSource, setInstallSource] = useState<string>('local');
|
||||||
const [installInfo] = useState<Record<string, any>>({}); // eslint-disable-line @typescript-eslint/no-explicit-any
|
const [installInfo] = useState<Record<string, any>>({}); // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
@@ -167,6 +169,7 @@ function PluginListView() {
|
|||||||
resetGithubState();
|
resetGithubState();
|
||||||
setModalOpen(false);
|
setModalOpen(false);
|
||||||
pluginInstalledRef.current?.refreshPluginList();
|
pluginInstalledRef.current?.refreshPluginList();
|
||||||
|
refreshPlugins();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user