mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-06-28 00:14:21 +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:
@@ -541,7 +541,25 @@ export default function DynamicFormItemComponent({
|
||||
return (
|
||||
<Select value={field.value} onValueChange={field.onChange}>
|
||||
<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>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
@@ -553,7 +571,12 @@ export default function DynamicFormItemComponent({
|
||||
<SelectLabel>{engineName}</SelectLabel>
|
||||
{kbs.map((base) => (
|
||||
<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>
|
||||
))}
|
||||
</SelectGroup>
|
||||
@@ -597,6 +620,11 @@ export default function DynamicFormItemComponent({
|
||||
<div className="flex items-center gap-2 flex-1">
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="font-medium flex items-center gap-2">
|
||||
{currentKb.emoji && (
|
||||
<span className="text-sm shrink-0">
|
||||
{currentKb.emoji}
|
||||
</span>
|
||||
)}
|
||||
{currentKb.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">
|
||||
@@ -686,7 +714,14 @@ export default function DynamicFormItemComponent({
|
||||
aria-label={`Select ${base.name}`}
|
||||
/>
|
||||
<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 && (
|
||||
<div className="text-sm text-muted-foreground">
|
||||
{base.description}
|
||||
|
||||
@@ -77,6 +77,7 @@ import {
|
||||
CollapsibleTrigger,
|
||||
} from '@/components/ui/collapsible';
|
||||
import { ChevronRight, Plus } from 'lucide-react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useSidebarData, SidebarEntityItem } from './SidebarDataContext';
|
||||
|
||||
// Compare two version strings, returns true if v1 > v2
|
||||
@@ -316,6 +317,7 @@ function NavItems({
|
||||
const canCreate = CREATABLE_CATEGORIES.includes(config.id);
|
||||
const isCollapseOnly = COLLAPSIBLE_ONLY_CATEGORIES.includes(config.id);
|
||||
const isPlugin = config.id === 'plugins';
|
||||
const isBot = config.id === 'bots';
|
||||
const isActive =
|
||||
selectedChild?.id === config.id ||
|
||||
pathname === routePrefix ||
|
||||
@@ -391,9 +393,9 @@ function NavItems({
|
||||
>
|
||||
<a
|
||||
href={itemRoute}
|
||||
className={
|
||||
isPlugin && !item.debug ? 'pr-6' : ''
|
||||
}
|
||||
className={cn(
|
||||
isPlugin && !item.debug ? 'pr-6' : '',
|
||||
)}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
router.push(itemRoute);
|
||||
@@ -404,11 +406,23 @@ function NavItems({
|
||||
{item.emoji}
|
||||
</span>
|
||||
) : item.iconURL ? (
|
||||
<img
|
||||
src={item.iconURL}
|
||||
alt=""
|
||||
className="size-4 rounded shrink-0"
|
||||
/>
|
||||
<span className="relative shrink-0">
|
||||
<img
|
||||
src={item.iconURL}
|
||||
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}
|
||||
<span className="truncate">{item.name}</span>
|
||||
{item.debug && (
|
||||
|
||||
@@ -18,6 +18,8 @@ export interface SidebarEntityItem {
|
||||
emoji?: string;
|
||||
iconURL?: string;
|
||||
updatedAt?: string; // ISO timestamp for sorting by most recently edited
|
||||
// Bot-specific fields
|
||||
enabled?: boolean;
|
||||
// Plugin-specific fields
|
||||
installSource?: string;
|
||||
installInfo?: Record<string, unknown>;
|
||||
@@ -63,6 +65,7 @@ export function SidebarDataProvider({
|
||||
name: bot.name,
|
||||
iconURL: httpClient.getAdapterIconURL(bot.adapter),
|
||||
updatedAt: bot.updated_at,
|
||||
enabled: bot.enable ?? true,
|
||||
})),
|
||||
);
|
||||
} catch (error) {
|
||||
|
||||
Reference in New Issue
Block a user