From 747954533932949a54d6a78b68b18258c7912c0d Mon Sep 17 00:00:00 2001 From: Junyan Qin Date: Thu, 25 Dec 2025 20:54:00 +0800 Subject: [PATCH] feat: implement models dialog for managing LLM and embedding models with dynamic URL handling --- .../components/home-sidebar/HomeSidebar.tsx | 48 +++- .../home-sidebar/sidbarConfigList.tsx | 21 -- .../models-dialog}/LLMConfig.module.css | 0 .../models-dialog/ModelsDialog.tsx} | 219 ++++++++++-------- .../component/ChooseRequesterEntity.ts | 0 .../component/ICreateEmbeddingField.ts | 0 .../component/ICreateLLMField.ts | 0 .../embedding-card/EmbeddingCard.module.css | 0 .../embedding-card/EmbeddingCard.tsx | 0 .../embedding-card/EmbeddingCardVO.ts | 0 .../embedding-form/EmbeddingForm.tsx | 0 .../component/llm-card/LLMCard.module.css | 0 .../component/llm-card/LLMCard.tsx | 0 .../component/llm-card/LLMCardVO.ts | 0 .../component/llm-form/LLMForm.tsx | 0 web/src/i18n/locales/zh-Hans.ts | 2 +- 16 files changed, 175 insertions(+), 115 deletions(-) rename web/src/app/home/{models => components/models-dialog}/LLMConfig.module.css (100%) rename web/src/app/home/{models/page.tsx => components/models-dialog/ModelsDialog.tsx} (57%) rename web/src/app/home/{models => components/models-dialog}/component/ChooseRequesterEntity.ts (100%) rename web/src/app/home/{models => components/models-dialog}/component/ICreateEmbeddingField.ts (100%) rename web/src/app/home/{models => components/models-dialog}/component/ICreateLLMField.ts (100%) rename web/src/app/home/{models => components/models-dialog}/component/embedding-card/EmbeddingCard.module.css (100%) rename web/src/app/home/{models => components/models-dialog}/component/embedding-card/EmbeddingCard.tsx (100%) rename web/src/app/home/{models => components/models-dialog}/component/embedding-card/EmbeddingCardVO.ts (100%) rename web/src/app/home/{models => components/models-dialog}/component/embedding-form/EmbeddingForm.tsx (100%) rename web/src/app/home/{models => components/models-dialog}/component/llm-card/LLMCard.module.css (100%) rename web/src/app/home/{models => components/models-dialog}/component/llm-card/LLMCard.tsx (100%) rename web/src/app/home/{models => components/models-dialog}/component/llm-card/LLMCardVO.ts (100%) rename web/src/app/home/{models => components/models-dialog}/component/llm-form/LLMForm.tsx (100%) diff --git a/web/src/app/home/components/home-sidebar/HomeSidebar.tsx b/web/src/app/home/components/home-sidebar/HomeSidebar.tsx index db2418df..764acfe2 100644 --- a/web/src/app/home/components/home-sidebar/HomeSidebar.tsx +++ b/web/src/app/home/components/home-sidebar/HomeSidebar.tsx @@ -6,7 +6,7 @@ import { SidebarChild, SidebarChildVO, } from '@/app/home/components/home-sidebar/HomeSidebarChild'; -import { useRouter, usePathname } from 'next/navigation'; +import { useRouter, usePathname, useSearchParams } from 'next/navigation'; import { sidebarConfigList } from '@/app/home/components/home-sidebar/sidbarConfigList'; import langbotIcon from '@/app/assets/langbot-logo.webp'; import { systemInfo } from '@/app/infra/http/HttpClient'; @@ -36,6 +36,7 @@ import { Badge } from '@/components/ui/badge'; import PasswordChangeDialog from '@/app/home/components/password-change-dialog/PasswordChangeDialog'; import ApiIntegrationDialog from '@/app/home/components/api-integration-dialog/ApiIntegrationDialog'; import NewVersionDialog from '@/app/home/components/new-version-dialog/NewVersionDialog'; +import ModelsDialog from '@/app/home/components/models-dialog/ModelsDialog'; import { GitHubRelease } from '@/app/infra/http/CloudServiceClient'; // Compare two version strings, returns true if v1 > v2 @@ -67,11 +68,19 @@ export default function HomeSidebar({ // 路由相关 const router = useRouter(); const pathname = usePathname(); + const searchParams = useSearchParams(); // 路由被动变化时处理 useEffect(() => { handleRouteChange(pathname); }, [pathname]); + // 检查 URL 参数,自动打开模型对话框 + useEffect(() => { + if (searchParams.get('action') === 'showModelSettings') { + setModelsDialogOpen(true); + } + }, [searchParams]); + const [selectedChild, setSelectedChild] = useState(); const { theme, setTheme } = useTheme(); const { t } = useTranslation(); @@ -85,6 +94,24 @@ export default function HomeSidebar({ ); const [hasNewVersion, setHasNewVersion] = useState(false); const [versionDialogOpen, setVersionDialogOpen] = useState(false); + const [modelsDialogOpen, setModelsDialogOpen] = useState(false); + + // 处理模型对话框的打开和关闭,同时更新 URL + function handleModelsDialogChange(open: boolean) { + setModelsDialogOpen(open); + if (open) { + const params = new URLSearchParams(searchParams.toString()); + params.set('action', 'showModelSettings'); + router.replace(`${pathname}?${params.toString()}`, { scroll: false }); + } else { + const params = new URLSearchParams(searchParams.toString()); + params.delete('action'); + const newUrl = params.toString() + ? `${pathname}?${params.toString()}` + : pathname; + router.replace(newUrl, { scroll: false }); + } + } useEffect(() => { initSelect(); @@ -252,6 +279,21 @@ export default function HomeSidebar({ )} + handleModelsDialogChange(true)} + isSelected={false} + icon={ + + + + } + name={t('models.title')} + /> + { @@ -414,6 +456,10 @@ export default function HomeSidebar({ onOpenChange={setVersionDialogOpen} release={latestRelease} /> + ); } diff --git a/web/src/app/home/components/home-sidebar/sidbarConfigList.tsx b/web/src/app/home/components/home-sidebar/sidbarConfigList.tsx index b3edb98a..868ac5b4 100644 --- a/web/src/app/home/components/home-sidebar/sidbarConfigList.tsx +++ b/web/src/app/home/components/home-sidebar/sidbarConfigList.tsx @@ -27,27 +27,6 @@ export const sidebarConfigList = [ zh_Hans: 'https://docs.langbot.app/zh/deploy/platforms/readme.html', }, }), - new SidebarChildVO({ - id: 'models', - name: t('models.title'), - icon: ( - - - - ), - route: '/home/models', - description: t('models.description'), - helpLink: { - en_US: 'https://docs.langbot.app/en/deploy/models/readme.html', - zh_Hans: 'https://docs.langbot.app/zh/deploy/models/readme.html', - }, - }), - new SidebarChildVO({ id: 'pipelines', name: t('pipelines.title'), diff --git a/web/src/app/home/models/LLMConfig.module.css b/web/src/app/home/components/models-dialog/LLMConfig.module.css similarity index 100% rename from web/src/app/home/models/LLMConfig.module.css rename to web/src/app/home/components/models-dialog/LLMConfig.module.css diff --git a/web/src/app/home/models/page.tsx b/web/src/app/home/components/models-dialog/ModelsDialog.tsx similarity index 57% rename from web/src/app/home/models/page.tsx rename to web/src/app/home/components/models-dialog/ModelsDialog.tsx index 7c33918e..6a624d17 100644 --- a/web/src/app/home/models/page.tsx +++ b/web/src/app/home/components/models-dialog/ModelsDialog.tsx @@ -1,11 +1,10 @@ 'use client'; import { useState, useEffect } from 'react'; -import { LLMCardVO } from '@/app/home/models/component/llm-card/LLMCardVO'; -import styles from './LLMConfig.module.css'; -import LLMCard from '@/app/home/models/component/llm-card/LLMCard'; -import LLMForm from '@/app/home/models/component/llm-form/LLMForm'; -import CreateCardComponent from '@/app/infra/basic-component/create-card-component/CreateCardComponent'; +import { Plus, MessageSquareText, Cpu, Info } from 'lucide-react'; +import { LLMCardVO } from './component/llm-card/LLMCardVO'; +import LLMCard from './component/llm-card/LLMCard'; +import LLMForm from './component/llm-form/LLMForm'; import { httpClient } from '@/app/infra/http/HttpClient'; import { LLMModel } from '@/app/infra/entities/api'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; @@ -15,15 +14,25 @@ import { DialogHeader, DialogTitle, } from '@/components/ui/dialog'; +import { Button } from '@/components/ui/button'; import { toast } from 'sonner'; import { useTranslation } from 'react-i18next'; import { extractI18nObject } from '@/i18n/I18nProvider'; -import { EmbeddingCardVO } from '@/app/home/models/component/embedding-card/EmbeddingCardVO'; -import EmbeddingCard from '@/app/home/models/component/embedding-card/EmbeddingCard'; -import EmbeddingForm from '@/app/home/models/component/embedding-form/EmbeddingForm'; +import { EmbeddingCardVO } from './component/embedding-card/EmbeddingCardVO'; +import EmbeddingCard from './component/embedding-card/EmbeddingCard'; +import EmbeddingForm from './component/embedding-form/EmbeddingForm'; -export default function LLMConfigPage() { +interface ModelsDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; +} + +export default function ModelsDialog({ + open, + onOpenChange, +}: ModelsDialogProps) { const { t } = useTranslation(); + const [activeTab, setActiveTab] = useState('llm'); const [cardList, setCardList] = useState([]); const [modalOpen, setModalOpen] = useState(false); const [isEditForm, setIsEditForm] = useState(false); @@ -37,9 +46,11 @@ export default function LLMConfigPage() { useState(null); useEffect(() => { - getLLMModelList(); - getEmbeddingModelList(); - }, []); + if (open) { + getLLMModelList(); + getEmbeddingModelList(); + } + }, [open]); async function getLLMModelList() { const requesterNameListResp = await httpClient.getProviderRequesters('llm'); @@ -134,7 +145,108 @@ export default function LLMConfigPage() { } return ( -
+ <> + { + if (!newOpen && (modalOpen || embeddingModalOpen)) { + return; + } + onOpenChange(newOpen); + }} + > + + + {t('models.title')} + + + +
+ + + + {t('llm.llmModels')} + + + + {t('embedding.embeddingModels')} + + + +
+ +
+ + {activeTab === 'llm' ? ( +

+ {t('llm.description')} +

+ ) : ( +

+ {t('embedding.description')} +

+ )} +
+ + +
+ {cardList.map((cardVO) => { + return ( +
{ + selectLLM(cardVO); + }} + > + +
+ ); + })} +
+
+ + +
+ {embeddingCardList.map((cardVO) => { + return ( +
{ + selectEmbedding(cardVO); + }} + > + +
+ ); + })} +
+
+
+
+
+ @@ -159,6 +271,7 @@ export default function LLMConfigPage() { /> + @@ -185,84 +298,6 @@ export default function LLMConfigPage() { /> - - -
-
- - - {t('llm.llmModels')} - - - {t('embedding.embeddingModels')} - - -
- -
-

- {t('llm.description')} -

-
-
- -
-

- {t('embedding.description')} -

-
-
-
- - -
- - {cardList.map((cardVO) => { - return ( -
{ - selectLLM(cardVO); - }} - > - -
- ); - })} -
-
- - -
- - {embeddingCardList.map((cardVO) => { - return ( -
{ - selectEmbedding(cardVO); - }} - > - -
- ); - })} -
-
-
-
+ ); } diff --git a/web/src/app/home/models/component/ChooseRequesterEntity.ts b/web/src/app/home/components/models-dialog/component/ChooseRequesterEntity.ts similarity index 100% rename from web/src/app/home/models/component/ChooseRequesterEntity.ts rename to web/src/app/home/components/models-dialog/component/ChooseRequesterEntity.ts diff --git a/web/src/app/home/models/component/ICreateEmbeddingField.ts b/web/src/app/home/components/models-dialog/component/ICreateEmbeddingField.ts similarity index 100% rename from web/src/app/home/models/component/ICreateEmbeddingField.ts rename to web/src/app/home/components/models-dialog/component/ICreateEmbeddingField.ts diff --git a/web/src/app/home/models/component/ICreateLLMField.ts b/web/src/app/home/components/models-dialog/component/ICreateLLMField.ts similarity index 100% rename from web/src/app/home/models/component/ICreateLLMField.ts rename to web/src/app/home/components/models-dialog/component/ICreateLLMField.ts diff --git a/web/src/app/home/models/component/embedding-card/EmbeddingCard.module.css b/web/src/app/home/components/models-dialog/component/embedding-card/EmbeddingCard.module.css similarity index 100% rename from web/src/app/home/models/component/embedding-card/EmbeddingCard.module.css rename to web/src/app/home/components/models-dialog/component/embedding-card/EmbeddingCard.module.css diff --git a/web/src/app/home/models/component/embedding-card/EmbeddingCard.tsx b/web/src/app/home/components/models-dialog/component/embedding-card/EmbeddingCard.tsx similarity index 100% rename from web/src/app/home/models/component/embedding-card/EmbeddingCard.tsx rename to web/src/app/home/components/models-dialog/component/embedding-card/EmbeddingCard.tsx diff --git a/web/src/app/home/models/component/embedding-card/EmbeddingCardVO.ts b/web/src/app/home/components/models-dialog/component/embedding-card/EmbeddingCardVO.ts similarity index 100% rename from web/src/app/home/models/component/embedding-card/EmbeddingCardVO.ts rename to web/src/app/home/components/models-dialog/component/embedding-card/EmbeddingCardVO.ts diff --git a/web/src/app/home/models/component/embedding-form/EmbeddingForm.tsx b/web/src/app/home/components/models-dialog/component/embedding-form/EmbeddingForm.tsx similarity index 100% rename from web/src/app/home/models/component/embedding-form/EmbeddingForm.tsx rename to web/src/app/home/components/models-dialog/component/embedding-form/EmbeddingForm.tsx diff --git a/web/src/app/home/models/component/llm-card/LLMCard.module.css b/web/src/app/home/components/models-dialog/component/llm-card/LLMCard.module.css similarity index 100% rename from web/src/app/home/models/component/llm-card/LLMCard.module.css rename to web/src/app/home/components/models-dialog/component/llm-card/LLMCard.module.css diff --git a/web/src/app/home/models/component/llm-card/LLMCard.tsx b/web/src/app/home/components/models-dialog/component/llm-card/LLMCard.tsx similarity index 100% rename from web/src/app/home/models/component/llm-card/LLMCard.tsx rename to web/src/app/home/components/models-dialog/component/llm-card/LLMCard.tsx diff --git a/web/src/app/home/models/component/llm-card/LLMCardVO.ts b/web/src/app/home/components/models-dialog/component/llm-card/LLMCardVO.ts similarity index 100% rename from web/src/app/home/models/component/llm-card/LLMCardVO.ts rename to web/src/app/home/components/models-dialog/component/llm-card/LLMCardVO.ts diff --git a/web/src/app/home/models/component/llm-form/LLMForm.tsx b/web/src/app/home/components/models-dialog/component/llm-form/LLMForm.tsx similarity index 100% rename from web/src/app/home/models/component/llm-form/LLMForm.tsx rename to web/src/app/home/components/models-dialog/component/llm-form/LLMForm.tsx diff --git a/web/src/i18n/locales/zh-Hans.ts b/web/src/i18n/locales/zh-Hans.ts index d747680c..9756dd93 100644 --- a/web/src/i18n/locales/zh-Hans.ts +++ b/web/src/i18n/locales/zh-Hans.ts @@ -104,7 +104,7 @@ const zhHans = { models: { title: '模型配置', description: '配置和管理可在流水线中使用的模型', - createModel: '创建模型', + createModel: '创建对话模型', editModel: '编辑模型', getModelListError: '获取模型列表失败:', modelName: '模型名称',