mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-06-09 07:16:04 +00:00
* fix(web): auto-redirect to wizard on first visit and change sidebar icons to blue * refactor(wizard): use backend metadata table instead of localStorage for wizard completion state - Add wizard_completed field to system info API (read from metadata table) - Add POST /api/v1/system/wizard/completed endpoint to mark wizard done - Frontend home layout checks systemInfo.wizard_completed for auto-redirect - Wizard calls markWizardCompleted API on skip/finish - Ensures consistent behavior across all browsers on the same instance * fix(wizard): update systemInfo in memory before navigation to prevent redirect loop * fix(monitoring): prevent horizontal overflow and unify empty state styles * fix(wizard): use Object.assign for systemInfo and await wizard completion API - Replace systemInfo reassignment with Object.assign in all 3 locations to preserve object identity across module imports - Await markWizardCompleted() POST in wizard skip/finish handlers instead of fire-and-forget to ensure backend persistence - Always re-fetch systemInfo in home layout to get latest wizard_completed state from backend * fix(wizard): prevent redirect loop by blocking navigation on failed status save - Refactor wizard_completed (boolean) to wizard_status (string: none/skipped/completed) - Remove ALL localStorage usage from wizard page (form state persistence) - Replace AlertDialogAction with Button so skip dialog stays open during POST - Add loading spinners for skip and complete actions - If POST fails, show error toast and keep dialog/button active for retry - If POST succeeds, update in-memory state and navigate * fix(wizard): fix row[0].value bug causing GET /info to always return wizard_status=none conn.execute(select(Entity)) returns Row with raw column values, not ORM entities. row[0] is the key column (a string), so row[0].value raises AttributeError which was silently swallowed by except-pass, making the GET endpoint always return wizard_status=none regardless of DB state. * fix(wizard): replace AlertDialog with Dialog for skip confirmation to remove slide animation * chore: optimize toast in wizard * fix(wizard): set default token value for Telegram adapter and initialize adapter config in wizard * feat(web): move webhook URL to dynamic form system, add market category filter, fix layout overflow - Add 'webhook-url' dynamic form field type rendered as read-only input with copy button, defined in adapter YAML specs instead of hardcoded in BotForm. Supports show_if conditions for optional-webhook adapters. - Remove hardcoded webhook display logic from BotForm.tsx, pass webhook URLs via systemContext to DynamicFormComponent. - Fetch webhook URLs after bot creation in wizard and pass to Step 1. - Support ?category= query param on /home/market page for filtering by component type (mirrors langbot-space behavior). - Link 'install knowledge engine' hint to /home/market?category=KnowledgeEngine. - Fix SidebarInset missing min-w-0 causing content overflow when sidebar is expanded. - Add vertical divider between plugin detail config and readme panels. - Fix infinite re-render loop in DynamicFormComponent by memoizing editableItems array. * fix: lint * fix(web): change systemInfo to const to satisfy prefer-const lint rule * fix: update adapter descriptions for clarity and usage requirements
99 lines
3.3 KiB
TypeScript
99 lines
3.3 KiB
TypeScript
'use client';
|
|
|
|
import { useEffect } from 'react';
|
|
import PluginForm from '@/app/home/plugins/components/plugin-installed/plugin-form/PluginForm';
|
|
import PluginReadme from '@/app/home/plugins/components/plugin-installed/plugin-readme/PluginReadme';
|
|
import { useSidebarData } from '@/app/home/components/home-sidebar/SidebarDataContext';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { Bug } from 'lucide-react';
|
|
|
|
/**
|
|
* Plugin detail page content.
|
|
* The `id` prop is the composite key "author/name".
|
|
*/
|
|
export default function PluginDetailContent({ id }: { id: string }) {
|
|
const { t } = useTranslation();
|
|
const { plugins, setDetailEntityName, refreshPlugins } = useSidebarData();
|
|
|
|
// Parse "author/name" composite key
|
|
const slashIndex = id.indexOf('/');
|
|
const pluginAuthor = slashIndex >= 0 ? id.substring(0, slashIndex) : '';
|
|
const pluginName = slashIndex >= 0 ? id.substring(slashIndex + 1) : id;
|
|
|
|
const plugin = plugins.find((p) => p.id === id);
|
|
|
|
// Set breadcrumb entity name
|
|
useEffect(() => {
|
|
setDetailEntityName(plugin?.name ?? `${pluginAuthor}/${pluginName}`);
|
|
return () => setDetailEntityName(null);
|
|
}, [plugin, pluginAuthor, pluginName, setDetailEntityName]);
|
|
|
|
function handleFormSubmit(timeout?: number) {
|
|
if (timeout) {
|
|
setTimeout(() => {
|
|
refreshPlugins();
|
|
}, timeout);
|
|
} else {
|
|
refreshPlugins();
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="flex h-full flex-col">
|
|
<div className="flex items-center gap-3 pb-4 shrink-0">
|
|
<h1 className="text-xl font-semibold">
|
|
{pluginAuthor}/{pluginName}
|
|
</h1>
|
|
{plugin?.debug ? (
|
|
<Badge
|
|
variant="outline"
|
|
className="text-[0.7rem] border-orange-400 text-orange-400"
|
|
>
|
|
<Bug className="size-3.5" />
|
|
{t('plugins.debugging')}
|
|
</Badge>
|
|
) : plugin?.installSource === 'github' ? (
|
|
<Badge
|
|
variant="outline"
|
|
className="text-[0.7rem] border-blue-400 text-blue-400"
|
|
>
|
|
{t('plugins.fromGithub')}
|
|
</Badge>
|
|
) : plugin?.installSource === 'local' ? (
|
|
<Badge
|
|
variant="outline"
|
|
className="text-[0.7rem] border-green-400 text-green-400"
|
|
>
|
|
{t('plugins.fromLocal')}
|
|
</Badge>
|
|
) : plugin?.installSource === 'marketplace' ? (
|
|
<Badge
|
|
variant="outline"
|
|
className="text-[0.7rem] border-purple-400 text-purple-400"
|
|
>
|
|
{t('plugins.fromMarketplace')}
|
|
</Badge>
|
|
) : null}
|
|
</div>
|
|
|
|
<div className="flex flex-1 flex-col md:flex-row overflow-hidden min-h-0 gap-6 max-w-full">
|
|
{/* Left side - Config */}
|
|
<div className="md:w-[380px] md:flex-shrink-0 overflow-y-auto overflow-x-hidden">
|
|
<PluginForm
|
|
pluginAuthor={pluginAuthor}
|
|
pluginName={pluginName}
|
|
onFormSubmit={handleFormSubmit}
|
|
/>
|
|
</div>
|
|
{/* Divider */}
|
|
<div className="hidden md:block w-px bg-border shrink-0" />
|
|
{/* Right side - Readme */}
|
|
<div className="flex-1 overflow-y-auto overflow-x-hidden min-w-0">
|
|
<PluginReadme pluginAuthor={pluginAuthor} pluginName={pluginName} />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|