mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-06-27 07:54:19 +00:00
refactor(web): migrate from Next.js to Vite + React Router (#2102)
* refactor(web): migrate from Next.js to Vite + React Router
* fix: update build pipelines for Vite migration (out → dist)
- Dockerfile: npm run build → npx vite build, web/out → web/dist
- pyproject.toml: package-data web/out/** → web/dist/**
- paths.py: support both web/dist (Vite) and web/out (legacy) with fallback
* fix: remove .next from git tracking, add to .gitignore
1334 cached files from web/.next/ were accidentally committed.
Added .next/ to both root and web/.gitignore.
* fix: update build process to use Vite and correct output directory
* fix: update pnpm-lock.yaml and eslint config for Vite migration
* style: fix prettier formatting issues
* fix: add eslint-plugin-react-hooks for Vite migration
* fix: remove undefined eslint rule reference, downgrade react-hooks plugin to v5
* fix(web): clean up remaining Next.js artifacts in Vite migration
- Add vite-env.d.ts for import.meta.env and asset type declarations
- Remove dead layout.tsx (providers already in main.tsx)
- Fix useSearchParams destructuring to [searchParams] tuple (11 locations)
- Replace process.env.NEXT_PUBLIC_* with import.meta.env.VITE_*
- Fix langbotIcon.src to langbotIcon (Vite returns URL string)
- Fix Link href to Link to for react-router-dom
- Fix navigate({ scroll: false }) to { preventScrollReset: true }
- Fix [router] dependency arrays to [navigate]
- Remove Next.js plugin from tsconfig, set rsc: false in components.json
- Replace next lint with eslint in lint-staged
* feat: add tools API endpoint and tools-selector form type
Backend:
- Add GET /api/v1/tools — list all available tools (plugin + MCP)
- Add GET /api/v1/tools/<tool_name> — get specific tool details
Frontend:
- Add TOOLS_SELECTOR form type for plugin config forms
- Multi-select dialog with tool name and description
- Add PluginTool entity type and API client methods
* Revert "feat: add tools API endpoint and tools-selector form type"
This reverts commit 3c637fc563.
This commit is contained in:
@@ -1,5 +1,3 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { toast } from 'sonner';
|
||||
import { Copy, Check, Trash2, Plus } from 'lucide-react';
|
||||
import { useRouter, usePathname, useSearchParams } from 'next/navigation';
|
||||
import { useNavigate, useLocation, useSearchParams } from 'react-router-dom';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
@@ -67,9 +65,10 @@ export default function ApiIntegrationDialog({
|
||||
onOpenChange,
|
||||
}: ApiIntegrationDialogProps) {
|
||||
const { t } = useTranslation();
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const searchParams = useSearchParams();
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const pathname = location.pathname;
|
||||
const [searchParams] = useSearchParams();
|
||||
const [activeTab, setActiveTab] = useState('apikeys');
|
||||
const [apiKeys, setApiKeys] = useState<ApiKey[]>([]);
|
||||
const [webhooks, setWebhooks] = useState<Webhook[]>([]);
|
||||
@@ -94,7 +93,9 @@ export default function ApiIntegrationDialog({
|
||||
if (open) {
|
||||
const params = new URLSearchParams(searchParams.toString());
|
||||
params.set('action', 'showApiIntegrationSettings');
|
||||
router.replace(`${pathname}?${params.toString()}`, { scroll: false });
|
||||
navigate(`${pathname}?${params.toString()}`, {
|
||||
preventScrollReset: true,
|
||||
});
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
@@ -108,7 +109,7 @@ export default function ApiIntegrationDialog({
|
||||
const newUrl = params.toString()
|
||||
? `${pathname}?${params.toString()}`
|
||||
: pathname;
|
||||
router.replace(newUrl, { scroll: false });
|
||||
navigate(newUrl, { preventScrollReset: true });
|
||||
}
|
||||
onOpenChange(newOpen);
|
||||
};
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import { SidebarChildVO } from '@/app/home/components/home-sidebar/HomeSidebarChild';
|
||||
import { useRouter, usePathname, useSearchParams } from 'next/navigation';
|
||||
import { useNavigate, useLocation, useSearchParams } from 'react-router-dom';
|
||||
import { sidebarConfigList } from '@/app/home/components/home-sidebar/sidbarConfigList';
|
||||
import langbotIcon from '@/app/assets/langbot-logo.webp';
|
||||
import { systemInfo, httpClient } from '@/app/infra/http/HttpClient';
|
||||
@@ -29,7 +27,7 @@ import {
|
||||
Github,
|
||||
Zap,
|
||||
} from 'lucide-react';
|
||||
import { useTheme } from 'next-themes';
|
||||
import { useTheme } from '@/components/providers/theme-provider';
|
||||
|
||||
import {
|
||||
DropdownMenu,
|
||||
@@ -244,9 +242,10 @@ function NavItems({
|
||||
sectionOpenState: Record<string, boolean>;
|
||||
onSectionToggle: (id: string, open: boolean) => void;
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const searchParams = useSearchParams();
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const pathname = location.pathname;
|
||||
const [searchParams] = useSearchParams();
|
||||
const sidebarData = useSidebarData();
|
||||
const { setPendingPluginInstallAction } = sidebarData;
|
||||
const { state: sidebarState, isMobile } = useSidebar();
|
||||
@@ -413,7 +412,7 @@ function NavItems({
|
||||
'bg-accent text-accent-foreground font-medium',
|
||||
)}
|
||||
onClick={() => {
|
||||
router.push(itemRoute);
|
||||
navigate(itemRoute);
|
||||
setPopoverOpen((prev) => ({
|
||||
...prev,
|
||||
[config.id]: false,
|
||||
@@ -471,7 +470,7 @@ function NavItems({
|
||||
)}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
router.push(itemRoute);
|
||||
navigate(itemRoute);
|
||||
}}
|
||||
>
|
||||
{item.emoji ? (
|
||||
@@ -623,7 +622,7 @@ function NavItems({
|
||||
<DropdownMenuItem
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
router.push('/home/market');
|
||||
navigate('/home/market');
|
||||
setPopoverOpen((prev) => ({
|
||||
...prev,
|
||||
[config.id]: false,
|
||||
@@ -638,7 +637,7 @@ function NavItems({
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setPendingPluginInstallAction('local');
|
||||
router.push('/home/plugins');
|
||||
navigate('/home/plugins');
|
||||
setPopoverOpen((prev) => ({
|
||||
...prev,
|
||||
[config.id]: false,
|
||||
@@ -652,7 +651,7 @@ function NavItems({
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setPendingPluginInstallAction('github');
|
||||
router.push('/home/plugins');
|
||||
navigate('/home/plugins');
|
||||
setPopoverOpen((prev) => ({
|
||||
...prev,
|
||||
[config.id]: false,
|
||||
@@ -669,7 +668,7 @@ function NavItems({
|
||||
type="button"
|
||||
className="p-1 rounded-sm text-muted-foreground hover:bg-accent hover:text-accent-foreground transition-colors"
|
||||
onClick={() => {
|
||||
router.push(`${routePrefix}?id=new`);
|
||||
navigate(`${routePrefix}?id=new`);
|
||||
setPopoverOpen((prev) => ({
|
||||
...prev,
|
||||
[config.id]: false,
|
||||
@@ -731,7 +730,7 @@ function NavItems({
|
||||
<DropdownMenuItem
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
router.push('/home/market');
|
||||
navigate('/home/market');
|
||||
}}
|
||||
>
|
||||
<Store className="size-4" />
|
||||
@@ -742,7 +741,7 @@ function NavItems({
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setPendingPluginInstallAction('local');
|
||||
router.push('/home/plugins');
|
||||
navigate('/home/plugins');
|
||||
}}
|
||||
>
|
||||
<Upload className="size-4" />
|
||||
@@ -752,7 +751,7 @@ function NavItems({
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setPendingPluginInstallAction('github');
|
||||
router.push('/home/plugins');
|
||||
navigate('/home/plugins');
|
||||
}}
|
||||
>
|
||||
<Github className="size-4" />
|
||||
@@ -766,7 +765,7 @@ function NavItems({
|
||||
className="p-1 rounded-sm text-sidebar-foreground/70 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground opacity-0 group-hover/category-header:opacity-100 transition-all"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
router.push(`${routePrefix}?id=new`);
|
||||
navigate(`${routePrefix}?id=new`);
|
||||
}}
|
||||
>
|
||||
<Plus className="size-3.5" />
|
||||
@@ -1029,9 +1028,10 @@ export default function HomeSidebar({
|
||||
}: {
|
||||
onSelectedChangeAction: (sidebarChild: SidebarChildVO) => void;
|
||||
}) {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const searchParams = useSearchParams();
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const pathname = location.pathname;
|
||||
const [searchParams] = useSearchParams();
|
||||
const { isMobile } = useSidebar();
|
||||
|
||||
useEffect(() => {
|
||||
@@ -1071,14 +1071,16 @@ export default function HomeSidebar({
|
||||
if (open) {
|
||||
const params = new URLSearchParams(searchParams.toString());
|
||||
params.set('action', 'showModelSettings');
|
||||
router.replace(`${pathname}?${params.toString()}`, { scroll: false });
|
||||
navigate(`${pathname}?${params.toString()}`, {
|
||||
preventScrollReset: true,
|
||||
});
|
||||
} else {
|
||||
const params = new URLSearchParams(searchParams.toString());
|
||||
params.delete('action');
|
||||
const newUrl = params.toString()
|
||||
? `${pathname}?${params.toString()}`
|
||||
: pathname;
|
||||
router.replace(newUrl, { scroll: false });
|
||||
navigate(newUrl, { preventScrollReset: true });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1087,14 +1089,16 @@ export default function HomeSidebar({
|
||||
if (open) {
|
||||
const params = new URLSearchParams(searchParams.toString());
|
||||
params.set('action', 'showAccountSettings');
|
||||
router.replace(`${pathname}?${params.toString()}`, { scroll: false });
|
||||
navigate(`${pathname}?${params.toString()}`, {
|
||||
preventScrollReset: true,
|
||||
});
|
||||
} else {
|
||||
const params = new URLSearchParams(searchParams.toString());
|
||||
params.delete('action');
|
||||
const newUrl = params.toString()
|
||||
? `${pathname}?${params.toString()}`
|
||||
: pathname;
|
||||
router.replace(newUrl, { scroll: false });
|
||||
navigate(newUrl, { preventScrollReset: true });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1165,7 +1169,7 @@ export default function HomeSidebar({
|
||||
// User click: update state AND navigate
|
||||
function handleChildClick(child: SidebarChildVO) {
|
||||
selectChild(child);
|
||||
router.push(child.route);
|
||||
navigate(child.route);
|
||||
}
|
||||
|
||||
function initSelect() {
|
||||
@@ -1226,7 +1230,7 @@ export default function HomeSidebar({
|
||||
tooltip="LangBot"
|
||||
>
|
||||
<img
|
||||
src={langbotIcon.src}
|
||||
src={langbotIcon}
|
||||
alt="LangBot"
|
||||
className="size-8 rounded-lg"
|
||||
/>
|
||||
@@ -1406,7 +1410,7 @@ export default function HomeSidebar({
|
||||
<DropdownMenuItem
|
||||
onClick={() => {
|
||||
setUserMenuOpen(false);
|
||||
router.push('/wizard');
|
||||
navigate('/wizard');
|
||||
}}
|
||||
>
|
||||
<Zap className="text-blue-500" />
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
'use client';
|
||||
|
||||
import React, {
|
||||
createContext,
|
||||
useContext,
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Plus, Boxes } from 'lucide-react';
|
||||
import { httpClient, systemInfo } from '@/app/infra/http/HttpClient';
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Plus, MessageSquareText, Cpu, Eye, Wrench, Check } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
'use client';
|
||||
|
||||
import { Plus, X } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Input } from '@/components/ui/input';
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Trash2, Eye, Wrench, Check } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
Plus,
|
||||
@@ -135,7 +133,7 @@ export default function ProviderCard({
|
||||
{isLangBotModels ? (
|
||||
<div className="w-9 h-9 rounded-lg overflow-hidden flex-shrink-0">
|
||||
<img
|
||||
src={langbotIcon.src}
|
||||
src={langbotIcon}
|
||||
alt="LangBot"
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
'use client';
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import ReactMarkdown from 'react-markdown';
|
||||
import remarkGfm from 'remark-gfm';
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
'use client';
|
||||
|
||||
import * as React from 'react';
|
||||
import { useState, useEffect } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
'use client';
|
||||
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import { httpClient } from '@/app/infra/http/HttpClient';
|
||||
import type {
|
||||
|
||||
Reference in New Issue
Block a user